1

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?

3
  • 5
    It's OK to have more than one model and use mapping between them. Why do you think that you can't do this? There are many articles or blog posts, that use the same classes for domain and storage, but it isn't always true for real applications. Commented Jan 6, 2017 at 19:19
  • I don't see any limitations here, yes it seems like there will be a lot of work to do, but hey... nothing comes without effort Commented Jan 6, 2017 at 19:53
  • @OlegSh what is so different between your object model and database that you're not able to map it with Entity Framework? It rarely happens in my experience. Commented Jan 9, 2017 at 9:21

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

22 Comments

Could you elaborate on why you're adding View Model, MVC, WPF and "Application Layer Model" to the equation while the original question was only about domain model and "relational DB model"? I'm not sure to see how the bulk of your answer supports your concluding point.
The reason the question was asked is because the OP is mislead into thinking that it is either a domain or an EF model but not both. I wanted to clarify that not only can you have both of them but you may even have one model as in M for your MVC (if needed) with some additional attributes. And it may not end there and you may create a viewmodel for wpf (if needed). Do you think I can improve the answer? I am open to suggestions. Thanks.
@konrad Yes if they all look the same then it is an overkill, but more importantly it means you are doing something clearly wrong. The point is they should not be the same. In the little hypothetical example I have given, even in this example they are not the same. Your second question, No I do not mean to name ORM classes DTO. The one ORM uses will most likely reflect your db design. DTOs may look exactly like what your ORM uses in which case you do not need an ORM but in some cases they may look very different and then you will need a DTO.
@konrad Btw in my example I have 3 customer classes but they are different. I was just proving a point. The domain model has a business rule which disallows underage members. The EF model just has 2 properties and nothing else (anemic). The model for MVC has validation rules and also shows a greeting. So each one has code specific to that layer.
@konrad Yes thats totally fine. For simple apps, I use two as well. Sometimes I use one layer because it is one-off appliacation. Then there are some apps that are more than 5 layers (I am working on a huge application which has been in dev for 7 years and will continue for another 4-5). So it all depends. Good luck.
|
-1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.