Take your CRUD to the next level with DDD concepts

Sometimes, in a software development, the level of complexity in part of the project can get to a point where the experienced developers will rethink their strategy. Domain-Driven Design can often help, but if the necessary prerequisites aren't there, it could be that DDD-Lite can help. Konrad Lukasik gives a simple example where some DDD patterns can help to clarify complex logic.

Let the journey begin

There is a skill that I particularly admire in a developer: It is being able to recognize correctly that something, whether code or process, is no longer working well.  It is useful because a project will so often start with right architecture and design but, over time, travels in unexpected direction. Without this skill, the project is liable to cross the ‘fit-for-purpose’ border and enter into ‘big-ball-of-mud’ land. Why? The project has changed, but the architecture hasn’t. No architecture is suitable for all types of software development project, yet we, who are assumed to be specialists, tend to be too eager to use the same solution for different problems. As in so many of life’s problems, ‘one size does not fit all’.

Many solutions start as mainly routine CRUD requirements (Create, Read, Update and Delete) but usually with some specific part that has considerable complexity. Software architecture should address those two areas separately. DDD (Domain-driven design) might seem a perfect tactic to address complex requirements, but it relies on access to domain experts and a shared intent to focus primarily on the domain and refine it iteratively.

Even when these prerequisites aren’t available, DDD might still be an inspiration for structuring code in these more complex areas of a development project because, in large part, it is plain good OOP. Such an approach, without defined context, ubiquitous language or even communication benefits, is called DDD-lite. It concentrates on technical-side patterns and concepts. Of course, it doesn’t mean that we forget about domain or communication – those elements are still present, but they are not at such a high level; nor are they explicit enough to warrant calling the approach ‘DDD’.

Aggregated root and action classes

Let’s look at how complexity may creep in your model, and how DDD concepts may help. Imagine we are implementing a classic ASP.NET MVC application with following layers:


MVC controllers execute business logic that is kept in the domain and this logic is exposed through a façade of application services, which use repositories to access the database. Entity Framework’s DbContext is internally used to implement repositories.

For the sake of example, let’s assume that we start with the following model:


We have a customer with a name and addresses. A customer can create orders – each one has delivery date, addresses and set of ordered products.

Initially our code may look similar to this:

Two repositories, Order and OrderLine, give access to the respective tables through a set of base operations. Order service combines those and exposes logic to higher layers. A Find method on service, for example, could be as simple as calling Find on OrderRepository and returning only order headers. The Save method, on the other hand, invokes Save on OrderRepository and iterates over order lines to persist each of them with the OrderLineRepository class: All reasonably good so far.

Now, let’s imagine that the customer changes his mind or we suddenly find out that the save operation is more complex. Depending on the order status, we have to perform a basic or more sophisticated validation. I often witnessed service evolving into something like that:

There is a new public method to submit an order. Save and Submit internally call the appropriate private method to do the validation (ValidateOnSave or ValidateOnSubmit). As the actual logic of order saving is common it is extracted into separate private method (SaveOrder).

If developer writing this piece of code would know DDD better, a solution might look like this:

One of key technical concepts of DDD is aggregated root. This is the parent entity, which controls access to its children, defines their lifetime and takes care about concurrent access to the same objects. This way, aggregate root can ensure the integrity of aggregates as a whole. All repositories should operate at aggregated root level. No other entities should be exposed directly through the methods of repository. That is the reason why OrderLineRepository has been removed. With the full logic of saving Order and OrderLine in the repository, there is no need to have a separate SaveOrder method on OrderService – this kind of logic is better kept at repository, where it is easily mockable and testable.

One addition – there is a new class, OrderValidator, named after an action and replacing logic that was kept previously in the private methods of OrderService. Contrary to what we have learned during Computer Science studies, it is better to not only call your classes after nouns, but also verbs. In our case, all the logic that is related to order validation is now kept in a separate class with single responsibility. Sub-validations, constituting on full validation, can be kept is private methods of OrderValidator (e.g. ValidateCustomerLimits). If we would need access to other areas of domain model inside OrderValidator, we can easily inject dependencies into that class without increasing overall class coupling too much and still have testability at the same level.

Domain events

Next, the customer might express a requirement to send an email notification to its recipients, and for legal reasons might need to generate an invoice at the time of order submission. A sufficient implementation, which often comes to mind could looks like this:

We have new repositories:

  • First to access email templates (EmailTemplateRepository)
  • Second to create invoices (InvoiceRepository)

Two new private methods on OrderService are called inside Submit to meet the customer’s requirements. Again the problem is with embedding the logic mostly in the OrderService class and, again, the DDD concept comes to the rescue.

Event-based programming is popular, but we habitually think about it only in terms of the user interface. Domain events are events that signal a change in the domain that we care about. They are especially important when multiple aggregate roots need to interact. What if we try to use it here to reduce the coupling? Let’s see.

Domain events can be implemented in many ways, but I am here following Udi Dahan’s recommendations. An empty IDomainEvent interface is used to mark the domain events – OrderPlaced in our example. The Event class has all the information related to the event (Order property). All events are pumped through central hub (DomainEvents static class), which finds subscribers by looking for classes implementing Handles<T> interfaces. We’ve got two of those for our OrderPlaced event: one is responsible for email notifications and the other one for invoices. The names of handlers have been chosen to best describe their intention. Unnecessary private methods on OrderService have been deleted.

It is worth mentioning that true domain events should be named in the past tense. The table below shows a few different conventions for naming events, of which domains are most legible.

Event name



Domain event


Domain event


CRUD-ish event


Trigger-like event

Table1- Examples of events

In my example, events are processed synchronously, but there are other viable alternative approaches. They could be assembled during actions and processed either right before transaction-commit or send to the queue to be handled asynchronously by background workers.

From the technical perspective, implementation may vary. My sample is based on the global event hub by Udi Dahan, but instead we could:

  • Use plain Old .NET events
  •  Use recent Reactive Extensions framework
  •   Simply return events from domain methods (which is easiest to test).


Finally, our customer may request an operation to clone an order. Naturally we may want to place such a simple operation inside OrderService.

But, what if it’s not that easy? What if some fields are omitted during cloning? What if we require access to additional repositories to fulfil the process? Do we have to impose those needs on OrderService and make it more difficult to test?

IoC (Inversion of Control) containers are very popular and this may have blinded us to the idea of writing custom factories. If our order cloning could be implemented as a factory, then that would relieve us from all the problems above. In this scenario, OrderService would be what it meant to be i.e. a façade over our business logic with nothing complex inside.


I have shown you how DDD concepts of aggregated root, action classes, domain events and custom factories can help you in everyday work to avoid transaction scripts for complex logic. Definitely not all elements of your system will be so complicated as to require those patterns, but you should generally follow a ‘thin application service, fat domain’ paradigm.