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 Slide


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

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 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 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 Slide

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

    View 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 Slide

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

    View 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 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 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 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 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 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 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 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 Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide