Rich domain model with behaviours and ORM

前端 未结 4 1298
孤城傲影
孤城傲影 2021-02-02 13:54

After watching NDC12 presentation \"Crafting Wicked Domain Models\" from Jimmy Bogard (http://ndcoslo.oktaset.com/Agenda), I was wandering how to persist that kind of domain mod

相关标签:
4条回答
  • 2021-02-02 14:04

    This is actually a very good question and something I have contemplated. It is potentially difficult to create proper domain objects that are fully encapsulated (i.e. no property setters) and use an ORM to build the domain objects directly.

    In my experience there are 3 ways of solving this issue:

    • As already mention by Luka, NHibernate supports mapping to private fields, rather than property setters.
    • If using EF (which I don't think supports the above) you could use the memento pattern to restore state to your domain objects. e.g. you use entity framework to populate 'memento' objects which your domain entities accept to set their private fields.
    • As you have pointed out, using CQRS with event sourcing eliminates this problem. This is my preferred method of crafting perfectly encapsulated domain objects, that also have all the added benefits of event sourcing.
    0 讨论(0)
  • 2021-02-02 14:06

    When doing DDD first thing, you ignore the persistence concerns. THe ORM is tighlty coupled to a RDBMS so it's a persistence concern.

    An ORM models persistence structure NOT the domain. Basically the repository must 'convert' the received Aggregate Root to one or many persistence entities. The Bounded Context matters a lot since the Aggregate Root changes according to what are you trying to accomplish as well.

    Let's say you want to save the Member in the context of a new offer assigned. Then you'll have something like this (of course this is only one possible scenario)

    public interface IAssignOffer
    {
        int OwnerId {get;}
        Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc);
        IEnumerable<Offer> NewOffers {get; }
    }
    
    public class Member:IAssignOffer
    {
        /* implementation */ 
     }
    
     public interface IDomainRepository
     {
        void Save(IAssignOffer member);    
     }
    

    Next the repo will get only the data required in order to change the NH entities and that's all.

    About EVent Sourcing, I think that you have to see if it fits your domain and I don't see any problem with using Event Sourcing only for storing domain Aggregate Roots while the rest (mainly infrastructure) can be stored in the ordinary way (relational tables). I think CQRS gives you great flexibility in this matter.

    0 讨论(0)
  • 2021-02-02 14:13

    Old thread. But there's a more recent post (late 2014) by Vaughn Vernon that addresses just this scenario, with particular reference to Entity Framework. Given that I somehow struggled to find such information, maybe it can be helpful to post it here as well.

    Basically the post advocates for the Product domain (aggregate) object to wrap the ProductState EF POCO data object for what concerns the "data bag" side of things. Of course the domain object would still add all its rich domain behaviour through domain-specific methods/accessors, but it would resort to inner data object when it has to get/set its properties.

    Copying snippet straight from post:

    public class Product
    {
      public Product(
        TenantId tenantId,
        ProductId productId,
        ProductOwnerId productOwnerId,
        string name,
        string description)
      {
        State = new ProductState();
        State.ProductKey = tenantId.Id + ":" + productId.Id;
        State.ProductOwnerId = productOwnerId;
        State.Name = name;
        State.Description = description;
        State.BacklogItems = new List<ProductBacklogItem>();
      }
    
      internal Product(ProductState state)
      {
        State = state;
      }
    
      //...
    
      private readonly ProductState State;
    }
    
    public class ProductState
    {
      [Key]
      public string ProductKey { get; set; }
    
      public ProductOwnerId ProductOwnerId { get; set; }
    
      public string Name { get; set; }
    
      public string Description { get; set; }
    
      public List<ProductBacklogItemState> BacklogItems { get; set; }
      ...
    }
    

    Repository would use internal constructor in order to instantiate (load) an entity instance from its DB-persisted version.

    The one bit I can add myself, is that probably Product domain object should be dirtied with one more accessor just for the purpose of persistence through EF: in the same was as new Product(productState) allows a domain entity to be loaded from database, the opposite way should be allowed through something like:

    public class Product
    {
       // ...
       internal ProductState State
       {
         get
         {
           // return this.State as is, if you trust the caller (repository),
           // or deep clone it and return it
         }
       }
    }
    
    // inside repository.Add(Product product):
    
    dbContext.Add(product.State);
    
    0 讨论(0)
  • 2021-02-02 14:15

    For AssignedOffers : if you look at the code you'll see that AssignedOffers returns value from a field. NHibernate can populate that field like this: Map(x => x.AssignedOffers).Access.Field().

    Agree with using CQS.

    0 讨论(0)
提交回复
热议问题