Category Archives: Domain Driven Design (DDD)

Two Types of Domain Events

You can find a good primer on domain events in this post by Udi Dahan. There are some issues with his approach, though that Jimmy Bogard raises and addresses in his post. However, I was left with two questions:

  1. Shouldn’t the domain event be dispatched/handled only when the transaction or the unit-of-work commits? Because whatever changes have been made to the state of the domain isn’t really permanent until that happens.
  2. There may be cases when domain events need to trigger changes to other domain objects in the same bounded context – and all of that needs to be persisted transactionally. In other words, in this scenario – it makes sense to have the event be dispatched just before the transaction commits. However, in this case, whatever ends up handling that event also needs access to the current transaction or unit-of-work that is in play – so that all the changes make it to persistence in one fell swoop of a commit.

That leads me to conclude that there are really two types of domain events that need to be handled differently. The first type as listed above would either be infrastructure-y things like sending out e-mails and such, or sending messages to other bounded contexts or external systems. The second type would be within the same bounded context but maintain certain kinds of relations within the domain that could not be modeled within the same aggregate (simply put, they take the place of database triggers in the DDD metaphor).

At this point, I have no further design level ideas on how they would be modeled. More on that later, hopefully. I do know that the thing raising the event should not need to know what kind of domain event it is going to raise. If I am a Foo and my Bar changes, all I care about is I need to raise a BarChanged event. I do not need to know what kind of domain event BarChanged is. I will let this percolate a bit.

DDD, meet SOA

There is a lot of discussion online around whether DDD and SOA can co-exist, and if so, what that looks like. I am of the opinion that they can co-exist and have arrived at a model that seems to work for me.┬áConsider a complex DDD system with several bounded contexts and contrast it to an SOA system – and I am including the flavor of SOA that I describe in this post.

So, how do the two come together? As I have mentioned before, one problem I have with straight-up SOA is that there is no identifiable “core” of the system where the “meat” is. This “meat” is in fact – the domain logic. Now, add to this the fact that the domain is the only thing that deals directly with the database (technically, it’s the infrastructure layer that handles persistence – but you get the idea – it is the domain constructs that are involved in persistence to/from a data store via repositories).

What if we modeled the entire domain as a service? It does not need to be strictly hosted – think of it as a service that is consumed by other services as a library. The repositories combined with the domain objects themselves can replace the accessor/persistor class of services. This makes sense in terms of volatility as well – since the domain really needs to be the part that is least volatile. You can then think of the application layer and the infrastructure layer as being made up of all the other services – and these would still follow the whole utility / computational service / use case handler hierarchy. You would not allow the domain layer to call utility services though – but that is something you would handle through domain events and the like. The presentation layer is still the presentation layer – and is where the UI lives, or the external hosting interface to your services.

The mode of communication between bounded contexts does not change. You can now think of each bounded context as its own SOA subsystem. Communication within bounded contexts would still happen through queues, messages and subscriptions. The application and infrastructure layer would also have “domain event handler” services that handle domain events – I guess you could argue that is just one type of a use case handler.┬áThe communication between bounded contexts, as mentioned earlier, stays the same.

A Method for Service Oriented Architecture (SOA)

When you adopt service oriented architecture (SOA), the most important aspect of your architecture and high-level design step when building a new system is obviously decomposition of the system into the right services. A prudent way to decompose a system into services is to first identity what parts of the system is more likely to change more frequently. Thus, you decompose by volatility and set up dependencies such that you always have more volatile services calling less volatile services. Within the same level of volatility, of course, you would further decompose services by function if needed.

This, it is said, makes for a much more maintainable system that is much more adaptable to business requirement changes – which are obviously the norm in our industry. There is a school of thought, however, that goes one level beyond this. It classifies a service as one of the following four types (in increasing order of volatility): utilities, accessors/persistors, computational services and use case handlers.

A utility service is something that deals solely with cross cutting concerns – throughout the system (such as security, logging, configuration), or throughout a given domain (e.g. persisting of documents in a larger system of which documents are a part). Utilities are mostly purely technical and can be called by all services.

An accessor/persistor is the service that sits closest to the data at hand – be it some external resource, or as is more likely to be the case, the database. These services deal primarily with retrieval and storage, but also logical grouping of the data in a manner that makes sense to the rest of the application (think aggregates in DDD terms), data-level validation, redaction for security, etc. Since these services are closest to the metal (that being the database), and we don’t want volatility to sneak in there, these need to be the least volatile services apart from utilities. These are the only services that can talk to data sources, and can be called by use case handlers and computational services. Utilities cannot call them. Other accessors/persistors cannot call them either.

A computational service will have hardcore computational logic that can change more often than, say, your database schema, but less often than specific use cases. They can call utilities and accessors/persistors; but usually it is advised that they be purely computational – in that they get the data they need to work with handed to them, they do their thing, and then they return their output. Only use case handlers can call them.

A use case handler is most volatile and executes business use cases by mostly orchestrating calls to less volatile services. They can call utilities, computational services and accessors/persistors as needed. They also handle operation level authentication and authorization if applicable. They are the external interface to the “service platform” in that all consumers, be it the UI or external systems – can only talk to use case handlers. Less volatile services are thus shielded. What if a use case handler needs to call another use case handler? Within a contiguous subsystem, that should not happen. If it happens, that is a design smell. That could become necessary to facilitate communication between two subsystems – in which case, it is a better idea to do it through queues, messages and subscriptions (think bounded contexts in DDD terms).

There are a few more guidelines to top these off:

  • Name your services so their role in the above taxonomy becomes clear. Instead of just “FooService”, think “FooUtility”, or “FooRepository” for accessors/persistors, “FooCalculator” or “FooEngine” for computational services, and “FooHandler” or “FooManager” for use case handlers.
  • As you are performing detailed design, keep an eye on the number of operations in a service. If it is starting to feel like a lot, perhaps it is time to decompose a bit further.
  • Treat each service as “the” unit of development for your system. This means – each service is a self-contained unit complete with tests and such. This also means that while you want to stay DRY within a service within reasonable limits, it may not be the best idea to strive for perfect DRYness across services.
  • Each operation should validate its requests and handle authentication and authorization if needed in a self-contained manner.

All of this also aligns pretty well with SOLID design principles. Now, all of this sounds great on paper; however, there are a few pain points to consider. If you strictly adhere to these restrictions, you will end up creating a lot more services than if you didn’t. For example, there are a lot of cases where the use case simply is to go get the data and go store the data. In such situations, you are forced to create a use case handler that is largely pass-through to an accessor/persistor. You could relax these rules in such situations, but then you have a dependency from your consumer to your accessor/persistor. If, at some time, the use case evolves, you need to make sure an use case handler is inserted at that point rather than the so-called “non-volatile” accessor/persistor being modified.

The other obvious pain point with this approach is code bloat. More services means more code, and that means more code to maintain. I think when you get to a system of a certain size, that volume of code becomes justifiable. So, there is a system size below which a lot of this is too much overhead. It is wise to identify that point and react accordingly. Perhaps better tooling could help, too – something tailored for building systems this way.

One problem I have with this – and in fact with SOA in general is that your system is made up of all these services that have logic distributed within them. If you decomposed by volatility and then by function, then granted – your logic is distributed properly. There still is no identiable “core” of the system where the “meat” is – so to speak. That is something that DDD addresses in my opinion. Hence my increasing interest in meshing DDD and SOA. More on that later.

Getting on the Domain Driven Design Bandwagon

Domain driven design has been around for quite a while. I believe the definitive book on it by Eric Evans came out first in 2004. For whatever reason, I had not been exposed to it in places I worked. I had been hearing about it for enough time and from enough smart people to give it a try. I researched it online a bit and went through quite a few articles. Especially, the set of articles on DDD by Jimmy Bogard (Los Techies) was quite helpful. Finally, I ended up buying Evans’ book and reading it cover to cover.

I liked what I saw. The whole idea behind keeping your domain logic encapsulated within your domain objects appealed to me. There were questions, obviously, but I figured it was worth trying out. So that is what I am deep into currently. The idea of entities, value objects, aggregates and aggregate roots makes sense, but at the same time, also raises questions – especially with regards to database performance. I am hoping I will arrive at satisfactory answers.

As things get more complex, other concepts such as bounded contexts and domain events enter the picture. While I get them in theory, my idea for now is to stay away from actually getting hands-on with those until I have a good handle on “vanilla” DDD. Another question I have is how this meshes with SOA – whether the two are complimentary or exclusive. I would hate to have to give up SOA to stick with DDD. In any case, it feels exciting – and I can’t believe it has been around for so many years and I never got into it.

For anyone getting into DDD, I strongly recommend reading Evans’ book. In software timescale, it was written aeons ago (when Java was the state-of-the-art, mind you). But all of it still applies, and if you’re working with something like C#, as I am, things become even easier since you have so much more power with these modern languages.

So, for the moment, let’s say I am on the bandwagon. Hopefully I don’t have to get off.