How to best manage DTOs in Clean arch DDD project
Im struggling to understand how to manage DTOs and returns types for a scenario in my project.
I have a Domain project with DomainObjects and Repository Interfaces, Service interfaces and so on.
I have a infrastructure layer which implements the repos and services.
An application layer with cqrs, so each use case has a query or command with "dtos" for that. We use mediatr also.
Then I have a API layer which uses fastendpoints to define the endpoints, this calls the Application layer via mediatr.
I understand that I could have treated the API layer as pretty much the Application layer since it will basically just call down to the app layer but there is another legacy project that still needs to work which uses the application layer.
So first question is the fastendpoints uses request/response dtos and the application layer with mediatr uses virtually identical CommandHandler/QueryHandler/Responses. For now I have them as separate Dtos and map but it's a lot of boilerplate. Im considering sharing at least the return type. So lets say I search for Article the application layer would return ArticleDTO which is defined in the application layer or some other "contracts" layer and then the fastendpoint would return that as well. Is that bad or a acceptable compromise to avoid a bunch of mapping?
Second question is, I have a external service to search for Articles, it's implemented in the infrastructure layer with a service Interface implementation in the domain layer. This means I need to know in the domain layer about a DTO from the external service, since it is defined in the interface. It feels wrong to have dtos in the domain layer? Should I not implement the interface in the domain layer at all and talk directly with the infrastructure layer? Or should I just define it as IArticleSearchService<T> ?
9 Replies
I could have treated the API layer as pretty much the Application layerapi layer does external access stuff, like autodoc (e.g. swagger), authorization, validation (eventually), etc having a shared objects project is accepted practice (as a reference: https://abp.io/docs/9.0/framework/architecture/best-practices/module-architecture) although this: "the application layer with mediatr uses virtually identical CommandHandler/QueryHandler/Responses" catches my attention a bit, but ok
This means I need to know in the domain layer about a DTO from the external servicea dto is just data transfer object, you use when you need to transfer data to a remote host or whatever, so it's not a problem instead i would say this is a more interesting question: "should I just define it as IArticleSearchService<T> ?" because you have to identify which feature this is, and for who
It is very similar right now at least. For example a use case like "SubmitOrder" will have a API endpoint "SubmitOrderEndpoint" which calls the SubmitOrderCommand via mediatr. Then it enters domain layer. Ofc there could be multiple commandhandlers that listen to submitorder events in application layer I guess.
It is for returning the data to the API endpoint and then the user. It does not return the DomainObject Article since it is from an external index. But it has knowledge of the domain object via ID. So since it really has very little to do with the domain layer im unsure of where I should put it. Is going straight to infra layer the better way?
external index means that even if it's not article it's still a kind of surrogate of it? like a flattened version or a subset of the fields?
Subset of fields yes
It's a elasticsearch index
there are a few factors to consider; personally i wouldn't have an issue using infrastructure from application, but if you have everything implemented as app->dom->infra and only this feature would differ then for sake of clarity having a domain service woud probably help, even if thin (consideration still goes to how much organization and interaction with other parts of the service this feature has)
if/when you (and eventually team) get more experience and are comfortable being a little more creative (and have sufficient internal documentation) then things could change
Thanks for the input, I think you are right for the sake of clarity the domain service and a dto in domain layer is the way to go
since nobody asked the important question yet, is there a reason you need to use CA/DDD?
i guess atm nobody knew better
It's a very large application with lots of different modules/domains. The legacy part is build on a n-tier architecture/clean ish hybrid but it has degraded over the years. We have looked at full out vertical slice arch but decided against it. Clean arch was chosen partially cause we are used to it and in order to improve testability and some other factors. And we hade trouble mapping some of the current flows to a vertical slice type of approach. This may just be due to lack of expertise in the area though.
DDD we want to implement to decouple some parts that have grown together to much and will help our future selfs maintain a more decoupled code base.