Upgrade to Pro — share decks privately, control downloads, hide ads and more …

scala-on-ddd

Yohei TSUJI
September 09, 2017

 scala-on-ddd

「2017/9/9 Scala関西Summit 2017」、「2017/10/21 関ジャバ'17 10月度」 、「2018/3/18 Scala Matsuri 2018」でお話した、実践ScalaでDDD の発表資料です。
(English version -> https://speakerdeck.com/crossroad0201/practice-ddd-with-scala-en )

サンプルコードもあわせて参照してください。
https://github.com/crossroad0201/ddd-on-scala

目次
1.DDDとは
- DDD
- DDDのコンポーネント
2.ScalaとDDD
3.Scala実装スタイル
4.アーキテクチャ
- レイヤ構成
- エラー処理
5.コンポーネントの実装
- アプリケーションサービス
- エンティティ
- バリューオブジェクト
- ロールオブジェクト
- ファクトリ
- リポジトリ
- ドメインサービス
- ドメインイベント・イベントパブリッシャ
- クエリモデル・クエリプロセッサ
- イベントサブスクライバ
- インターフェース
6.DDDの実践

Yohei TSUJI

September 09, 2017
Tweet

More Decks by Yohei TSUJI

Other Decks in Programming

Transcript

  1. “ 2017-09-09 実践ScalaでDDD 2 辻 陽平 Yohei TSUJI ” フリーランスのプログラマ

    - Java 17年くらい、Scala 2年弱 - 主にエンタープライズ系システムの ソフトウェアアーキテクトを担当 - 現在は Scala / AWS / DDD で クラウドサービスの開発中 - 車好き、F1好き、たまに登山 Twitter / GitHub / Qiita @crossroad0201
  2. DDDのコンポーネント(1 / 2) 2017-09-09 実践ScalaでDDD 10 集約 ドメインの中で、整合性を 保証しなければならない まとまり。

    ファクトリ 「エンティティ」を新しく作 成する。 エンティティ 「集約」の中で、一意な識 別子で識別される概念。 通常は集約の中に1つ。 リポジトリ 「エンティティ」をデータス トアに永続化し、データス トアから「エンティティ」を 取得する。 バリューオブジェクト 「集約」の中で、識別子で 識別する必要がない値。 エンティティに従属する。 ドメインサービス ドメインのビジネスルー ル(業務の仕様)を実行 する。
  3. CQRS(Command Query Responsibility Segregation) DDDのコンポーネント(2 / 2) 2017-09-09 実践ScalaでDDD 11

    ドメインイベント ドメインで発生する、ビジ ネス上の事象を表現する オブジェクト。 イベントサブスクライバ 「ドメインイベント」を受信 して「クエリモデル」を更 新し、ビューを最新に保 つ。 イベントパブリッシャ 「ドメインイベント」をメッ セージキューに発行する。 クエリプロセッサ 「クエリモデル」を検索す る。 クエリモデル ドメインをある切り口で見 た読み取り専用のビュー。 複数の「集約」を合成した ビューも定義できる。 アプリケーション サービス ドメインの各コンポーネン トを使って、ユースケース を実現する。
  4. ドメインの仕様を自然に表現できる 2017-09-09 実践ScalaでDDD 15 // ユーザを取得して、もし存在しなければエラー val user = userRepository.get(userId)

    ifNotExists NotFoundError - レシーバー と メソッド の区切りに半角スペースが使える。 - メソッドの引数が1つだけなら、( ) を省略できる。 - もともとの言語仕様かのように、表現力を拡張していける。 コードが自然文(英文)の仕様書になる。
  5. case classで簡単に型が作れる 2017-09-09 実践ScalaでDDD 17 case class User(id: UserId, name:

    UserName) - コンストラクタは定義しなくていい。 - Getter / Setter も定義しなくていい。 - #equals()、#hashCode()、#toString() のデフォルト実装。 - #copy() メソッドでイミュータブルなプログラミングが楽。 大量の型を作るDDDではコード量削減効果大
  6. 変数と関数の区別がない 2017-09-09 実践ScalaでDDD 18 class Example { val foo: String

    = … } new Example().foo プロパティ <-> 導出プロパティの変更が容易 class Example { def foo: String = … } new Example().foo // 影響なし
  7. 関数(メソッド)をネストできる 2017-09-09 実践ScalaでDDD 19 def remindTodayScheduleTo(id: UserId) = { def

    findUser = { … } def getScheduleOf(user: User) = { … } def sendReminderTo(user: User, schedule: Schedule) = { … } 処理ステップに、細かく名前を付け易い。 - その関数を使う場所(スコープ)が明確。 - ネストした関数から、外側の関数の引数を参照できる。 - private メソッドだらけにならない。
  8. 2017-09-09 実践ScalaでDDD 21 // イミュータブル case class User(name: UserName) {

    def renameTo(name: UserName): User = copy(name = name) } イミュータブルに作る // ミュータブル class User(var name: UserName) { def renameTo(name: UserName): Unit = this.name = name } - var(再代入可)は使わず、val(再代入不可)を使う。 case class にすると自動的に val に。 - 値を書き換えるのではなく、値の異なる別インスタンスを作って返す。 case class の copy() メソッドで簡単に実装できる。
  9. 2017-09-09 実践ScalaでDDD 23 object User { def create(…) = {

    // Userを生成して返すだけ User(…) } } val user = User.create(…) userRepository.save(user) 副作用を起こさない(局所化する) object User { def create(…) = { val user = User(…) // Userの生成と同時に保存 // 保存処理が副作用になる userRepository.save(user) } } val user = User.create(…)
  10. 2017-09-09 実践ScalaでDDD 24 def remindTodayScheduleTo(id: UserId) = { def findUser

    = { … } def getTodaysScheduleOf(user: User) = { … } def sendReminderTo(user: User, schedule: Schedule) = { … } for { user <- findUser todaysSchedule <- getTodaysScheduleOf(user) sentResult <- sendReminderTo(user, schedule) } yield sentResult } for内包式を使いこなす ローカル関数を定義して 処理ステップに名前を付ける for内包式で処理ステップをつなげて 処理フローを作る
  11. 2017-09-09 実践ScalaでDDD 25 object User { def create(name: UserName)(implicit idGenerator:

    IDGenerator) = {…} } implicit val idGenerator: IDGenerator = … val user = User.create("WHO") // idGeneratorが自動的に渡される implicitパラメタを活用する - ドメインの関心事ではないパラメタ(DBセッションなど)は、 implicit parameter で暗黙的に渡すことでドメインロジックから ノイズを排除する。
  12. 2017-09-09 実践ScalaでDDD 26 case classのcopy()は外から使わない - case classに自動生成される copy() メソッドを乱用すると、

    コードの意図がわからなくなるので、自クラス内でのみ使用し、 外からは直接使用しない。 case class User(name: UserName) { def renameTo(name: UserName): User = copy(name = name) } user.renameTo("Taro") case class User(name: UserName) // 何を意図しているのかわからない user.copy(name = "Taro")
  13. 2017-09-09 実践ScalaでDDD 27 for { user <- findUser todaysSchedule <-

    getTodaysScheduleOf(user) sentResult <- sendReminderTo(user, schedule) } yield sentResult 変数名はむやみに略さない for { u <- findUser s <- getTodaysScheduleOf(u) r <- sendReminderTo(u, s) } yield r 関数型言語の文化では、 変数名を x, y などに略し がち… むやみに略すとコードが 数学の方程式のように なって読みづらくなる。
  14. 2017-09-09 実践ScalaでDDD 28 def findUser: Either[Option[User]] = { … }

    for { // Option[User] maybeUser <- findUser } yield for { // User user <- maybeUser : } yield … Option型の変数はmaybeを付ける - 値がないかもしれない変数 (Option型)は、変数名に maybeを付けるとわかりやす い。 - Scalaでは型推論で型を省略 することが多いので、Option かどうかが変数名からパッと 見てわかると便利。
  15. 2017-09-09 実践ScalaでDDD 29 for { user <- findUser todaysSchedule <-

    getTodaysScheduleOf(user) sentResult <- sendReminderTo(user, schedule) } yield sentResult 自動コードフォーマッタを利用 for { user <- findUser todaysSchedule <- getTodaysScheduleOf(user) sentResult <- sendReminderTo(user, schedule) } yield sentResult - Scalafmt や Scalareform のプラグインをsbtに組み 込んで使う。 - 特に for内包式 と match 式は自動フォーマットで 揃えると、コードがグッと 読みやすくなる。
  16. レイヤと責務 2017-09-09 実践ScalaでDDD 32 ドメインの知識をモデル化。 名詞句は型(クラス)に、動詞句は振る舞 い(メソッド)として実装する。 ドメインモデル の状態を永続化する。 永続化処理は

    副作用 になる。 ドメインモデル を使用して、ユースケース を実現する。 Web画面 や REST API、Command Line Interface など、ユースケースを起動するた めのユーザーインターフェースを提供する。 インフラストラクチャ レイヤ インターフェース レイヤ アプリケーション レイヤ ドメイン レイヤ
  17. レイヤ構成 2017-09-09 実践ScalaでDDD 33 インターフェース アプリケーション ドメイン インフラストラクチャ レイヤ化アーキテクチャ ドメイン

    が インフラストラクチャ に依存してしまう。 - ドメイン が副作用を起こしてしまう。 - ドメイン のコードに、インフラストラクチャ の コードが混在してしまう。 - ミドルウェア や サービス、データストレージ を 柔軟に変更できない。 - ドメイン のユニットテスト がやりにくい。
  18. レイヤ構成 2017-09-09 実践ScalaでDDD 34 インターフェース アプリケーション ドメイン インフラストラクチャ ドメイン中心アーキテクチャ ドメイン

    が どのレイヤにも依存し ない。 - ドメイン から副作用を排除。 - ただし、アプリケーション は インフラストラクチャ に依存し たまま。
  19. レイヤ構成 2017-09-09 実践ScalaでDDD 35 オニオンアーキテクチャ The Onion Architecture : part

    1 http://jeffreypalermo.com/blog/the -onion-architecture-part-1/ - ヘキサゴナルアーキテクチャ も 基本的な考え方は同じ。 - クリーンアーキテクチャは、より GUIアプリに特化?
  20. レイヤ構成 2017-09-09 実践ScalaでDDD 36 インターフェース アプリケーション ドメイン インフラストラクチャ ドメイン も

    アプリケーション も イ ンフラストラクチャ に依存しない。 - インターフェース で、 アプリケーションとドメイン に インフラストラクチャ を合成 (依存性注入)して使う。 オニオンアーキテクチャ
  21. エラー処理 2017-09-09 実践ScalaでDDD 39 Option Some(値がある) or None(値がない) のいずれか。 Try

    Success(成功) or Failure(失敗) のいずれか。 予測不能なエラーに 使う。 Either Right(正常) or Left(異常) のいずれか。 予測可能なエラーに 使う。 Scalaの値をラップする型
  22. エラー処理 2017-09-09 実践ScalaでDDD 41 scala.util.Either 異常時に、任意のエラー値を返すことができる。 単純な String のエラーメッセージやエラーコードを返すのではなく、独 自にエラーオブジェクトを作って返すようにする。

    - ドメイン や アプリケーション のエラー処理は Either で。 - 独自のエラーオブジェクトにもスタックトレースを持たせておいたほ うが、エラーの発生箇所の特定が容易。 def assign(id: UserId): Either[TaskAlreadyClosed, Task] = { … }
  23. エラー処理 2017-09-09 実践ScalaでDDD 42 scala.util.Try Javaの例外(Throwable)をラップして戻り値として返せるようにする。 例外が発生した場合、関数の戻り値が Failure(Throwable) になる。 -

    例外が発生し得る処理を Try { … } で囲む。 - インフラストラクチャ では Javaのライブラリ(JDBCドライバなど)を使 うことが多く、エラー時は例外がスローされるので、インフラストラク チャでは Try を使うと楽。 def save(user: User): Try[User] = { … }
  24. エラー処理 2017-09-09 実践ScalaでDDD 43 各レイヤで発生するエラーの特性 ドメインルールの違反など、ユーザーの操 作に起因するエラー。 データストレージやネットワークの障害、連 携する外部サービスのダウンなど、ユー ザーに起因しない想定外のエラー。

    ユースケースの実行が継続できない場合 にエラー。 主にユーザーの操作に起因する。 値の指定誤りなど、ユーザーの操作に起 因するエラー。 エラーメッセージの多言語化が必要かも。 インフラストラクチャ レイヤ インターフェース レイヤ アプリケーション レイヤ ドメイン レイヤ
  25. エラー処理 2017-09-09 実践ScalaでDDD 45 インターフェース アプリケーション サービス リポジトリ イベント パブリッシャ

    リポジトリ実装 イベント パブリッシャ実装 ドメイン サービス エンティティ ファクトリ クエリ プロセッサ クエリ プロセッサ実装 Javaライブラリ (JDBCドライバ等) 例外 Try Either [ドメインエラー] Either [サービスエラー] Try { … } で囲って、 例外 を Try に変換。 Either[ドメインエラー] および Try を、 Either[サービスエラー] に変換。 Ideas by Yoshitaka Fujii (@yoshiyoshifujii)
  26. エラー処理 2017-09-09 実践ScalaでDDD 46 アプリケーションサービスでの変換 アプリケーションサービスで、 - ドメインで発生する Either[ドメインエラー, …]

    - インフラストラクチャで発生する Try を Either[サービスエラー, …] に変換する。 より自然に変換を書けるように、ヘルパー関数を作っておくとアプリ ケーションサービスのコードが煩雑なエラー処理で汚れない。
  27. エラー処理 2017-09-09 実践ScalaでDDD 47 trait TaskService { def assignToTask( …

    ) = { for { // 値がなかったらサービスエラー task <- taskRepository.get(taskId) ifNotExists NotFoundError // ドメインで異常が発生したらサービスエラー assignedTask <- assignee.assignTo(task) ifLeftThen asServiceError // インフラで失敗したらサービスエラー savedTask <- taskRepository.save(assignedTask.entity) ifFailureThen asServiceError _ <- taskEventPublisher.publish(assignedTask.event) ifFailureThen asServiceError } yield savedTask } }
  28. 2017-09-09 実践ScalaでDDD 49 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ 各コンポーネントの実装
  29. 2017-09-09 実践ScalaでDDD 51 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ アプリケーションサービス
  30. アプリケーションサービス 2017-09-09 実践ScalaでDDD 52 - ドメインレイヤを使って、ユースケースを実現することが責務。 - あくまでもドメインを使うのみ。ドメインの知識がアプリケーション サービスに漏れ出さないようにする。 -

    インフラストラクチャレイヤには依存しない。 インフラを変更しても、ユースケースを実現するコードはそのまま動 作できる。ユースケースレベルのユニットテストも容易。 - トランザクション境界になる。 実装のポイント
  31. アプリケーションサービス 2017-09-09 実践ScalaでDDD 53 - アプリケーションサービス のメソッドの処理は、基本的に ①リポジトリ を使って エンティティ

    を取得 or エンティティ を生成。 ②エンティティ or ドメインサービス のメソッドで業務の処理。 ③リポジトリ で処理した エンティティ を保存。 ④イベントパブリッシャ で ドメインイベント を発行。 の流れになる。この流れを for内包式 でつなげる。 - ドメインレイヤ、インフラストラクチャレイヤから発生するエラーを、 ユースケースのエラー に変換する。 実装のポイント
  32. アプリケーションサービス 2017-09-09 実践ScalaでDDD 54 trait TaskService { val userRepository: UserRepository

    val taskRepository: TaskRepository val taskEventPublisher: TaskEventPublisher def createNewTask(name: TaskName, authorId: UserId) = tx { implicit unitOfWork => for { author <- userRepository.get(authorId) ifNotExists NotFoundError createdTask = author.createTask(name) savedTask <- taskRepository.save(createdTask.entity) ifFailureThen asServiceError _ <- taskEventPublisher.publish(createdTask.event) ifFailureThen asServiceError } yield savedTask } } コードサンプル
  33. 2017-09-09 実践ScalaでDDD 55 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ エンティティ
  34. エンティティ 2017-09-09 実践ScalaでDDD 56 - 通常、集約 の中で エンティティ は1つ。(集約ルート) -

    一意な識別子 エンティティID を持つ。 #equals()、#hashCode() をオーバーライドし、エンティティID でオブ ジェクトの同一性を比較。 - プロパティは バリューオブジェクト で。プリミティブな型は使わない。 - 他集約との関連は、関連先のエンティティIDだけを保持し、 エンティティ そのものを保持しない。 実装のポイント
  35. エンティティ 2017-09-09 実践ScalaでDDD 57 - ドメインの振る舞い(ユビキタス言語の動詞句)は、エンティティ のメ ソッドとして実装。 ドメインの振る舞いが アプリケーションサービス

    に寄ってしまうと、エ ンティティ がただのDTOになってしまう。(ドメインモデル貧血症) - エンティティ のメソッドは、戻り値として 状態が変わった エンティティ と ドメインイベント を返す。 - エンティティ のメソッドとして実装できないような振る舞いは、ドメイン サービス に実装。(あまりないと思う) 実装のポイント
  36. エンティティ 2017-09-09 実践ScalaでDDD 58 case class Task( id: TaskId, name:

    TaskName, state: TaskState, authorId: UserId, comments: Comments ) { def close: Either[TaskAlreadyClosed, DomainResult[Task, TaskClosed] = { val task = copy(state = TaskState.Closed) val event = TaskClosed(task.id) Right(DomainResult(task, event)) } : } コードサンプル
  37. 2017-09-09 実践ScalaでDDD 59 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ バリューオブジェクト
  38. バリューオブジェクト 2017-09-09 実践ScalaでDDD 60 - ドメインの名詞句はすべて バリューオブジェクト にする。 - タイプエイリアス(Ex.

    type UserName = String)では型安全にならない ので、ちゃんと型を作る。 - scala.AnyVal を継承する。 実行時の バリューオブジェクト の生成コストを抑えることができる。 - オブジェクトの集合は ファーストクラスコレクション を作り、List や Seq、Map などのコレクション型のまま扱わない。 実装のポイント
  39. バリューオブジェクト 2017-09-09 実践ScalaでDDD 61 - インターフェース や インフラストラクチャ で頻発する バリューオブ

    ジェクト と プリミティブ型 との相互変換は、implicit conversion と 型 クラスパターン を使って自動的に変換されるようにすると実装負担 を軽減できる。 実装のポイント
  40. バリューオブジェクト 2017-09-09 実践ScalaでDDD 62 trait Value[T] extends Any { def

    value: T } case class TaskName(value: String) extends AnyVal // バリューオブジェクト sealed abstract class TaskState(val value: String) // 列挙型 object TaskState { case object Opened extends TaskState("OPENED") case object Closed extends TaskState("CLOSED") } case class Comment(value: String) extends AnyVal case class Comments(comments: Seq[Comment]) { // ファーストクラスコレクション def add(comment: Comment) = copy(comments = comments :+ comment) } コードサンプル
  41. バリューオブジェクト 2017-09-09 実践ScalaでDDD 63 package object infrastructure { // プリミティブ値

    -> バリューオブジェクト に自動変換(型クラスパターン) implicit def wrapValue[P, V <: Value[P]](value: P)(implicit conv: P => V): V = conv(value) // バリューオブジェクト -> プリミティブ値 に自動変換 implicit def unwrapValue[P](value: Value[P]): P = value.value } import infrastructure._ implicit val toTaskName: String => TaskName = (v) => TaskId(v) val taskName: TaskName = "タスク名" // StringがTaskNameに自動変換される val strTaskName: String = taskName // TaskNameがStringに自動変換される コードサンプル
  42. 2017-09-09 実践ScalaでDDD 64 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ ロールオブジェクト
  43. ロールオブジェクト 2017-09-09 実践ScalaでDDD 66 実装のポイント - 関連端(ロール)の名前で型を作る。 - 関連先の エンティティ

    に手を加えることなく、自集約に特有の振る 舞いを追加できる。 - implicit class で定義することで、エンティティ を自動的に ロールオブ ジェクトに変換。 - ロールオブジェクト を主語として、SVO形式で関連を扱うコードが書 ける。
  44. ロールオブジェクト 2017-09-09 実践ScalaでDDD 67 package user { case class User(id:

    UserId) } package task { case class Task(id: TaskId, assigneeId: UserId) { def assign(assignee: User) = copy(assigneeId = assignee.id) } // 「タスクを担当するユーザー」を表現するロールオブジェクト // Userエンティティを修正することなく、assignTo() と言うメソッドを追加。 implicit class Assignee(user: User) { def assignTo(task: Task): Task = task.assign(user) } } コードサンプル
  45. ロールオブジェクト 2017-09-09 実践ScalaでDDD 68 val user: User = … val

    task: Task = … // ロールオブジェクトを作らない場合 val assignedTask = task.assign(user) // ロールオブジェクトを作った場合 // パッケージを import することで、エンティティがロールオブジェクトに自動変換されるようになる import task._ val assignedTask = user.assignTo(task) コードサンプル
  46. 2017-09-09 実践ScalaでDDD 69 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ ファクトリ
  47. ファクトリ 2017-09-09 実践ScalaでDDD 70 - エンティティ の生成には2パターンある。 ①単独で生成される エンティティ。 ②他の集約の

    エンティティ から生成される エンティティ。 - 単独で生成される場合は、その エンティティ のコンパニオンオブジェ クトにファクトリメソッドを実装。 - 他の集約の エンティティ から生成される場合は、ロールオブジェクト にファクトリメソッドを実装。 実装のポイント
  48. ファクトリ 2017-09-09 実践ScalaでDDD 72 // 単独で生成されるパターン case class User(id: UserId,

    name: UserName) object User { def create(name: UserName)(implicit idGenerator: EntityIdGenerator) = User(id = UserId(idGenerator.genId), name = name) } val user = User.create(UserName("Taro")) // 他の集約から生成されるパターン case class Task(id: TaskId, name: TaskName, authorId: UserId) implicit class Author(user: User) { def createTask(name: TaskName)(implicit idGenerator: EntityIdGenerator) = Task(id = TaskId(idGenerator.genId), name = name, authorId = user.id) } val task = user.createTask(TaskName("Example task.")) コードサンプル
  49. 2017-09-09 実践ScalaでDDD 73 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ リポジトリ
  50. リポジトリ 2017-09-09 実践ScalaでDDD 74 - エンティティ を、従属する バリューオブジェクト も含めてまるごと永 続化する。エンティティ

    の一部だけを永続化、とかはない。 - ドメインレイヤ ではインターフェース(トレイト)だけを定義。 実装は インフラストラクチャレイヤ で。 - 通常、以下のメソッドに加えて、いくつかの検索系メソッドを持つ。 ①エンティティID で、エンティティ を1件取得する ②エンティティ を保存する(新規/更新は区別しない) ③エンティティ を削除する(削除があり得ないなら定義しない) 実装のポイント
  51. リポジトリ 2017-09-09 実践ScalaでDDD 75 - リポジトリ で扱うのは、自集約の エンティティ のみ。 他集約の

    エンティティ も含めて扱う(JOINして検索、など)必要が有 る場合は、クエリモデル にする。 - リポジトリ実装では、バリューオブジェクト と プリミティブ値 との相互 変換が多発するので implicit conversion で自動変換すると楽。 実装のポイント
  52. リポジトリ 2017-09-09 実践ScalaでDDD 76 package domain.task trait TaskRepository { def

    get(id: TaskId): Try[Option[Task]] def save(task: Task): Try[Task] def delete(task: Task): Try[Task] } package infrastructure.rdb.task trait TaskRepositoryOnRDB extends TaskRepository { import infrastructure._ // バリューオブジェクトとプリミティブ値の自動変換 import infrastructure.task._ def get(id: TaskId): Try[Option[Task]] = { … } def save(task: Task): Try[Task] = { … } def delete(task: Task): Try[Task] = { … } } コードサンプル
  53. 2017-09-09 実践ScalaでDDD 77 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ ドメインサービス
  54. ドメインサービス 2017-09-09 実践ScalaでDDD 79 object TaskService { def applyBusinessRuleTo(task: Task):

    Task = { … } } val task: Task = … val processedTask = TaskService.applyBusinessRuleTo(task) コードサンプル
  55. 2017-09-09 実践ScalaでDDD 80 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ ドメインイベント・イベントパブリッシャ
  56. ドメインイベント・イベントパブリッシャ 2017-09-09 実践ScalaでDDD 81 - ドメインで起こる「業務上の事象」ごとに、ドメインイベントの型を作る。 (◦◦が作成された、◦◦が変更された…など) - CQRS(Command Query

    Responsibility Segregation)パターンでは、 ドメインイベント を介して、クエリモデル(読み取り専用のビュー)を 非同期に更新する。 - マイクロサービスアーキテクチャでは、サービス同士を非同期に連 携するのに ドメインイベント をやり取りする。 実装のポイント
  57. ドメインイベント・イベントパブリッシャ 2017-09-09 実践ScalaでDDD 82 - ドメインイベント では エンティティ を保持しない。 必要な項目だけをプロパティとして持つ。

    - ドメインイベント は、エンティティ および ドメインサービス のメソッド で生成し、処理結果といっしょに返す。 - イベントパブリッシャ は、ドメインレイヤ ではインターフェース(トレイ ト)だけを定義。実装は インフラストラクチャレイヤ で。 実装のポイント
  58. ドメインイベント・イベントパブリッシャ 2017-09-09 実践ScalaでDDD 83 package domain.task case class TaskCreated( taskId:

    TaskID, taskName: TaskName, authorId: UserId ) case class DomainResult[ENTITY, EVENT](entity: ENTITY, event: EVENT) class Author(…) { // 戻り値として、生成したエンティティと、エンティティが生成されたことを通知するイベントを返す def createTask(…): DomainResult[Task, TaskCreated] = { val task: Task = … DomainResult(task, TaskCreated(task.id, task.name, task.authorId)) } } コードサンプル
  59. ドメインイベント・イベントパブリッシャ 2017-09-09 実践ScalaでDDD 84 trait TaskEventPublisher { def publish(event: TaskCreated):

    Try[TaskCreated] } package infrastructure.kafka.task trait TaskEventPublisherOnKafka extends TaskEventPublisher { def publish(event: TaskCreated): Try[TaskCreated] = { … } } コードサンプル
  60. 2017-09-09 実践ScalaでDDD 85 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ クエリモデル・クエリプロセッサ
  61. クエリモデル・クエリプロセッサ 2017-09-09 実践ScalaでDDD 86 - クエリモデル には、複数の 集約 の情報をJOINして持たせることがで きる。

    - クエリモデル のプロパティに、ドメインの バリューオブジェクト は使 わない。 - クエリプロセッサ には、クエリモデル を取得したり検索するための読 み取りメソッドを定義する。更新系のメソッドは持たない。 - クエリプロセッサ は、クエリレイヤ ではインターフェース(トレイト)だ けを定義。実装は インフラストラクチャレイヤ で。 実装のポイント
  62. クエリモデル・クエリプロセッサ 2017-09-09 実践ScalaでDDD 87 package query case class TaskView( taskId:

    String, taskName: String, authorName: String ) case class TaskSearchResult(hits: Int, items: Seq[TaskView]) trait TaskQueryProcessor { def searchTasks(keyword: String): Try[TaskSearchResult] } package infrastructure.elasticsearch trait TaskQueryProcessorOnES extends TaskQueryProcessor { def searchTasks(keyword: String): Try[TaskSearchResult] = { … } } コードサンプル
  63. 2017-09-09 実践ScalaでDDD 88 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ イベントサブスクライバ
  64. イベントサブスクライバ 2017-09-09 実践ScalaでDDD 89 - ドメインイベント をサブスクライブ(購読)し、クエリモデル を最新化 する。 -

    コンテキスト をまたぐデータ連携では、必要な他コンテキストの ドメ インイベント をサブスクライブし、自コンテキスト内にそのコピーを作 成・使用することもできる。 - イベントサブスクライバ の実装は、イベントキューとして使用するミド ルウェアやサービスに強く依存する。 実装のポイント
  65. 2017-09-09 実践ScalaでDDD 90 ファクトリ リポジトリ ドメイン サービス クエリ プロセッサ クエリ

    モデル イベント パブリッシャ アプリケーション サービス インターフェース エンティティ バリュー オブジェクト ロール オブジェクト ドメイン イベント イベント サブスクライバ インターフェース
  66. インターフェース 2017-09-09 実践ScalaでDDD 91 - アプリケーションサービス を呼び出して、ユースケースを起動する。 - アプリケーション/クエリ/ドメイン に、使用するインフラストラクチャ

    を選択して合成し、実行可能なオブジェクトを作る。 - 実装は採用するフレームワーク(Play Framework、Akka HTTP…etc) による。 実装のポイント
  67. インターフェース 2017-09-09 実践ScalaでDDD 92 trait TaskController { val taskService: TaskService

    def example = { taskService.createNewTask(TaskName("Example", UserId("TEST")) } } class TaskControllerImpl extends TaskController { // 使用するコンポーネントに、インフラストラクチャの実装を合成 override val taskService = new TaskService with TransactionAware { override val userRepository = new UserRepository with UserRepositoryOnRDB override val taskRepository = new TaskRepository with TaskRepositoryOnRDB override val taskEventPublisher = new TaskEventPublisher with TaskEventPublisherOnKafka } } コードサンプル
  68. 継続的な学習が必要 2017-09-09 実践ScalaでDDD 96 - DDDに正解はない。 - 実践して、学び、改良して自分たちなりのスタイルを作っていく。 - 採用するテクノロジは、自分たちのスキルセットと相談して…

    - マイクロサービスアーキテクチャ なら、コンテキストごとにサービス を分割して開発していくので、新しい学びを段階的に導入しやすい。