C#でドメイン駆動開発 Entityの書き方と使い方とテスト駆動!⑨

当サイトではアフィリエイト広告を利用しています。

C#でドメイン駆動開発

ドメイン駆動開発では意味のあるひとかたまりのデータをEntityとして扱います。データベースの1行分のデータと考えるといいと思います。

Entity

先ほどはGetDataの戻り値がfloatでした。実務では複数の項目を返却するシナリオのほうが断然多いでしょう。

今回はデータベースの1行分の値

「計測ID」

「計測日時」

「計測値」

を返却するシナリオを返却するシナリオを考えてみましょう。

 

データベースのテーブルをSELECTしたりINSERTなどに使用するひとかたまりをドメイン駆動開発ではEntity(エンティティ)と呼びます。

Entityの決め事としては,「一意」であるということです。同じEntityが複数あってもその一つを特定できる必要があります。

データベースのプライマリキーと同じです。ですのでDBからSELECTした1行がEntityだという理解でよいと思います。

今回の例では「計測ID」がKeyとなります。中身はstringでGuidの文字列が入っているということにしましょう。

GuidとはC#ではGuid.NewGuid().ToString()で得られる文字列で,マックアドレス並みに一意になる値といわれています。(完全に重複しないわけではないがほぼないものという感じです)

Entityの作成

Entityを作ってみましょう。まずDomain層に新規フォルダで「Entities」を作成します。その中に新規クラスで「MeasureEntity」を作成します。

語尾のEntityはつけない人もいますが,私はつけたほうがわかりやすいと思うのでつけています。※つけたほうが名前を見ただけでそのクラスの使われ方が想像できる

public MeasureEntity(string measureId,DateTime measureDate,float measureValue)
{
    MeasureId = measureId;
    MeasureDate = measureDate;
    MeasureValue = measureValue;
}

 public string MeasureId { get; }
 public DateTime MeasureDate { get; }
 public float MeasureValue { get; }

まずコンストラクタではプロパティで使用する値すべてを受け取っています。完全コンストラクタパターンといわれるものですが,可能な限りこのパターンのほうがコードの見通しは良くなります。

要するにMeasureIdだけ設定してあとは設定していないなどという実装ができなくなります。

コンストラクタでNullの可能性のある変数のNullチェックをしていればなおよいでしょう。

この場合であればstringだけがNullの可能性があります。今回はこのままいくとしましょう。エンティティの紹介のため先に本番コードを書きましたが,実際はテストコードから記述します。

それではテストコードを書いていきましょう。Testsプロジェクトにテストクラスを新規で作成しましょう。名前はLatestViewModelTestとしましょう。

直近値の値を表示する画面のViewModelとします。テストコードを書きましょう。

[TestMethod]
public void 直近値_シナリオ()
{
     var viewModel = new LatestViewModel();
}

LatestViewModelがコンパイルエラーになっているのでWinFormのViewModelsにLatestViewModelを作成しましょう。

前回と同じようにアクセス修飾子をpublicにしてViewModelBaseを継承します。

public class LatestViewModel : ViewModelBase
{
}

LatestViewModelTestに戻り,LatestViewModelの行でCtrl+ドット,Enterを行って,usingを追加します。

ここで一度テストしましょう。テスト結果は成功するはずです。次にテストコードを書いていきます。

viewModel.MeasureDate.Is("2017/01/01 13:00:00");
viewModel.MeasureValue.Is("1.23m/s");

MeasureDateとMeasureValueがコンパイルエラーになっているのでそれぞれをCtrl+ドット,Enterで自動生成します。

自動生成したコードをデータバインドできる形に書き換えます。内容はMeasueViewModelでやったのと同じ形です。

private string _measureValue = "";
private string _measureDate = "";

public string MeasureDate
{
    get { return _measureDate; }
    set { SetProperty(ref _measureDate, value); }
}

public string MeasureValue
{
    get { return _measureValue; }
    set { SetProperty(ref _measureValue, value); }
}

テストをしましょう。NGになります。今回はいきなり日付と計測値を表示するようにテストコードを書いたため,テストは通りません。今回はデータベースから値をとる想定です。

データベースにはMeasuresというテーブルがあって,その直近値を取得するシナリオです。

IMeasureRepositoryというリポジトリーを作成しましょう。

public interface IMeasureRepository
{
    MeasureEntity GetLatest();
}

最初に作成したEntityを返却するように定義しています。これをViewModelから呼び出します。

private IMeasureRepository _measureRepository;
public LatestViewModel(IMeasureRepository measureRepository)
{
     _measureRepository = measureRepository;
     var entity = _measureRepository.GetLatest();
     MeasureDate = entity.MeasureDate.ToString("yyyy/MM/dd HH:mm:ss");
     MeasureValue = Math.Round(entity.MeasureValue, 2) + "m/s";
}

これで一度テストしてみましょう。コンパイルエラーになります。テストコードでLatestViewModelへのコンストラクタに引数をセットしていません。今回もMoqをしようしてMockを作成しましょう。

var measureMock = new Mock<IMeasureRepository>();
var measure = new MeasureEntity("guidA", "2017/01/01 13:00:00".ToDate(), 1.23456f);
measureMock.Setup(x => x.GetLatest()).Returns(measure);

var viewModel = new LatestViewModel(measureMock.Object);
viewModel.MeasureDate.Is("2017/01/01 13:00:00");
viewModel.MeasureValue.Is("1.23m/s");

テストを実行します。成功すると思います。UIとインフラストラクチャを作成しましょう。WinFormのViewsフォルダに「LatestView」というFormを作成し,ラベルを一つ貼り付けてNameをMeasureValueLabel,フォントを36とします。画面のコードを表示しつぎのように書きます。

public LatestView()
{
    InitializeComponent();
    var viewModel = new LatestViewModel();
    this.DataBindings.Add("Text", viewModel, nameof(viewModel.MeasureDate));
    MeasureValueLabel.DataBindings.Add("Text", viewModel,nameof(viewModel.MeasureValue));
}

画面タイトルに日付,今回追加したラベルに計測値が出るようにバインドしています。LatestViewModelの生成でコンパイルエラーが出ているので対応しましょう。まずインフラ層のFakeフォルダにMeasureFakeを作成します。

internal sealed class MeasureFake : IMeasureRepository
{
    public MeasureEntity GetLatest()
    {
        return new MeasureEntity("guidA", "2017/01/01 13:00:00".ToDate(), 1.23456f);
    }
}

IMeasureRepositoryを実装しGetLatestで適当なMeasureEntityを返却します。Factoriesクラスで今回のクラスが生成できるようにコードを加えます。

public static IMeasureRepository CreateMeasureRepository()
{
    return new MeasureFake();
}

LatestViewModelのデフォルトコンストラクタでCreateMeasureRepository()を呼び出しましょう。

【LatestViewModel】

public LatestViewModel() : this(Factories.CreateMeasureRepository())
{
}

これでLatestViewを表示する準備ができました。ただLatestViewを呼び出す画面がないため,今回新たにMenuViewというFormを追加しましょう。

画像のとおりボタンを配置しそれぞれのボタンよりMeasureView,LatestViewを呼び出せるようにしましょう。

private void MeasureButton_Click(object sender, EventArgs e)
{
    using (var f = new MeasureView())
    {
        f.ShowDialog();
    }
}

private void LatestButton_Click(object sender, EventArgs e)
{
    using (var f = new LatestView())
    {
        f.ShowDialog();
    }
}

Program.csのMain関数も次のように書き換えます。

Application.Run(new MenuView());

これで画面が呼び出せるはずです。実行してみましょう。LatestViewを表示するとタイトルに日時,ラベルに計測値が最初から表示されているはずです。