RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

ドメイン駆動設計(DDD) 初心者がドメインサービスについて分かった気になるまでの道のり

f:id:tech-rakus:20200511125337j:plain

こんにちは、goldminer です。

はじめに

昨年から新しいプロジェクトに携わることになり、そのプロジェクトではドメイン駆動設計(DDD)を取り入れています。 それまで DDD をやったことがなかったので色々と試行錯誤しながら進めていて、特にドメインサービスについては自分の中での捉え方の変化が激しかったのでまとめてみました。 これから DDD を始めようという方の一助になれば幸いです。

以下のフレームワークアーキテクチャを採用した例を記述しています。

ドメインサービスとは

ドメインサービスについて調べてみると概ね次のように説明されています。

ドメインにおける重要なプロセスの内、エンティティや Value Object の責務とするべきではないものを
ドメインサービスとして定義する

最初に作ったドメインサービス

「ログインパスワードはハッシュ化してデータベースに保存する」というセキュリティ要件を対応した際に作成したドメインサービスです。 パスワードは Password クラス(エンティティ)が保持しますが、それをハッシュ化する責務までをも Password クラスに負わせるのは適切でないと考えました。 そこでハッシュ化する責務を持ったドメインサービスを作成することにしました。

ハッシュ化の(ストレッチ回数などの)アルゴリズムは技術的にすぎるということでドメインサービスはインターフェースで定義し、実装クラスはインフラストラクチャ層に配置することにしました。

public interface HashPasswordService {

    /**
     * 平文のパスワードをハッシュ化する.
     */
    String hash(String plainPassword);
// インフラストラクチャ層に配置
@Service
public class HashPasswordServiceImpl implements HashPasswordService {

    public String hash(String plainPassword) {
        // ハッシュ化のアルゴリズムを実装している.
    }

このドメインサービスについて今の時点では次のように評価しています。

  • 良: エンティティが負うべきでないハッシュ化の責務をドメインサービスとして分離できている
  • 良: 技術的にすぎるハッシュ化のアルゴリズムをインフラストラクチャ層に分離できている
  • 悪: @Serviceはアプリケーションサービス*1にも使用しており、@Serviceの用法が混乱している

総じて悪くないと思っているのですが、最初に作成したドメインサービスがそうであったことで「ドメインサービスはインターフェースと実装クラスを分離したもの」という間違った思い込みをしてしまったように思います。

その後ドメインサービスを全く作成しない

上述したように「ドメインサービスはインターフェースと実装クラスを分離したもの」という間違った思い込みをしてしまったため、技術的な要素がほとんどないドメインではドメインサービスの出番がありません。 そして本来はドメインサービスとして作成すべきクラスを別のモデルで作成するという迷走を始めてしまいました...

「消費税を計算する」という例で書いています(実際は別の計算だったのですが、それを説明するのはとても大変なので日本人になじみのある消費税計算に置き換えています)。

public interface CalculateTaxPolicy {

    /**
     * 税込価格を計算する.
     * @param priceExcludingTax 税抜価格
     */
    BigDecimal priceIncludingTax(BigDecimal priceExcludingTax);
/**
 * 標準税率(10%)で計算する CalculateTaxPolicy の実装クラス.
 */
public class CalculateStandardTaxPolicy implements CalculateTaxPolicy {

    @Override
    public BigDecimal priceIncludingTax(BigDecimal priceExcludingTax) {
        BigDecimal taxRate = BigDecimal.valueOf(10); // 標準税率(10%)
        BigDecimal coefficient = BigDecimal.valueOf(100).add(taxRate)
                .scaleByPowerOfTen(-2);
        BigDecimal priceIncludingTax = priceExcludingTax.multiply(coefficient);
        priceIncludingTax = priceIncludingTax.setScale(0, RoundingMode.HALF_UP); // 四捨五入する
        return priceIncludingTax;
    }
/**
 * 軽減税率(8%)で計算する CalculateTaxPolicy の実装クラス.
 */
public class CalculateReducedTaxPolicy implements CalculateTaxPolicy {

    @Override
    public BigDecimal priceIncludingTax(BigDecimal priceExcludingTax) {
        BigDecimal taxRate = BigDecimal.valueOf(8); // 軽減税率(8%)
        // 以降は標準税率の場合と同じなので省略
    }

この設計についての評価は以下です。

  • 良: エンティティ(おそらく税抜価格は「商品」のようなクラスが持っている)が負うべきでない税込価格の計算の責務を分離できている
  • 悪: 「税込価格の計算」というプロセスを記述しているにもかかわらずドメインサービスではなくポリシーとして作成している

8%と10%という複数税率からポリシーを使うことを発想したところまではよかったのですが、プロセスまでをもポリシーに記述してしまってドメインサービスを使うという発想には至りませんでした。

もっとドメインサービスを活用しようと考える

ドメインサービスを作成する機会がまったくなく「これでよいのか?」と疑問に思ったことがきっかけです。 現時点でのドメインサービスの捉え方は冒頭にも書いたとおり「エンティティが負うべきでないドメインにおけるプロセスを記述したもの」です。 「消費税を計算する」の設計も今では次のような設計を考えています(実際にリファクタリング予定です)。

public interface TaxPolicy {

    /**
     * 税率を返す.
     */
    int rate();
/**
 * 標準税率(10%)を返す TaxPolicy の実装クラス.
 */
public class StandardTaxPolicy implements TaxPolicy {

    @Override
    public int rate() {
        return 10;
    }
/**
 * 軽減税率(8%)を返す TaxPolicy の実装クラス.
 */
public class ReducedTaxPolicy implements TaxPolicy {

    @Override
    public int rate() {
        return 8;
    }
public class CalculateTaxService {

    /**
     * 税込価格を計算する.
     */
    public BigDecimal priceIncludingTax(BigDecimal priceExcludingTax, TaxPolicy policy) {
        BigDecimal taxRate = BigDecimal.valueOf(policy.rate());
        BigDecimal coefficient = BigDecimal.valueOf(100).add(taxRate)
                .scaleByPowerOfTen(-2);
        BigDecimal priceIncludingTax = priceExcludingTax.multiply(coefficient);
        priceIncludingTax = priceIncludingTax.setScale(0, RoundingMode.HALF_UP); // 四捨五入する
        return priceIncludingTax;
    }

ちゃんとドメインサービスを活用できています。 今後もこんな感じでドメインサービスを組み込んで設計を考えていきたいです。

*1:ヘキサゴナルアーキテクチャの用語

Copyright © RAKUS Co., Ltd. All rights reserved.