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 1
    at Mt.Fuji 2014
    実践
    ScalaでDDD
    DDD on Scala

    View full-size slide


  2. 2017-09-09 実践ScalaでDDD 2
    辻 陽平
    Yohei TSUJI

    フリーランスのプログラマ
    - Java 17年くらい、Scala 2年弱
    - 主にエンタープライズ系システムの
    ソフトウェアアーキテクトを担当
    - 現在は Scala / AWS / DDD で
    クラウドサービスの開発中
    - 車好き、F1好き、たまに登山
    Twitter / GitHub / Qiita
    @crossroad0201

    View full-size slide

  3. このセッションについて
    2017-09-09 実践ScalaでDDD 3
    こんなエンジニアのみなさまに…
    - DDD(ドメイン駆動設計)は実践していて、Scalaが気になっている。
    - Scalaは使っていて、DDDを導入しようと思っている。
    - ScalaもDDDもやってないが、良さそうならやってみたい。
    - ScalaもDDDもやっていて、なにか役に立ちそうな情報があれば….

    View full-size slide

  4. コンテンツ
    2017-09-09 実践ScalaでDDD 4

    View full-size slide

  5. 1. DDDとは
    What is DDD
    2017-09-09 実践ScalaでDDD 5

    View full-size slide

  6. DDD(ドメイン駆動設計)とは
    2017-09-09 実践ScalaでDDD 6
    オブジェクト指向で
    変更が容易なソフトウェア を開発するための
    体系化された原則集
    - 顧客も開発者も、共通の「ユビキタス言語」で会話。
    - 業務の関心事で「コンテキスト」を分割する。
    - ソフトウェアを構成するコンポーネントとその責務の定義。
    …etc

    View full-size slide

  7. DDD(ドメイン駆動設計)とは
    2017-09-09 実践ScalaでDDD 7
    ドメインの知識を
    ドメインの言葉で
    コードに落とし込む
    - ドメイン・・・システム化しようとする対象業務
    - ドメインの言葉・・・ユビキタス言語

    View full-size slide

  8. DDD(ドメイン駆動設計)とは
    2017-09-09 実践ScalaでDDD 8
    エリック・エヴァンスのドメイン駆動設計
    実践ドメイン駆動設計
    なにはともあれ…
    ただし、関数型言語で副作用を起こさずにDDDを実践するためには
    解釈の仕方や設計に工夫が必要…

    View full-size slide

  9. DDDの登場人物
    (コンポーネント)
    2017-09-09 実践ScalaでDDD 9

    View full-size slide

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

    View full-size slide

  11. CQRS(Command Query Responsibility Segregation)
    DDDのコンポーネント(2 / 2)
    2017-09-09 実践ScalaでDDD 11
    ドメインイベント
    ドメインで発生する、ビジ
    ネス上の事象を表現する
    オブジェクト。
    イベントサブスクライバ
    「ドメインイベント」を受信
    して「クエリモデル」を更
    新し、ビューを最新に保
    つ。
    イベントパブリッシャ
    「ドメインイベント」をメッ
    セージキューに発行する。
    クエリプロセッサ
    「クエリモデル」を検索す
    る。
    クエリモデル
    ドメインをある切り口で見
    た読み取り専用のビュー。
    複数の「集約」を合成した
    ビューも定義できる。
    アプリケーション
    サービス
    ドメインの各コンポーネン
    トを使って、ユースケース
    を実現する。

    View full-size slide

  12. 2. Scalaと
    DDD
    Scala with DDD
    2017-09-09 実践ScalaでDDD 12

    View full-size slide

  13. Scala と DDD は…
    2017-09-09 実践ScalaでDDD 13

    View full-size slide

  14. 相性がいい!
    2017-09-09 実践ScalaでDDD 14

    View full-size slide

  15. ドメインの仕様を自然に表現できる
    2017-09-09 実践ScalaでDDD 15
    // ユーザを取得して、もし存在しなければエラー
    val user = userRepository.get(userId) ifNotExists NotFoundError
    - レシーバー と メソッド の区切りに半角スペースが使える。
    - メソッドの引数が1つだけなら、( ) を省略できる。
    - もともとの言語仕様かのように、表現力を拡張していける。
    コードが自然文(英文)の仕様書になる。

    View full-size slide

  16. オブジェクト指向と関数型
    2017-09-09 実践ScalaでDDD 16
    ドメインの静的側面(概念モデル)は
    オブジェクト指向で。
    動的側面(ユースケース)は関数型で。
    両方のいいとこ取りができる。
    静的な型を持つオブジェクト指向言語であり、関数型言語でもある。
    case class User(id: UserId, name: UserName) {
    val renameTo: UserName => User = name => copy(name = name)

    View full-size slide

  17. case classで簡単に型が作れる
    2017-09-09 実践ScalaでDDD 17
    case class User(id: UserId, name: UserName)
    - コンストラクタは定義しなくていい。
    - Getter / Setter も定義しなくていい。
    - #equals()、#hashCode()、#toString() のデフォルト実装。
    - #copy() メソッドでイミュータブルなプログラミングが楽。
    大量の型を作るDDDではコード量削減効果大

    View full-size slide

  18. 変数と関数の区別がない
    2017-09-09 実践ScalaでDDD 18
    class Example {
    val foo: String = …
    }
    new Example().foo
    プロパティ <-> 導出プロパティの変更が容易
    class Example {
    def foo: String = …
    }
    new Example().foo // 影響なし

    View full-size slide

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

    View full-size slide

  20. Scala programming style
    3.Scala実装
    スタイル
    2017-09-09 実践ScalaでDDD 20

    View full-size slide

  21. 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() メソッドで簡単に実装できる。

    View full-size slide

  22. 2017-09-09 実践ScalaでDDD 22
    副作用を起こさない(局所化する)
    - 戻り値として返らないものはすべて副作用。
    - ソフトウェアでは必ず何らかの永続化処理(副作用)を伴うので、副
    作用を完全に無くすことはできないが、副作用を起こすコードを局所
    化することはできる。
    - DDDにおいては、コアとなる ドメインロジック から副作用を排除する
    ことが重要。

    View full-size slide

  23. 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(…)

    View full-size slide

  24. 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内包式で処理ステップをつなげて
    処理フローを作る

    View full-size slide

  25. 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 で暗黙的に渡すことでドメインロジックから
    ノイズを排除する。

    View full-size slide

  26. 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")

    View full-size slide

  27. 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 などに略し
    がち…
    むやみに略すとコードが
    数学の方程式のように
    なって読みづらくなる。

    View full-size slide

  28. 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
    かどうかが変数名からパッと
    見てわかると便利。

    View full-size slide

  29. 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
    式は自動フォーマットで
    揃えると、コードがグッと
    読みやすくなる。

    View full-size slide

  30. 4.アーキ
    テクチャ
    Architecture
    2017-09-09 実践ScalaでDDD 30

    View full-size slide

  31. レイヤ構成
    2017-09-09 実践ScalaでDDD 31

    View full-size slide

  32. レイヤと責務
    2017-09-09 実践ScalaでDDD 32
    ドメインの知識をモデル化。
    名詞句は型(クラス)に、動詞句は振る舞
    い(メソッド)として実装する。
    ドメインモデル の状態を永続化する。
    永続化処理は 副作用 になる。
    ドメインモデル を使用して、ユースケース
    を実現する。
    Web画面 や REST API、Command Line
    Interface など、ユースケースを起動するた
    めのユーザーインターフェースを提供する。
    インフラストラクチャ レイヤ インターフェース レイヤ
    アプリケーション レイヤ
    ドメイン レイヤ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. レイヤ構成
    2017-09-09 実践ScalaでDDD 35
    オニオンアーキテクチャ
    The Onion Architecture : part 1
    http://jeffreypalermo.com/blog/the
    -onion-architecture-part-1/
    - ヘキサゴナルアーキテクチャ も
    基本的な考え方は同じ。
    - クリーンアーキテクチャは、より
    GUIアプリに特化?

    View full-size slide

  36. レイヤ構成
    2017-09-09 実践ScalaでDDD 36
    インターフェース
    アプリケーション ドメイン
    インフラストラクチャ
    ドメイン も アプリケーション も イ
    ンフラストラクチャ に依存しない。
    - インターフェース で、
    アプリケーションとドメイン に
    インフラストラクチャ を合成
    (依存性注入)して使う。
    オニオンアーキテクチャ

    View full-size slide

  37. エラー処理
    2017-09-09 実践ScalaでDDD 37

    View full-size slide

  38. エラー処理
    2017-09-09 実践ScalaでDDD 38
    Scalaでは例外をスローしない
    - 関数型プログラミングでは、戻り値として返らない例外すら副作用。
    - エラー処理では例外をスローするのではなく、
    戻り値を 正常 or エラー でラップして返す。
    - 戻り値をラップする型を揃えることで、エラー処理が書きやすくなる。

    View full-size slide

  39. エラー処理
    2017-09-09 実践ScalaでDDD 39
    Option
    Some(値がある)
    or
    None(値がない)
    のいずれか。
    Try
    Success(成功)
    or
    Failure(失敗)
    のいずれか。
    予測不能なエラーに
    使う。
    Either
    Right(正常)
    or
    Left(異常)
    のいずれか。
    予測可能なエラーに
    使う。
    Scalaの値をラップする型

    View full-size slide

  40. エラー処理
    2017-09-09 実践ScalaでDDD 40
    scala.Option
    ドメインの概念上、あったりなかったりする値を表現するために使う。
    エラーの内容を表現できないので、エラー処理では使わない。
    - リポジトリ の取得系メソッドで、該当する エンティティ が存在しない
    かもしれないとき。
    - エンティティ で、値が任意のプロパティ。
    def getUser(id: UserId): Option[User] = { … }

    View full-size slide

  41. エラー処理
    2017-09-09 実践ScalaでDDD 41
    scala.util.Either
    異常時に、任意のエラー値を返すことができる。
    単純な String のエラーメッセージやエラーコードを返すのではなく、独
    自にエラーオブジェクトを作って返すようにする。
    - ドメイン や アプリケーション のエラー処理は Either で。
    - 独自のエラーオブジェクトにもスタックトレースを持たせておいたほ
    うが、エラーの発生箇所の特定が容易。
    def assign(id: UserId): Either[TaskAlreadyClosed, Task] = { … }

    View full-size slide

  42. エラー処理
    2017-09-09 実践ScalaでDDD 42
    scala.util.Try
    Javaの例外(Throwable)をラップして戻り値として返せるようにする。
    例外が発生した場合、関数の戻り値が Failure(Throwable) になる。
    - 例外が発生し得る処理を Try { … } で囲む。
    - インフラストラクチャ では Javaのライブラリ(JDBCドライバなど)を使
    うことが多く、エラー時は例外がスローされるので、インフラストラク
    チャでは Try を使うと楽。
    def save(user: User): Try[User] = { … }

    View full-size slide

  43. エラー処理
    2017-09-09 実践ScalaでDDD 43
    各レイヤで発生するエラーの特性
    ドメインルールの違反など、ユーザーの操
    作に起因するエラー。
    データストレージやネットワークの障害、連
    携する外部サービスのダウンなど、ユー
    ザーに起因しない想定外のエラー。
    ユースケースの実行が継続できない場合
    にエラー。
    主にユーザーの操作に起因する。
    値の指定誤りなど、ユーザーの操作に起
    因するエラー。
    エラーメッセージの多言語化が必要かも。
    インフラストラクチャ レイヤ インターフェース レイヤ
    アプリケーション レイヤ
    ドメイン レイヤ

    View full-size slide

  44. エラー処理
    2017-09-09 実践ScalaでDDD 44
    エラーの変換
    利用者にはユーザーフレンドリーなエラー(何が、どう不正で、どうす
    れば解決できるのか)を返したいが、ドメイン や インフラストラクチャ
    でユーザーインターフェースを意識すべきではない。
    ドメイン や インフラストラクチャ で発生したエラーを、上位レイヤで
    ユーザーフレンドリーなエラーに変換(読み替え)する必要がある。

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  47. エラー処理
    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
    }
    }

    View full-size slide

  48. 5.コンポーネ
    ントの実装
    Implementing Components
    2017-09-09 実践ScalaでDDD 48

    View full-size slide

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

    View full-size slide

  50. 50
    重視すること
    アプリケーションサービスのコードが、
    仕様書(ユースケース記述)になることを目指す。
    - アプリケーションサービスが、ドメインのクライアント(利用者)であり、
    ドメインの表現の豊かさを映す鏡。
    - アプリケーションサービスのコードを自然に読み書きできるように
    ドメインを作る。

    View full-size slide

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

    View full-size slide

  52. アプリケーションサービス
    2017-09-09 実践ScalaでDDD 52
    - ドメインレイヤを使って、ユースケースを実現することが責務。
    - あくまでもドメインを使うのみ。ドメインの知識がアプリケーション
    サービスに漏れ出さないようにする。
    - インフラストラクチャレイヤには依存しない。
    インフラを変更しても、ユースケースを実現するコードはそのまま動
    作できる。ユースケースレベルのユニットテストも容易。
    - トランザクション境界になる。
    実装のポイント

    View full-size slide

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

    View full-size slide

  54. アプリケーションサービス
    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
    }
    }
    コードサンプル

    View full-size slide

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

    View full-size slide

  56. エンティティ
    2017-09-09 実践ScalaでDDD 56
    - 通常、集約 の中で エンティティ は1つ。(集約ルート)
    - 一意な識別子 エンティティID を持つ。
    #equals()、#hashCode() をオーバーライドし、エンティティID でオブ
    ジェクトの同一性を比較。
    - プロパティは バリューオブジェクト で。プリミティブな型は使わない。
    - 他集約との関連は、関連先のエンティティIDだけを保持し、
    エンティティ そのものを保持しない。
    実装のポイント

    View full-size slide

  57. エンティティ
    2017-09-09 実践ScalaでDDD 57
    - ドメインの振る舞い(ユビキタス言語の動詞句)は、エンティティ のメ
    ソッドとして実装。
    ドメインの振る舞いが アプリケーションサービス に寄ってしまうと、エ
    ンティティ がただのDTOになってしまう。(ドメインモデル貧血症)
    - エンティティ のメソッドは、戻り値として 状態が変わった エンティティ
    と ドメインイベント を返す。
    - エンティティ のメソッドとして実装できないような振る舞いは、ドメイン
    サービス に実装。(あまりないと思う)
    実装のポイント

    View full-size slide

  58. エンティティ
    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))
    }
    :
    }
    コードサンプル

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  61. バリューオブジェクト
    2017-09-09 実践ScalaでDDD 61
    - インターフェース や インフラストラクチャ で頻発する バリューオブ
    ジェクト と プリミティブ型 との相互変換は、implicit conversion と 型
    クラスパターン を使って自動的に変換されるようにすると実装負担
    を軽減できる。
    実装のポイント

    View full-size slide

  62. バリューオブジェクト
    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)
    }
    コードサンプル

    View full-size slide

  63. バリューオブジェクト
    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に自動変換される
    コードサンプル

    View full-size slide

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

    View full-size slide

  65. ロールオブジェクト
    2017-09-09 実践ScalaでDDD 65
    - DDDでは特に定義されていない。(と思う)
    - 集約 をまたいだ エンティティ と エンティティ の関連をモデル化。
    実装のポイント

    View full-size slide

  66. ロールオブジェクト
    2017-09-09 実践ScalaでDDD 66
    実装のポイント
    - 関連端(ロール)の名前で型を作る。
    - 関連先の エンティティ に手を加えることなく、自集約に特有の振る
    舞いを追加できる。
    - implicit class で定義することで、エンティティ を自動的に ロールオブ
    ジェクトに変換。
    - ロールオブジェクト を主語として、SVO形式で関連を扱うコードが書
    ける。

    View full-size slide

  67. ロールオブジェクト
    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)
    }
    }
    コードサンプル

    View full-size slide

  68. ロールオブジェクト
    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)
    コードサンプル

    View full-size slide

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

    View full-size slide

  70. ファクトリ
    2017-09-09 実践ScalaでDDD 70
    - エンティティ の生成には2パターンある。
    ①単独で生成される エンティティ。
    ②他の集約の エンティティ から生成される エンティティ。
    - 単独で生成される場合は、その エンティティ のコンパニオンオブジェ
    クトにファクトリメソッドを実装。
    - 他の集約の エンティティ から生成される場合は、ロールオブジェクト
    にファクトリメソッドを実装。
    実装のポイント

    View full-size slide

  71. ファクトリ
    2017-09-09 実践ScalaでDDD 71
    - エンティティIDの生成はインフラストラクチャに依存することが多いの
    で、ファクトリ にはID生成器を外部から与えるようにしおく。
    ユニットテストで、生成されるIDを制御できるのでテストしやすい。
    実装のポイント

    View full-size slide

  72. ファクトリ
    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."))
    コードサンプル

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  76. リポジトリ
    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] = { … }
    }
    コードサンプル

    View full-size slide

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

    View full-size slide

  78. ドメインサービス
    2017-09-09 実践ScalaでDDD 78
    - ドメインのビジネスルールを実装した関数の集まり。
    - エンティティ のメソッドとしては実装できないような、複雑なドメインの
    処理を実装。必要であれば作る、くらいで作る機会はあまりない。
    - ファサード ではない。
    ドメインサービス からは、リポジトリ や イベントパブリッシャ は使わ
    ず、副作用を起こさないようにする。
    実装のポイント

    View full-size slide

  79. ドメインサービス
    2017-09-09 実践ScalaでDDD 79
    object TaskService {
    def applyBusinessRuleTo(task: Task): Task = { … }
    }
    val task: Task = …
    val processedTask = TaskService.applyBusinessRuleTo(task)
    コードサンプル

    View full-size slide

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

    View full-size slide

  81. ドメインイベント・イベントパブリッシャ
    2017-09-09 実践ScalaでDDD 81
    - ドメインで起こる「業務上の事象」ごとに、ドメインイベントの型を作る。
    (○○が作成された、○○が変更された…など)
    - CQRS(Command Query Responsibility Segregation)パターンでは、
    ドメインイベント を介して、クエリモデル(読み取り専用のビュー)を
    非同期に更新する。
    - マイクロサービスアーキテクチャでは、サービス同士を非同期に連
    携するのに ドメインイベント をやり取りする。
    実装のポイント

    View full-size slide

  82. ドメインイベント・イベントパブリッシャ
    2017-09-09 実践ScalaでDDD 82
    - ドメインイベント では エンティティ を保持しない。
    必要な項目だけをプロパティとして持つ。
    - ドメインイベント は、エンティティ および ドメインサービス のメソッド
    で生成し、処理結果といっしょに返す。
    - イベントパブリッシャ は、ドメインレイヤ ではインターフェース(トレイ
    ト)だけを定義。実装は インフラストラクチャレイヤ で。
    実装のポイント

    View full-size slide

  83. ドメインイベント・イベントパブリッシャ
    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))
    }
    }
    コードサンプル

    View full-size slide

  84. ドメインイベント・イベントパブリッシャ
    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] = { … }
    }
    コードサンプル

    View full-size slide

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

    View full-size slide

  86. クエリモデル・クエリプロセッサ
    2017-09-09 実践ScalaでDDD 86
    - クエリモデル には、複数の 集約 の情報をJOINして持たせることがで
    きる。
    - クエリモデル のプロパティに、ドメインの バリューオブジェクト は使
    わない。
    - クエリプロセッサ には、クエリモデル を取得したり検索するための読
    み取りメソッドを定義する。更新系のメソッドは持たない。
    - クエリプロセッサ は、クエリレイヤ ではインターフェース(トレイト)だ
    けを定義。実装は インフラストラクチャレイヤ で。
    実装のポイント

    View full-size slide

  87. クエリモデル・クエリプロセッサ
    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] = { … }
    }
    コードサンプル

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  91. インターフェース
    2017-09-09 実践ScalaでDDD 91
    - アプリケーションサービス を呼び出して、ユースケースを起動する。
    - アプリケーション/クエリ/ドメイン に、使用するインフラストラクチャ
    を選択して合成し、実行可能なオブジェクトを作る。
    - 実装は採用するフレームワーク(Play Framework、Akka HTTP…etc)
    による。
    実装のポイント

    View full-size slide

  92. インターフェース
    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
    }
    }
    コードサンプル

    View full-size slide

  93. 2017-09-09 実践ScalaでDDD 93
    実装例は、GitHubのサンプルコードを参照してください。
    https://github.com/crossroad0201/ddd-on-scala

    View full-size slide

  94. Practicing DDD
    6.DDD
    の実践
    2017-09-09 実践ScalaでDDD 94

    View full-size slide

  95. さらにこの先にあるもの…
    2017-09-09 実践ScalaでDDD 95
    以下のようなテクノロジを統合していく。
    - イベントソーシング
    ドメインオブジェクトの永続化で、データをUPDATEするのではなく、
    イベントをINSERTしていくことで状態を保存。
    取得時は蓄積されているイベントを順番に再適用(リプレイ)するこ
    とで、ドメインオブジェクトの状態を復元する。
    - アクターモデル
    Akka Stream を使ったアクターモデルによるプログラミング。

    View full-size slide

  96. 継続的な学習が必要
    2017-09-09 実践ScalaでDDD 96
    - DDDに正解はない。
    - 実践して、学び、改良して自分たちなりのスタイルを作っていく。
    - 採用するテクノロジは、自分たちのスキルセットと相談して…
    - マイクロサービスアーキテクチャ なら、コンテキストごとにサービス
    を分割して開発していくので、新しい学びを段階的に導入しやすい。

    View full-size slide

  97. 2017-09-09 実践ScalaでDDD 97
    at Mt.Fuji 2016

    View full-size slide