ドメインモデル駆動開発をやってみる(4)

ユースケースコントローラのサービス化

前回ドメインモデルを実行する実装非依存の抽象化エンジンを作成しました。今回はこのアイデアをさらに拡張して、ユースケースコントローラをサービス化して利用するシナリオを考えてみます。

目標となるシナリオは、登録されたユースケースコントローラのサービスを取得し、採番された伝言を作成し、伝言を登録して、コールバックの確認を行うというものです。かなりアプリケーション機能に近いレベルの機能を実装していますが、ドメインモデルやユースケースコントローラはPlainなObjectのみで構成しています。

[TestInitialize()]
public void MyTestInitialize() 
{
    messageModelRuntime = new ModelRuntime();
    messageModelRuntime.AddService(new SimpleMessageUseCaseService());
}

[TestMethod]
public void TestCallBack()
{
    var messageUserCaseService 
        = messageModelRuntime.GetService();

    phoneMessage = messageUserCaseService.CreateMessage();

    phoneMessage.Action = ResponseAction.CallBack;
    phoneMessage.CallBackPhoneNo = "06-666-6666";
    phoneMessage.ReceiptDateTime = DateTime.Now;
    phoneMessage.ReceiptPerson = new Employee() { Name = "yamada taro" };
    phoneMessage.TargetPerson = new Employee() { Name = "suzuki hanako" };
    phoneMessage.CallerPerson = new CustomerCaller(null, new Customer(), "Yamamoto");
    messageUserCaseService.EntryMessage(phoneMessage);

    Assert.AreEqual(MessageStatus.WaitCallBack, 
                messageUserCaseService.GetStatus(phoneMessage));

    messageUserCaseService.CallBack(phoneMessage);

    Assert.AreEqual(MessageStatus.Done,
                messageUserCaseService.GetStatus(phoneMessage));
}


ドメインモデル上でのユースケースコントローラの定義

通常ユースケースコントローラはアプリケーションロジックの領域を取り扱うためメール送信などドメインの原理原則以外の個別アプリケーション要件の要素に依存したり、また、ワークフローなどの特別な実装テクノロジに依存することがあります。しかし、ここでは、ドメインモデルでのユースケースコントローラの定義はあくまでも抽象化されたサービスとして取り扱います。これによって、ドメインモデルの世界で、あたかも適切に実装されたユースケースコントローラがあるという前提で処理を取り扱うことができるようになり、ドメインモデルからワークフローを実行したりなどの処理を記述することも可能になりかなりリッチなモデルとして記述することができます。

namespace TelephoneMessage.Model
{
    public enum MessageStatus
    {
        None,
        Created,
        WaitConfirm,
        WaitCallBack,
        Done,
    }

    public interface IMessageUseCaseService
    {
        Message CreateMessage();
        void EntryMessage(Message msg);
        void Confirm(Message msg);
        void CallBack(Message msg);
        MessageStatus GetStatus(Message msg);
    }
}

PlainなObjectで実装した簡易的なユースケースコントローラのサービスです。ユースケースコントローラが状態遷移をしタイムアウトや並列性のある処理などが混ざると単純なクラスでは表現力では難しくなります。この例ではGetStatusのステータス値を取る際にタイムアウトしていないかのチェックをするなど直観的でないコードを記述する必要が出てきます。(Stateパターンなどを利用して作成すればもう少しましになるかもしれませんが、それはそれで多くのコードが必要になります)

public class SimpleMessageUseCaseService
    : IMessageUseCaseService
{
    ...
    public void EntryMessage(Message msg)
    {
        msg.Validate();
        var context = messageToWFContext[msg.Id];
        context.PhoneMessage = msg;
        
        if (msg.Action == ResponseAction.CallBack)
        {
            context.CurrentStatus = MessageStatus.WaitCallBack;
        }
        else
        {
            context.CurrentStatus = MessageStatus.WaitConfirm;
        }
    }

    public MessageStatus GetStatus(Message msg)
    {
        CheckTimeout(msg);
        var context = messageToWFContext[msg.Id];
        return context.CurrentStatus;       
    }

    private void CheckTimeout(Message msg)
    {
        var context = messageToWFContext[msg.Id];
        if (context.CurrentStatus != MessageStatus.Done)
        {
            if (DateTime.Now.Subtract(context.PhoneMessage.ReceiptDateTime).TotalSeconds > 5)
            {
                if (msg.Action == ResponseAction.CallBack)
                {
                    Console.WriteLine("Send Mail");
                }
                context.CurrentStatus = MessageStatus.Done;
            }
        }
    }
}

WFを利用したユースケースコントローラのサービス

前々回書いたように、ユースケースは一般的にフローや状態遷移として表現しやすいくWFを使って記述することが可能です。タイムアウトや並列処理なども簡単に記述・実装できます。先ほどの例と見比べてください。

public class MessageUseCaseService
    : IMessageUseCaseService, IPhoneMessageService
{
    ...
    public void EntryMessage(Message msg)
    {
        msg.Validate();

        var context = MessageUseCaseService.messageToWFContext[msg.Id];
        if (Entried != null)
            Entried(null, new MessageEntryEventArgs(context.Id, context.PhoneMessage));
        RunWorkflow(context.Id);
    }

    public MessageStatus GetStatus(Message msg)
    {
        return MessageUseCaseService.messageToWFContext[msg.Id].CurrentStatus;
    }
    ...
}


よりリッチなドメインモデルに向けて

ユースケースコントローラをドメインモデル内でサービスとして取り扱うアイデアは、ビジネスプロセスの大きな流れをドメインモデルで取り扱えることができるようになり、ドメインモデルをよりリッチにすることが可能です。さらにWFを使うと同期的な処理以外も含めて様々なフローを簡単に実装できる強力なソリューションになります。