简体   繁体   中英

Should a DTO be generated by a domain entity or from persistence?

When it comes to layered applications with modern ORMs, I'm often unsure of how the specific classes should be created to adhere to so-called "best practices" while also paying attention to performance requirements.

Consider that you may have any number of the following types of objects in an application:

  1. Domain Entities - these are rich classes that contain business logic (right?) and, depending on the ORM capabilities, may directly relate to the persistence design.

  2. DTOs - these are simpler classes that strip business logic in order to pass data around to internal and external clients. Sometimes these are flattened, but not always.

  3. View Models - these are similar to DTOs in that they're simpler and devoid of business logic, but they are usually pretty flat and often contain additional bits that relate to the UI they're serving.

The challenge I have is that in some cases the mapping of domain entities or any persistence-oriented class to a simpler entity like a DTO or ViewModel prevents you from making important performance optimizations.

For Example:

Let's say I have some domain entities that look like this:

public class Event
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime EventDate { get; set; }

    // These would be reference types in most ORMs
    // Pretend in the setter I have logic to ensure the headliner =/= the opener
    public Band Headliner { get; set; }
    public Band Opener { get; set; }
}

public class Band
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Genre Genre { get; set; }
}

In the real world these might be much more complicated, with various business logic, maybe some validation calls, etc.

If I'm exposing a public API, my DTO might look very much like this example, sans any business logic.

If I also have, say, an MVC web app I want to show a list of events on, I might want a view model that would look something like this:

public class EventViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime EventDate { get; set; }

    public int HeadlinerId { get; set; }
    public string HeadlinerName { get; set; }
    public int OpenerId { get; set; }
    public string OpenerName { get; set; }
}

Often, people just pull a full domain entity with references, then use a mapping utility to hydrate the view model.

However, let's say I have tens of thousands of records. Now the ORM is probably creating a storm of queries to populate the full reference objects (which may be much more complicated than this example, with their own references). It doesn't take long for performance to start to seriously suffer.

What's the question?

I know I'm not the only one to run into this issue, so I'm curious to know how people maintain a layered application while still accounting for the need to maintain performance while generating multiple objects that represent the same underlying domain information .

It doesn't feel right to have two Event -ish objects representing the same persisted data, but at the same time it doesn't seem like the persistence layer should know about DTOs or view models, otherwise what's the point of striving for separation?

So how do you solve this? Does persistence know about strict, detailed representations of domain entities as well as lighter-weight depictions of the data in those entities? Are those lighter-weight depictions DTOs or some domain entity lite?

There is no simple answer to your question, because it really depends on what you want to achieve with your architecture. This is a classical architecture trade-off.

That also means you need to decide for yourself. Make sure you know the advantages and drawbacks of each approach, then decide for your project. Here is a list of pros and cons:

Advantages of strict separation

  • Ability to adapt and tune structures for the responsibilities of the specific layer. The persistence DTO could store data differently than the domain entity to support a complicated query case, for example.
  • Ability to support data migration cases. With separate persistence DTOs, you have the option to load "old" DTO formats and convert it to the "new" domain entity.
  • Ability to simplify DTOs returned to the outside world, eg through an API. This is something that almost always makes sense when using DDD, because using DDD is usually an indication that the domain is complex.
  • Better separation of concerns for developers. Often, strict layering leads to increased possibilities for teams to work on the same feature in parallel, eg one in the persistence and one in the domain.
  • Depending on the feature set of the ORM or database, using the domain entities directly in the persistence is not even an option. If it is an option, it may be more complicated than having dedicated DTOs.

Advantages of shared classes

  • Less code for the same functionality.
  • Usually faster development time for new functionality.
  • Smaller conceptual overhead. I consider this a minor point, because DTOs and view-models are well-known concepts, but it may be an issue depending on the team.

As you see, I don't consider performance an advantage for the shared approach. The primary reason is that a well-designed object-to-object mapping is orders of magnitudes faster than loading the data from the DB. So I'm pretty confident that performance issues in the strict separation approach are due to other problems, but not the layering.

With the above points (and possible more that are specific to your environment) you should be able to make a decision. I've worked with both approaches in the past, but for projects of a certain size, I always choose the strict separation approach.

Josh,

The Domain Entities must be independent of ORM, in fact, all the Domain Layer should not depend of any other layer, if you are following DDD principles. The DTO's is just to carry data between layers, and in most cases, it is used in Repository's Interfaces, as a return of the methods. And the interfaces of the Repository, as the Service's, should stay in the Domain layer.

It doesn't feel right to have two Event-ish objects representing the same persisted data, ...

Actually, this is not necessarily bad. Your EventViewModel might be eventually consistent with your Event Class. Your Event makes sure all Event business rules are met, while your EventViewModel might be updated by listening to domain events , emitted by (for instance) the Event class. This is sometimes called projecting - an EventViewModelProjection listens to Event domain events (no pun intended) and projects those on EventViewModels .

but at the same time it doesn't seem like the persistence layer should know about DTOs or view models, ...

Well, if you choose to persist DTOs and view models, then persistence logic should be coded somewhere .

otherwise what's the point of striving for separation ... So how do you solve this ... Are those lighter-weight depictions DTOs or some domain entity lite?

It is impossible to give you one, definitive answer - these are all design considerations depending strongly on your specific context. If you run into performance issues, then using domain events like I mentioned can be a good idea.

You might be interested in reading about cqrs and eventual-consistency to get some ideas.

DTOs don't have behavior, they are for data transfer.

ViewModels contain some behavior about the presentation, so they are not DTOs as well. You can use DTOs in the presentation if you do not have any view specific behavior.

Domain entities and ORM entities are not the same. What you are doing is probably active record and not domain model. You should be able to replace the ORM with your favorite persistence logic. They must be decoupled.

I think you are confusing DTOs with value objects, which are business objects and indeed have behavior and are persisted. You usually describe something with a value object if it does not have an identity and it contains behavior or multiple values which belong together. For example an address, a phone number, an id, etc. can be a value object.

You cannot use domain objects to transfer data to the presentation. The presentation must not be able to access and modify domain objects directly, that's why we use DTOs to send data between the presentation and the application services. The application services can access domain objects.

Often, people just pull a full domain entity with references, then use a mapping utility to hydrate the view model.

However, let's say I have tens of thousands of records. Now the ORM is probably creating a storm of queries to populate the full reference objects (which may be much more complicated than this example, with their own references). It doesn't take long for performance to start to seriously suffer.

To minimize number of queries and improve performance:

  1. Request multiple objects in a single call (for example, 100 Events)

  2. Request related objects with main object in a single call (for example, 100 Events with Headliner and Opener)

  3. Cache objects to lookup already requested objects instead of requesting again

  4. Queue requests from ViewModels (each ViewModel tells which objects it needs, then all objects are requested in a single call and every ViewModel is given back objects it asked for)

Depending on what layer you are querying, Object means DTO in context of Service Layer or Entity in context of Domain / Persistence layer.

I think that this is one of the first problem that arise when you start to experiment with DDD: performance when querying end displaying data.

The key concept here is that the domain model must focuses on operations, enforcing business rules and eventually trigger events, rather than provide informations. Sure you can still use it as source of data to display to user, but, if you incur in performance issues, better is if you evaluate the use of the Command-Query Responsibility Segregation pattern ( CQRS ).

With it, data to display are represented by another model (specifically the Data Model), which can be, in your example, the EventViewModel class. The Data Model is designed independently from the domain model and, usually, is designed in a way that building it from the data source is performant (ie: no object mapping needed).

Domain Entities Represents your domain/business. For example, in mortgage domain an Escrow Account is a Domain Entity. Escrow Account is identified by its account number. This Entity does not represent your table schema, this does not know anything about your database.

public class Escrow
{
public Guid AccountId {get; set;}
public decimal GetBalance()
}

View Models

I always keep view models separate from Domain and DTOs, as i want View Models to represent my view and nothing else. This will have data annotations, validation logic etc.

DTOs and ORM Entities

Now this is the tricky bit. I keep both DTOs and ORM entities in the same project and make sure that they do not have any dependencies on anything and they are just POCOs. I start with creating ORM Entities and add or create DTOs as and when they are required.

I use entities created for ORM across layers, I use them as DTOs wherever possible. I do not add or remove properties to these ORM Entities. If I need a slightly different structure than the ORM entities for my services or any other layers in my application, I create new POCO for that requirement and they are available to all layers.

For Example, If I need a calculated value that I want to pass on to UI layer that is not available in a ORM Entity (as they are not persisted) then I create a new POCOs with only the required fields.

I use AutoMapper to copy data between objects

In a Domain-Driven-Design (DDD), the domain layer should be ignorant of the persistence, presentation, caching, logging and other infrastructure services. This can be achieve with the use of abstractions (using interfaces instead of concrete services on dependent services). You can apply SOLID principles which can help you create good software architecture:

S is single responsibility principle (SRP)
O stands for open closed principle (OCP)
L Liskov substitution principle (LSP)
I interface segregation principle (ISP)
D Dependency injection principle (DIP)

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