10

I have come from a transaction script world and I am just starting to take a look at DDD. I am unsure of the correct way to integrate a DDD design with database persistence. This is what I have:

A service class called OrganisationService whose interface contains methods for retrieving and saving away instances of Organisation domain objects. Organisation is an aggregate root and has other data related to it: Members and Licenses. EF6 database first DBContext is used within the OrganisationService to retrieve OrganisationDB entities and the related MemberDB and LicenseDB entities. These all get transformed into their domain object class equivalents when retrieved by the OrganisationService and loaded into the Organisation domain object. This object looks like so:

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }
}

I am not using the Repository pattern in the OrganisationService...I am using EF itself as the Repository as it seems that EF6 has largely made repository redundant now.

At this point in the design the Organisation domain object is anaemic: it looks like the EF POCO Organisation class. The OrganisationService class looks a lot like a Repository!

Now I need to start adding logic. This logic includes managing an Organisations Licenses and Members. Now in the transaction script days I would add methods into the OrganisationService for handling these operations and call into a Repository to interact with the DB, but with DDD I believe this logic should be encapsulated within the Organisation domain object itself...

This is where I am unsure of what I should be doing: I will need to persist this data back to the database as part of the logic. Does this mean I should use DbContext within the Organisation domain object to do this? Is using the repository/EF within the domain object bad practice? If so, where does this persistence belong?

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }

    public void AddLicensesToOrganisation(IList<License> licensesToAdd)
    {
        // Do validation/other stuff/

        // And now Save to the DB???
        using(var context = new EFContext())
        {
            context.Licenses.Add(...
        }

        // Add to the Licenses collection in Memory
        Licenses.AddRange(licensesToAdd);
    }
}

Should I instead just be mutating the Organisation domain object in memory and then pushing it back to the OrganisationService for persistence? Then I have to track what actually changed on the object (which is what EF does to its own POCOs! I am kind of coming to feel that EF isn't just a repository replacement, but also could be the domain layer!)

Any guidance here is appreciated.

2

You can take either approach and have it work well - there are, of course, pros and cons.

Entity Framework is definitely intended to suffuse your domain entities. It does work well when your domain entities and data entities are the same classes. It's much nicer if you can rely on EF to keep track of the changes for you, and just call context.SaveChanges() when you're finished with your transactional work. It also means that your validation attributes don't have to be set twice, once on your domain models and once on your persisted entities - things like [Required] or [StringLength(x)] can be checked in your business logic, allowing you to catch invalid data states before you try to do a DB transaction and get an EntityValidationException. Finally, it's quick to code - you don't need to write a mapping layer or repository, but can instead work directly with the EF context. It's already a repository and a unit of work, so extra layers of abstraction don't accomplish anything.

A downside to combining your domain and persisted entities is that you end up with a bunch of [NotMapped] attributes scattered throughout your properties. Often, you will want domain-specific properties which are either get-only filters on persisted data, or are set later in your business logic and not persisted back into your database. Some times, you'll want to express your data in your domain models in a way that doesn't work very well with a database - for example, when using enums, Entity will map these to an int column - but perhaps you want to map them to a human-readable string, so you don't need to consult a lookup when examining the database. Then you end up with a string property which is mapped, an enum property which isn't (but gets the string and maps to the enum), and an API which exposes both! Similarly, if you want to combine complex types (tables) across contexts, you may wind up with a mapped OtherTableId and an unmapped ComplexType property, both of which are exposed as public on your class. This can be confusing for someone who isn't familiar with the project, and unnecessarily bloats your domain models.

The more complex my business logic/domain, the more restrictive or cumbersome I find combining my domain and persisted entities to be. For projects with short deadlines, or that don't express a complex business layer, I feel that using EF entities for both purposes is appropriate, and there's no need to abstract your domain away from your persistence. For projects which need maximum ease-of-extension, or that need to express very complicated logic, I think you're better off separating the two and dealing with the extra persistence complexity.

One trick to avoiding the trouble of manually tracking your entity changes is to store the corresponding persisted entity ID in your domain model. This can be filled automatically by your mapping layer. Then, when you need to persist a change back to EF, retrieve the relevant persistent entity before doing any mapping. Then when you map the changes, EF will detect them automatically, and you can call context.SaveChanges() without having to track them by hand.

public class OrganisationService
{
    public void PersistLicenses(IList<DomainLicenses> licenses) 
    {
        using (var context = new EFContext()) 
        {
            foreach (DomainLicense license in licenses) 
            {
                var persistedLicense = context.Licenses.Find(pl => pl.Id == license.PersistedId);
                MappingService.Map(persistedLicense, license); //Right-left mapping
                context.Update(persistedLicense);
            }
            context.SaveChanges();
        }
    }
}
  • Is there any problem when combining different approaches with different complexity of data? if you have something that maps nicely to DB, just use EF directly. If you have something that doesn't, then introduce a mapping/abstraction before using EF. – Euphoric Jul 23 '14 at 6:10
  • @Euphoric the differences of domain and db can be handled in the mapping. I can't think of a use case where adding the domain twice (non persistent and persistent) and handle change tracking by hand is less work than adding mappings for special cases. Can you point me to one? (I assume using NHibernate, maybe EF is more limited?) – Firo Jul 23 '14 at 6:35
  • Very helpful, practical and informative answer. Marking as the answer. Thanks! – MrLane Aug 20 '14 at 3:29
3

i don't know EF6 but other ORM handle this transparently so you wont have to add the licences to the context explicitly, it will detect them when saving changes. So the method will be down to

public void AddLicenses(IEnumerable<License> licensesToAdd)
{
    // Do validation/other stuff/
    // Add to the Licenses collection in Memory
    Licenses.AddRange(licensesToAdd);
}

and the code around it

var someOrg = Load(orgId);

someOrg.AddLicenses(someLicences);

SaveChanges();
  • My domain objects are not the Entities, so adding them to the Licenses collection, which is just a List will not cut it. The question is not so much about EF though as it is where the actual persistence takes places. All persistence in EF must take place within a context scope. The question is where does this belong? – MrLane Jul 22 '14 at 23:20
  • 2
    domain entities and persisted entities can/should be the same otherwise you introduce another layer of abstraction which 99% of the time adds no value and change tracking is lost. – Firo Jul 23 '14 at 6:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.