同僚の登壇資料作成をScalaで手伝った話 / ScalaMatsuri 2019 LT

同僚の登壇資料作成をScalaで手伝った話 / ScalaMatsuri 2019 LT

2142d496cbef8a4913560466f3e9c8f9?s=128

bakenezumi

June 28, 2019
Tweet

Transcript

  1. 同僚の登壇資料作成を Scalaで手伝った話 細西 伸彦(Hosonishi Nobuhiko) AdTech Studio / CyberAgent, Inc.

    helped to create my co-worker slide using Scala
  2. 自己紹介 細西 伸彦 ソフトウェアエンジニア 去年SIerより現職へ転職 Scala歴 5年くらい bakenezumi @bake_nezumi Hosonishi

    Nobuhiko Software Engineer Work on Scala for 5 years
  3. 話すこと 同じチームの阿川さん(@atty303)が登壇することになり、 一部資料の手伝いをやらせてもらいました。 素材を一部Scalaを使って 作成したのでそれについて 話します。 I helped to create

    (my co-worker) Agawa-san’s slide using Scala
  4. 1.プロジェクト間の依存関係を図示する 2.プロジェクトからエンティティ、 値オブジェクトのクラスを抽出する

  5. プロジェクト間の依存関係 を図示する visualizing dependencies between projects

  6. 欲しかった図 SBTのサブプロジェクトの依存関係の図 Wanted figure: Dependency between SBT sub projects

  7. addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.1") $ sbt sbt:glaze> dependencyBrowseGraphHtml use

    sbt-dependency-graph it can create dot script and HTML with dependencyBrowseGraphHtml command plugins/plugins.sbt sbt-dependency-graphを利用 https://github.com/jrudolph/sbt-dependency-graph dependencyBrowseGraphHtmlコマンドでdotスクリプトとHTMLが作れる
  8. 実行結果

  9. 見にくいので外部ライブラリの依存を全削除して再実行 Remove dependency on external libraries and rerun

  10. 再実行結果 プロジェクト間の依存関係は出力できたのでOK (コンパイルはもちろん通らない)

  11. プロジェクトからエンティ ティ、値オブジェクトのクラ スを抽出する extract entity and value object classes from

    project
  12. 欲しかったもの Wanted List of entity classes, List of value object

    classes - エンティティクラスの一覧 - 値オブジェクトクラスの一覧
  13. REPL で以下を実行 import collection.JavaConverters._ val isConcrete = (clz: Class[_]) =>

    !clz.isInterface && !java.lang.reflect.Modifier.isAbstract(clz.getModifiers) val getTopLevelClassesRecursive = (pkg: String) => com.google.common.reflect.ClassPath.from(Thread.currentThread().getContextClassLoader()).getTopLevelClassesRe cursive(pkg).stream().map[Class[_]](_.load()).collect(java.util.stream.Collectors.toSet()).asScala.filter(isC oncrete) val myDomainClasses = Set("glaze.kernel.domain", "glaze.progad.domain").flatMap(getTopLevelClassesRecursive) val getMemberNames = (clz: Class[_]) => (clz.getDeclaredMethods.map(_.getName) ++ clz.getDeclaredFields.map(_.getName)).toSet val hasId = (clz: Class[_]) => getMemberNames(clz).exists(_ == "id") val partitionIntoEntityAndValueObject = (classes: Set[Class[_]]) => classes.partition(hasId) val (entities, valueObjects) = partitionIntoEntityAndValueObject(myDomainClasses) val format = (clz: Set[Class[_]]) => clz.map(_.getSimpleName).toSeq.sorted.mkString(", ") println(format(entities)) println(format(valueObjects)) run with REPL
  14. 実行結果 scala> println(format(entities)) Ad, Advertiser, AdvertiserListOwner, App, AppList, AppListMeta, AudienceIdentifierList,

    AudienceIdentifierListMeta, Audit, Banner, Bid, BidRequest, BidResponse, BudgetAccount, Campaign, CompleteCandidate, Content, Conversion, FixedPricingBidAgentConfig, Imp, InsertionOrder, LineItem, PartialCandidate, Producer, Publisher, Segment, Site, StandardBidAgentConfig, StandardBidRequest, SystemListOwner scala> println(format(valueObjects)) AcceptedActivity, Activity, ActivitySet, Ad, AdPreviewEndpoint, AdSuite, AdTypeDescriptor, Adjust, AdsDigest, AggregatePeriod, AggregateSpending, AggregatedFrequency, AggregationGroup, AndThenAdFilter, AndThenCandidateFilter, AndroidAppId, AppDescriptor, AppListEntry, AppTargeting, AppsFlyer, AssetPath, AtomicFeature, AttachmentPoint, AttributionId, Audience, AudienceDescriptor, AudienceIdentifierListEntry, AudienceProto, AudienceStats, AudienceTargeting, AudioPlacement, BidAgentConfigured, BidContext, BidDeliveryTracking, BidPayload, BidProto, BidResponse, BitSetDescriptorSet, Bitrate, BlankPayload, BlankProto, BoundAssets, BuiltinMeasurement, CallToAction, CandidateEvaluation, CandidateHeader, Candidates, CapTarget, ClickDeliveryTracking, ClickMeasurementTracking, ClickPayload, ClickProto, ClickThroughRate, ClickTracker, CompleteRichCreative, ContentCategoryDescriptor, ConversionEvent, ConversionRate, CoproductUrl, CostPerAcquisition, CostPerClick, CostPerView, CostRate, Cpm, Credit, CustomerActionMeasurementTracking, CustomerActionPayload, CustomerActionProto, DelayedFeedback, DeliveryBoundEndpoints, DeliveryContext, DeliveryEvent, DeliveryEventEncoder, DeliveryEventProto, DeliveryUnboundEndpoints, DeniedActivity, Device, DeviceProto, Dimension, DisplayPlacement, DomainDescriptor, DurationRange, EnvironmentDescriptor, EnvironmentTargeting, EventForAggregate, ExchangeDescriptor, ExtensionTrackingType, FactorizationMachine, Execution result
  15. 解説① // JavaのSetからScalaのSetに変換するために必要 import collection.JavaConverters._ // 具象クラスか判定する val isConcrete =

    (clz: Class[_]) => !clz.isInterface && !java.lang.reflect.Modifier.isAbstract(clz.getModifiers) // 指定パッケージ配下の具象クラスをClassPathから取得する。aws-sdkがguavaを使っていたのでそれを利用 val getTopLevelClassesRecursive = (pkg: String) => com.google.common.reflect.ClassPath.from(Thread.currentThread() .getContextClassLoader()).getTopLevelClassesRecursive(pkg).stream() .map[Class[_]](_.load()).collect(java.util.stream.Collectors.toSet()) .asScala.filter(isConcrete) // ドメインレイヤのパッケージに含まれる具象クラス val myDomainClasses = Set("glaze.kernel.domain", "glaze.progad.domain").flatMap(getTopLevelClassesRecursive)
  16. 解説② // クラスのメンバー名を取得 val getMemberNames = (clz: Class[_]) => (clz.getDeclaredMethods.map(_.getName)

    ++ clz.getDeclaredFields.map(_.getName)).toSet // "id" をメンバーにもつか。エンティティの判定に使う val hasId = (clz: Class[_]) => getMemberNames(clz).exists(_ == "id") // クラス群をエンティティとそれ以外(バリューオブジェクト)に分割 val partitionIntoEntityAndValueObject = (classes: Set[Class[_]]) => classes.partition(hasId) val (entities, valueObjects) = partitionIntoEntityAndValueObject(myDomainClasses) val format = (clz: Set[Class[_]]) => clz.map(_.getSimpleName).toSeq.sorted.mkString(", ") println(format(entities)) println(format(valueObjects))
  17. おまけ ((pkgs: Set[String]) => (format: Set[Class[_]] => String) => (effect:

    String => Unit) => (((_: Set[Class[_]]).partition(clz => (clz.getDeclaredMethods.map(_.getName) ++ clz.getDeclaredFields.map(_.getName)).toSet.exists(_ == "id")).productIterator.map(_.asInstanceOf[Set[Class[_]]]).map(format))(pkgs.flatMap(pkg => collection.JavaConverters.asScalaSetConverter(com.google.common.reflect.ClassPath.from(Thread.curr entThread().getContextClassLoader()).getTopLevelClassesRecursive(pkg).stream().map[Class[_]](_.loa d()).collect(java.util.stream.Collectors.toSet())).asScala.filter(clz => !clz.isInterface && !java.lang.reflect.Modifier.isAbstract(clz.getModifiers))))).foreach(effect))(Set("glaze.kernel.do main", "glaze.progad.domain"))(_.map(_.getSimpleName).toSeq.sorted.mkString(", "))(println) 一文で by one sentence
  18. Thank you Special thanks to @atty303, @junary309