
Specification(スペック)パターンは、DDDの中でも“ビジネスルールを柔軟に表現する”ための重要な設計パターンです。
「条件判定をオブジェクト化」して、複雑な条件ロジックをスッキリ整理する目的で使われます。
複雑な if 文や where 条件をカプセル化し、再利用・組み合わせ可能にするパターンなので、覚えておくと効率化が増すでしょう。
そして、「ビジネスルールの表現力を上げる」ためのパターンと言ってもいいでしょう。
課題に対する目的
このSpecificationパターンは、次のような課題に対応する事を目的としています。
「if 文が増えてコードが読みにくい」場合に、条件をクラスにまとめて読みやすくしたり、
「同じ条件を何度も書いてしまう」場合に、再利用できるようにすることができます。
また、「条件を動的に組み合わせたい」時に、AND / OR / NOT、などで合成できるのが特徴です。
サンプルコード
基本構造
class Specification {
isSatisfiedBy(candidate) {
throw new Error("must implement isSatisfiedBy()");
}
and(other) {
return new AndSpecification(this, other);
}
or(other) {
return new OrSpecification(this, other);
}
not() {
return new NotSpecification(this);
}
}
class AndSpecification extends Specification {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
isSatisfiedBy(candidate) {
return this.left.isSatisfiedBy(candidate) && this.right.isSatisfiedBy(candidate);
}
}
class OrSpecification extends Specification {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
isSatisfiedBy(candidate) {
return this.left.isSatisfiedBy(candidate) || this.right.isSatisfiedBy(candidate);
}
}
class NotSpecification extends Specification {
constructor(spec) {
super();
this.spec = spec;
}
isSatisfiedBy(candidate) {
return !this.spec.isSatisfiedBy(candidate);
}
}
ユーザーの会員資格チェックの実行例
次の条件で実行するサンプルです。
・年齢が18歳以上
・メール認証済み
・プレミアム会員であること
class AdultSpecification extends Specification {
isSatisfiedBy(user) {
return user.age >= 18;
}
}
class VerifiedSpecification extends Specification {
isSatisfiedBy(user) {
return user.emailVerified === true;
}
}
class PremiumSpecification extends Specification {
isSatisfiedBy(user) {
return user.isPremium === true;
}
}
// 条件を合成
const validMemberSpec = new AdultSpecification()
.and(new VerifiedSpecification())
.and(new PremiumSpecification());
// 判定
const user = { age: 25, emailVerified: true, isPremium: false };
console.log(validMemberSpec.isSatisfiedBy(user)); // false
応用例(実際のDDD文脈で)
・OrderCanBeCancelledSpecification(注文キャンセル可能判定)
・UserCanPostArticleSpecification(投稿許可条件)
・ProductIsAvailableSpecification(商品販売可否)
このような、ビジネスのルール名をそのままクラス名にすることで、コードがドメイン言語に近づくのが最大の魅力です。
メリット
・条件ロジックを再利用できる(複数箇所で同じ条件を使える)
・条件を組み合わせやすい(動的なAND/OR構成)
・ビジネスルールを「命名されたオブジェクト」として表現できる
・テストしやすい(条件単体でユニットテスト可能)
デメリット
・クラス数が増える(小規模だとオーバーエンジニアリングになりがち)
・条件が単純なら if 文の方が分かりやすい場合もある
・動的合成に慣れていないと読みづらいコードになることも
構造
[ Entity / ValueObject ]
│
▼
[ Specification ] ← 条件を表現
│
▼
[ DomainService ] ← 複合ロジックを組み立て
│
▼
[ ApplicationService ] ← フロー制御
あとがき
Class定義の継承(Extends)を使って、クラス命名を自由にした、他人が読んでも分かりやすいプログラムが書きやすくなるパターンですね。
ネスト(条件やループなどの階層構造)を増やさないと言うのは、プログラミングコーディングの基本ですが、それにはいくつかの対処法があります。
今回のSpecificationパターンは、それを解消する1つのパターンです。
さほど難しくない上、コーディングルールとして整えておきたいデザインパターンですね。
0 件のコメント:
コメントを投稿