Slide 1

Slide 1 text

同僚の登壇資料作成を Scalaで手伝った話 細西 伸彦(Hosonishi Nobuhiko) AdTech Studio / CyberAgent, Inc. helped to create my co-worker slide using Scala

Slide 2

Slide 2 text

自己紹介 細西 伸彦 ソフトウェアエンジニア 去年SIerより現職へ転職 Scala歴 5年くらい bakenezumi @bake_nezumi Hosonishi Nobuhiko Software Engineer Work on Scala for 5 years

Slide 3

Slide 3 text

話すこと 同じチームの阿川さん(@atty303)が登壇することになり、 一部資料の手伝いをやらせてもらいました。 素材を一部Scalaを使って 作成したのでそれについて 話します。 I helped to create (my co-worker) Agawa-san’s slide using Scala

Slide 4

Slide 4 text

1.プロジェクト間の依存関係を図示する 2.プロジェクトからエンティティ、 値オブジェクトのクラスを抽出する

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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が作れる

Slide 8

Slide 8 text

実行結果

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

欲しかったもの Wanted List of entity classes, List of value object classes - エンティティクラスの一覧 - 値オブジェクトクラスの一覧

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

実行結果 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

Slide 15

Slide 15 text

解説① // 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)

Slide 16

Slide 16 text

解説② // クラスのメンバー名を取得 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))

Slide 17

Slide 17 text

おまけ ((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

Slide 18

Slide 18 text

Thank you Special thanks to @atty303, @junary309