C # MongoDB: comment cartographier correctement un objet de domaine?

j'ai récemment commencé à lire le livre D'Evans sur le design piloté par domaine et j'ai commencé un petit projet d'échantillon pour acquérir de l'expérience en DDD. En même temps, je voulais en savoir plus sur MongoDB et j'ai commencé à remplacer mes dépôts EF4 SQL par MongoDB et le dernier pilote officiel C#. Maintenant, cette question concerne la cartographie MongoDB. Je vois qu'il est assez facile de cartographier des objets simples avec des getters publics et des setters - pas de douleur là. Mais j'ai des difficultés à cartographier des entités de domaine sans public setter. Comme j'ai appris, la seule approche vraiment propre pour construire une entité valide est de passer les paramètres requis dans le constructeur. Considérons l'exemple suivant:

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; }
    }
}

évidemment l'automappage ne fonctionne pas ici. Maintenant je peux cartographier ces trois classes à la main via BsonClassMaps et ils seront stockées de la même amende. Le problème c'est que quand je veux les charger à partir de la DB je charge comme BsonDocuments, et les analyser dans mon objet de domaine. J'ai essayé beaucoup de choses, mais a finalement échoué pour trouver une solution propre. Est-ce que je dois vraiment produire des Dto avec des getters/setters publics pour MongoDB et les mapper sur mes objets de domaine? Peut-être que quelqu'un pourrait me donner quelques conseils à ce sujet.

23
demandé sur shA.t 2011-04-21 16:59:26

5 réponses

il est possible de sérialiser/deserialiser des classes où les propriétés sont en lecture seule. Si vous essayez de garder vos objets de domaine persistance ignorants, vous ne voudrez pas utiliser BsonAttributes pour guider la sérialisation, et comme vous l'avez souligné AutoMapping nécessite des propriétés de lecture/écriture, de sorte que vous devriez enregistrer les cartes de classe vous-même. Par exemple, la classe:

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; } }
}

peut être mappé en utilisant le code d'initialisation suivant:

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

noter que le les champs privés ne peuvent pas seulement être lus. Notez également que la désérialisation contourne votre constructeur et initialise directement les champs privés (la sérialisation.net fonctionne de cette façon également).

voici un programme complet qui teste ceci:

http://www.pastie.org/1822994

14
répondu Robert Stam 2011-04-22 16:56:26

j'irais avec l'analyse des documents BSON et je déplacerais la logique d'analyse dans une usine.

définir D'abord une classe de base d'usine, qui contient une classe de constructeur. La classe builder agira comme DTO, mais avec validation supplémentaire des valeurs avant de construire l'objet domain.

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);
        }
    }
}

maintenant, créez une sous-classe d'usine dans votre dépôt. Cette usine construira des objets de domaine à partir des documents 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();
        }
    }
}

Les avantages de cette approche:

  • Le constructeur est responsable de la construction de l'objet de domaine. Cela vous permet de déplacer une validation triviale hors de l'objet domain, surtout si l'objet domain n'expose aucun constructeur public.
  • l'usine est responsable de l'analyse des données source.
  • l'objet domaine peut se concentrer sur les règles d'affaires. Il n'est pas gêné par l'analyse ou la validation triviale.
  • la classe d'usine abstraite définit contrat générique, qui peut être mis en œuvre pour chaque type de données source dont vous avez besoin. Par exemple, si vous avez besoin d'une interface avec un service web qui renvoie XML, il vous suffit de créer une nouvelle sous-classe d'usine:

    public class TransportWebServiceWrapper
    {
        private class TransportFactory : TransportFactory<XDocument>
        {
            protected override Transport Create(XDocument source, TransportBuilder builder)
            {
                // Construct domain object from XML.
            }
        }
    }
    
  • la logique d'analyse des données source est proche de l'origine des données, c'est-à-dire que l'analyse des documents BSON se trouve dans le dépôt, L'analyse XML se trouve dans le wrapper du service web. Cela permet de grouper les logiques liées. ainsi.

inconvénients:

  • Je n'ai pas encore essayé cette approche pour des projets complexes et de grande envergure, mais seulement pour des projets à petite échelle. Il peut y avoir des difficultés dans certains scénarios que je n'ai pas encore rencontrés.
  • c'est tout un code pour quelque chose apparemment simple. Surtout les constructeurs peuvent se développer assez grand. Vous pouvez réduire la quantité de code dans les constructeurs en convertissant tous les WithXxx() méthodes simples propriété.
3
répondu Niels van der Rest 2011-04-21 14:04:35

une meilleure façon d'aborder cette question consiste maintenant à utiliser MapCreator (qui a peut-être été ajouté après que la plupart de ces réponses aient été écrites).

par exemple, j'ai une classe appelée Time avec trois propriétés readonly: Hour,Minute et Second. Voici comment je l'obtenir pour stocker ces trois valeurs dans la base de données et de construire de nouvelles Time objets lors de la désérialisation.

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
répondu Ian Mercer 2015-10-26 05:25:19
0
répondu Roy Dictus 2011-04-22 21:27:31

Niels a une solution intéressante mais je propose une approche très différente: Simplifiez votre modèle de données.

je dis cela parce que vous essayez de convertir les entités de style RDBMS en MongoDB et cela ne correspond pas très bien, comme vous l'avez trouvé.

une des choses les plus importantes à considérer lors de l'utilisation d'une solution NoSQL est votre modèle de données. Vous avez besoin de libérer votre esprit de beaucoup de ce que vous savez sur SQL et les relations et de penser plus sur les documents incorporés.

et rappelez-vous, MongoDB n'est pas la bonne réponse pour chaque problème alors essayez de ne pas le forcer à l'être. Les exemples que vous suivez peuvent très bien fonctionner avec des serveurs SQL standards mais ne vous tuez pas en essayant de comprendre comment les faire fonctionner avec MongoDB - ils ne le font probablement pas. Au lieu de cela, je pense qu'un bon exercice serait d'essayer de trouver la bonne façon de modéliser les données de L'exemple avec MongoDB.

-1
répondu Bryan Migliorisi 2011-04-22 01:32:02