0
votes

I'm currently working on an architecture and patterns for our next project. I'm considering using DDD, but since the project will be of medium scale, I'm trying to make it as simple as possible from the perspective of code duplication and overall maintenance.

The layers look as follows, basically each layer is a separate assembly:

DB -> Domain -> Application -> Web API -> Clients

For DB access I'm using EF Core 1.0.

Here is my current design (simplified) of the classes in data and domain layers and which I'm not very happy with.

Domain:

class Task
{
  private int State { get; set; }
  private string Description { get; set; }
  private int CreatedById { get; set; }            
}

Data (EF):

class TaskData
{
  public int State { get; set; }
  public string Description { get; set; }
  public int CreatedById { get; set; }
  public User CreatedBy { get; set; }
}

Ideally I would like to use my domain entity directly with the ORM, but the Task and TaskData are not identical. In Task entity I don't need the navigation property CreatedBy, I'm fine with just an Id, so I don't want to pollute the domain with stuff it doesn't care about.

In data model I'm using the navigation property for some reports, so this join is useful in some cases.

As you can see, if I can't map domain entities directly, I have to do some mapping in the data layer. To be more specific, in the repository via. manual mapper class. Because my domain entity has no public getters and setters, I can't map TaskData to Task entity on a property basis.

This leads me to memento pattern, so I create new class, which seems to be just plain DTO:

class TaskSnapshot
{
  public int State { get; set; }
  public string Description { get; set; }
  public int CreatedById { get; set; }
}

And my original Task entity looks now like:

class Task
{
  ...           
  public Task(TaskSnapshot snapshot)
  {
    this.State = snapshot.State;
    this.Description = snapshot.Description;
    this.CreateById = snapshot.CreatedById;
  }

  public TaskSnapshot ToSnapshot()
  {
    return new TaskSnapshot() 
    {
      State = this.State, 
      Description = this.Description, 
      CreatedBy = this.CreatedBy };
  }
}

As you can see, it takes three classes with different purpose, but very similar content to create and maintain. And it's just data and domain layers. The "duplication" continues in other layers too.

Now when I decide to add a new field, I need to remember all the places where to add it and also correctly assign. I'm afraid this will often lead to bugs, because someone from the team just forgets to update all the code.

What I could do:

  1. Make getters and setters public on the domain entities, so I wouldn't need Snapshots. -> Definitely no!

  2. Add unnecessary properties (CreatedBy navigation) to the Task entity and use it directly with ORM. -> I would rather not.

  3. Transform Snapshot classes to data model and use them with ORM. -> I probably wouldn't mind navigation properties there, but it would mean that data model is part of the domain assembly. -> I don't know, I don't like it.

Is there a recommendation how could I effectively reduce number of classes or concentrate ALL the assignments (mapping) to one place/mapper class without compromising domain?

Thank you.

1
Just a question - why not use code first? This way you map database to your domain layer directly. - L-Four
I'm using code first, but in my data model I need to define navigation properties, which I don't need in the domain entity. So the two models are not identical. - Tom Shane
Here there is a couple of strategies: vaughnvernon.co/?p=879 - jlvaquero
"In data model I'm using the navigation property for some reports, so this join is useful in some cases" - I think moving reports into a different Bounded Context might make things easier. This means that your Task entity will be same as TaskData. Next if you make the Task entity property getters public and setters private, you can use Task entity as your EF Persistence entity as well. Take a look @ thedatafarm.com/data-access/… - Sri Harsha Velicheti
@TomShane Why you dont want to threat ORM object as Domain object? It will make your life much more easier. Also to make your ORM/Domain model "rich" you can use extension methods in you business Layer. - Maris

1 Answers

0
votes

Based on your comment below your question, the first question I would ask is: why don't you want navigation properties in the domain model? As you see it complicates the mapping, but to what concrete advantage?

Also take a look here, where is stated: "Navigation through association properties is a key concept in DDD."

So to me, sounds like you can keep things really simple by allowing navigation properties in your domain model - like CreatedBy, why should it not be a core concept in your domain? Of course, you would want to disable lazy loading and only load navigation properties if requested explicitly.