👋

ユースケースにDCIアーキテクチャを採用してドメイン駆動設計を簡略化する

2024/01/09に公開

この記事ではバックエンドにクリーンアーキテクチャとドメイン駆動設計を採用したケースを想定しています。
DCIアーキテクチャをユースケースに適用する際に必要なポイントを焦点を当てています。
記事内容はドメイン層とユースケース層のみの説明になるのでご了承ください。

ドメイン層

クリーンアーキテクチャ+ドメイン駆動設計を採用した一般的なバックエンドのドメイン層は下記のようなモジュール構成になります。

エンティティ(Entities):

エンティティは、一意の識別子を持つ対象で、対象のライフサイクルを追跡します。例えば、顧客や注文などがエンティティとしてモデル化されます。

バリューオブジェクト(Value Objects):

バリューオブジェクトは、識別子ではなく値に焦点を当てたオブジェクトです。例えば、日付範囲や金額、住所などがバリューオブジェクトとしてモデル化されます。これらは不変であり、同じ値を持つものは同一と見なされます。

集約(Aggregates):

関連性の高いエンティティやバリューオブジェクトをまとめたものが集約です。集約は一つ以上のルートエンティティを持ち、これが集約全体を制御します。トランザクションの整合性を保つために、集約内の変更は集約のルートを通じてのみ行われるべきです。

リポジトリ(Repositories):

リポジトリは永続化とデータの取得を担当し、ドメインモデルを永続化ストレージと連携させます。リポジトリは、エンティティや集約の永続化と取得を行います。

ファクトリ(Factories):

ファクトリは、複雑なオブジェクトの作成プロセスを抽象化します。エンティティやバリューオブジェクトの生成が複雑であり、また一貫性を保つ必要がある場合に使用されます。

サービス(Services):

ドメイン層においてロジックが特定のエンティティやバリューオブジェクトに属さない場合、サービスとしてモデル化されます。これにより、特定の概念に属さない一般的な操作やルールを表現できます。

仕様(Specifications):

仕様は特定の条件を表し、これに適合するエンティティやバリューオブジェクトを抽出するために使用されます。例えば、特定の条件を満たす顧客を取得する仕様があるとします。
これらの要素は、ドメイン駆動設計においてビジネスドメインをモデル化し、複雑さを制御するために使われます。これにより、ビジネスの核となる概念やロジックがシステム内で一貫して理解され、維持されることが期待されます。

ユースケース

ドメイン駆動設計(DDD)におけるユースケースは、システムがユーザーや外部システムとどのように対話し、価値を提供するかを記述する概念です。ユースケースは、具体的なビジネスの機能や要件に焦点を当て、それを実現するための操作やフローを表現します。

以下は、ドメイン駆動設計におけるユースケースに関するいくつかの重要なポイントです:

ビジネスの機能を表現:

ユースケースは、ビジネスの機能や要件を表現します。これは、システムがどのようにしてビジネス価値を提供するかを具体的に示すものです。例えば、注文の処理、支払いの処理、顧客の登録などがユースケースとなります。
ユーザーや外部システムとの対話をモデル化:

ユースケースは、ユーザーや外部システムとのインタラクションをモデル化します。これにより、システムが外部とどのようにやり取りし、要求された機能を実現するかを理解しやすくなります。

境界コンテキストとの関連付け:

ドメイン駆動設計では、異なるコンテキスト(境界コンテキスト)が定義され、それぞれのコンテキスト内での言葉や概念が異なることがあります。ユースケースは、特定の境界コンテキストに属し、そのコンテキスト内で意味を持ちます。
ドメインサービスの利用:

ユースケースの実現には、ドメインサービスを活用することがあります。ユースケースが単一のエンティティや集約だけでなく、複数の概念やルールにまたがる場合、それを処理するためのドメインサービスが必要となります。

アプリケーションサービスとの連携:

ユースケースはアプリケーションサービスを介して実現されます。アプリケーションサービスは、ユースケースの要求を受け取り、それをドメイン層に伝達して実際のビジネスロジックを起動します。
ユースケースは、システムの中でビジネス要件を明確にし、ドメインモデルとの一貫性を保ちながらシステムの設計を進める上で重要な役割を果たします。

DCIアーキテクチャ

DCI(Data, Context, Interaction)アーキテクチャは、オブジェクト指向のソフトウェア設計アーキテクチャの一種です。DCIは、特にドメイン駆動設計(DDD)において、ビジネスドメインのモデリングとその実現に焦点を当てたアーキテクチャの考え方です。アーキテクチャの名前にある3つの要素、Data(データ)、Context(コンテキスト)、Interaction(相互作用)に注目します。

Data(データ):

Dataは、アプリケーションのデータや状態を表現します。これは通常、エンティティやバリューオブジェクトなど、ビジネスドメインの概念を具現化したものです。

Context(コンテキスト):

Contextは、アプリケーションのビジネスルールや振る舞いを表現します。DCIでは、特定の文脈やユースケースにおいて、データがどのように相互作用するかを強調します。これにより、特定の文脈でのビジネスロジックを明確にし、理解しやすくします。

Interaction(相互作用):

Interactionは、データとコンテキストが相互に作用する部分を指します。これは、特定のユースケースやシナリオにおいて、データがどのようにコンテキストと連携し、具体的な動作をするかを示します。DCIでは、この相互作用を強調して、データとコンテキストが共同で振る舞うことが重要であると考えます。
DCIの目的は、従来のオブジェクト指向のアーキテクチャがしばしばデータと振る舞い(コンテキスト)を分離しすぎてしまうという課題に対処することです。DCIは、データとコンテキストの結びつきを強調し、特定の文脈でのビジネスロジックをより自然に表現できるようにします。これにより、柔軟性が向上し、ビジネスドメインのモデリングと実現がしやすくなります。

DCIアーキテクチャをドメイン駆動設計のユースケースに適用する利点とは?

DCIアーキテクチャをドメイン駆動設計(DDD)のユースケースに適用する主な利点は、ドメイン駆動設計の原則に従って、ドメインを採用する文脈ごとに実装できることにあります。このアプローチにより、異なる文脈やユースケースごとに最適なデータと振る舞いの組み合わせを選択することが可能となります。

DCIでは、データ(Data)はエンティティやバリューオブジェクトなどのドメインモデルで表現され、コンテキスト(Context)はそれぞれの文脈やユースケースに対応します。このような設計手法により、特定のユースケースに焦点を当て、その文脈に最適なビジネスロジックを実現することができます。

DCIの利点を以下のように文章化できます:

DCIアーキテクチャは、DDDの原則に従い、異なる文脈やユースケースごとに最適なデータと振る舞いの組み合わせを提供します。これにより、各文脈で必要なビジネスロジックを明確にモデル化し、データとコンテキストが特定のユースケースにおいて共同で振る舞うことが容易になります。これは、柔軟性と保守性を高め、ドメイン駆動設計の原則に基づいたクリーンなコードの実現に寄与します。各文脈において特有の要件やビジネスルールを適用することで、システム全体がよりビジネスに焦点を当てた設計となり、要求される価値を提供しやすくなります。

DCIアーキテクチャをユースケースに適用する際の実装例

DCIアーキテクチャをユースケースに適用する場合、DCIアーキテクチャの Data(データ)、Context(コンテキスト)、Interaction(相互作用) を以下の三つの要素に置き換える必要があります。

Data(データ)=Bean
Context(コンテキスト)=Actor
Interaction(相互作用)=Role

下記の文章でそれぞれの要素について詳細に説明します。

Bean

MVC(Model-View-Controller)モデルでは、ビジネスロジック(モデル)、ユーザーインターフェース(ビュー)、およびアプリケーションの制御フローを管理するコントローラが分離されています。"bean"は、通常、データの保持や操作を目的としたオブジェクトを指します。

MVCモデルで、コントローラはユーザーからの入力を受け取り、それに基づいてモデルの状態を変更し、最終的にビューを更新します。ここで、画面からの入力データを一時的に保持するためにオブジェクトを使用することがあります。このオブジェクトが、通常、Javaのコンテキストでは"bean"と呼ばれます。

この"bean"は、通常、以下の特徴を持ちます:

プロパティ: 入力フォームや画面からのデータを格納するためのフィールド。これにアクセスするためのゲッターとセッターを提供します。

シリアライズ可能: JavaのBeanは通常、java.io.Serializable インタフェースを実装して、データをシリアライズして保存できるようにします。

デフォルトコンストラクタ: デフォルトの引数を取るコンストラクタを持っていることが期待されます。

このようなBeanを使用することで、コントローラは入力データを保持し、それをモデルに渡すなどの操作を行うことができます。このBeanは、通常、特定のユーザーインターフェースコンポーネント(たとえば、フォーム)からのデータを格納するのに使用されます。

Actor

"Actor"(アクター)という言葉には複数の意味がありますが、一般的には以下の2つの主要な意味があります。

俳優(Actor in Performing Arts):

俳優や女優は、演技を通じてキャラクターを演じる人々を指します。演劇、映画、テレビ、舞台などでキャラクターになりきり、脚本に基づいてセリフを言ったり、動作や表情で感情や物語を表現します。俳優は役柄や役どころに応じて様々な演技スキルが求められ、演技の力で観客に物語や感情を伝えます。
行為者(Actor in a System or Process):

プロジェクト管理やシステム設計などの文脈では、「アクター」はシステムやプロセス内で特定の役割や機能を果たす個人、グループ、またはシステムを指します。これらのアクターは、システムやプロセスにおいて何らかのアクションや活動を行います。例えば、ソフトウェアのユーザー、外部のサービス、データベース、センサーなどがアクターとして機能することがあります。

Actorとはユースケースで実現するRole(役割)を演じる人です。
ActorはRole(役割)をこなすことができる能力、スキル、年齢、経歴、資格、所属組織などが必要なので主にユーザー自身の実体を表すプロパティをフィールド変数として所持することになります。

Role

"Role"(ロール)は、様々な文脈で使用される単語であり、その意味は文脈によって異なります。以下に、いくつかの一般的な意味や使用例を示します。

役割または職務: "Role" は、個人や物事が果たすべき役割や職務を指すことがあります。例えば、ビジネスや組織の文脈では、個々の従業員や部門が担当する役割や責任があります。

劇の登場人物: ドラマや劇などの文学的な作品では、登場人物がそれぞれの "role" を持っており、物語を進行させます。

コンピューターサイエンス: ソフトウェアや情報システムの設計や開発においては、"role" が特定の機能や権限を持つエンティティを指すことがあります。例えば、アクセス制御の文脈での "role" は、ユーザーグループに割り当てられた特定の権限のセットを指すことがあります。

社会学や心理学: 個人が社会や集団の中で果たす役割や期待される振る舞いを指すことがあります。社会的な "role" は、文化や環境によって異なることがあります。

演劇や演技: 俳優が舞台や映画で演じる "role" は、特定のキャラクターを表現するための演技やパフォーマンスを指します。
"Role"(ロール)ではドメイン層より呼び出されたクラスやメソッドを使用してユースケースを実現するために必要なクラスやメソッドを提供します。

DCIアーキテクチャに対応したプログラミング言語

DCIアーキテクチャはJavaではmixin、ScalaではTraitなどの手法が採用されます。
以下に実装例を示します。

// Mixin(Mixin)
// 相互作用(Interaction)
interface Auditable {
    default void auditLog(String username,String password) {
        // ログの記録ロジック
        System.out.println("Audit Log: User accessed the system." +username +password);
    }
}
interface AuthCheck {
    default void check(String username,String password) {
        // 認証権限をチェック
    }
}

// データ(Data)
class UserData  {
    private String username;
    private String password;

    // コンストラクタやアクセッサメソッドなど...
}

// 文脈(Context)
// Javaではインターフェース側でインスタンス生成ができないため、
// ドメイン層のインスタンスはここで生成する
class AuthenticationContext implements Auditable,AuthCheck{
 public void authenticate(UserData user) {
        // 認証ロジック...

        // ログの記録
        auditLog(user.username,user.password);
    }
 public void check(UserData user) {
        // 認証情報をチェック...
	check(user.username,user.password);
    }
}


// クライアントコード
public class Main {
    public static void main(String[] args) {
        // データと文脈の結びつけ
        UserData userData = new UserData();
        Auditable auditable = new AuthenticationContext();
        // データと文脈を組み合わせて相互作用を実行
        authContext.authenticate(userData);
	//インターフェースを多重継承できるため
	//インターフェースを切り替えるだけでユースケースシナリオを切り替えることが可能
	 AuthCheck authCheck = new AuthenticationContext();
	 authCheck.check(userData);
    }
}

Discussion