Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

ScalaMatsuri 2016

6,064 views

Published on

ScalaMatsuri 2016

Published in: Engineering
  • Be the first to comment

ScalaMatsuri 2016

  1. 1. Heads-on Domain Driven Development in Scala Yoshitaka Fujii @yoshiyoshifujii 2016 ScalaMatsuri Scalaでドメイン駆動設計に真正面から取り組んだ話
  2. 2. Who am I ? • Yoshitaka Fujii • @yoshiyoshifujii • Joined MOTEX in April, 2014 • Web Application Engineer • Scala (7 months)
 Java, Python 自己紹介。エムオーテックス株式会社。Scala歴7ヶ月。
  3. 3. We are looking for a Scala of Engineers !! Scalaエンジニア募集中!!
  4. 4. Isn't it difficult? 難しくないですか ?
  5. 5. Design approach 設計手法に過ぎない。
  6. 6. and Design Pattern あと、デザインパターン
  7. 7. Let’s implement !! さあ、実装しよう!!
  8. 8. https://goo.gl/DQB6Ak
  9. 9. too difficult … 難しい…
  10. 10. Today’s topic 今日、お話したいこと
  11. 11. Amateur Team ScalaもDDDも経験の無い私たちのチームが
  12. 12. Hard Fighting 日々悩み悪戦苦闘した内容について
  13. 13. I'd like to share what we learned!! 同じような悩みを持つ皆様の一助になればうれしく思います。
  14. 14. Agenda • Layered Architecture • CQRS • is-a Root Entity • Message アジェンダです。
  15. 15. Layered Architecture レイヤー化アーキテクチャについて
  16. 16. Reprinted from Eric Evans (2003, pp.68) Domain Driven Design.
  17. 17. Review おさらい
  18. 18. Point of
 Layered Architecture レイヤー化アーキテクチャの要約。
  19. 19. Point of Layered Architecture 1. To clarify the responsibilities of the layer 2. Depends only on the lower layer 3. Isolating the Domain レイヤーの責務を明確にすること。下位レイヤーだけに依存す ること。ドメインを隔離すること。
  20. 20. User Interface Layer • Showing information to the user. • Interpreting the user’s commands. • The external actor might sometimes be another computer system rather than a human user. ユーザに情報を表示する。ユーザからのコマンドを解釈する。 外部アクタは別のコンピュータシステムのこともある。
  21. 21. Application Layer • Defines the jobs the software. • Directs the expressive domain objects to work our problems. • This layer is kept thin. • It does not contain business rules or knowledge. ソフトウェアの仕事を定義、ドメインオブジェクの問題解決を 導く。薄く保つ。ビジネスルールや知識を含まない。
  22. 22. Domain Layer • Concepts of the business. • Information about the business solution. • Business rules. • This layer is the beat of business software. ビジネスの概念。ビジネスが置かれた状況に関する情報。ビジ ネスルール。ビジネスソフトウェアの核心。
  23. 23. Infrastructure Layer • Provides generic technical capabilities that support the higher layers. • Message sending for the application. • Persistence for the domain. • Drawing widgets for the UI 上位レイヤを支える技術的機能を提供。Applicationのための メッセージ送信。Domainのための永続化。UIの描画。
  24. 24. First wall 最初の壁 https://goo.gl/ErmMRx
  25. 25. We wondered how the tasks within a layer can be implemented... どのようにレイヤーの責務を実装しようか…
  26. 26. User Interface Application Domain Infrastructure
  27. 27. User Interface Application Domain Infrastructure ViewModelForm Entity Record SQL JSON Entity Result Set ViewModel
  28. 28. Form • So-called Form • It express POST data from view. • Making corresponding to each View. いわゆるForm。ViewからのPOST/PUTデータを表す。
  29. 29. case class XxxForm(email: String, name: String) def form(): Form[XxxForm] = Form( mapping( "email" -> email, "name" -> nonEmptyText(maxLength = 80)) (XxxForm.apply)(XxxForm.unapply))
  30. 30. ViewModel • Synonymous with the MVVM pattern • Holding a state for drawing a view. • Form => Application => Domain MVVMパターンのViewModelと同義。Viewを描画するための 状態の保持。UI=>Application=>Domainと受け渡す。
  31. 31. case class XxxForm(email: String, name: String) case class XxxViewModel(email: String, name: String) def convertForm2ViewModel(form: XxxForm): XxxViewModel = XxxViewModel( form.email, form.name)
  32. 32. User Interface Application Domain Infrastructure ViewModelForm Entity Record SQL JSON Entity Result Set ViewModel
  33. 33. User Interface Application Domain Infrastructure ViewModelForm Entity Record SQL JSON Entity Result Set ViewModel Form => ViewModel ViewModel => Entity Entity => SQL
  34. 34. User Interface Application Domain Infrastructure ViewModelForm Entity Record SQL JSON Entity Result Set ViewModel Form => ViewModel ViewModel => Entity Entity => SQL ResultSet => Record Record => Entity Entity => ViewModel ViewModel => JSON
  35. 35. So we tried, but... と、いうことですが…
  36. 36. Second wall 次の壁 https://goo.gl/ErmMRx
  37. 37. When we implemented, the responsibilities started to leak across the boundaries. 実装してみると、責務の境界が、混じるようになりました。
  38. 38. User Interface x
 Domain たとえば、User Interface に Domain が。
  39. 39. def xxxForm(id: Option[Long]): Form[XxxForm] = Form( mapping( "name" -> nonEmptyText(maxLength = 128) .verifying(“error.uniqueness.name", !service.isSameNameExisting(_, id)), "path" -> nonEmptyText(maxLength = 128), "lockVersion" -> optional(longNumber)) (XxxForm.apply)(XxxForm.unapply))
  40. 40. Application x Domain たとえば、Application に Domain が。
  41. 41. private def validateExists(vm: ViewModel): Either[Error, ViewModel] = { repository.findBy(vm.email) match { case Some(_) => Left("not exists!") case None => Right(vm) } }
  42. 42. Need a plan to make the boundaries more clear 境界を見失わないための工夫が必要。
  43. 43. Thinking… どのようにレイヤーの責務を実装しようか…
  44. 44. Communication コミュニケーション
  45. 45. Review レビュー
  46. 46. Analysis Modeling 分析モデリング
  47. 47. To clarify the responsibilities of the layer • Communication • Review • Analysis Modeling レイヤーの責務が混じらないよう、コミュニケーションと Reviewと分析モデリングを活用した。
  48. 48. Third wall 次の壁 https://goo.gl/ErmMRx
  49. 49. Those pesky Services 曲者のService。
  50. 50. Service of three types 3つのService
  51. 51. Partitioning Services into Layers • Application Service • Domain Service • Infrastructure Service アプリケーション層、ドメイン層、インフラ層のそれぞれにサ ービスってありますよね。
  52. 52. Reprinted from Eric Evans (2003, pp.107) Domain Driven Design.
  53. 53. How do we know which service to use when? どう責務を使い分けています?
  54. 54. Overuse of Services leads to leakage of Domain knowledge to other layers. Serviceを多用するとDomainロジックが流出しがち。
  55. 55. Thinking… 3つのServiceをどのように扱うか…
  56. 56. Responsibility of Service • Once, approach to Application Service. • Temporarily forgive the outflow of Domain logic. • Repeat to consider refactoring. Application Serviceに寄せる。Domainロジックの流出を一時 的に許容。Refactoringを繰り返し検討。
  57. 57. Refactoring リファクタリング
  58. 58. Scrum スクラム
  59. 59. Day of Refactoring リファクタリングをする日
  60. 60. STOP knowledge leaks • Refactoring • Scrum • Day of Refactoring ドメインの知識の流出を、リファクタリング、スクラム、リフ ァクタリングする日を使って継続的に改善した。
  61. 61. Forth wall 次の壁 https://goo.gl/ErmMRx
  62. 62. too many conversion logics コンバート処理が多いですよね?
  63. 63. User Interface Application Domain Infrastructure ViewModelForm Entity Record SQL JSON Entity Result Set ViewModel Form => ViewModel ViewModel => Entity Entity => SQL ResultSet => Record Record => Entity Entity => ViewModel ViewModel => JSON 1 2 3 4567
  64. 64. 7 conversions just for a roundtrip いって、かえってするだけなのに、7回も…
  65. 65. Just live with it!! 仕方ない!!
  66. 66. Some sacrifices are unavoidable to isolate the Domains. Domainを隔離するためなら、多少の犠牲はやむを得ない!!
  67. 67. Domain isolation = DDD !! Domainを隔離することが、DDDだ!!
  68. 68. Conversion makes the Domain boundaries clear Convertすることで、Domainの境界も分かりやすい
  69. 69. Responsibility is easy to understand in isolation. 責務が分離され、分かりやすい
  70. 70. Still, too many conversions... とはいえ、Convert処理多いよなー
  71. 71. Command Query Responsibility Segregation コマンドクエリ責務分離
  72. 72. Agenda • Layered Architecture • CQRS • is-a Root Entity • Message CQRS
  73. 73. CQRS • CQRS stands for 
 Command Query Responsibility Segregation • First proposed by Greg Young • Martin Fowler http://martinfowler.com/bliki/CQRS.html Greg Youngが提唱したのをMartin Fowlerがブログで紹介し た。
  74. 74. About CQRS • CQS (Command query separation) • It states that every method should either be a command that performs an action • or a query that returns data to the caller • but not both あらゆるメソッドは、「コマンド」もしくは「クエリ」のいず れか一方で、その両方はダメ。
  75. 75. In the first place… そもそも…
  76. 76. Tackling Complexity in the Heart of Software ソフトウェアの複雑さに立ち向かう
  77. 77. View the list 一覧を表示する
  78. 78. User Interface Application Domain Infrastructure ViewModelForm Entity Record SQL JSON Entity Result Set ViewModel Form => ViewModel ViewModel => Entity Entity => SQL ResultSet => Record Record => Entity Entity => ViewModel ViewModel => JSON
  79. 79. more complexity ? 複雑にしてない?
  80. 80. Query Model 呼び出し元にデータを戻す「クエリ」について。
  81. 81. User Interface Application Domain Infrastructure ViewModelForm Record SQL JSON Result SetForm => ViewModel ViewModel => SQL ResultSet => Record QueryModel => JSON Record => QueryModel QueryModel
  82. 82. Query Model • Query Model into the Infrastructure Layer • Application Layer direct connection to Infrastructure Layer • Result Set => Record => Query Model • Query Model is returned to User Interface Layer Infrastructure層にQuery Modelを作る。 Application層から直接 Infrastructure層。 Query ModelはUser Interface層まで持ち込みOK!!
  83. 83. Command Model 何らかのアクションを実行する「コマンド」について。
  84. 84. User Interface Application Domain Infrastructure ViewModelForm Entity Record SQL JSON Entity Result Set ViewModel Form => ViewModel ViewModel => Entity Entity => SQL ResultSet => Record Record => Entity Entity => ViewModel ViewModel => JSON
  85. 85. Command Model is simple Layered Architecture Command Model は従来のレイヤー化アーキテクチャ
  86. 86. Many of the business logic is Command. ビジネスロジックの多くはCommand
  87. 87. Command tends to be smaller than Query Command Modelがシステムの占める割合は少なめ
  88. 88. Summary of CQRS • Simple retrieval process is separated into queries. • Direct connection to the Infrastructure Layer. • Simple things made simply. 単純な取得処理はクエリとして分離。直接インフラ層にアクセ ス。シンプルなものはシンプルに作る。
  89. 89. Agenda • Layered Architecture • CQRS • is-a Root Entity • Message ROOTエンティティがis-aの関連を持っている場合
  90. 90. is-a Root Entity ROOTエンティティがis-aの関連を持っている場合
  91. 91. Root Entity has is-a Root Entityをis-aの関連とする設計にすることってありますよ ね?
  92. 92. is-a is confusing is-aの関連をRoot Entityにするとややこしい。
  93. 93. One Repository ? or Two Repository ? Repositoryは、1つ?2つ?
  94. 94. Should the parent track the children? 親が子をしっているの?
  95. 95. Awkward in terms of OO オブジェクト指向的に変
  96. 96. Should the Service specify the children? クライアントが子を指定するの?
  97. 97. Awkward in terms of the aggregation 集約の概念的に変
  98. 98. Take a look at the source code コードで見てみる
  99. 99. trait RootEntity { val id: Option[Long] val name: String } case class Sub1( id: Option[Long], name: String) extends RootEntity case class Sub2( id: Option[Long], name: String) extends RootEntity
  100. 100. Parent tracking the children 集約の親が子を知っているパターン
  101. 101. trait RootEntityRepository { object Sub1Repository { def save(entity: Sub1): Option[Sub1] = ??? } object Sub2Repository { def save(entity: Sub2): Option[Sub2] = ??? } def save(types: String, entity: RootEntity) = { types match { case "Sub1" => Sub1Repository.save(entity) case "Sub2" => Sub2Repository.save(entity) case _ => None } } } object RootEntityRepository extends RootEntityRepository
  102. 102. trait Service { def createSub1 = { val sub1 = Sub1(None, "sub1") RootEntityRepository.save("Sub1", sub1) } def createSub2 = { val sub2 = Sub2(None, "sub2") RootEntityRepository.save("Sub2", sub2) } }
  103. 103. Service specifying the children サービスが子を指定するパターン
  104. 104. trait RootEntityRepository[E <: RootEntity] {} trait Sub1Repository {} trait Sub2Repository {} object Sub1Repository extends RootEntityRepository[Sub1] with Sub1Repository object Sub2Repository extends RootEntityRepository[Sub2] with Sub2Repository
  105. 105. trait Service { def createSub1 = { val sub1 = Sub1(None, "sub1") Sub1Repository.save(sub1) } def createSub2 = { val sub2 = Sub2(None, "sub2") Sub2Repository.save(sub2) } }
  106. 106. Which pattern is more suitable... どっちのケースが適しているか…
  107. 107. To clarify the hierarchy どのヒエラルキーに属するRepositoryか明示する。
  108. 108. trait RootEntityRepository[E <: RootEntity] {} trait Sub1Repository {} trait Sub2Repository {} object Sub1Repository extends RootEntityRepository[Sub1] with Sub1Repository object Sub2Repository extends RootEntityRepository[Sub2] with Sub2Repository
  109. 109. It eliminates redundant processing 冗長な処理を簡略化する。
  110. 110. trait RootEntityRepository { object Sub1Repository { def save(entity: Sub1): Option[Sub1] = ??? } object Sub2Repository { def save(entity: Sub2): Option[Sub2] = ??? } def save(types: String, entity: RootEntity) = { types match { case "Sub1" => Sub1Repository.save(entity) case "Sub2" => Sub2Repository.save(entity) case _ => None } } } object RootEntityRepository extends RootEntityRepository
  111. 111. Summary of is-a Root Entity • Service specifying the children • To clarify the hierarchy • It eliminates redundant processing Serviceが子を知っているパターンを採用
  112. 112. Agenda • Layered Architecture • Dependency Injection • is-a Root Entity • CQRS • Message 最後にMessageについて。
  113. 113. Message
  114. 114. Low Layer 浅いレイヤーのエラーメッセージはUIに直接返せる
  115. 115. Deep Layer 深いレイヤーのエラーメッセージはそう簡単じゃない
  116. 116. Infrastructure Layer インフラ層
  117. 117. Unrecoverable Exception 復旧不可能な例外
  118. 118. Domain Layer ドメイン層
  119. 119. I want to return a message on the screen なんらかのメッセージを返したい
  120. 120. Type of Message • Option • Either • Try • Validation
  121. 121. Option • Some or None. Why None ? • None is Exception • Exception is Exception SomeかNoneで、なぜNoneか分からない。Noneがそもそも 異常なこともある。その場合は例外にしたい。
  122. 122. Either • Left or Right. Left is error message. • Monad (salaz) • Single message only Left or RightでLeftにメッセージが詰められる。Monadなコ ーディング可能。ただしメッセージは1つ。
  123. 123. Try • Define the type of Exception • Simple error handling • Throw to the higher layer 例外の型を定義。throw形式でエラー時の制御が容易。処理を 中断し上位のレイヤーまで到達可能。
  124. 124. Validation (Scalaz) • Return the stack error • But not Monad • not be used in for-yield • Applicative エラーを詰めて返すことが可能。Monadではない。for式で使 えない。Applicative。
  125. 125. Stack ? or not エラー時のメッセージをStackしたいか、したくないか。
  126. 126. Validation したいならValidation。
  127. 127. or not Try (or Either) したくないならTry (or Either)
  128. 128. But … ただし…
  129. 129. Validation is Applicative. not Monad. ValidationはApplicativeであり、Monadではないよね。
  130. 130. sealed abstract class Validation[+E, +A] final case class Success[A](a: A) extends Validation[Nothing, A] final case class Failure[E](e: E) extends Validation[E, Nothing]
  131. 131. def ap[EE >: E, B](x: => Validation[EE, A => B]) (implicit E: Semigroup[EE]): Validation[EE, B] = (this, x) match { case (Success(a), Success(f)) => Success(f(a)) case (e @ Failure(_), Success(_)) => e case (Success(f), e @ Failure(_)) => e case (Failure(e1), Failure(e2)) => Failure(E.append(e2, e1)) }
  132. 132. case class Person(name: String, age: Int) def validateName(name: String) = if (name.length > 1) name.successNel else "invalid name".failureNel def validateAge(age: Int) = if (age >= 0) age.successNel else "invalid age".failureNel
  133. 133. scala> (validateName("Yoshida") |@| validateAge(27)) (Person) res0: scalaz.Validation[scalaz.NonEmptyList[String],Person] = Success(Person(Yoshida,27)) scala> (validateName("") |@| validateAge(-1))(Person) res1: scalaz.Validation[scalaz.NonEmptyList[String],Person] = Failure(NonEmptyList(invalid name, invalid age))
  134. 134. Summary of Message • Unrecoverable exception • Domain Layer return a message to screen • Validation or Try or Either 復旧不可能な例外もあるが、ドメイン層から何らかのメッセー ジを返したいとき、ValidationとTry(or Either)を使い分け。
  135. 135. Summary まとめ
  136. 136. Scala Scalaで悪戦苦闘
  137. 137. Scala x DDD ScalaとDDDでさらに苦悩
  138. 138. We hope this was helpful to you!! 同じような悩みを持つ皆様の一助になればうれしく思います。
  139. 139. Thank you for your attention. ご清聴ありがとうございました。

×