🥵
ドメインモデル貧血症という言葉にとらわれる前に理解すること
前提
バックエンドのMVCアーキテクチャを例にする。単純なブログサービスを考える。
- ユーザーがいて、ユーザーが記事を作成・編集・削除する。
- ユーザーは自分のアカウント情報を編集できる。
モデルは2つ。
- ユーザー
- 記事
フレームワークのアーキテクチャは コントローラー - サービス - レポジトリ の3層とする。それぞれの責務は
- コントローラーはIOに関することのみ興味を持つ
- サービスはユースケースの中心。
- レポジトリはDBに関することのみ興味をもつ。
理解すること
ここでいうモジュールとはそのモデルに関するファイル群のことを指す。例えばユーザーモジュールは
- UserController
- UserService
- UserRepository
のこと。
ロジックを散らばらせない。ロジックの散らばり具合で3例。
別のモジュールにそのモデルのロジックが書いてある。
例: なぜかブログサービスにユーザーアクティベート実装がある。関係ないのであればユーザーサービスでやる。
bad
class BlogService {
activateUser(userId){
const user = await this.userSerivce.findUserById(userId)
if(user.status === 'ONBOAEDING'){
throw new Error('ONBOAEDINGステータスのときのみactivateできます');
}
user.status = 'ACTIVE';
await this.userService.update(user);
}
}
good
class UserService {
activateUser(userId){
const user = await this.userSerivce.findUserById(userId)
if(user.status === 'ONBOAEDING'){
throw new Error('ONBOAEDINGステータスのときのみactivateできます');
}
user.status = 'ACTIVE';
await this.userService.update(user);
}
}
コントローラー - サービス - レポジトリの3層において、サービス以外にモデルのロジックが書いてある。
例: ユーザーアクティベート処理をコントローラーでやってしまっている。サービスにロジックは閉じ込める。
bad
class UserController {
async activate(userId){
const user = await this.userSerivce.findUserById(userId)
if(user.status === 'ONBOAEDING'){
throw new Error('ONBOAEDINGステータスのときのみactivateできます');
}
user.status = 'ACTIVE';
await this.userService.update(user);
}
}
good
class UserController {
async activate(userId){
await this.userSerivce.activate(userId);
}
}
class UserService {
async activate(userId){
const user = await this.userRepository.findUserById(userId)
if(user.status === 'ONBOAEDING'){
throw new Error('ONBOAEDINGステータスのときのみactivateできます');
}
user.status = 'ACTIVE';
await this.userRepository.update(user);
}
}
サービス内において、if文が出てきたり、直接モデルの値を更新している。
bad
class UserService {
async activate(userId){
const user = await this.userRepository.findUserById(userId)
if(user.status === 'ONBOAEDING'){
throw new Error('ONBOAEDINGステータスのときのみactivateできます');
}
user.status = 'ACTIVE';
await this.userRepository.update(user);
}
}
good
class UserService {
async activate(userId){
const user = await this.userRepository.findUserById(userId)
user.activate();
await this.userRepository.update(user);
}
}
class User {
// 省略
activate(){
if(user.status === 'ONBOAEDING'){
throw new Error('ONBOAEDINGステータスのときのみactivateできます');
}
this.status = 'ACTIVE';
}
}
まとめ
ポイントは
- 再利用性
- 修正用意性
そのために
- ロジックをできる限りまとめる
ロジックとは基本的には
- if文などの条件
- モデルの値の変更
- 整合性を保つためのルール(例えばcという値は必ずaとbの足し算担っていないといけないなど)
そうすれば「コアはシンプルに保ち、周辺部は多様にする、ツリー型のワークフロー」になる。
Discussion