2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめてのアドベントカレンダーAdvent Calendar 2024

Day 16

【ドメイン駆動設計入門】-ドメイン駆動設計とはなにか?

Last updated at Posted at 2024-12-12

はじめに

本記事は、ドメイン駆動設計の基本概念を初心者向けに解説するものです。専門用語をできるだけ避け、具体例を交えてわかりやすく説明します。

対象読者層

  • ドメイン駆動設計をこれから学びたい方
  • 抽象的な説明に苦手意識がある方
  • 専門用語の多さに戸惑いを感じている方

要約

  • ドメイン駆動設計は、業務内容に注目してソフトウェアを開発する手法
  • ドメイン駆動設計は、業務内容を深く理解し、業務課題を正確にコードへ反映させることが重要
  • 「ドメイン」は業務領域、ドメインモデルはその業務を図や設計で表現したもので、ドメインオブジェクトはその具体的なソースコード

本記事で登場する用語説明

用語 概要 具体例
ドメイン 業務の対象領域を指す 勤怠管理、在庫管理
ドメインモデル ドメインの概念やルールを抽象化したもの 社員とタイムカードの関係性を表すクラス図など
ドメインオブジェクト ドメインモデルを構成する具体的なオブジェクト(ソースコード) 社員オブジェクト、タイムカードオブジェクト
  • ドメインは業務そのもの
  • ドメインモデルは業務を図や設計で表現したもの
  • ドメインオブジェクトはモデルを具体的なプログラムで表現したもの

なぜドメイン駆動設計を学ぶのか?

ドメイン駆動設計の目的を一言で表すと、複雑な業務ルールをソフトウェアに忠実に反映させ、ビジネスの変化に柔軟に対応するためです。

ドメイン駆動設計が解決しようとしている課題は、次の通りです。

  • 業務知識とソフトウェア設計のギャップ
  • 複雑な業務ロジックの整理
  • 業務の変更に対する柔軟性の欠如

ソフトウェア開発でありがちな問題を、以下のように解決することを目的としています。

  • 業務内容とソフトウェアの乖離が無く、ソフトウェア導入前の課題が解決できる
  • 複雑な業務内容を、うまくコードに落とし込む
  • 業務内容に変更が発生しても、柔軟に変更できる

ドメイン駆動設計とは?

ドメイン駆動設計では、ソフトウェアで解決したい問題(業務)そのものに注目することが重要です。

はじめに「ドメイン」という言葉の意味を押さえておきましょう。
「ドメイン」を日本語訳すると、領域になります。
ソフトウェア開発における「ドメイン」は、プログラムを適用する対象となる業務領域
を指しています。

では、プログラムを適用する対象となる業務領域とは、具体的にどのようなものか整理していきましょう。

勤怠管理業務を例にしてみる

勤怠管理システムを導入する前は、以下のように業務を行っていると仮定します。

  • 社員は、出勤/退勤時にタイムカード(紙)に出勤/退勤時間を記載する
  • 有休を取りたい場合、社員は紙の稟議書を書いて上司からハンコをもらう必要がある
  • 月末になると、人事部が社員のタイムカードを回収し勤務時間を集計して給与を決定する

タイムカード(紙)で管理している勤怠管理では、以下の課題があります。

  • 紙に出勤/退勤時間を書いているため、改ざんされる可能性がある
  • 稟議書を持って、上司にわざわざハンコを貰いに行く手間がかかる
  • 勤務時間はデータ化されていないため、人事部での給与計算に時間がかかる

etc...

これらの課題を解決するために、開発されたのが勤怠管理システムです。
社員の勤怠をソフトウェアで管理することで、上記の課題が解決できます。

つまり、プログラムを適用する対象となる業務領域というのは、ソフトウェアで解決したい問題(業務)そのものになります。

ドメイン駆動設計の本質は、開発における当たり前を実践すること

ソフトウェアを開発するにあたり、ユーザーの業務内容・課題をエンジニアが認識することは非常に重要です

先ほど例に上げた勤怠管理システムも、エンジニアが勤怠管理に関する業務知識を持っている必要があります。
業務知識が欠けてしまうと、ユーザーが本当に必要だったものとは違うものが出来上がってしまいます。

言われてみると、ユーザーの業務内容・課題をエンジニアが認識することはソフトウェア開発の基本に思えるかもしれません。
しかし、エンジニアはどうしても目先の新しい技術に注目しがちです。

  • アーキテクチャ
  • デザインパターン
  • プログラミング言語

etc...
これらの知識も重要ですが、
ユーザーの業務内容を理解し、何を解決したいのかに注目することが前提になります。

ドメイン駆動設計は、ソフトウェア開発における当たり前のことを実践する開発手法になります。

ドメインモデルは、特定の業務内容を図や表で表したもの

エンジニアがユーザーの業務内容を深く理解するためには、ドメインモデルが重要です。
ドメインモデルを作成することで、システムの核となる概念や用語を整理し、それらを視覚的に表現できます。また、これはソースコードを書く際の重要な指針となるドキュメントとしても役立ちます。

勤怠管理というドメイン(業務領域)を例にしましょう。
勤怠管理と一言で言っても、様々な業務が存在します。

  • 給与計算
  • 出勤/退勤管理
  • 休暇申請
  • 残業申請

これらの業務内容を、図などを使って抽象的にしたものをドメインモデルと呼びます。
そして、業務内容を図などに落とし込んでいく作業をモデリングと呼びます。

ドメインモデルには様々な形式がある

ドメインモデルには様々なものが存在します。以下はその一部です。

  • シーケンス図
  • クラス図
  • ユースケース図
  • 状態遷移図

etc...

ここでは「社員の出勤業務」を、シーケンス図、クラス図という形のドメインモデルへ落とし込んだ例を紹介します。

【シーケンス図】社員が出勤ボタンを押下して業務を開始する を表現したドメインモデル

【クラス図】社員が出勤ボタンを押下して業務を開始する を表現したドメインモデル

ここではわかりやすさを重視して、日本語でクラス図を書いています

業務内容と直接関係のない要素はドメインモデルへ反映しない

モデリングをするうえで、どこまでモデルへ落とし込むかを考えることは重要です。

例えば社員が出勤ボタンを押下して業務を開始するドメインモデルでは、
社員がPCを起動する、勤怠管理システムを立ち上げる といった事は反映していません。

勤怠管理システムを作るうえで、上記の要素は出勤業務において重要ではないためです。
業務内容と直接関係ない要素をドメインモデルに含めると、モデルの複雑さが増してしまいます。
また、開発者間での認識のズレが生じやすくなり、コミュニケーションコストが増加してしまいます。

必要最低限の要素で、業務内容を表現したドメインモデルを構築することが重要です

ドメインオブジェクトは、ドメインモデルをソースコードに落とし込んだもの

ドメインモデルを駆使して表現してきた業務内容を、コードレベルまで落とし込んだものがドメインオブジェクトになります。

例として、社員が出勤ボタンを押下して業務を開始する業務をクラス図として表現したドメインモデルを、
ドメインオブジェクトに落とし込んだものを紹介します。

社員が出勤ボタンを押下して業務を開始する を表現したドメインオブジェクト

サンプルコードは、Java言語で書かれています。

勤務状態クラス
public enum WorkStatus {
    /** 出勤中 */
    WORKING,
    /** 退勤済み */
    OFF_DUTY
}
社員クラス
public class Employee {
     /** 社員ID */
    private String employeeId;
    /** 名前 */
    private String name;
    /** 勤務状態 */
    private WorkStatus status;
     /** タイムカード */
    private Timecard currentTimecard;

    /** コンストラクタ */
    public Employee(String employeeId, String name) {
        this.employeeId = employeeId;
        this.name = name;
        // 初期状態は、退勤済み
        this.status = WorkStatus.OFF_DUTY;
    }

    /** 出勤します */
    public void beginWork(String date, BeginWorkTime beginorkTime) {
        if (status == WorkStatus.WORKING) {
            // すでに出勤中の場合はエラーとする
            throw new IllegalStateException("Already working.");
        }
        this.status = WorkStatus.WORKING;
        this.currentTimecard = new Timecard(date);
        this.currentTimecard.recordBeginWork(beginorkTime);
    }

    /** 退勤します */
    public void finishWork(FinishWorkTime finishWorkTime) {
        if (status == WorkStatus.OFF_DUTY) {
            // すでに退勤済の場合はエラーとする
            throw new IllegalStateException("Not currently working.");
        }
        this.status = WorkStatus.OFF_DUTY;
        if (currentTimecard != null) {
            this.currentTimecard.recordFinishWork(finishWorkTime);
        }
    }

    // ゲッターメソッドは省略
}
タイムカードクラス
public class Timecard {
     /** 日付 */
    private String date;
    /** 出勤時間 */
    private BeginWorkTime beginorkTime;
    /** 退勤時間 */
    private FinishWorkTime finishWorkTime;

    /** コンストラクター(ここでは簡潔さを重視して、文字列で日付を扱っています) */
    public Timecard(String date) {
        this.date = date;
    }

    /** 出勤時間を記録します */
    public void recordBeginWork(BeginWorkTime beginWorkTime) {
        if (this.beginorkTime != null) {
            throw new IllegalStateException("beginorkTime has already been recorded.");
        }
        this.beginorkTime = beginorkTime;
    }

    /** 退勤時間を記録します */
    public void recordFinishWork(FinishWorkTime finishWorkTime) {
        if (this.finishWorkTime != null) {
            throw new IllegalStateException("finishWorkTime has already been recorded.");
        }
        this.finishWorkTime = finishWorkTime;
    }

    // ゲッターメソッドは省略
}
出勤時刻クラス
public class BeginWorkTime {
    /** 時 */
    private int hours;
    /** 分 */
    private int minutes;
    /** 秒 */
    private int seconds;

    /** コンストラクタ */
    public WorkTime(int hours, int minutes, int seconds) {
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
    }

    // ゲッターメソッドは省略
}
退勤時刻クラス
public class FinishWorkTime {
    /** 時 */
    private int hours;
    /** 分 */
    private int minutes;
    /** 秒 */
    private int seconds;

    /** コンストラクタ */
    public ClosingTime(int hours, int minutes, int seconds) {
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
    }

    // ゲッターメソッドは省略
}

サンプルコード解説

  1. 勤務状態の定義 (WorkStatus)

    • 社員が現在「出勤中」または「退勤済み」のどちらかを管理します
  2. 社員クラス (Employee)

    • 社員のID、名前、勤務状態、タイムカードを管理しています
    • 出勤・退勤の処理を実行し、勤務状態を変更しつつタイムカードに記録します
  3. タイムカードクラス (Timecard)

    • 出勤日、出勤時間、退勤時間を記録します
    • 重複して記録しようとした場合はエラーを発生します
  4. 時刻管理クラス (BeginWorkTime, FinishWorkTime)

    • 出勤時刻や退勤時刻を具体的な「時・分・秒」で管理します
    • 業務上で重要となる値に対応する型を用意することで、以下の恩恵を受けることができます
      • 引数の受け渡しミス防止
      • その値に関連するメソッドをクラスに集約しやすい

業務内で使われる言葉をドメインオブジェクトでも使う

ドメインオブジェクトの特徴は、業務内で使用する言葉がソースコードでも使用されていることです
これはドメインオブジェクトの核心となる部分です。

例えば、「社員がタイムカードで出勤する」業務を、
サンプルコードでは「社員クラスがタイムカードクラスの出勤メソッドを呼ぶ」ことで表現しています。

業務内で使用する言葉をソースコード上でも使用することは、以下のメリットがあります。
(一部のみ記載)

  • 業務担当者と開発担当者間での、認識のズレが減る
  • ソースコードを読んだだけで、業務ロジックを直感的に理解できるようになる
  • 業務仕様が変更になる際、変更箇所を迅速に特定できる
  • ソースコードがそのまま業務仕様の補足資料になる

さいごに

ドメイン駆動設計の流れを簡単にまとめると、以下になります。

しかし、以下のステップは作業の抽象度が高く、即座に実践するのも難しいです。

  • ドメインに対する理解を深める;
  • ドメインの概念をドメインモデルに落とし込む;

しかし、ドメインオブジェクトに関してはソースコードベースなので比較的とっつきやすいです。
また、ドメインオブジェクトを表現する設計はある程度パターン化されています。

  • 業務知識を表現するパターン

    • 値オブジェクト
    • エンティティ
    • ドメインサービス
  • アプリケーションを実現するためのパターン

    • リポジトリ
    • アプリケーションサービス
    • ファクトリ
  • 業務知識を表現する、発展的なパターン

    • 集約
    • 仕様

次回以降の記事では、これら設計パターンについて詳しく書こうと思います。
ここまで読んでいただき、ありがとうございました!

値オブジェクトについて

エンティティについて

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?