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

画面を作成する

ユースケースコントローラまで作成できたので画面を作ることにします。シナリオは伝言の確認・登録、従業員マスタ・メンテをターゲットにします。

自分宛ての伝言一覧

新しい伝言の登録

従業員マスタ・メンテ

リポジトリの改訂

マスタ・メンテ画面を作るにあたって、データの追加・削除をリポジトリに追加します。また、検索処理をよりプリミティブなIQueryableを返すGetTableメソッド用意して、Findメソッドは拡張メソッドで定義するようにしました。
これによって、リポジトリ経由でもLINQが持っているソートやグルーピングなどの機能をフル活用できるようになります。このあたりのパターンは今までと発想を変えないと変な制限を与えてLINQの良いところを殺してしまうので注意が必要ですね。

public interface IRepository
{
    IQueryable GetTable();
    TEntity Add(TEntity entity);
    void Remove(TEntity entity);
}

public static class RepositoryExtender
{
    public static List Find(
        this IRepository repository, Expression> query)
    {
        return repository.GetTable().Where(query).ToList();
    }
    ...
}


画面の実装

伝言登録の画面構成はデザイナを使って簡単に作成できます。新しいListViewというコントロールが加わったようです。試しに使ってみました。検索結果からオブジェクトをナビゲートして関連データもEval("ReceiptPerson.Name") のようにして表示することができます。また、今回新しいデータソースにLinqDataSourceが追加されているのですが更新処理はDataContextを利用する必要があるようなので今回は採用しませんでした。

<asp:ListView ID="ListView1" runat="server" DataSourceID="ObjectDataSource1">
...
    <ItemTemplate>
        <tr id="Tr4" runat="server" style="background-color:#DCDCDC;color: #000000;">
            <td id="Td3" runat="server">
                <asp:LinkButton ID="LinkButton1" runat="server" 
                    CommandArgument='<%# Eval("Id") %>' CommandName="Done" 
                    OnCommand="LinkButton1_Command" Text='<%# Eval("Id") %>'></asp:LinkButton>
            </td>
            <td id="Td4" runat="server">
                <asp:Label ID="ReceiptDateTimeLabel" runat="server" 
                    Text='<%# Eval("ReceiptDateTime", "{0:t}") %>' />
            </td>
            <td id="Td5" runat="server">
                <asp:Label ID="ActionLabel" runat="server" Text='<%# Eval("Action") %>' />
            </td>
            <td id="Td6" runat="server">
                <asp:Label ID="ReceiptPersonLabel" runat="server" 
                    Text='<%# Eval("ReceiptPerson.Name") %>' />
            </td>
            <td id="Td7" runat="server">
                <asp:Label ID="TargetPersonLabel" runat="server" 
                    Text='<%# Eval("TargetPerson.Name") %>' />
            </td>
...
    </ItemTemplate>
</asp:ListView>

伝言を登録する処理の実装です。画面(aspx)側のコードは最小限で、ユースケースをキックする程度コードになります。

public partial class EntryMessage
    : MessageUseCasePage
{
    ...

    protected void EntryButton_Click(object sender, EventArgs e)
    {
        if (!Page.IsValid) return;
        ExecuteAction(delegate() {
            BuildMessage();
            UseCaseService.EntryMessage(PhoneMessage);
            Response.Redirect("MessageList.aspx");
        });
    
    }

}


アプリケーションの処理は、ユースケースコントローラからドメインモデルを利用し実行されます。複雑な検証処理もこの通り動きます。


画面ヘルパー

1つのユースケースを複数の画面で構成することはよくあります。この場合画面間でデータを共有する仕組みが必要になりますが、ユースケースで共有するデータを保持するヘルパーを利用すると便利です。さらに検索処理などをヘルパー側に切り出して実装することで、画面(aspx)での処理を最小限にできます。ヘルパー自体は特別なベースクラスを必要しないように実装可能で単体テストが可能です。何度も言いますがテストの難しいコード部を最低限にできます。

public class MessageUseCasePageHelper
    ...

    public Employee ReceiptPerson
    {
        get { return (Employee)Session["ReceiptPerson"]; }
        set { Session["ReceiptPerson"] = value; }
    }

    public Message PhoneMessage
    {
        get { return (Message)Session["PhoneMessage"]; }
        set { Session["PhoneMessage"] = value; }
    }

    public List GetCurrentReceiptPersonMessageList(bool todayOnly)
    {
        if (todayOnly)
        {
            var now = DateTime.Now;
            return Runtime.MessageRepository.Find(
                    p => p.ReceiptPerson == ReceiptPerson
                        && p.ReceiptDateTime.Day == now.Day
                        && p.ReceiptDateTime.Month == now.Month
                        && p.ReceiptDateTime.Year == now.Year);
        }
        else
        {
            return Runtime.MessageRepository.Find(
                    p => p.ReceiptPerson == ReceiptPerson);
        }
    }
    ...
}

個々の画面おいて、更新系の処理はをユースケースコントローラをキックする処理、検索系は画面ヘルパーの利用してDataSourceに設定する処理でほぼ記述できてしまいます。

public partial class MessageList 
    : MessageUseCasePage
{
    ...
    protected void Button1_Click(object sender, EventArgs e)
    {
        ExecuteAction(delegate()
        {
            PhoneMessage = UseCaseService.CreateMessage();
            Response.Redirect("EntryMessage.aspx");
        });
    }

    protected void LinkButton1_Command(object sender, CommandEventArgs e)
    {
        ExecuteAction(delegate()
        {
            var msg = Runtime.MessageRepository.FindById(int.Parse((string)(e.CommandArgument)));
            UseCaseService.Confirm(msg);
            RefreshGrid();
        });
    }

    private void RefreshGrid()
    {
        adapter.DataSource = Helper.GetCurrentReceiptPersonMessageList(CheckBox1.Checked);
        this.ListView1.DataBind();
    }
}


残るはデータベースの実装

画面まで作成できました。データベースはありませんがアプリケーションを実際に作成して実行することができました。あとは永続化を実装するだけです。既存コードにどの程度影響するかやってみないとわかりませんが、興味深いところです。