简体   繁体   English

DTO应该由域实体还是持久性生成?

[英]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. 当谈到具有现代ORM的分层应用程序时,我常常不确定如何创建特定类以遵守所谓的“最佳实践”,同时还要关注性能要求。

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. 域实体 - 这些是包含业务逻辑的丰富类(对吗?),并且根据ORM功能,可能与持久性设计直接相关。

  2. DTOs - these are simpler classes that strip business logic in order to pass data around to internal and external clients. DTO - 这些是更简单的类,它们剥离业务逻辑,以便将数据传递给内部和外部客户端。 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. 视图模型 - 这些与DTO类似,因为它们更简单,没有业务逻辑,但它们通常非常扁平,并且通常包含与它们所服务的UI相关的其他位。

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. 我遇到的挑战是,在某些情况下,域实体或任何面向持久性的类映射到更简单的实体(如DTO或ViewModel)会阻止您进行重要的性能优化。

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. 如果我公开了一个公共API,我的DTO可能看起来非常像这个例子,没有任何业务逻辑。

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: 如果我还有一个MVC网络应用程序,我想要显示一个事件列表,我可能想要一个看起来像这样的视图模型:

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). 现在,ORM可能会创建一个查询风暴来填充完整的引用对象(这可能比这个示例复杂得多,有自己的引用)。 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? 让两个Event -ish对象表示相同的持久化数据感觉不对,但同时看起来持久层看起来不应该知道DTO或视图模型,否则争取分离的重点是什么?

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? 是那些重量较轻的描述DTO或某些域实体精简版?

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. 例如,持久性DTO可以以不同于域实体的方式存储数据以支持复杂的查询案例。
  • 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. 使用单独的持久性DTO,您可以选择加载“旧”DTO格式并将其转换为“新”域实体。
  • Ability to simplify DTOs returned to the outside world, eg through an API. 能够简化返回到外部世界的DTO,例如通过API。 This is something that almost always makes sense when using DDD, because using DDD is usually an indication that the domain is complex. 这在使用DDD时几乎总是有意义,因为使用DDD通常表明域很复杂。
  • 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. 根据ORM或数据库的功能集,在持久性中直接使用域实体甚至不是一个选项。 If it is an option, it may be more complicated than having dedicated DTOs. 如果是一种选择,它可能比拥有专用的DTO更复杂。

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. 我认为这是一个小问题,因为DTO和视图模型是众所周知的概念,但它可能是一个问题,取决于团队。

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. 域实体必须独立于ORM,事实上,如果您遵循DDD原则,所有域层都不应该依赖于任何其他层。 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. DTO只是在层之间传递数据,在大多数情况下,它在Repository的接口中使用,作为方法的返回。 And the interfaces of the Repository, as the Service's, should stay in the Domain layer. 并且Repository的接口(作为Service)应该保留在Domain层中。

It doesn't feel right to have two Event-ish objects representing the same persisted data, ... 让两个Event-ish对象表示相同的持久化数据感觉不对,...

Actually, this is not necessarily bad. 实际上,这并不一定是坏事。 Your EventViewModel might be eventually consistent with your Event Class. 您的EventViewModel 最终可能与您的Event一致 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. 您的Event确保满足所有Event业务规则,而您的EventViewModel可能通过侦听由(例如) Event类发出的域事件来更新。 This is sometimes called projecting - an EventViewModelProjection listens to Event domain events (no pun intended) and projects those on EventViewModels . 这有时称为投影 - 一个EventViewModelProjection监听Event域事件(没有双关语)并在EventViewModels上投影。

but at the same time it doesn't seem like the persistence layer should know about DTOs or view models, ... 但与此同时,持久层似乎不应该知道DTO或视图模型,......

Well, if you choose to persist DTOs and view models, then persistence logic should be coded somewhere . 好吧,如果你选择坚持DTO并查看模型,那么应该在某处对持久性逻辑进行编码。

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? 否则,争取分离的重点是什么......那么你如何解决这个问题......那些重量较轻的描述是DTO还是某些领域的实体?

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. 您可能有兴趣阅读有关cqrs最终一致性的内容以获得一些想法。

DTOs don't have behavior, they are for data transfer. DTO没有行为,它们用于数据传输。

ViewModels contain some behavior about the presentation, so they are not DTOs as well. ViewModels包含有关演示文稿的一些行为,因此它们也不是DTO。 You can use DTOs in the presentation if you do not have any view specific behavior. 如果您没有任何特定于视图的行为,则可以在演示文稿中使用DTO。

Domain entities and ORM entities are not the same. 域实体和ORM实体不相同。 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. 您应该能够用您喜欢的持久性逻辑替换ORM。 They must be decoupled. 他们必须脱钩。

I think you are confusing DTOs with value objects, which are business objects and indeed have behavior and are persisted. 我认为你将DTO与值对象混淆,后者是业务对象,实际上有行为并且是持久的。 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. 例如,地址,电话号码,id等可以是值对象。

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. 演示文稿必须无法直接访问和修改域对象,这就是我们使用DTO在演示文稿和应用程序服务之间发送数据的原因。 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). 现在,ORM可能会创建一个查询风暴来填充完整的引用对象(这可能比这个示例复杂得多,有自己的引用)。 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) 在一次调用中请求多个对象(例如,100个事件)

  2. Request related objects with main object in a single call (for example, 100 Events with Headliner and Opener) 在一次调用中请求与主对象相关的对象(例如,100个带有顶棚和开启器的事件)

  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) 来自ViewModel的队列请求(每个ViewModel告诉它需要哪些对象,然后在一次调用中请求所有对象,并且每个ViewModel都会返回它要求的对象)

Depending on what layer you are querying, Object means DTO in context of Service Layer or Entity in context of Domain / Persistence layer. 根据您要查询的层, Object表示在域/持久层的上下文中服务层或Entity的上下文中的DTO

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. 我认为这是您开始尝试DDD时出现的第一个问题之一:查询结束显示数据时的性能。

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 ). 当然,您仍然可以将其用作向用户显示的数据源,但是,如果您遇到性能问题,最好是评估使用命令查询责任隔离模式( CQRS )。

With it, data to display are represented by another model (specifically the Data Model), which can be, in your example, the EventViewModel class. 有了它,要显示的数据由另一个模型(特别是数据模型)表示,在您的示例中,它可以是EventViewModel类。 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. 我总是将视图模型与Domain和DTO分开,因为我希望View Models能够代表我的视图而不是其他任何东西。 This will have data annotations, validation logic etc. 这将有数据注释,验证逻辑等。

DTOs and ORM Entities DTO和OR​​M实体

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. 我将DTO和OR​​M实体保留在同一个项目中,并确保它们对任何事物都没有任何依赖关系,它们只是POCO。 I start with creating ORM Entities and add or create DTOs as and when they are required. 我首先创建ORM实体,并在需要时添加或创建DTO。

I use entities created for ORM across layers, I use them as DTOs wherever possible. 我使用为层的ORM创建的实体,我尽可能将它们用作DTO。 I do not add or remove properties to these ORM Entities. 我不向这些ORM实体添加或删除属性。 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. 如果我需要与我的服务的ORM实体或我的应用程序中的任何其他层稍微不同的结构,我会为该要求创建新的POCO,并且它们可供所有层使用。

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. 例如,如果我需要一个计算值,我想传递给ORM实体中不可用的UI层(因为它们没有被保留),那么我创建一个只包含必需字段的新POCO。

I use AutoMapper to copy data between objects 我使用AutoMapper在对象之间复制数据

In a Domain-Driven-Design (DDD), the domain layer should be ignorant of the persistence, presentation, caching, logging and other infrastructure services. 在域驱动设计(DDD)中,域层应该不知道持久性,表示,缓存,日志记录和其他基础结构服务。 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: 您可以应用SOLID原则来帮助您创建良好的软件架构:

S is single responsibility principle (SRP) S是单一责任原则(SRP)
O stands for open closed principle (OCP) O代表开放闭合原理(OCP)
L Liskov substitution principle (LSP) L Liskov替换原理(LSP)
I interface segregation principle (ISP) 我接口隔离原则(ISP)
D Dependency injection principle (DIP) D依赖注入原则(DIP)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 应用层的处理程序是否应该返回 DTO 或域 object - Should handler from the Application layer return DTO or domain object EntityFramework是否生成了实体持久性愚昧 - Are EntityFramework generated Entity Persistence Ignorance 域层应该直接接收依赖项DTO吗? - Should the domain layer receive a dependency DTO directly? DTO和实体是否都有输入验证 - Should DTO and Entity both have input validations 通过继承从实体映射到 dto - Mapping from entity to dto with inheritence AutoMapper最佳实践 - 我是否应该向DAO询问有关实现从DTO到域对象的映射的信息? - AutoMapper best practices - Should I be asking the DAO for information to fulfill mapping from DTO to domain object? 使用AutoMapper从实体映射到DTO或DTO到实体时出错 - Error when mapping from Entity to DTO or DTO to Entity using AutoMapper 从域模型继承持久性模型以节省使用实体框架的映射工作有哪些缺点? - What are the disadvantages to inheriting Persistence Models from Domain Models to save mapping effort using Entity Framework? 通过NHibernate和AutoMapper将DTO的entityID转换为Domain的实体 - Converting a DTO's entityID to a Domain's entity through NHibernate and AutoMapper 如何减少域/实体/ DTO对象的重复? - How do I reduce duplication of domain/entity/DTO objects?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM