18

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 model.
This is sample class from presentation:

public class Member
{
    List<Offer> _offers;

    public Member(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
        _offers = new List<Offer>();
    }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public IEnumerable<Offer> AssignedOffers { 
        get { return _offers; }
    }

    public int NumberOfOffers { get; private set; }

    public Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc)
    {
        var value = valueCalc.CalculateValue(this, offerType);
        var expiration = offerType.CalculateExpiration();
        var offer = new Offer(this, offerType, expiration, value);
        _offers.Add(offer);
        NumberOfOffers++;
        return offer;
    }
}

so there are some rules contained in this domain model:
- Member must have first and last name
- Number of offers can't be changed outside
- Member is responsible for creating new offer, calculating its value and assignment

If if try to map this to some ORM like Entity Framework or NHibernate, it will not work. So, what's best approach for mapping this kind of model to database with ORM?
For example, how do I load AssignedOffers from DB if there's no setter?

Only thing that does make sense for me is using command/query architecture: queries are always done with DTO as result, not domain entities, and commands are done on domain models. Also, event sourcing is perfect fit for behaviours on domain model. But this kind of CQS architecture isn't maybe suitable for every project, specially brownfield. Or not?

I'm aware of similar questions here, but couldn't find concrete example and solution.

Hrvoje Hudo
  • 8,994
  • 5
  • 33
  • 42
  • I just watched the same video, and I was wondering the same thing. What do you think about passing a poco in the constructor, and also having a readonly property on the Member class to return a clone of that poco? That way you can get data in and out of the domain object in order to persist it or to pass it around. – stralsi Oct 03 '12 at 19:03
  • Something like object snapshot? It would work probably but would also require some hacking to get it work with ORM tool. I personally don't see any easy way, and it would bring lot of abstractions and generalisations which you would have to fight throughout app dev. Event sourcing is the only way to go IMO – Hrvoje Hudo Oct 10 '12 at 08:39
  • I actually just watched this video and was thinking the same thing; does that mean you need a set of DTO/POCO objects for the data/persistence layer that your ORM hydrates and then use a mapper like AutoMapper to map to a domain object? Does something like that happen in the repository? It seems like an ORM like EF Code First expects a POCO with getters and setters. – Abe Apr 23 '13 at 23:19
  • 1
    @Abe yep, it looks to me that EF model should go below domain model, and use AutoMapper magic when saving or loading stuff. Also, for UI there are another bunch of ViewModels, again mapped from domain model - not EF model. Business logic should be in those domain models, and not in SomethingManager classes that you can everywhere. IMHO :) – Hrvoje Hudo Apr 27 '13 at 16:38
  • Cool! Do you find it to be tedious/heavy to write that much mapping code though? EF to Domain Model and then Domain to MVC ViewModel... There's also possibly domain to WCF contract, etc... It does looks like you can AutoMapper has a ConstructUsing method that lets you create a new object instead of using getters and setters on the domain model as shown in http://stackoverflow.com/questions/2239143/automapper-how-to-map-to-constructor-parameters-instead-of-property-setters – Abe Apr 27 '13 at 21:57
  • The video is located here https://vimeo.com/43598193 – Steven T. Cramer Mar 30 '19 at 05:45

4 Answers4

12

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.
David Masters
  • 8,069
  • 2
  • 44
  • 75
2

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);
superjos
  • 12,189
  • 6
  • 89
  • 134
1

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.

Luka
  • 4,075
  • 3
  • 35
  • 61
  • I don't see a private/protected or internal constructor in your sample. So NHibernate will use the default one which is parameter-less. I would use this type of constructor you did only for assuring that the object gets populated in code, not by orm. – Luka Jul 01 '12 at 18:40
  • Looa a this post for NHibernate and default constructors: http://kozmic.pl/category/nhibernate/ – Luka Jul 01 '12 at 18:46
0

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.

Hrvoje Hudo
  • 8,994
  • 5
  • 33
  • 42
MikeSW
  • 16,140
  • 3
  • 39
  • 53