C# – Separating Domain Object and Data Models

cdomain-driven-designdomain-objectsentity-frameworkorm

I looked through a lot of articles, blogs, and SO topics about separating domain object and data models. Almost every answer said: You should have separate classes for the domain and data persistence, because…

I understand why it is good, but I haven't found any even pseudo example to solve this problem, and I have following thoughts – maybe everyone knows that it should be implemented in this way, but no one does it?

In my solutions, I'm using Automapper to achieve this (mapping domain to DB model, and domain to DTO's), but I would like to compare my approach with any other, but as I mentioned I can't find any good example.

Maybe there are just words about good practices which do not exist in real, working applications, and it is easier to use just domain classes as data models used by form, which can give a lot of benefits like caching or change tracking?

I'm currently working on an application, where data are stored in the old database, which scheme… sucks a lot, so using DB models as domain models, in this case, causes that my domain sucks too.

What do you think about this?

ORM features && faster development process > separating domain and data models && slower development process?

Best Answer

I've came up with a NuGet package, which helps me doing exactly what you said. I'm using the classical DDD approach:

  • Domain --> DataModel = AutoMapper
  • Domain --> DTO = AutoMapper
  • DataModel --> Domain = Factory
  • DTO --> Domain = Factory

On first glance, it seems like hell of a overkill to have 3 models, but it allows us to design the domain without having ever to bother with technical concerns, for example having to set read only fields as private setters, because the ORM can't set read only fields etc.

The whole Repository layer code can be found here: https://github.com/DrMueller/MLH.DataAccess Currently, it's using a MongoDB, but the pattern is pretty much the same. An example how to use it, can be found here: https://github.com/DrMueller/MLH.WebApiExtensions

How it works:

Domain Model:

public class Individual : AggregateRoot
{
    public Individual(string id, string firstName, string lastName, DateTime birthdate)
        : base(id)
    {
        Guard.ObjectNotNull(() => firstName);
        Guard.ObjectNotNull(() => lastName);

        FirstName = firstName;
        LastName = lastName;
        Birthdate = birthdate;
    }

    public DateTime Birthdate { get; }
    public string FirstName { get; }
    public string LastName { get; }
}

create a Repository for the Aggregate Root, for example:

public class IndividualRepository : RepositoryBase<Individual, IndividualDataModel>
{
    public IndividualRepository(IDataModelRepository<IndividualDataModel> dataModelRepository, IDataModelAdapter<IndividualDataModel, Individual> dataModelAdapter) : base(dataModelRepository, dataModelAdapter)
    {
    }
}

A datamodel, which represents the "Entity":

public class IndividualDataModel : DataModelBase
{
    public DateTime Birthdate { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

And a DataModelAdapter, which maps in both directions:

public class IndividualDataModelAdapter : DataModelAdapterBase<IndividualDataModel, Individual>
{
    private readonly IIndividualFactory _individualFactory;

    public IndividualDataModelAdapter(IIndividualFactory individualFactory, IMapper mapper) : base(mapper)
    {
        _individualFactory = individualFactory;
    }

    public override Individual Adapt(IndividualDataModel dataModel)
    {
        return _individualFactory.Create(
            dataModel.FirstName,
            dataModel.LastName,
            dataModel.Birthdate,
            dataModel.Id);
    }
}

That's pretty much it, the implementation of the Repository has access to the datamodel and queryables, and the Interface works just with the Domain Models.

To answer your question: The infrastructure code can be easily made so generic, that there are minimal delays. All you need to do is to keep the possibility to define the mapping and overrule it for special needs.