$30 off During Our Annual Pro Sale. View Details »

Scalaでのドメインモデリングのやりかた

 Scalaでのドメインモデリングのやりかた

実装コードを中心に、ドメインモデルの設計に対する考え方や改善方法についてまとめた資料です。

かとじゅん
PRO

November 09, 2018
Tweet

More Decks by かとじゅん

Other Decks in Programming

Transcript

  1. Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 20128
    かとじゅん(@j5ik2o)
    Scala
    関西Summit 2018 1 / 40

    View Slide

  2. ChatWork
    テックリード
    github/j5ik2o
    scala­ddd­base
    scala­ddd­base­akka­http.g8
    reactive­redis
    reactive­memcached
    翻訳レビュー
    エリックエヴァンスのドメイン駆動設計
    Akka
    実践バイブル
    自己紹介
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 2 / 40

    View Slide

  3. アジェンダ
    以下のためのヒントを順不同で説明します
    1.
    ドメインオブジェクトを見つけるには
    2.
    ドメインオブジェクトを作るには
    3.
    ドメインオブジェクトを育てるには
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 3 / 40

    View Slide

  4. ドメインモデルを生み出す、ドメイン分析は1980
    年代はじめからあるらしい。
    1995
    年発刊のオブジェクト指向方法序説〈基盤編〉ジェームズ マーチン著で、ドメインとい
    う用語が登場する。
    歴史を紐解きたい方は、こちらをごらんください。ドメインもしくはドメインモデルという概
    念が登場する書籍一覧
    FYI:
    ドメインという概念は昔からある
    Domain analysis ­ Wikipedia
    引用:
    In software engineering, domain analysis, or product line analysis, is the process of analyzing related software systems in a
    domain to find their common and variable parts. It is a model of wider business context for the system. The term was coined
    in the early 1980s by James Neighbors. Domain analysis produces domain models using methodologies such as domain
    specific languages, feature tables, facet tables, facet templates, and generic architectures, which describe all of the systems
    in a domain. Several methodologies for domain analysis have been proposed.
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 4 / 40

    View Slide

  5. どこからはじめるか
    ドメインオブジェクトは、「システム」内部で利用される。 ドメインオブジェクトを知るには、システムを取り巻く環境を知
    る必要がある ドメインに造詣が深くなければ、リレーションシップ要件駆動分析(RDRA)
    で要件を洗い出すことを推奨する
    リレーションシップ要件駆動分析(RDRA)
    網羅的で整合性のある要件定義をUML
    の表現力を 使って、要件定義としてまとめる手法
    今回はRDRA
    の話はほとんどないです。知りたいかたは→
    ドメインモデリングの始め方 ­ AWS DevDay Tokyo 2018
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 5 / 40

    View Slide

  6. システム価値
    コンテキストモデル
    要求モデル
    システム外部環境
    業務フロー
    利用シーン
    概念モデル
    システム境界
    ユースケースモデル(今日はここから考える)
    画面/
    帳票モデル(Optional)
    プロトコル/
    イベントモデル(Optional)
    システム
    ドメインモデル
    データモデル
    システムを語るのに不可欠な要素
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 6 / 40

    View Slide

  7. ユースケースからドメインモデルを考える
    カンファレンス告知サイト
    カンファレンス主催者が、カンファレンスを作成する
    カンファレンス主催者が、カンファレンスを公開する
    ユーザが、カンファレンスに参加する
    ユーザが、参加したカンファレンスをキャンセルする
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 7 / 40

    View Slide

  8. ユースケースの分析
    凡例
    A
    はアクター
    B
    はバウンダリ
    E
    はエンティティ(
    ドメインオブジェクト)
    C
    はコントロール
    ユースケース
    カンファレンス主催者(A)
    が、カンファレンス管理画面(B)
    からカンファレンス
    (E)
    を作成する(C)
    カンファレンス主催者(A)
    が、カンファレンス管理画面(B)
    からカンファレンス
    (E)
    を公開する(C)
    ユーザ(A)
    が、カンファレンス告知ページ(B)
    からカンファレンス
    (E)
    に参加する(C)
    ユーザ(A)
    が、カンファレンス告知ページ(B)
    から参加したカンファレンス
    (E)
    をキャンセルする(C)
    カンファレンス主催者(A)
    が、カンファレンス管理画面(B)
    からカンファレンス
    (E)
    の参加者一覧
    (E)
    を確認する(C)
    CRUD
    よりも、ドメイン知識を表す判断・加工・計算を表すコントロールに注目する
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 8 / 40

    View Slide

  9. エンティティ
    カンファレンス(
    集約)
    コントロール
    作成する
    公開する
    参加する
    キャンセルする
    エンティティとコントロール
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 9 / 40

    View Slide

  10. コードにする前に
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 10 / 40

    View Slide

  11. レイヤーを分割しよう
    レイヤーをプロジェクトとして分割し、依存の方向を制御してください
    循環参照を作ってからでは遅いです!
    //
    インフラストラクチャ層
    val infrastructure = (project in file("infrastructure")).settings(...)
    //
    ドメイン層
    val domain = (project in file("domain")).settings(...).dependsOn(infrastructure)
    //
    ユースケース層
    val useCase = (project in file("use-case")).settings(...).dependsOn(domain, infrastructure)
    //
    インターフェイス層
    val interface = (project in file("interface")).settings(...).dependsOn(useCase, infrastructure)
    //
    アプリケーション本体
    val boot = (project in file("boot")).settings(...).dependsOn(interface, infrastructure)
    val root = (project in file(".")).settings(...).dependsOn(boot)
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 11 / 40

    View Slide

  12. 最初のカンファレンス実装
    これはドメインモデルを反映した実装といってよいか?
    case class Conference(id: Long, status: Int, name: String,
    limit: Int participants: Seq[Long])
    MVC
    の父、Trygve Reenskaug
    氏は、モデルは知識の表象と定義した。さて、どれが知識だろうか?
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 12 / 40

    View Slide

  13. 最初のカンファレンス実装
    これはドメインモデルを反映した実装といってよいか?
    case class Conference(id: Long, status: Int, name: String,
    limit: Int participants: Seq[Long])
    MVC
    の父、Trygve Reenskaug
    氏は、モデルは知識の表象と定義した。さて、どれが知識だろうか?
    ドメイン知識の正体は、CRUD
    以外の判断
    /
    加工
    /
    計算の振る舞いである。 このモデルにはそれがない。本当にないか?重要な
    側面を見落としてないだろうか?
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 12 / 40

    View Slide

  14. 実際のコード表現
    参加者の追加
    val conference = Conference(id = 1L, status = 1, name = "Scala
    関西Summit 2018",
    limit = 5000, participants = Seq.empty)
    val newConfernece = conference.copy(
    participants = conference.participants :+ 10L /* userAccountId */
    )
    なんとかならないか。これがDDD
    の真骨頂?
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 13 / 40

    View Slide

  15. 実際のコード表現
    参加者の追加
    val conference = Conference(id = 1L, status = 1, name = "Scala
    関西Summit 2018",
    limit = 5000, participants = Seq.empty)
    val newConfernece = conference.copy(
    participants = conference.participants :+ 10L /* userAccountId */
    )
    なんとかならないか。これがDDD
    の真骨頂?
    ちがいます
    !!!
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 13 / 40

    View Slide

  16. ドメインモデルに知識を集約する
    ユースケースからCRUD
    以外の判断/
    加工/
    計算の関心事を抜き出し割り当てる
    //
    作成するはインスタンスを作成する?
    case class Conference(id: ConferenceId, name: ConferenceName, ... ) {
    //
    非公開と公開で何が違うのか?状態が違うだけか?
    def publish: Conference = ...
    //
    参加するの定義とは?
    def addParticipants(...): Conference = ...
    //
    キャンセルじゃなくて、参加者の削除?
    def removeParticipants(...): Conference = ...
    // "
    の"
    で接続する用語は集約の属性になることが多い
    def getParticipants: Participants = ...
    }
    曖昧な部分がかなりある…
    。 保持する値は値オブジェクトとして実装。
    case class ConferenceId(value: Long) {
    require(value > 0L)
    //
    コード表現に変換できる
    def asCode: Code = Code(value)
    }
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 14 / 40

    View Slide

  17. 集約の境界定義
    カンファレンスの中に何が含まれるのか?
    カンファレンスは特定される必要があるので、何らかの識別子が必要
    カンファレンスのタイトル
    カンファレンスの場所
    カンファレンスの開催時期
    カンファレンスの主催者
    カンファレンスの参加者
    ユースケースだけに頼り切らない。モデルの整合性が成り立つかも検証する
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 15 / 40

    View Slide

  18. エンティティ=集約ではない
    集約は、一つ以上のエンティティと値オブジェクトを包括する
    集約ごとにリポジトリを実装する
    強い整合性の境界を持つ
    隣り合う属性は、強い関連性があり、同じタイミングで更新さ
    れる場合、同一の境界としてふさわしい。
    顧客の名前と住所の変更は同じようなタイミングで更新さ
    れる、など
    上記条件を外れる場合は、適切な集約を見つけて所有権を持つ
    集約を変更し、整合性は結果整合を選択する
    FYI:
    集約とリポジトリの関係
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 16 / 40

    View Slide

  19. 参加者は内部か外部か?
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 17 / 40

    View Slide

  20. どちらがよいか?
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 18 / 40

    View Slide

  21. メリット
    関連の増大を軽減できる
    参加者上限を厳密に守ることができる
    デメリット
    大量の参加者を扱うことは現実的ではない
    大きい集約はロックも大きくなる
    //
    カンファレンス集約
    case class Conference(id: ConferenceId, status: ConferenceStatus, name: ConferenceName,
    limit: ParticipantLimit, participants: Set[Participat]) {
    //
    不変条件の表明
    require(limit.isLessThan(participants))
    //
    閉じた操作によって、コンストラクタで不変条件を検証する
    def addParticipants(otherParticipants: Set[Participant]): Conference =
    copy(participants = participants ++ otherParticipants)
    }
    参加者を含むカンファレンス集約
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 19 / 40

    View Slide

  22. メリット
    ID
    を用いて単独で検索できる
    参加者の件数が多い場合でも対応できる
    デメリット
    弱い整合性になるため、カンファレンスが読めるときに、参加者が読める
    とは限らない。その逆も。
    厳密な参加者上限を設けることができない(
    チェック・ゼン・アクト操作に
    割り込み発生するなど)
    //
    カンファレンス集約
    case class Conference(id: ConferenceId, status: ConferenceStatus,
    name: ConferenceName, limit: ParticipantLimit) { // ...
    }
    //
    参加者集約
    case class Participant(id: ParticipantId, ConferenceId: ConferenceId,
    userAccountId: UserAccount) {
    // ...
    }
    参加者は独立した集約
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 20 / 40

    View Slide

  23. ロックの衝突が起きやすくなる
    ①→
    ②→

    ②:
    ①のロックと衝突・更新の失敗
    ②→
    ③→

    ③①:
    ②のロックの衝突・更新の失敗
    ③→
    ①→

    ②:
    ③と①ロックの衝突・更新の失敗
    複数の集約を更新する操作をRDB
    などのトランザクション
    で束ねることは、結局巨大な集約を定義しているに等し
    い。また、境界定義もユースケースによって変動してしま
    うため、モデルの知識だけでは整合性について把握するこ
    とが難しくなる
    対策についてはアンカンファレンスで話しましょう
    (笑
    FYI:
    こんなユースケースは要注意
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 21 / 40

    View Slide

  24. カンファレンスの状態
    カンファレンスの下書き状態 もしくは 公開状態か
    公開した後に、もう一度非公開にすることもある…
    状態には、非公開と公開の状態があればいいことがわかった
    整理できていないなら、状態遷移図を書くといいかも
    case class Conference(id: ConferenceId, status: ConferenceStatus, limit: ParticipantLimit, name: ConferenceName, ...) {
    // ...
    }
    import enumeratum._
    sealed trait ConferenceStatus extends EnumEntry
    object ConferenceStatus extends Enum[ConferenceStatus] {
    val values = findValues
    case object Private extends ConferenceStatus
    case object Public extends ConferenceStatus
    }
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 22 / 40

    View Slide

  25. 以下のように集約の型(
    トップレベル)
    を別々にしてしまうと、同一集合と
    して扱えなくなります。
    case class PrivateConference(id: PrivateConferenceId, ...)
    case class PublicConference(id: PublicConferenceId, ...)
    リポジトリは集約の型ごとに定義されるので、
    class PrivateConferenceRepository { ... }
    class PublicConferenceRepository { ... }
    別々の集合を扱うことになる
    val privateConferences = privateRepository.resolveByOwnerId(ownerId)
    val publicConferences = publicRepository.resolveByOwnerId(ownerId)
    問題
    :
    状態ごとに集約の型を定義すると
    ...
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 23 / 40

    View Slide

  26. 集約のトップレベルの型を変えずに、サブ型を導入する
    sealed trait Conference { ... }
    case class PrivateConference extends Conference { ... }
    case class PublicConference extends Conference { ... }
    //
    トップレベルに対するリポジトリを定義する
    class ConferenceRepository { ... }
    すべてひとつの集合として扱うことができる
    val conferences: Seq[Conference] = conferenceRepository
    .resolveByOwnerId(ownerId)
    //
    部分集合を取得する問い合わせメソッドがあってもよい
    val conferences: Seq[PrivateConference] = conferenceRepository
    .resolvePrivateByOwnerId(ownerId)
    Conferences.foreach {
    case Conference: PrivateConference =>
    // ...
    case Conference: PublicConference =>
    // ...
    }
    解決
    :
    集約の型にサブ型を導入する
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 24 / 40

    View Slide

  27. VO
    で暗黙的な概念を明示的にする
    ドメイン知識の正体は、判断/
    加工/
    計算の処理。以下のような判断を値オブジェクトの責務にする
    require(limit >= 1)
    require(limit >= participants.size)

    参加者上限という、暗黙的な概念を明示的にするリファクタリング
    require(limit.isLessThen(participants))
    参加者の上限についてはParticipantLimit
    の中に集約できる(DDD
    仕様パターン)
    case class ParticipantLimit(value: Int) {
    require(value >= 1)
    def isLessThen(participants: Seq[Participant]): Boolean =
    if (value >= participants.size) true else false
    }
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 25 / 40

    View Slide

  28. コレクション操作の設計を改善する
    コレクションを操作する処理は、複雑・冗長・見通しが悪くなりがち。設計の意図が汲み取りづらい
    //
    非ファーストクラスコレクション
    case class Item(id: Long, price: Long)
    val items: Set[Item] = Set(...)
    val totalPrice: Long = items.foldLeft(0L)(_ + _.price)
    val filteredByPrice: Set[Item] = items.filter(_.price > 10)
    ファーストクラスコレクションに集約し、意図を明確にする
    //
    ファーストクラスコレクション
    case class Items(values: Seq[Item]) {
    def totalPrice: Long = values.foldLeft(0L)(_ + _.price)
    def filteredByPrice(price: Long): Seq[Item] = values.filter(_.price > price)
    }
    val items: Items = Items(...)
    val totalPrice = items.totalPrice
    val filteredByPrice = items.filteredByPrice(10L)
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 26 / 40

    View Slide

  29. 閉じた操作の導入で凝集度を高める
    DDD
    のしなやか設計に含まれるパターン:
    閉じた操作(CLOSURE OF OPERATIONS)
    半群が持つ性質をモデリングに利用している
    add
    はItems
    にしか依存しないので、余計な概念の混入さけ凝集度を高めて変更に強くできる
    case class Items(values: Seq[Item]) {
    def add(other: Items): Items =
    copy(values = values ++ other.value)
    }
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 27 / 40

    View Slide

  30. 問題
    :Getter
    によるロジックの流出・分散
    プリミティブ型をGet
    して操作するから、ドメイン知識がドメインオブジェクトから流出・分散する
    case class Conference(id: CategoryId, category: String, ...)
    val conference = Conference(id = ..., category = "IT/Database/HBase", ...)
    ちらばったドメインロジックはドメイン層から離れたところに…

    val categorySplits = conference.category.split("/")
    val root = categorySplits.headOption //
    ルートカテゴリの取得?
    val parent = categorySplits(categorySplits.size - 1) //
    親カテゴリの取得?
    無用に複雑で意図も汲み取りにくい
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 28 / 40

    View Slide

  31. 解決
    :
    値オブジェクトへのロジックの集約
    プリミティブ型をGetter
    で返さない
    Get
    して操作するロジック(
    判断/
    加工/
    計算)
    をドメインオブジェクトに集約する
    case class Category(path: String, parent: Option[Category] = None) {
    require(path.nonEmpty)
    require(path.size <= 255)
    def root: Option[Category] = {
    @tailrec
    def loop(current: Category): Option[Category] = current.parent match {
    case None => Some(current)
    case Some(p) => loop(p)
    }
    loop(this)
    }
    def asString: String = parent.map(_.asString).fold(path)(v => s"$v/$path")
    }
    case class Conference(..., category: Category, ...)
    val root = conference.category.root //
    ルートカテゴリを返す
    val parent = conference.category.parent //
    親カテゴリを返す
    val path = conference.category.asString //
    ルートカテゴリを返す
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 29 / 40

    View Slide

  32. Getter
    を使わざるを得ない場合
    JSON
    に変換するなど用途でどうしてもGetter
    が必要な場合がある
    プロパティに長い名前を使うことでロジックの流出を感知できるようにする
    case class Items(breachEncapsulationOfValues: Seq[Item]) {
    private val values = breachEncapsulationOfValues
    def totalPrice: Long = values.foldLeft(0L)(_ + _.price)
    def filteredByPrice(price: Long): Seq[Item] = values.filter(_.price > price)
    }
    Getter
    を使うコードがあれば、リファクタリングのチャンス
    items.breachEncapsulationOfValues.filter(_.price == 100L).foldLeft(0L)(_ + _.price)

    def totalPrice(price: Long): Long = values.filter(_.price > price).foldLeft(0L)(_ + _.price)
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 30 / 40

    View Slide

  33. FYI:
    エンティティの責務と構造
    エンティティの主たる責務は、同一性・連続性。値を保持することが主ではない
    次の例は、責務にふさわしくない
    trait Employee {
    val id: EmployeeId
    val name: EmployeeName
    val emailAddress: EmailAddress
    val pref: Pref
    val cityName: CityName
    val addressName: AddressName
    val buildingName: Option[BuildingName]
    }
    値オブジェクトに集約することが望ましい。振る舞いも値オブジェクトに移動しよう
    trait Employee {
    val id: EmployeeId
    val profile: EmployeeProfile
    }
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 31 / 40

    View Slide

  34. 最終的なカンファレンスの実装
    //
    参加者
    case class Participant(userAccountId: UserAccountId, createdAt: ZonedDateTime) {
    // equals, hashCode
    は userAccountId
    のみに基づく
    }
    //
    ファーストクラスコレクションとしての参加者の集合
    case class Participants(breachEncapsulationOfValues: Set[Participant]) {
    require(values.nonEmpty)
    private val values = breachEncapsulationOfValues
    //
    閉じた操作。他の型と結合できなくなるパターン
    def add(other: Participants): Participants = copy(values = values ++ other.values)
    def size: Int = values.size
    }
    //
    カンファレンス
    case class Conference(id: ConferenceId, status: ConferenceStatus, category: Category,
    name: ConferenceName, limit: ParticipantLimit, participants: Participatns) {
    //
    不変条件の検証
    require(limit.isLessThen(participants))
    //
    閉じた操作
    def addParticipants(participants: Participant*): Conference =
    copy(participants = participants.add(participants))
    }
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 32 / 40

    View Slide

  35. 実際のコード表現
    改善前
    val conference = Conference(id = 1L, status = 1, name = "Scala
    関西Summit 2018",
    limit = 5000, participants = Seq.empty)
    val newConfernece = conference.copy(participants =
    conference.participants.copy(participants =
    conference.participants + 10L /* userAccountId */)
    )
    )
    改善後
    val conference = Conference(id = ConferenceId(1L), status = ConferenceStatus.Private,
    name = ConferenceName("Scala
    関西Summit 2018", en = "Scala Kansai Summit 2018),
    limit = PerticipantLimit(5000), paticipants = Participants.empty)
    val newConference = conference.addParticipants(Particitpant(userAccountId = 10L))
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 33 / 40

    View Slide

  36. 予測可能な設計を目指す
    1.
    できる限りクライアント目線で意図が明白になるI/F
    を設計する
    2.
    結果が予測しやすいように、副作用のない関数(Scala
    は問題になることが少ない)
    3.
    不変条件の表明も忘れずに
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 34 / 40

    View Slide

  37. 意図の明白なインタフェース
    問題
    モデルの使うために実装を見なければわからないようであれば、カプセル化が正しく機能しているとは言えない
    モデル設計の意図がわかりにくければ、誤った使い方をしてしまうかもしれない
    解決
    クラスやプロパティ、メソッドの名前には、必ずユビキタス言語を利用する
    TDD
    のように、利用者の視点から命名することも助けになる
    describe("
    カンファレンスについて") {
    it("
    カンファレンスに参加者を追加できる") {
    conference1.addParticipants(Particitpant(userAccountId)) shouldBe expectedConference
    }
    }
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 35 / 40

    View Slide

  38. 副作用のない関数
    (1/2)
    問題
    副作用が伴う操作は結果の予測が難しい
    結果的に実装を理解しなければならなくなる
    安全に組み合わでできないため、表現力が低下する
    解決
    引数を加工して結果を返すだけのロジックはできるだけ副作用のない関数化する
    状態を変化させる操作(
    コマンド)
    は最小限に留める
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 36 / 40

    View Slide

  39. 副作用のない関数
    (2/2)
    Scala
    ではデフォルトが不変なので意識しなくても実践できるが、mutable
    コレクションなどの可変オブジェクトや、Actor
    を使
    う場合は要注意
    class EmployeActor(id: EmployeeId) extends Actor {
    private var state: Option[Employee] = None
    override def receive: Receive = {
    // ...
    case UpdateDeptId(id, deptId) if this.id == id && state.nonEmpty =>
    //
    状態の可変は最小限に留める
    state = state.map(_.withDeptId(deptId))
    // ...
    }
    }
    case class Employee(id: EmployeeId, name: EmployeName, deptId: DeptId, ...) {
    //
    副作用のない関数
    def withDeptId(value: DeptId): EmployId = copy(deptId = value)
    // copy
    メソッドも隠蔽したい場合は、case class
    やめる…

    }
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 37 / 40

    View Slide

  40. 表明
    不変条件の表明を行う { INV & P } A { INV & Q }
    Immutable
    の場合は INV
    は、コンストラクタで行う
    case class Conference(id: ConferenceId, status: ConferenceStatus, category: Category,
    name: ConferenceName, limit: ParticipantLimit, participants: Participatns) {
    //
    不変条件の検証
    require(limit.isLessThen(participants))
    def addParticipants(participants: Participant*): Conference = {
    //
    ここでのINV
    チェックは不要
    copy(participants = participants.add(participants))
    //
    ここでのINV
    チェックは不要
    }
    }
    コンパイル時の表明として、型で不変条件を表現する方法もある
    final case class NonEmptyList[+A](file:///Users/j5ik2o/Source/slides/domain-modeling-in-scala-scala-ks-2018/head: A, tail: List[A]) ..
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 38 / 40

    View Slide

  41. まとめ
    重要なことは、モデルを軸にコミュニケーションできる状態にすること
    すくなくともユビキタス言語を含むユースケースで相互理解できること
    ドメインモデルもユースケースから探査することで見つけることができる
    この前提のもとで、今回の実装テクニックを生かすと、有益。
    集約の境界定義
    VO
    を有効活用する
    ファーストクラスコレクション
    Getter
    には気をつける
    予測可能な設計を心がける
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 39 / 40

    View Slide

  42. 一緒に働くエンジニアを募集しています!
    http://corp.chatwork.com/ja/recruit/
    Scala
    でのドメインモデリングのやり方
    Scala
    関西Summit 2018 40 / 40

    View Slide