generated at
Always-Valid Domain Model
考え方は昔からあるが、この言葉の出自はVladimir Khorikovさんだと思われる。

要は
> ドメインオブジェクトは、Validな状態でしか生成・存在できないようにしよう
というもの。

Validな状態でしか生成できないようにする
会員のドメインモデルを考える。属性としてemailAddressを持っていて、これは通知メールを送るのに使われる。
Member.java
public class Member { String emailAddress; }

RegisterMemberUseCase.java
public class RegisterMemberUseCase { private SaveMemberPort saverMemberPort; void reister(RegisterMemberCommand command) { // これを呼び忘れると大問題 if (!command.getEmailAddress().matches("^\\S+@\\S+\\.\\S+$")) { throw new IllegalArgumentException(); } Member member = new Member(command.getEmailAddress()); saveMemberPort.save(member); } }

ドメインモデルを生成するのに不正なメールアドレスを渡さないようにする責務が利用側にある。
こうなると、バリデーションを忘れると不正なメールアドレスを持ったMemberオブジェクトが生成されてしまう。

java
class Member { String emailAddress; public boolean isValid() { return emailAddress.matches("^\\S+@\\S+\\.\\S+$"); } }

バリデーションをドメインオブジェクトに移したところで、isValidを呼ぶ責務は依然として呼び出し側にある。

そこで、そもそも不正な状態でオブジェクトを生成できないようにする。
Member.java
@Getter public class Member { String emailAddress; public Member(String emailAddress) { if (!emailAddress.matches("^\\S+@\\S+\\.\\S+$")) { throws new IllegalArgumentException(); } this.emailAddress = emailAddress; } }

属性がアトミックでないとAlways-Validにできないこともある
Eメールアドレスが検証済みでないと通知が送れないとする。これを実現するために先のモデルに、Eメールアドレスが検証済みかどうかのemailVerified属性を追加する。そして、検証用のサービスVerifyEmailServiceを用いて、検証がOKであれば、

java
@Getter public class Member { String emailAddress; boolean emailVerified; } public interface VerifyEmailService { boolean verify(String emailAddress); } public class VerifyEmailUseCase { private VerifyEmailService verifyEmailService; private SaveMemberPort saveMemberPort; public void verify(VerifyEmailCommand command) { if (verifyEmailService.verify(command.getEmailAddress())) { Member member = new Member(command.getEmailAddress(), true); saveMemberPort.save(member); } else { throw new FailToVerifyEmailException(); } } }

こうなるとEメール自体と、EメールをVerifyするアクションとその結果がバラバラになって、ビジネス的整合性を保つ責務が、またもや呼び出し側に行ってしまう。すなわちVerifyを呼び忘れると、Eメールアドレスが未検証なのに、検証済みフラグがONのMemberオブジェクトが作れてしまう。

これを防ぐには、未検証のEメールアドレス(UnverifiedEmailAddress)と検証済みEメールアドレス(VerifiedEmailAddress)の2つの型を定義する。検証済みEメールアドレスの型は、EメールのVerifyアクションを通じてしか生成できないようにし、メール送信機能においても検証済みEメールアドレスのみを受け付けるように書いておけば、誤って未検証Eメールアドレスにメール送信してしまう事故を防げる。

java
@Getter public abstract class EmailAddress { String value; } public class VerifiedEmailAddress extends EmailAddress { VerifiedEmailAddress(String value) { super(value); } } public class UnverifiedEmailAddress extends EmailAddress { public UnverifiedEmailAddress(String value) { super(value); } } public class Member { EmailAddress emailAddress; } public interface VerifyEmailService { VerifiedEmailAddress verify(UnverifiedEmailAddress emailAddress); } public interface SendNotificationService { void send(VerifiedEmailAddress emailAddress); }

というように、常にValidな状態を保つには、
なんでも入る緩い型
暗黙的な複数の属性の関連
を排除し、型によって業務ルールを表していくこと(Design with types)に他ならない。