Background: For my own clarity / self education, I am trying to implement a simple Order Entry application using TDD + DDD. My primary goal is to keep the architecture clean by separating concerns.
I have four layers (for now) ...
Persistence/DAL with a CustomerRepository class that can perform GetById, Save, operations on the "aggregate root", a Customer and its related Orders and OrderItems. In order to allow for "poor man's dependency injection"
Domain/BLL layer containing "business entity" classes that perform fine-grained operations to help create new Orders, applying Tax, Discounts, Shipping logic based on order size and customer location.
Application Facade (App services / orchestration) containing chunky, coarser-grained classes to orchestrate the "business entities" and reduce the chatter with the presentation (and potentially a WebServices layer).
Presentation layer
Furthermore, I want to pass POCO DTO's between key layers ... particularly between Persistence=>Domain layers, and ApplicationFacade=>Presentation layers. So, I have CustomerDto, OrderDto, OrderItemDto with the appropriate relationships defined in a shared package.
I want to inject an implementation of the ICustomerRepository into the Customer "business entity" class using Constructor Injection, then call a Customer.Save() on the "business entity" to kick off the create/update process, ultimately calling the Save method on the CustomerRepository. After all, the Customer is the "aggregate root" and has all the information needed to save ... it also is the "keeper" of the injected CustomerRepository.
Problem: This is where I hit a snag. I want to keep the Domain/BLL Layer as pure as possible and avoid coupling it to any third-party frameworks and APIs, but the Customer.Save() method needs to translate the Customer "aggregate root" and all its Orders and OrderItems into their DTO versions for transport to the injected Persistence layer CustomerRepository ... and that is a job for Automapper.
The problem is ... If I don't put Automapper in the Domain/BLL layer, I'm not really sure where it should go.
It doesn't feel right to put it in the ApplicationFacade even though it's job is orchestration.
It definitely doesn't feel right to put it in the Domain/BLL layer because I want to keep it pristine.
Therefore, I feel like I've missed something ... that I'm approaching this with a fundamental misunderstanding of how the working parts should all come together to complete this task. Any suggestions? (Please be gentle, I'm new to all this, and new to SO. Let me know if I need to show some code of what I have so far.)
It's on the outer layer but it only references the Core. In my case the infrastructure layer also houses Automapper. The core defines a simple interface (let's say IAutoMapper ) and a simple object that exists in the infrastructure implements it and the object can be passed to the UI layer through dependency injection.
As represented in the figure 3, the DDD layered architecture contains four levels of abstraction which are the user interface layer, the application layer, the domain layer and the infrastructure layer [2]. Each layer depends only on layers of the same level as well on the ones beneath it [2]. ...
To answer your specific question I will speak more generally about your architecture. The architecture you've devised for your project is subtly different from a typical architecture used with DDD. While the way you've partitioned responsibilities is typical, in DDD, domain classes aren't responsible for their own persistence. In fact a mantra of DDD is persistence ignorance. This basically means that domain classes are persistence ignorant - they don't have a Save
method. Instead, application services (application facade layer in your architecture) coordinate with repositories to reconstitute and persist domain entities.
Next, when implementing repositories, there is usually no need for an explicit DTO between the underlying persistence technology and domain classes. With ORMs such as NHibernate and EF, mappings between domain classes and relational tables are expressed with either mapping classes or XML based configurations. As a result, your domain classes are mapped implicitly - no need for DTO. Some cases do call for a DTO, in which case the mapping between the DTO and domain class should be encapsulated by the repository implementation. This is one place where you could invoke AutoMapper.
Finally, it is a common practice to communicate between the presentation layer and the application/domain layer with DTOs. To determine exactly where the mapping should take place you have to dig deeper into the architecture that is a best fit your your project. Most modern DDD architectures are based on the Hexagonal architecture. In a hexagonal architecture, your domain is at the center and all other "layers" adapt the domain to specific technologies. For instance, a repository can be seen as an adapter between the domain and a specific database technology. ASP.NET WebAPI can be seen as an adapter between the domain and HTTP/REST. Likewise, the presentation layer can be seen as an adapter between the domain and a specific. DTOs can manifest within each of these adapters and it is the adapter's responsibility to map to and from these DTOs.
A typical example would be to use application services to establish a facade over your domain. No DTOs at play yet. Next, you create a service layer (open host service in DDD) with ASP.NET WebAPI - an adapter. You create ASP.NET WebAPI specific DTOs and it is up to this adapter to map to and from these DTOs. Therefore, if you use AutoMapper, it should be invoked in this layer. This can be done either explicitly or in an aspect-oriented way with action filters. The same holds for the presentation layer. The presentation layer could be coupled directly to application services, or to an ASP.NET WebAPI open host service. In either case, it is the responsibility of the presentation layer to map between domain classes (from application service or DTOs from WebAPI) and its own primitives such as a ViewModel.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With