グローバルな識別子を持つエンティティが集約になります。
- 売上(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]は列です。リストと同様と考えてもらっていいです。