問題描述
在大型 .NET 項目中管理 DTO 和映射 (Managing DTOs and mapping in large .NET project)
My team and I are building a large .NET WinForms applications. The application uses various "Services" to get data to and from our database. Each "service" lives in its own solution, and handles a specific type of data. So, for example, our "ContactsService" manages retrieving / saving contacts to our database.
Typically, we have been building DTOs for each service. So, we might have a "ContactDTO" that has simple string properties for each piece of data on the contact. Now, we also have a business layer "Contact" class that has the exact same properties, and perhaps a few extra methods with some business logic. On top of that, the "ContactsService" has its own Contact class, which is hydrated from the ContactDTO.
It has become a huge pain to manage all of our DTOs and mapping. Currently, sending a contact to be stored in the database looks like this:
- Map client Contact to ContactDTO
- Map ContactDTO to service Contact
- Save Contact
- Map service Contact to ContactDTO
- Map ContactDTO to client Contact
This just feels crappy. If we add a property to our client Contact class, we have to add the property and mappings in 3-4 places.
What are we doing wrong and how can we make our lives easier? Would it be simpler to use something like Json.NET and have JSON DTOs? We checked out AutoMapper, but some team members thought it was too complicated.
參考解法
方法 1:
I've run into this a lot, but in my case I've chosen to accept it because the decision to use DTOs was an intentional one - I want my service, client, proxy, and contract assemblies to be cleanly separated.
When using WCF, the layout I prefer is something like this (using your Contact example):
- Client assembly
- Contact (has client-y characteristics)
- Shared Contracts assembly
- Contact (the agreed-upon common DTO)
- Proxy assembly
- Uses the Contact from the shared Contracts assembly
- Service assembly
- Contact (has service-y business logic characteristics, e.g. this may be the type exposed by an ORM layer like Entity Framework)
I'm now sharing the contracts between the service and the client. If I ever need to version them independently, then I can just make a copy of the shared Contracts assembly and re-target the Proxy assembly to use the copy, and then modify the two Contracts assemblies independently. But in most cases that I've worked in, I own both the client and the service, so it's convenient to share the Contracts assembly between the two.
That's the only optimization that I can think of when you make the architectural decision to isolate your components with DTOs, at least without using code generation tools (I don't know of any good ones, but haven't had to look into them).
Edit: When not using WCF, you obviously won't need the 'Proxy' assembly.
方法 2:
AutoMapper isn't particularly complicated. If you follow the conventions, e.g. Contact.Address1 becomes ContactAddress1 on the DTO you won't have to write much code apart from the call to Mapper.Map. Alternatively you can use code generation but it will still be tricky to manage change.
Can I ask why you feel the need to have both a ContactDTO and a service contact? Can you not just pass the service Contact around? I known it's not best practice but it might save you from RSI
EDIT: Disregard my last point - for some reason I thought you were mapping the service Contact to a database entity such as NHibernate/Entity Framework
方法 3:
Some things to speed up the process to consider:
I wrote a code generators that you could pick the table(s) and columns from the database and have it generate a C# DTO. This saves a lot of needless typing and can generate DTOs a lot quicker. A little time up front, but when you have a table with 20 columns you need to map, it helps.
I also wrote code generators to map the DTOs to stored procedure for save, delete, and query operations. Again a time saver because a lot of them end up being very similar. If you have of lot of tedious "grunt" code being written, consider a code generator to do it.
Use an entity framework or an ORM mapper for the back end. This can save a lot of time but you have to invest in the ORM knowledge and learn how it works.
Create a common set of DTOs that are used from client to database. This may not be practical in some situations when you need proxies or smaller DTOs for the client, but you can try to have some common DTOs that are passed from client all the way back to the server.
Remove some of the layers. I know this sounds a bit of an anti pattern, but in some cases the onion doesn't have to be so thick.
(by TaylorOtwell、Lars Kemmann、user528573、Jon Raynor)