ユースケースを実現する、ドメインオブジェクトを組み合わせ実行するスクリプト

  • ユーザー機能
    • ユーザーを登録する
    • ユーザー情報を変更する
    • ユーザーを削除する
public class UserApplicationService
{
  private readonly IUserRepository userRepository;
  private readonly UserService userService
 
  public UserApplicationService(IUserRepository userRepository, UserService userService){
    this.userRepository = userRepository;
    this.userService = userService;
  }
 
  public void Register(string name) {
    var user = new User(new UserName(name));
 
    if (userService.exists(user)) throw new Exception();
 
    userRepository.Save(user);
  }
}

DTOを使う

アプリケーションサービスがドメインオブジェクトを返してしまうと、クライアントが直接ドメインオブジェクトのメソッドを使うことができてしまう。

ドメインオブジェクトを公開してしまうと、本来アプリケーションサービス1箇所に記述されるべきロジックが様々な箇所に散ってしまうので、DRYに反する危険性がある。

// ApplicationServiceを呼び出すクライアント
public class Client
{
  private UserApplicationService userApplicationService;
 
  public void ChangeName(string id, string name)
  {
    var target = userApplicationService.Get(id);
    var newName = new UserName(name);
    target.ChangeName(newName); // クライアントが直接ドメインオブジェクトのメソッドを呼び出している
  }
}

DTOに移し替えることで、外部に必要なデータだけを公開できる。

public class UserData
{
  public UserData(User source) {
    Id = source.Id.Value;
    Name = source.Name.Value;
    // MailAddress = source.MailAddress.Value こんな感じで簡単に増やせる
  }
 
  public string Id { get; }
  public string Name { get; }
}

CommandObjectを使う

例えばユーザー情報を変更する場合、変更できる情報が増えればメソッドのシグニチャも増えていく。

// シグニチャがどんどん肥大化していく
public void Update(string userId, string name = null, string mailAddress = null) {
}

これを解決するため、CommandObjectを利用する。

public class UserUpdateCommand
{
  public UserUpdateCommand(string id, string name = null, string mailAddress = null){
    Id = id;
    Name = name;
    MailAddress = mailAddress;
  }
 
  public string Id {get;}
  public string Name { get; }
  public string MailAddress { get; }
}

アプリケーションサービスの注意点

  • ドメインロジックを書かない
    • 例えばユーザー登録・更新の重複チェックはドメインサービスに任せる
    • ドメインのルールはドメインオブジェクトに閉じ込めることで、DRYを保てる
  • 凝集度 を高める
  • 状態を持たない
    • インスタンスの状態によってふるまいを変えない。