简体   繁体   中英

DDD and Entity Framework classes

I have read many articles about DDD and understood, that I should use my domain model classes in the Infrastructure level, so, I should use the same classes as Entity Framework infrastructure and use them to generate tables (code-first approach) etc. But my domain model can be fully different than Relational DB model.

Why I can't create one more model, infrastructure model, to create relational DB model and don't mix domain model with EF classes?

Consider this simple example:

Domain Model

public class Customer
{
    public Customer(IRegistrar registrar)
    {
        this.registrar = registrar;
    }

    public int Age
    {
        get
        {
            // Just for this example. This will not work for all locals etc but beyond the point here.
            var today = DateTime.Today;
            return today.Year - this.DateOfBirth.Year;
        }
    }

    public DateTime DateOfBirth { get; set; }

    public int Register()
    {
        if (this.Age < 18)
        {
            throw new InvalidOperationException("You must be at least 18 years old");
        }

        int id = this.registrar.Register(this);

        return id;
    }
}

public interface IRegistrar 
{
    public int Register(Customer customer);
}

A lot of people when they do not have a domain model will do this in an MVC controller:

public ActionResult Search(Customer customer)
{
    var today = DateTime.Today;
    var age = today.Year - this.DateOfBirth.Year;
    if (age < 18)
    {
        // Return an error page or the same page but with error etc.
    }

    // All is good
    int id = this.registrar.Register(customer);

    // The rest of code
}

There are a few issues with that:

  1. What if the developer forgets to make the check for age before calling registrar ? Many people will say, well that is a bad developer. Well whatever the case is, this type of code is prone to bugs.

  2. The product is doing well so CFO decides to open up the API because there are many developers out there who are making great UI interfaces for customer registration and they want to use our API. So the developers go ahead and create a WCF service like this:

     public int Register(Customer customer) { var today = DateTime.Today; var age = today.Year - this.DateOfBirth.Year; if (age < 18) { // Return a SOAP fault or some other error } int id = this.registrar.Register(customer); // The rest of code }
  3. Now the developers can forget to make the check for age in 2 different places.

  4. The code is also in 2 different places. If there is a bug, we need to remember to fix it in 2 different places.
  5. If the company starts operating in places where the legal age is 21, we need to find all the places and add this rule.
  6. If we are discussing the rules with BA, well we need to look through all the applications and find the rules.

In the above case we only have one rule: Age must be greater than 18. What if we had many more rules and many more classes? You can see where this will go.


EF Model

Your EF model may be like this:

public class Customer
{
    public int Id { get; set; }
    public DateTime DateOfBirth { get; set; }  

    // It may have a foreign key etc.    
}

Application Layer Model

And your model for MVC view maybe like this:

public class Customer
{
    // Or instead of Domain.Customer, it may be a CustomerDto which is used
    // to transfer data from one layer or tier to another.
    // But you get the point.
    public Customer(Domain.Customer customer)
    {
        this.DateOfBirth = customer.DateOfBirth;
        this.Age = customer.Age;
        if (this.DateOfBirth.DayOfYear == DateTime.Today.DayOfYear)
        {
            this.Greeting = "Happy Birthday!!!";
        }
    }
    public int Age { get; set; }

    [Required(ErrorMessage = "Date of birth is required.")]
    [Display(Name = "Data of birth")]
    public DateTime DateOfBirth { get; set; }

    public string Greeting { get; set; }
}

Here is a question: How many EF models have you seen with the Display attribute? I will let you decide if the EF model should concern itself with how it is displayed in the UI. Just the assumption that my EF model will be displayed in UI is wrong. Maybe the only consumers of my class is another web service. I don't think Display should be in the EF model but some may not agree with me; you make the call.

There are loads of questions on stackoverflow about people asking that sometime PropertyX is required and sometimes it is not, how can I do this? Well if you did not put Required attribute on your EF model and use your EF model in your view, then you would not have this issue. There will be one model for the view where PropertyX is a required field. That model will decorate PropertyX with the Required attribute, while another model for the view that does not require PropertyX will not decorate the property with the Required attribute.


ViewModels

And then you may have a viewmodel for a customer for a WPF application and you may have a javascript viewmodel for the frontend (KnockoutJS viewmodel).


Conclusion and answer to your question

So in conclusion, you can have different domain models than your entity models. Your domain model should be unaware of the database. If you decide to remove a column from one table due to normalization and put it into a table of its own, your entity model will be affected. Your domain model should not be affected.

I have read arguments on the net such as "this design takes too long, I just want to roll something out quickly and give it to the client and get paid". Well if you are not designing a product which will need to be maintained and features will be added to it but you are just designing a quick little site for your client then do not use this approach. No design applies to every situation. The point to take away is that your design should be chosen wisely with future in mind.

Also the conversion from entity model to domain to a model for MVC does not need to be done manually. There are libraries out there which will do this for you easily such as AutoMapper .

But I have to admit, there are tons of examples on the net and also in use in many applications where the entity models are used throughout the application and rules are implemented everywhere with loads of if statements.

Relation of this to DDD

When I read your question, I find something that catches the eye. It is this:

I have read many articles about DDD and understood, that I should use my domain model classes in the Infrastructure level, so, I should use the same classes as Entity Framework infrastructure and use them to generate tables (code-first approach)

To be honest, the best source of DDD knowledge it still the Blue Book. I know, I know, it is thick and hard to read. May be have a look at DDD Distilled by Vernon. The conclusion should be that DDD is not really about dealing with persistence but in deeper insight of the domain, better understanding your domain experts. Definitely, it says nothing about ORM.

Domain Model persistence

Domain models usually consist of objects (if we talk about object-oriented models) with state and behaviour. A model would have one or more entities and may be some value objects. In many cases you only have one entity per bounded context. Entities are grouped in Aggregates , that change together, forming transaction boundaries. This means that each change within the Aggregate is one transaction, no matter how many entities this change touches. Each Aggregate has one and only one entity, the Aggregate Root , which exposes public methods for others to work with the whole Aggregate .

So your Repository should take care of:

  • Persisting the whole Aggregate (no matter how many entities are there) within one transaction, for new and updated objects
  • Fetching the whole Aggregate from your persistence store, by its identity ( Aggregate Root Id property)

You for sure will need some Queries but they can query how they want as soon as they do not amend the domain model state. Many add querying methods to the Repository but it is up to you. I would implement them as a separate static class with DbContext extension methods.

Models not matching each other

You mentioned that your persistence model does not match the domain model. This might be the case although for many situations it is not the case. There are a few ways of dealing with this:

  • Keep state separate of the behaviour and have it as a property in the domain object. Like Order with AddLine and so on, and OrderState with all these Total , CustomerId and stuff like this. Bear in mind that this might not work nice for complex aggregates.
  • Concentrate on the two main methods of the Repository that I mentioned above - Add and Get . Each Repository works for one type of Aggregate only and how you map between them is up to you.
  • Combined with the point above, you can reconsider using ORM and do something else. Basically you can just use ADO.NET but the easiest is to use some sort of document-oriented stuff like NoSQL although many would disagree. Check also this article about PostgreSQL JSONB storage as persistence.

Remember that the main point is to have the Repository that will do the work for you and potentially (probably this would never happen but still) use another store.

You might also be interested in another Vernon's article where he discusses using EF specifically.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM