AdventureWorksをモデリングしてDDDしながらドメインモデルで実装してみる(10)
ドメインモデルにサービスを導入する
サービスというといくつかあるのですが、ここではSOAやFacadeなどのテクニカル的なものではなく、ビジネス・オブジェクト*1が提供する機能のサービスを指します。このため、ここでのサービスは画面や通信、永続化メカニズムに依存しないPlainなObjectで構成されます。
ビジネス・オブジェクトとしては典型的なものとしてはエンティティがありますので、エンティティの機能もサービスとして考えれます。実際利用されるサービスの多くはエンティティのものになります。ただ、サービスのいくつかはエンティティとは関連しなかったり、エンティティの責務範囲を超えることもあります。このような場合は新しくサービスのクラスを導入します。
たとえば「休暇申請」サービスは多くの処理は従業員エンティティで実行できそうなのですが、上司がいない場合のビジネスルールは従業員エンティティの責務をこえる特定処理のビジネスポリシーになっています。このまま実装すすめると従業員エンティティが肥満児になっていく可能性があります。したがって、従業員エンティティにこのサービスを実装することは避けたくなります。このような場合は新しく休暇申請サービスを導入します。
public class VacationService public void RequireVacationToManager(Employee emp, DateTimeRange requiring, EmployeeVactionType type) { //休暇申請(消費した時間が減る) emp.RequireVacation(requiring, type); //送信先の取得(上司がいない場合は人事部) var manager = emp.Manager ?? Employee.Find(Human_Resources_Administrative); //メール送信 var mailSvc = ServiceRepository.Find<IEMailService>(); mailSvc.Send(manager.Contact.Email.Address, GetEMailContent(emp, requiring)); } }
引数にEmployeeID(int型)など汎用的な型を利用するほうが良いように感じるかもしれませんが、このサービスはFacadeではないのでEmployee型を利用します。
ちなみに、C#であれば従業員の拡張メソッドとしてサービスを実装できます。MIX-INによるクラスへの機能追加で、クラスの内部ではなく外側からの機能追加になります。従業員エンティティの肥満児化を防ぎながら従業員エンティティを利用性をあげてくれます。
public static class EmploeeExtesionService { public static void RequireVacationToManager( this Employee emp, DateTimeRange requiring, EmployeeVactionType type) { emp.RequireVacation(requiring, type); var manager = emp.Manager ?? Employee.Find(Human_Resources_Administrative); var mailSvc = ServiceRepository.Find<IEMailService>(); mailSvc.Send(manager.Contact.Email.Address, GetEMailContent(emp, requiring)); } } //従業員(191)が休暇申請(2009/6/1-2009/6/2)をする Employee.Find(191).RequireVacationToManager( new DateTimeRange(DateTime.Parse("2009/6/1"), DateTime.Parse("2009/6/2")), EmployeeVactionType.Vacation);