DDDとは#
- ドメイン駆動設計
- ドメインとはなにか
- ドメインに駆動して設計するということはつまり「自分たちのビジネスを中心に考えてソフトウェアの設計をしましょう」
DDDをやる理由#
- 自分たちが儲けるため(課題を解決するため)のソフトウェアをつくるため
- ソフトウェアの制約のせいで儲けを追求できない->ダメ
ドメイン層(Entity)#
エンティティとは#
- そのIdentityによって区別されるもの
- 可変である
値オブジェクト#
- エンティティじゃないもの
- その値によって価値が決まる
実装寄りの話#
- ドメインモデル貧血症
- ドメインロジックが集まっている
class Human {
id: string
height: int
weight: int
int BMI() {
return height * height / weight
}
}
// 実際に使うとき
const nanakani = new Human(168, 60)
nanakani.BMI()
class Human {
id: string
height: int
weight: int
}
// 実際
const nanakani = new Human(168, 60)
const nanakaniBmi = nanakani.height * nanakani.height / nanakani.weight
const jinYang = ...
const jinYangBmi = jinYang.height ...
ドメインサービス#
- (複数の)ドメインモデルを引数にとってドメインモデルを返す(返さなくてもいい)関数
- 複数のドメインを扱うときにつかう
- 使いすぎに注意
- Entityや値オブジェクトにもたせると不自然なもののみ持たせる
- code:身長比較
function shinchouHikaku(a: Human, b: Human) -> int {
return a.height - b.height
}
function shinchouHikaku(a: Human, b: Human) -> int {
if a.height > b.height {
return a.height - b.height
} else {
return b.height - a.height
}
}
class Human {
id: string
height: int
weight: int
friends: Human[]
void addFriend(other: Human) {
self.friends.add(other)
}
}
function makeFriend(a: Human, b: Human) {
a.addFriend(b)
b.addFriend(a)
}
class Human {
id: string
pets: Pet[]
}
class Pet {
id: string
owner: Human
}
function makePet(h: Human, p: Pet) {
h.addPets(p)
p.selectOwner(h)
}
Repository#
- Identityを素にEntityを復元(再構築)したり保存したりする
- ドメイン層(アプリケーション層)では、その振る舞いのみ定義する
- 存在だけ認知している/どういった概念かだけ知っている
- Repository例
interface HumanRepository {
fromIdentity(id: string) -> Human
find170OverHumans() -> Human[]
save(humans: Human[])
}
- その中だけは整合性が取れてないといけないという境界
- 集約単位で保存しましょうねというRepositoryの方針がある
Factory#
アプリケーション層(Usecase)#
- アプリケーションとは何か
- お金を稼がないもの
- 実際にドメインモデルを協調させるやつ
- ドメインオブジェクトを操作して、ある特定の利用者の目的を達成するように導くサービス
- フレンド登録ユースケース
class MakeFriendUsecase {
humanRepository: humanRepository
constructor(humanRepository: HumanRepository) {
this.humanRepository = humanRepository
}
void makeFriend(a: id{string}, others: id{string}[]) {
const humanA = this.humanRepository.fromIdentity(a)
const humans = this.humanRepository.fromIdentity(a)
for other in others {
const human = this.humanRepository.fromIdentity(a)
domainService.makeFriend(humanA, human)
}
this.humanRepository.save([humanA, ...humans])
}
void make170Friend(a: id{string}) {
const humanA = this.humanRepository.fromIdentity(a)
const humans = this.humanRepository.find170OverHumans()
for other in others {
const human = this.humanRepository.fromIdentity(a)
domainService.makeFriend(humanA, human)
}
this.humanRepository.save([humanA, ...humans])
}
}
インフラ層#
- お金を消費するもの
- 具体的な実装の層
- DBはMySQLを使う、フレームワークはHono/Railsを使うとか
- サーバーのエンドポイントがここにある
- main関数から起動する実装
function main() {
const db = MySQL::new("127.0.0.1:3306")
const makeFriendUsecase = new MakeFriendUsecase(new MysqlHumanRepositoryImpl(db))
// テストなら↓
const testDB = testdb()
const makeFriendUsecase = new MakeFriendUsecase(new TestMysqlHumanRepositoryImpl(db)
// ↓↓↓クリーンアーキテクチャならこの辺がAdapter層として分離される
const route = Route::new("get", (req: Request) => {
const a = req.body.self
const others = req.body.others
makeFriendUsecase.makefriend(a, others)
return new Response({ message: "success" })
})
// ↑↑↑
app.run(route)
}
- Repositoryの実装をここで行う
- code:RepositoryImpl
class MysqlHumanRepositoryImpl implements HumanRepository {
db: MysdbqlDB
constructor(db: MysdbqlDB) {
this.db = db
}
fromIdentity(id: string) -> Human {
const record = this.db.from("humans").select("*").where("id = ${id}")
return new Human({ id: record[0], height: record[1], weight: record[2] })
}
save(humans: Human[]) {
省略
}
170() {}
}
class TestMysqlHumanRepositoryImpl implements HumanRepository {
db: []
constructor() {
this.db = []
}
fromIdentity(id: string) -> Human {
const record = this.db.find(id)
return new Human({ id: record[0], height: record[1], weight: record[2] })
}
save(humans: Human[]) {
this.db.append()
}
170() {}
}