dobashi
2019-09-08
DDDの流派の話は置いとく(どうせ新しいのすぐ出るし)
project/
- controller/
- model/
- repo/
- service/
*SPAならviewがない
project/
- controller/
- model/
- service/
- domain/
- persistence|infrastructure
- view/
MVC
Simple版
project/lib/
- project/
- service/
- domain/
- repo
- project_web/
- controller/
- view/
Phoenix
現代ではjsonやprotocol buffersなどの汎用的なプロトコルにして個別に差し替えできるようにすべき
project/
- api/
- service/
- domain/
- ap/
- service/
- repo
- web/
- controller
- view
class UserController(val service: UserService) {
get("/users"), req, res -> {
service.list()
}
}
service apiやdomain定義を言語非依存の設定ファイルにしてWeb/APからそれぞれ見るのはアリ?
-> それならSwaggerとかの方がいい
昔はWeb-AP間通信に言語依存のRPCを使っていた
id | name | zip | prefecture | address | |
---|---|---|---|---|---|
1 | Akihito | a@b.c | 100-0000 | tokyo | Chiyoda1 |
2 | Naruhito | x@y.z | 602-0881 | kyoto | Kamigyou |
Users
id | name | zip | prefecture | address | |
---|---|---|---|---|---|
1 | あさひ株式会社 | a@b.c | 100-0000 | tokyo | Chiyoda1 |
2 | 夕陽(株) | x@y.z | 106-0000 | tokyo | Minato3 |
Companies
都道府県マスタはPKがid: string
data class User (
val id: Long
val name: String
val email: String
val zip: String
val perfecture: String
val address: String
)
data class Company (
val id: Long
val name: String
val email: String
val zip: String
val perfecture: String
val address: String
)
typealias UserId = Long
typealias UserName = String
data class User (
val id: UserId
val name: UserName
val email: Email
val zip: Zip
val address: Address
)
typealias CompanyId = Long
typealias CompanyName = String
data class Company (
val id: CompanyId
val name: CompanyName
val email: Email
val zip: Zip
val address: Address
)
data class email(
val address: String
){
fun isValid(): Boolean = ...
/* 携帯キャリアアドレスかどうか */
fun isCareer(): Boolean = ...
}
typealias TelArea = Int
typealias TelCity = Int
typealias TelLocal = Int
data class Tel(
val area: TelArea
val city: TelCity
val local: TelLocal
){
/* 実在しうる電話番号かどうか */
fun isValid(): Boolean = ...
fun findPrefecture(): List[Prefecture] = ...
companion object {
fun parse(value: String): Tel = ...
}
}
data class Zip(
val major: Int
val minor: Int
){
/* 実在する郵便番号かどうか */
fun isValid(): Boolean = ... // 外部のAPI叩く?
companion object {
fun isValid(value: String): Boolean = isValid(parse(value))
fun isValid(major: Int, minor: Int): Boolean = ...
}
}
data class Address(
val prefecture: Prefecture
val value: String // street address
val building: String
)
Repo
Domain Model
郵便番号が8桁になってもロジックをZipクラスだけに押し込める
Value Object
(Cのsize_t,time_t)
First Class Collection
(☓User[] ○Users)
Tel#findPref()について、pref.-areaは一部多対多の関係になるので、PrefectureTelRelationテーブル、クラスを別途用意した方が良い。
Addressクラス内にCountry, Zipを持ちたいという人もいるかもしれない。
キャッシュするなら問題ないが、DB/Network問い合わせが発生するとモデルからサービスの依存関係が発生するのでモデルのメソッドではなくAddressService#findPref(Tel)にすべき。
ビジネスドメインでは曖昧さや朝令暮改が倍増
ドメインが大きく息の長いプロジェクトほどDDDを採用した方がコードを綺麗に保てる。
という人がアーキテクトとしてプロダクトのライフサイクル終了まで付き合ってくれるならDDDは理想
自分のプロダクトが認められてイスラエルの企業に雇いたいと言われたら行きたいです!
トランザクションスクリプト
テーブルモジュール
ほとんどのケースの業務システムで妥当な選択だと思われる(※個人の感想です)
controllerはユースケースと1:1
serviceはこのアプリが提供するビジネスロジックをきれいな形で提供し、modelで返す
repoがmodelとして機能しているようなF/Wを使う場合は、service配下にテーブルモジュールごとにディレクトリを切って、service,repo,modelをその中に置いたほうが見通しがよくなる場合もある
serviceのI/Oはあえてロジックを持たないDTOとしてserviceがAPIサーバーとする方法もアリ
DDDではアンチパターン(FatModel,ThinServiceであるべき)
精緻なドメイン分析よりサービスにロジックがあるほうが楽