.NET Solution structure of an enterprise application

After reading multiple DDD-books (all two of them) and studying architectures like Onion, Clean and Hexagonal, I have tried to come up with a good .NET solution structure that enables developing a well architected enterprise application with domain-driven development while following the SOLID principles. In this blog post I will build such a solution step-by-step while explaining the reasons behind each design decision. You can find the complete solution from the GitHub with one commit for each step of the post.

I hope this blog post is useful for other developers struggling to map the high-level concepts of SOLID, DDD and architecture into an actual code.

Preface

Before we dive into code and architecture, it’s worth to mention that this is a huge topic to discuss. So huge, that there are in fact several books on each smaller subtopic. That in mind, it’s obvious that I can’t address every detail in this post. For example, I won’t go into details what aggregate roots are or how dependency injection works. The architecure that I present here is not my invention. I just map the high level concepts that I have learned from the masters, into actual code and solution structure.

The architecture and it’s implementation is not specific to any domain, but to illustrate it with actual code, I have chosen to use Invoicing as an example domain for this application. We will implement an application that allows user to change a duedate of an invoice.

1. Domain

We start by creating a new solution with Visual Studio. Let’s choose a library project and give it a name EnterpriseApplication. Visual studio creates a new solution with one library project. Let’s rename this project to Domain. This project will be the core of our application containing all the entities. I recommend creating a folder for each aggregate root right under the domain project. This convention makes aggregate boundaries visible on the solution level and you can immediately see what are the key concepts of the domain. Notice that we organize the code around domain concepts instead of technical concepts like factories, mappers, validators etc. This does not only help communicate the domain, but also makes it easier to find the piece of code that you are looking for.

For the sake of demonstration let’s add Invoice aggregate root. First I add IInvoice interface that represents the concept in our domain. Next we need to create a factory to be able to create our invoices, so I add InvoiceFactory class and also an actual implementation class for the IInvoice interface. Factories and the entities that they create are highly cohesive and belong next to each other in the solution, that’s why I put them side-by-side into the domain project.

namespace EnterpriseApplication.Invoice
{
    public interface IInvoice
    {
        void ChangeDuedate(DateTime newDuedate);
    }
}
namespace EnterpriseApplication.Invoice
{
    public class Invoice : IInvoice
    {
        public Guid Id { get; set; }
        public string Number { get; set; }
        public DateTime Duedate { get; set; }

        public void ChangeDuedate(DateTime newDuedate)
        {
            // There is more complex logic, but thats not the 
            // point of this blog post and therefore skipped.
            Duedate = newDuedate;
        }
    }
}
namespace EnterpriseApplication.Invoice
{
    public class InvoiceFactory
    {
        public virtual IInvoice Create(string number, DateTime duedate)
        {
            return new Invoice
            {
                Id = Guid.NewGuid(),
                Number = number,
                Duedate = duedate
            };
        }
    }
}

There is still one concept to add to our domain project and that is repository for our aggregate root. Not the implementation, but the interface! So let’s add IInvoiceRepository to the Invoice folder. We add the interface there, because whoever depends on the domain and it’s aggregates also wants to create (factory) and persist or reconstitute (repository) those aggregates. Therefore it’s a natural place for the interface.

using System;

namespace EnterpriseApplication.Invoice
{
    public interface IInvoiceRepository
    {
        IInvoice GetById(Guid id);
        IInvoice GetByNumber(string invoiceNumber);

        void Save(IInvoice invoice);
    }
}

DomainTo conclude, our Domain project consists of folders that represent aggregates of the domain. Each folder contains factory, entity and repository interface of the specific aggregate. This is an aggregate in its simplest form. Usually there are also multiple value objects and entities related to the aggregate.

2. Business

Next step is to create a new project for Business layer. We add a new library project to the solution and call it Business. Where as the domain layer contains the domain logic in the form of aggregates, the business layer is a home of the business logic. This layer implements all the use cases of the application. One use case is usually one business operation that operates on one or more aggregates of the domain. Therefore business layer naturally depends on the domain layer. So let’s add a project reference from Business to Domain.

If you think about any business application, they always consist of two kind of operations: commands and queries. Commands modify the state of the system, but never return any data. On the contrary, queries allow reading the system state without modifying it. This idea is also known as Command Query Responsibility Segregation (CQRS). However, I won’t introduce separate read model in this example. Instead I use one model to implement both, queries and commands, meanwhile still making a clear separation on those operations on the architectural level. There is no reason that would prevent introducing the new read model for queries as the need arises, but I would always start with a single model and use it as long as it’s feasible.

BusinessTo make this idea visible in our solution architecture, let’s create two new folders under our Business project and name them Commands and Queries. Now under the Commands folder I create a folder for each business operation / use-case. This way by looking the business project you can instantly see all the business operations that are supported by the application. Folders and business operations should be part of the Ubiquitous Language just like the domain aggregates are.

For this example, let’s add a simplified business operation to change the invoice due date. We create a new folder for it and call it  ChangeInvoiceDuedate and right under it we create new class called ChangeInvoiceDuedateCommand. I use a naming convention of suffixing all the entry points of the business commands with Command. This becomes handy later when we configure our DI container.

namespace Business.Commands.ChangeInvoiceDuedate
{
    public class ChangeInvoiceDuedateCommand
    {
        IInvoiceRepository invoiceRepository;
        InvoiceDueDateChangeValidator validator;

        public ChangeInvoiceDuedateCommand(IInvoiceRepository invoiceRepository)
        {
            this.invoiceRepository = invoiceRepository;
            this.validator = new InvoiceDueDateChangeValidator();
        }

        public void Execute(ChangeInvoiceDuedateRequest request)
        {
            if(validator.IsDuedateValid(request.NewDuedate))
            {
                IInvoice invoice = invoiceRepository.GetByNumber(request.Number);
                invoice.ChangeDuedate(request.NewDuedate);
                invoiceRepository.Save(invoice);
            }
            else
            {
                throw new InvalidDueDateException();
            }
        }
    }
}

To implement ChangeInvoiceDuedateCommand I use constructor injection pattern to inject dependencies of the command. As a dependency we need IInvoiceRepository to be able to fetch the invoice which due date should be changed. Notice that we can access this repository interface since it was located to domain project that business depends on. For the sake of this example, I also added a class InvoiceDueDateChangeValidator to illustrate that business layer contains not only entity calls but also business rules that are not part of the aggregates. Rules when due date of an invoice can be changed are part of the business operation. How the due date is changed and how it modifies the aggregate state is part of the domain logic and therefore in the invoice aggregate. Also notice that validator is not injected, but created by the command. It’s highly cohesive with the command and there is no need to inject it from the outside.

namespace Business.Commands.ChangeInvoiceDuedate
{
    public class InvoiceDueDateChangeValidator
    {
        public bool IsDuedateValid(DateTime duedate)
        {
            // Dummy validation for illustration purposes
            return duedate > DateTime.Today;
        }
    }
}

One more noteworthy design decision here is the request object that comes as a parameter to the command. Whenever command or query is called, request object is given as a parameter (and the only parameter). The request encapsulates all the data that is needed for that specific query or command. Requests are simple data transfer objects (DTO) and are named with Request suffix.

namespace Business.Commands.ChangeInvoiceDuedate
{
    public class ChangeInvoiceDuedateRequest
    {
        public string Number { get; set; }
        public DateTime NewDuedate { get; set; }
    }
}

I won’t go into details of implementing a query in this example. I will mention however that all the queries return Response objects that are similar to requests but move to another direction. So basically in query implementation you fetch what ever aggregates are needed to produce the response and then map. To conclude, all the data that crosses the business layer boundary is within request and response DTOs.

3. Persistence

Now that we have a domain and business layer in place, let’s create a Persistence layer! I add a third project to our solution and name it Persistence. As you can imagine, the responsibility of this layer is to implement data access for our domain. You might recall that our repository interfaces were put into domain project. Persistence layer implements those interfaces so let’s add a new project reference from persistence to domain. Now that we have referenced the domain, let’s add a new class called InvoiceRepository and make it implement the IInvoiceRepository introduced earlier in Step 1. In this example I will use MongoDB to actually implement the repository. If you choose to use SQL database I recommend using Entity Framework or NHibernate instead. Below is a naive implementation of the repository that is sufficient for this example.

namespace Persistence
{
    public class InvoiceRepository : MongoRepository, IInvoiceRepository
    {
        public InvoiceRepository(MongoClient mongo) : base(mongo) { }

        public IInvoice GetById(Guid id)
        {
            return Invoices.AsQueryable<Invoice>()
                           .Single(c => c.Id == id);
        }

        public IInvoice GetByNumber(string invoiceNumber)
        {
            return Invoices.AsQueryable<Invoice>()
                           .Single(c => c.Number == invoiceNumber);
        }

        public void Save(IInvoice invoice)
        {
            Invoices.Save(invoice);
        }

        MongoCollection Invoices
        {
            get { return Database.GetCollection<IInvoice>("invoices"); }
        }
    }
}

That’s all when it comes to persistence layer. It’s a rather thin layer containing repository implementations. Since the persistence project depends on the domain project it can easily instantiate entities when reconstituting objects from the database. In case of MongoDb, there isn’t even need to do manual mapping since mongo does everything for you automatically. One thing to point out is that the domain aggregates do not use constructor injection pattern. Instead they instantiate the dependencies themselves. Again, classes within the aggregates are highly cohesive with each other and therefore can depend on each other as needed.

4. Cross-cutting Concerns

Now we have three projects in our solution: Domain, Business and Persistence. All with very specific responsibilities. Both Business and Persistence depend on Domain, but there are no other dependencies between the projects. Next it’s time to stitch everything together. Let’s create a fourth project to our solution and call it CrossCuttingConcerns. As the name of the project implies this project contains all the features that are cross-cutting to all layers of the application. These include for example logging and auditing, but more importantly dependency injection.

CCCLet’s add a new folder for each cross-cutting concern under the project. I create folders named DependencyInjection and Logging for this example app. In real apps there could be also Security, Auditing, Monitoring, RequestValidation etc.

Next, let’s add project references from the CrossCuttingConcerns project to all the three projects we created before: Domain, Business and Persistence. Due to the nature of cross-cutting concerns, it’s ok that this project depends on all the others. More importantly, domain project still depends on nothing and business depends only on domain. Those are the two projects that are the core of our system, the part that make our system unique and valuable. In contrast, Persistence and CrossCuttingConcerns are the projects that implement responsibilities that are common to all enterprise systems including database access, logging and DI, just to mention few. If you think about it, these two projects are the places where we want to utilize already existing technologies like IOC containers, ORMs, Mongo drivers, Validation frameworks, Logging frameworks and the list just goes on.

This is great, because our architecture makes it so that all these external dependencies are in the projects that do not contain any business or domain logic. Decoupling the external dependencies is important, because we don’t want to depend on those details. Instead we want those details to depend on our core! Dependency injection (see DIP) allows us to inject these cross-cutting concerns into appropriate places of the application stack without making the stack depend on those concerns or their implementations. This can be done with decorator pattern or by using intercepting supported by some DI containers.

In this example app I use Castle Windsor as IOC. I won’t go into details how Castle works, but I have chosen it because it implements two crucial features: Intercepting and Convention based registering.

Let’s start by implementing simple error logging. Castle makes it easy, because it provides integration for log4net out-of-the-box. I’ll create an ExceptionLogger class into the Logging folder and make it implement IInterceptor which is castle’s interface for intercepting method calls. Implementing the logger itself is easy. Below is the full implementation of the error logger that can be used throughout the system.

namespace CrossCuttingConcerns.Logging
{
    public class ExceptionLogger : IInterceptor
    {
        ILogger log;

        public ExceptionLogger(ILogger log)
        {
            this.log = log;
        }

        public void Intercept(IInvocation invocation)
        {
            try
            {
                invocation.Proceed();
            }
            catch (Exception e)
            {
                var message = string.Format("Method '{0}' of class '{1}' failed.", 
                                            invocation.Method.Name, invocation.TargetType.Name);
                log.Error(message, e);
                throw;
            }
        }
    }
}

Now that we have all the pieces in place, let’s implement Composition root of the application. I will do this by creating a new class CompositionRoot under DependencyInjection folder. Castle Windsor supports splitting the composition root into smaller components called Installers. I prefer creating an installer per project/layer to keep my codebase well organized. Below you can see the implementation of the CompositionRoot and all the installers.

namespace CrossCuttingConcerns.DependencyInjection
{
    public class CompositionRoot
    {
        public virtual void ComposeApplication(IWindsorContainer container)
        {
            container.AddFacility<LoggingFacility>(f => f.UseLog4Net());

            container.Install(
                new CrossCuttingConcerns(),
                new Persistence(),
                new Domain(),
                new Business()
            );
        }
    }
}
namespace CrossCuttingConcerns.DependencyInjection
{
    public class Domain : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Classes.FromAssemblyContaining<InvoiceFactory>()
                                      .Where(type => type.Name.EndsWith("Factory"))
                                      .WithServiceSelf()
                                      .LifestyleSingleton());
        }
    }

    public class Business : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {

            container.Register(Classes.FromAssemblyContaining<ChangeInvoiceDuedateCommand>()
                                      .Where(type => type.Name.EndsWith("Query"))
                                      .WithServiceSelf()
                                      .Configure(c => c.LifestyleSingleton().Interceptors<ExceptionLogger>()));

            container.Register(Classes.FromAssemblyContaining<ChangeInvoiceDuedateCommand>()
                                      .Where(type => type.Name.EndsWith("Command"))
                                      .WithServiceSelf()
                                      .Configure(c => c.LifestyleSingleton().Interceptors<ExceptionLogger>()));
        }
    }

    public class Persistence : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            RegiterMongoDb(container);
            RegisterRepositories(container);
        }

        protected virtual void RegiterMongoDb(IWindsorContainer container)
        {
            var mongoClient = new MongoClient("mongodb://localhost");
            container.Register(Component.For<MongoClient>().Instance(mongoClient));
        }

        void RegisterRepositories(IWindsorContainer container)
        {
            container.Register(Classes.FromAssemblyContaining<MongoRepository>()
                                      .BasedOn<MongoRepository>()
                                      .WithServiceFirstInterface()
                                      .LifestyleSingleton());
        }
    }

    public class CrossCuttingConcerns : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For<ExceptionLogger>());
        }
    }
}

As you can see, I register most the classes by convention for each layer. I also bind the logging interceptor to all Commands and Queries. This guarantees that any exception thrown from domain, business or persistence will always get logged. Thanks to convention based configuration, there is no need to modify composition root when we add new aggregates, repositories or business operations to our application. It all just works as long as the classes are named by convention.

One important aspect of the dependency injection here is that we inject dependencies only at the boundaries of the layers to decouple them from each other. However, within the domain and business layers I tend to create dependencies locally, since the classes are highly cohesive with each other within use-cases and aggregates.

5. Services layer

So far we have four projects in our solution: Domain, Business, Persistence and CrossCuttingConcerns. These four projects together fully implement the system, but there is still one minor problem to solve. We can’t use the system at all! We need a delivery mechanism over business layer to be able to call commands and queries of the system. In this example, I will create a WCF service over the business layer to enable access to our domain. This layer could be REST, MVC, WPF or even a command line application, but for this example it’s WCF. There is also no reason why this layer should be limited to only one. We could have WCF and WPF living side-by-side in our application.

Let’s create a new empty ASP.NET Web project to our solution and call it Services. This project’s responsibility is just to enable remote access to domain. It does not contain any logic and it’s a really thin layer over the others. No other project depend on this layer. Services layer itself depends on CrossCuttingConcerns and Business. So let’s add project references for those two dependencies! Next we implement a trivial InvoiceService that has a method for changing the due date of an invoice. This class binds remote interface to our business layer. We inject our ChangeInvoiceDuedateCommand as a constructor parameter into our service so that it can delegate the call to the business layer.

public class InvoiceService : IInvoiceService
{
    ChangeInvoiceDuedateCommand changeInvoiceDuedate;

    public InvoiceService(ChangeInvoiceDuedateCommand changeInvoiceDuedate)
    {
        this.changeInvoiceDuedate = changeInvoiceDuedate;
    }
        
    public void ChangeInvoiceDuedate(string invoiceNumber, DateTime newDuedate)
    {
        var request = new ChangeInvoiceDuedateRequest 
        { 
            Number = invoiceNumber, 
            NewDuedate = newDuedate 
        };
            
        changeInvoiceDuedate.Execute(request);
    }
}

That’s almost all that there is to services layer (in this example application). But there is still one trick we need to do. We need to somehow register our service to IOC so that the WCF framework can create the services for us with dependencies in place. To do this we need to tell WCF framework to use our IOC container as a dependency resolver. We also need to register services on the service layer to the container before rest of the application is configured within the composition root. To do this let’s add Global.asax file to our Services project. This class contains a method Application_Start() that is executed when the application is started by IIS. Below is the code illustrating how to bootstrap dependency injection with WCF.

public class Global : System.Web.HttpApplication
{
    WindsorContainer container;

    protected void Application_Start(object sender, EventArgs e)
    {
        container = new WindsorContainer();
        container.AddFacility<WcfFacility>();

        container.Register(Classes.FromThisAssembly()
                                  .Where(type => type.Name.EndsWith("Service"))
                                  .WithServiceDefaultInterfaces()
                                  .Configure(component => component.Named(component.Implementation.FullName)));

        new CompositionRoot().ComposeApplication(container);
    }

    protected void Application_End(object sender, EventArgs e)
    {
        if (container != null)
            container.Dispose();
    }
}

As you notice, we actually create the WindsorContainer already in the service layer, configure controllers to it by convention and then pass the container to our composition root, which in turn, configures the rest of the application. We do this, because we can’t configure controllers in the composition root that is located inside CrossCuttingConcerns project. Remember that CrossCuttingConcerns does not depend on Services project. We could move the whole dependency injection configuration to Service layer, but that would couple DI tightly to delivery mechanism. What if we want to add WebAPI next to WCF? No, we don’t want to couple those two concerns too tightly. By locating DI to CrossCuttingConcerns, we have it separated while allowing any top layer to utilize it to configure the application.

Conclusions

SolutionLet’s take a step back and see what we have achieved here. We have implemented a basic structure of an enterprise system in .NET. It consists of five projects, Domain, Business, Persistence, CrossCuttingConcerns and Services. All these have clear responsibilities and interfaces. The architecture is not specific to any domain. Domain specific code is always located in the Domain and Business projects that are completely independent from the rest of the system. This total decoupling between domain specific logic and technical requirements is one of the biggest advantages of this architecture.

This is an enterprise application architecture in its simplest form. It’s not rare that we have sub domains side by side with core domain or Application layer on top of the business layer. This blog post was already way too long without those, so I just left them out of this exercise.

Pyhsical dependencies

Physical dependencies of the projects

Pros

  • Architecture follows SOLID principles.
  • Solution structure screams the domain with a folder structure that is built around the domain concepts and processes. Notice that there are no folders named Validators, Exceptions, Factories or so.
  • Clear separation of concerns on the project level. All the code could be in one big project, but I find it helpful to organize code on layer level. Also managing dependencies between these projects enforces decoupling the high level concepts from each other.
  • Cross-cutting concerns are isolated and put into a clearly named folders. You can immediately see what are the cross-cutting features of the system on the solution level. More importantly, logging, security, audit etc. are not polluting the domain and business code.
  • Persistence implementation is separated from the domain allowing domain model to differ from the persistence model. This also enables adding technical features to persistence in OCP manner. For example, adding caching is as easy as creating a caching decorator for repository implementation and configuring it with composition root.
  • Service layer stays extremely thin and rest of the application doesn’t need to know about it’s existence.
  • Using Request and Response DTOs on the business layer boundary decouples the upper layers from the domain concepts. Business layer can provide API suitable for the upper layers. These DTOs also define the data interface of the application: what data is needed for each operation.
  • All technical frameworks are isolated from the actual business code which makes it easy to switch any as needed. Wanna use Unity as IOC? Just rewrite the stuff within DependencyInjection folder. Wanna use EntityFramework instead of MondoDb, just rewrite a repository in Persistence. Wanna use WebAPI instead of WCF? Just implement another Service layer next to WCF. None of these changes, require any changes to the core of the application.
  • Adding a new business operation to the system is easy. Add a new Command to the business layer and possibly new functionality to one or more aggregates. This follows the OCP principle which states that the system should be open for extension, but close for modifications. We don’t need to touch any existing business operations while adding a new one. We also shouldn’t need to modify aggregates since the domain concepts stay the same across the business operations. We might need to add new domain functionality though.
  • I feel I have to mention testing here. It’s a whole new topic for another blog post that I wrote earlier, but let’s just mention that testing an application following this architecture is not only easy, but fun!

Cons

  • Dependency injection cannot be kept completely in Cross-cutting concerns, because of the nature of it. We need to be able to register the very “top level” classes of the application to the container and those classes are always on the layer above everything else.

Your turn! Leave a comment and help me improve this solution structure and architecture. Tell me what are the weak points of it. Is there a way to make it less complex without compromising benefits it provides?

After feedback I wrote a follow-up blog post. Read it from here.

 
  • Juha S.

    Great job with this Lauri!

  • KM

    the invoice service argument list could get incredibly long using this method depending on how many queries and command the service performs

    • http://www.taimila.com/ taimila

      Good point! This is a problem indeed. One way to solve this is to create RequestProcessor class that can take any request and resolve command to execute based on received request type. This way every service in the service layer depends only on this one class (or interface). Here is an example implementation of such class http://pastebin.com/mUxYWYWw.

      The class takes the container itself as a constructor parameter. In general this is a bad idea since we want to keep our application container agnostic. However, in this special case I think it’s a good trade-off. After all, cross-cutting concerns is a project with technical code to make it all work together. Business and domain logic still remain container agnostic.

      This solution also requires us to use marker interfaces for requests and commands. You can see them here: http://pastebin.com/ucSMKZuT

      • Emanuel G.

        MediatR seems to fit perfectly!

  • mieleton

    What about client side? I have on top of that sort of project / solution stack another solution for iOs / Android mobile app that call REST API and receive DTOs from there. Where would you put the DTO classes that both Service and client projects needs?

    • http://www.taimila.com/ taimila

      Service layer is the part of the application that should handle all the remote call specific needs. In this case, I would propably create a new project containing the DTOs and then map requests and responses at the service layer to those DTOs. For mapping I would propably use EmitMapper which is very fast and simple object-mapper for C#.

      So ServiceLayer project would depends on DTOs project. After all, those DTOs are specific for that REST interface.

      In my example application, I assumed the client is JavaScript application. For example AngularJS or Aurelia UI. In that case I would not introduce separate DTOs and mappings, since they would provice very little compared the work and amount of code they would require.

  • Paul Schroeder

    Thanks for sharing this, Lauri! Your post definitely helped me think through a few things that have been plaguing my own similar incarnation for some time.

    One remaining nit is having all the commands and queries namespaced down the the folder level (i.e. sample code – “Business.Commands.ChangeInvoiceDuedate”). Doing so, even with the RequestProcessor approach outlined in pastebin addendums within the comments, could result in littering the service classes with lots of using statements as the request objects get instantiated.

    ErrorLogger Interceptor Note:
    For those experimenting at home (I was using Web API), I find that the ExceptionLogger interceptor will not fire unless the Execute methods within the Command and Query classes inside the Business project are marked as *virtual*. I believe this has to do with components registered as a class service, not interface services. There was no error and everything seemed registered correctly, but the Windsor interceptor would not trigger. Marking the equivalent of ChangeInvoiceDuedateCommand’s Execute method as virtual allowed it to be intercepted.

    Note, the above problem went away and the virtual modifiers were not required once I started using the RequestProcessor class along with the IRequestProcessor interface. Also note, the RequestProcessor approach requires use of “.WithServiceFirstInterface()” within the “cross cutting” Business container’s fluent registrations.

    • http://www.taimila.com/ taimila

      Thanks for valuable feedback! You are right on intercepting. I have forgotten to make Execute method virtual which prevents Castle to apply interceptor. Any method that needs to be intercepted must be virtual or interceptor has to be bound to an interface.

      When it comes to requests, I get the annoyance of multiple using statements in services. I have put request and command into the same namespace, because they are highly cohesive. They are always used together and they are always changed together. The request is also always physically close to command in the same folder which I have found benefical when working on a use case. This can be seen as vertical slicing of the application.

      One can indeed put all the requests into one folder/namespace which makes working on service layer a bit nicer exprience. This has no technical downsides as I see it now. So, I would courage to take which ever approach one feels more comfortable to work with.

      • Paul Schroeder

        Tell me if I’m missing something here – are there up to 4 serializations required to do a single query?

        1) Database -> ORM service (i.e. Entity Framework entities)
        2) EF -> domain classes that implement an interface (i.e. Invoice : IInvoice)
        3) Domain -> DTO objects converted in the businss layer for consumption by services layer
        4) DTO -> serialized string (i.e. JSON or XML) from services to client

      • http://www.taimila.com/ taimila

        This is one possible solution, but I would try to minimize the amount of mappings needed. In my example I have 2 mappings per query and I would argue that’s enough. Those mappings are in the service and persistence layers. In other words, at the boundaries of the application.

        In this example code I haven’t used the request dto as a parameter of the WCF call, but that’s something I would do by default in real application. WCF/WebAPI would then do the mapping from JSON/XML into the request dto defined in a business layer. I see no benefit in introducing an extra layer of dtos on the service layer. Those dtos would be one-to-one with request dtos. That would add code (dtos + mapping) without a benfit I can think of. If you have one, please tell me!

        When it comes to query operations, I would define response dtos and map aggregates to those. Response dtos should be designed based on client’s needs. So they rarely map one-to-one with aggregates. One response dto might contain data from multiple aggregates.

        The second mapping is in the persistence layer where I map domain aggregates to the data storage. In this example, I use MongoDB which does the serialization for me. I haven’t used Entity Framework, but at least with Mongo and NHibernate I would map directly to domain aggregates. Again without introducing an extra layer of DTOs to the persistence. This seems to be a controversial topic and I have written another blog post about the relationship of the domain and persistence layer.

        To sum up, for me the most important thing is to keep domain and business logic technology agnostic. So, if adding DTOs to the technical layers, such as service and persistence, helps with implementation or solves some issue, I see no problem adding them. But by default, I would try to avoid them.

      • Paul Schroeder

        Still experimenting…if I get anywhere good, I may try to write a blog post on it as it’s too complicated to describe in comments here. Regardless, here’s a summation (apologies if it doesn’t make complete sense):

        I wrote a combined processor to handle both commands and queries; using generics for the return type and wrapping that return type in a “service result” class that can report success and/or errors to the caller.

        I made a separate DTO project that is referenced by the persistence, business, domain, and services projects. On queries, the persistence layer maps Entity Framework objects to DTO’s immediately. These DTO’s can contain child DTO’s, or compose whatever result is necessary via other DTO’s. Domain objects, consumed by the Business layer are instantiated using these DTO’s. This model allows me to remain agnostic of EF in all but the Peristence project. Data returned between the business classes and the services layer consists solely of DTO’s (previously embedded in the Domain classes and now extracted).

        It’s more complicated than I would like, but this approach does allow me to cut out some mapping and now it looks like (not counting DB -> EF):

        1) EF objects mapped to DTO’s. DTO’s are embedded, not really mapped, inside Domain classes. Services layer receives DTO’s from the Business layer (extracted in whole from Domain classes).

        2) DTO’s received in the Services layer from the Business layer may have to be composed/used to create ViewModel classes and/or serialized to JSON, XML, or whatever is returned from the Services layer to the client.

        One downside is that I find myself injecting “IWindsorContainer container” into each controller. Perhaps it isn’t horrible to inject and/or access the container there; I believe it’s needed to have the container resolve the needed request processor because of its implementation using generics.

        We’ll see how it goes from here…I may eventually want to post this on GitHub to have others pick it apart (er…provide feedback).

  • https://www.fabiosilvalima.net Fabio Silva Lima

    Great job! Excelent!