web-dev-qa-db-ja.com

C#MongoDB:ドメインオブジェクトを正しくマップする方法は?

私は最近、Evansのドメイン駆動設計の本を読み始め、DDDの経験を積むために小さなサンプルプロジェクトを開始しました。同時に、MongoDBについてもっと知りたいと思い、SQL EF4リポジトリをMongoDBと最新の公式C#ドライバーに置き換え始めました。さて、この質問はMongoDBマッピングについてです。単純なオブジェクトをパブリックゲッターとセッターでマッピングするのは非常に簡単であることがわかります-そこに苦痛はありません。しかし、パブリックセッターなしでドメインエンティティをマッピングするのは困難です。私が学んだように、有効なエンティティを構築するための唯一の本当にクリーンなアプローチは、必要なパラメーターをコンストラクターに渡すことです。次の例を考えてみましょう。

public class Transport : IEntity<Transport>
{
    private readonly TransportID transportID;
    private readonly PersonCapacity personCapacity;

    public Transport(TransportID transportID,PersonCapacity personCapacity)
    {
        Validate.NotNull(personCapacity, "personCapacity is required");
        Validate.NotNull(transportID, "transportID is required");

        this.transportID = transportID;
        this.personCapacity = personCapacity;
    }

    public virtual PersonCapacity PersonCapacity
    {
        get { return personCapacity; }
    }

    public virtual TransportID TransportID
    {
        get { return transportID; }
    } 
}


public class TransportID:IValueObject<TransportID>
{
    private readonly string number;

    #region Constr

    public TransportID(string number)
    {
        Validate.NotNull(number);

        this.number = number;
    }

    #endregion

    public string IdString
    {
        get { return number; }
    }
}

 public class PersonCapacity:IValueObject<PersonCapacity>
{
    private readonly int numberOfSeats;

    #region Constr

    public PersonCapacity(int numberOfSeats)
    {
        Validate.NotNull(numberOfSeats);

        this.numberOfSeats = numberOfSeats;
    }

    #endregion

    public int NumberOfSeats
    {
        get { return numberOfSeats; }
    }
}

明らかに、自動マッピングはここでは機能しません。これで、これら3つのクラスをBsonClassMapsを介して手動でマップでき、問題なく保存されます。問題は、それらをDBからロードする場合、それらをBsonDocumentsとしてロードし、ドメインオブジェクトに解析する必要があることです。私はたくさんのことを試みましたが、最終的にはきれいな解決策を得ることができませんでした。 MongoDBのパブリックゲッター/セッターを使用してDTOを作成し、それらをドメインオブジェクトにマップする必要がありますか?多分誰かが私にこれについていくつかのアドバイスを与えることができます。

26
hoetz

プロパティが読み取り専用であるクラスをシリアル化/逆シリアル化することが可能です。ドメインオブジェクトの永続性を無視しないようにしようとしている場合は、BsonAttributesを使用してシリアル化をガイドする必要はありません。また、AutoMappingには読み取り/書き込みプロパティが必要であるため、クラスマップを自分で登録する必要があります。たとえば、クラス:

public class C {
    private ObjectId id;
    private int x;

    public C(ObjectId id, int x) {
        this.id = id;
        this.x = x;
    }

    public ObjectId Id { get { return id; } }
    public int X { get { return x; } }
}

次の初期化コードを使用してマッピングできます。

BsonClassMap.RegisterClassMap<C>(cm => {
    cm.MapIdField("id");
    cm.MapField("x");
});

プライベートフィールドは読み取り専用ではないことに注意してください。デシリアライズはコンストラクターをバイパスし、プライベートフィールドを直接初期化することにも注意してください(.NETシリアル化もこのように機能します)。

これをテストする完全なサンプルプログラムは次のとおりです。

http://www.pastie.org/1822994

15
Robert Stam

BSONドキュメントの解析を行い、解析ロジックをファクトリに移動します。

まず、ビルダークラスを含むファクトリ基本クラスを定義します。ビルダークラスはDTOとして機能しますが、ドメインオブジェクトを構築する前に値の検証を追加します。

_public class TransportFactory<TSource>
{
    public Transport Create(TSource source)
    {
        return Create(source, new TransportBuilder());
    }

    protected abstract Transport Create(TSource source, TransportBuilder builder);

    protected class TransportBuilder
    {
        private TransportId transportId;
        private PersonCapacity personCapacity;

        internal TransportBuilder()
        {
        }

        public TransportBuilder WithTransportId(TransportId value)
        {
            this.transportId = value;

            return this;
        }

        public TransportBuilder WithPersonCapacity(PersonCapacity value)
        {
            this.personCapacity = value;

            return this;
        }

        public Transport Build()
        {
            // TODO: Validate the builder's fields before constructing.

            return new Transport(this.transportId, this.personCapacity);
        }
    }
}
_

次に、リポジトリにファクトリサブクラスを作成します。このファクトリは、BSONドキュメントからドメインオブジェクトを構築します。

_public class TransportRepository
{
    public Transport GetMostPopularTransport()
    {
        // Query MongoDB for the BSON document.
        BsonDocument transportDocument = mongo.Query(...);

        return TransportFactory.Instance.Create(transportDocument);
    }

    private class TransportFactory : TransportFactory<BsonDocument>
    {
        public static readonly TransportFactory Instance = new TransportFactory();

        protected override Transport Create(BsonDocument source, TransportBuilder builder)
        {
            return builder
                .WithTransportId(new TransportId(source.GetString("transportId")))
                .WithPersonCapacity(new PersonCapacity(source.GetInt("personCapacity")))
                .Build();
        }
    }
}
_

このアプローチの利点:

  • ビルダーは、ドメインオブジェクトの構築を担当します。これにより、特にドメインオブジェクトがパブリックコンストラクターを公開していない場合に、ドメインオブジェクトから簡単な検証を移動できます。
  • 工場はソースデータを解析する責任があります。
  • ドメインオブジェクトはビジネスルールに焦点を合わせることができます。解析や簡単な検証に煩わされることはありません。
  • 抽象ファクトリクラスは、必要なソースデータのタイプごとに実装できる汎用コントラクトを定義します。たとえば、XMLを返すWebサービスとインターフェイスする必要がある場合は、新しいファクトリサブクラスを作成するだけです。

    _public class TransportWebServiceWrapper
    {
        private class TransportFactory : TransportFactory<XDocument>
        {
            protected override Transport Create(XDocument source, TransportBuilder builder)
            {
                // Construct domain object from XML.
            }
        }
    }
    _
  • ソースデータの解析ロジックは、データの発信元に近いです。つまり、BSONドキュメントの解析はリポジトリにあり、XMLの解析はWebサービスラッパーにあります。これにより、関連するロジックがグループ化されたままになります。

いくつかの欠点:

  • 私はこのアプローチを大規模で複雑なプロジェクトではまだ試していませんが、小規模なプロジェクトでのみ試しています。私がまだ遭遇していないいくつかのシナリオでは、いくつかの問題があるかもしれません。
  • これは、一見単純に見える何かのためのかなりのコードです。特にビルダーはかなり大きくなる可能性があります。すべてのWithXxx()メソッドを単純なプロパティに変換することで、ビルダー内のコードの量を減らすことができます。
3

これを処理するためのより良いアプローチは、MapCreatorを使用することです(これらの回答のほとんどが書かれた後に追加された可能性があります)。

例えばTimeHourMinuteの3つの読み取り専用プロパティを持つSecondというクラスがあります。これらの3つの値をデータベースに格納し、逆シリアル化中に新しいTimeオブジェクトを構築する方法を次に示します。

BsonClassMap.RegisterClassMap<Time>(cm =>
{
    cm.AutoMap();
    cm.MapCreator(p => new Time(p.Hour, p.Minute, p.Second));
    cm.MapProperty(p => p.Hour);
    cm.MapProperty(p => p.Minute);
    cm.MapProperty(p => p.Second);
}
2
Ian Mercer

C#のMongoDB用のオープンソースORMであるNoRMについて考えてみましょう。

ここにいくつかのリンクがあります:

http://www.codevoyeur.com/Articles/20/A-NoRM-MongoDB-Repository-Base-Class.aspx

http://lukencode.com/2010/07/09/getting-started-with-mongodb-and-norm/

https://github.com/atheken/NoRM (ダウンロード)

0
Roy Dictus