Skip to content

Instantly share code, notes, and snippets.

@j5ik2o
Last active October 5, 2023 05:22
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save j5ik2o/5682650 to your computer and use it in GitHub Desktop.
Save j5ik2o/5682650 to your computer and use it in GitHub Desktop.
集約ってどんなもの?

グローバルな識別子を持つエンティティが集約になります。

ドメインモデル

  • 売上(Sale)
  • 売上明細(SaleDetail)

コード例

実装を書いてないメソッドもあるけど、適当に脳内でイメージしてね。

ドメイン層

ユビキタス言語にある概念とマッピングできるオブジェクト群を定義しようね。

// Saleはグローバルな識別子を持つエンティティであり、集約(アグリゲート)である。
// 内部にはSaleDetailを内包している。
trait Sale {
  val id : Int
  val name: String
  val saleDetails: Seq[SaleDetail]

  def addSaleDetail(saleDetail: SaleDetail) = saleDetails += saleDetail
  def removeSaleDetail(saleDetail: SaleDetail) = saleDetails -= saleDetail 
}

// SaleDetailはグローバルなエンティティではなく、ローカルなエンティティである。
// Saleを経由しないと参照を得ることができない。(そういう意味ではSale#id, Sale#naemも同じ)
trait SaleDetail {
  val no : Int // 売上内での売上詳細の順序
  val productId: Int // 商品ID
  val amount: Int // 数量
  def price = productRepository.resolveById(product).getPrice * amount // 価格は振る舞いによって導出する
}

// Saleの永続化のためのリポジトリ責務。
trait SaleRepository {
  def store(sale: Sale): Unit
  def resolveBy(id: Int): Sale
}

// DB用のリポジトリ実装
// IOは必ず集約単位で行われる。
trait SaleRepositoryOnDB extends SaleRepository {

  val saleDao: SaleDao
  val saleDetailDao: SaleDetailDao

  def store(sale: Sale): Unit = {
    withTranscation {
      val saleTable = convertToSaleTable(sale)
      val saleDetailTables = convertToSaleDetails(sale)
      saleDao.inserOrUpdate(saleTable)
      saleDetailDao.insertOrUpdates(saleDetailTables)
    }
  }

  def resolveById(id: Int): Sale = {
    val saleTable = saleDao.findById(id)
    val saleDetailTables = saleDetailDao.findBySaleId(id)
    convertToSale(saleTable, saleDetailTables)
  }
 
}

// リポジトリにはいろいろな実装がありえる。
trait SaleRepositoryOnMemory extends SaleRepository { ... }
trait SaleRepositoryOnNoSQL extends SaleRepository { ... }
trait SaleRepositoryOnAPI extends SaleRepository { ... }

インフラ層

RDBMSの気持ちになってちゃんと設計実装しようねって層。もちろんDataMapperじゃなくてActiveRecordでもよいね。 この事例の場合は、ドメインモデル:テーブルモデル が 1:Nになっている。

trait SaleTable {
  val id: Int // 売上ID
  val name: String // 名前
}

trait SaleDetailTable {
  val no: Int // 番号
  val saleId: Int // 売上ID
  val productId: Int // 商品ID
  val amount: Int // 数量
}

trait SaleDao {

  def insertOrUpdate(saleTable: SaleTable) = // SQL発行
  
  def findById(id: Int) = // SQL発行。結果セットをTableに変換

}

trait SaleDetailDao {

  def insertOrUpdate(saleDetailTable: SaleDetailTable) = // SQL発行

  def insertOrUpdates(saleDetailTables: Seq[SaleDetailTable]) = // ...

  def findById(id: Int): SaleDetailTable = // ...

  def findBySaleId(saleId: Int): Seq[SaleDetailTable] = // ...

}
  • traitは実装が書けるインターフェイス。classを書くのが面倒だったのでtraitにした。ここではclassみたいなものと思って読んでもらっていいです。
  • Seq[T]は列です。リストと同様と考えてもらっていいです。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment