August 2009

Volume 24 Number 08

Domain Models - Employing the Domain Model Pattern

By Udi Dahan | August 2009

This article discusses:

  • Domain model pattern
  • Scenarios for using the domain model pattern
  • Domain events
  • Keeping the business in the domain
This article uses the following technologies:
Domain Model Pattern

In this article, we’ll go through the reasons to (and not to) employ the domain model pattern, the benefits it brings, as well as provide some practical tips on keeping the overall solution as simple as possible.

Contents

What Is It?
Reasons Not to Use the Domain Model
Technology
Reasons to Use the Domain Model
Scenarios for Not Using the Domain Model
Scenarios for Using the Domain Model
More Complex Interactions
Cross-Cutting Business Rules
Domain Events and Their Callers
Explicit Domain Events
Testability
Commands and Queries
Keeping the Business in the Domain
Concurrency
Finding a Comprehensive Solution
Works Cited

If you had come to me a few years ago and asked me if I ever used the domain model pattern, I would have responded with an absolute "yes." I was sure of my understanding of the pattern. I had supporting technologies that made it work.

But, I would have been completely wrong.

My understanding has evolved over the years, and I've gained an appreciation for how an application can benefit from aligning itself with those same domain-driven principles.

In this article, we'll go through why we'd even want to consider employing the domain model pattern (as well as why not), the benefits it is supposed to bring, how it interacts with other parts of an application, and the features we'd want to be provided by supporting technologies, and discuss some practical tips on keeping the overall solution as simple as possible.

What Is It?

The author of the domain model pattern, Martin Fowler, provides this definition (Fowler, 2003):

An object model of the domain that incorporates both behavior and data.

To tell you the truth, this definition can be interpreted to fit almost any piece of code—a fairly good reason why I thought I was using the pattern when in fact I wasn't being true to its original intent.

Let's dig deeper.

Reasons Not to Use the Domain Model

In the text following the original description, I had originally blown past this innocuous passage, but it turns out that many important decisions hinge on understanding it.

Since the behavior of the business is subject to a lot of change, it's important to be able to modify, build, and test this layer easily. As a result you'll want the minimum of coupling from the Domain Model to other layers in the system.

So one reason not to make use of the domain model pattern is if the business your software is automating does not change much. That's not to say it does not change at all—but rather that the underlying rules dictating how business is done aren't very dynamic. While other technological and environmental factors may change, that is not the context of this pattern.

Some examples of this include supporting multiple databases (such as SQL Server and Oracle) or multiple UI technologies (Windows, Web, Mobile, and so on). If the behavior of the business hasn't changed, these do not justify the use of the domain model pattern. That is not to say one could not get a great deal of value from using technologies that support the pattern, but we need to be honest about which rules we break and why.

Reasons to Use the Domain Model

In those cases where the behavior of the business is subject to a lot of change, having a domain model will decrease the total cost of those changes. Having all the behavior of the business that is likely to change encapsulated in a single part of our software decreases the amount of time we need to perform a change because it will all be performed in one place. By isolating that code as much as possible, we decrease the likelihood of changes in other places causing it to break, thus decreasing the time it takes to stabilize the system.

Scenarios for Not Using the Domain Model

This leads us to the No. 1 most common fallacy about employing domain models. I myself was guilty of making this false assumption for a number of years and see now where it led me astray.

Fallacy: Any persistent object model is a domain model

First of all, a persistent object model does not inherently encapsulate all the behaviors of the business that are likely to change. Second, a persistent object model may include functionality that is not likely to change.

The nature of this fallacy is similar to stating that any screwdriver is a hammer. While you can (try to) hammer in nails with a screwdriver, you won't be very effective doing it that way. One could hardly say you were being true to the hammer pattern.

Bringing this back to concrete scenarios we all know and love, let's consider the ever-present requirement that a user's e-mail address should be unique.

For a while, I thought that the whole point of having a domain model was that requirements like this would be implemented there. However, when we consider the guidance that the domain model is about capturing those business behaviors that are subject to change, we can see that this requirement doesn't fit that mold. It is likely that this requirement will never change.

Therefore, choosing to implement such a requirement in the part of the system that is about encapsulating the volatile parts of the business makes little sense, may be difficult to implement, and might not perform that well. Bringing all e-mail addresses into memory would probably get you locked up by the performance police. Even having the domain model call some service, which calls the database, to see if the e-mail address is there is unnecessary. A unique constraint in the database would suffice.

This pragmatic thinking is very much at the core of the domain model pattern and domain-driven design and is what will keep things simple even as we tackle requirements more involved than simple e-mail uniqueness.

Scenarios for Using the Domain Model

Business rules that indicate when certain actions are allowed are good candidates for being implemented in a domain model.

For example, in an e-commerce system a rule stating that a customer may have no more than $1,000 in unpaid orders would likely belong in the domain model. Notice that this rule involves multiple entities and would need to be evaluated in a variety of use cases.

Of course, in a given domain model we'd expect to see many of these kinds of rules, including cases where some rules override others. In our example above, if the user performing a change to an order is the account manager for the account the customer belongs to, then the previous rule does not apply.

It may appear unnecessary to spend time going through which rules need to apply in which use cases and quickly come up with a list of entities and relationships between them—eschewing the "big design up front" that agile practices rail against. However, the business rules and use cases are the very reasons we're applying the domain model pattern in the first place.

When solving these kinds of problems in the past, I wouldn't have thought twice and would have quickly designed a Customer class with a collection of Order objects. But our rules so far indicate only a single property on Customer instead—UnpaidOrdersAmount. We could go through several rules and never actually run into something that clearly pointed to a collection of Orders. In which case, the agile maxim "you aren't gonna need it" (YAGNI) should prevent us from creating that collection.

When looking at how to persist this graph of objects, we may find it expedient to add supporting objects and collections underneath. We need to clearly differentiate between implementation details and core business behaviors that are the responsibility of the domain model.

More Complex Interactions

Consider the requirement that when a customer has made more than $10,000 worth of purchases with our company, they become a "preferred" customer. When a customer becomes a preferred customer, the system should send them an e-mail notifying them of the benefits of our preferred customer program.

What makes this scenario different from the unique e-mail address requirement described previously is that this interaction does necessarily involve the domain model. One option is to implement this logic in the code that calls the domain model as follows:

public void SubmitOrder(OrderData data) { bool wasPreferredBefore = customer.IsPreferred; // call the domain model for regular order submit logic if (customer.IsPreferred && !wasPreferredBefore) // send email }

One pitfall that the sample code avoids is that of checking the amount that constitutes when a customer becomes preferred. That logic is appropriately entrusted to the domain model.

Unfortunately, we can see that the sample code is liable to become bloated as more rules are added to the system that needs to be evaluated when orders are submitted. Even if we were to move this code into the domain model, we'd still be left with the following issues.

Cross-Cutting Business Rules

There may be other use cases that result in the customer becoming preferred. We wouldn't want to have to duplicate that logic in multiple places (whether it's in the domain model or not), especially because refactoring to an extracted method would still require capturing the customer's original preferred state.

We may even need to go so far as to include some kind of interception/aspect-oriented programming (AOP) method to avoid the duplication.

It looks like we'd better rethink our approach before we cut ourselves on Occam's razor. Looking at our requirements again may give us some direction.

When a customer has become a [something] the system should [do something].

We seem to be missing a good way of representing this requirement pattern, although this does sound like something that an event-based model could handle well. That way, if we're required to do more in the "should do something" part, we could easily implement that as an additional event handler.

Domain Events and Their Callers

Domain events are the way we explicitly represent the first part of the requirement described:

When a [something] has become a [something] ...

While we can implement these events on the entities themselves, it may be advantageous to have them be accessible at the level of the whole domain. Let's compare how the service layer behaves in either case:

public void SubmitOrder(OrderData data) { var customer = GetCustomer(data.CustomerId); var sendEmail = delegate { /* send email */ }; customer.BecamePreferred += sendEmail; // call the domain model for the rest of the regular order submit logic customer.BecamePreferred -= sendEmail; // to avoid leaking memory }

While it's nice not having to check the state before and after the call, we've traded that complexity with that of subscribing and removing subscriptions from the domain event. Also, code that calls the domain model in any use case shouldn't have to know if a customer can become preferred there. When the code is directly interacting with the customer, this isn't such a big deal. But consider that when submitting an order, we may bring the inventory of one of the order products below its replenishment threshold—we wouldn't want to handle that event in the code, too.

It would be better if we could have each event be handled by a dedicated class that didn't deal with any specific use case but could be generically activated as needed in all use cases. Here's what such a class would look like:

public class CustomerBecamePreferredHandler : Handles<CustomerBecamePreferred> { public void Handle(CustomerBecamePreferred args) { // send email to args.Customer } }

We'll talk about what kind of infrastructure will make this class magically get called when needed, but let's see what's left of the original submit order code:

public void SubmitOrder(OrderData data) { // call the domain model for regular order submit logic }

That's as clean and straightforward as one could hope—our code doesn't need to know anything about events.

Explicit Domain Events

In the CustomerBecamePreferredHandler class we see the reference to a type called CustomerBecamePreferred—an explicit representation in code of the occurrence mentioned in the requirement. This class can be as simple as this:

public class CustomerBecamePreferred : IDomainEvent { public Customer Customer { get; set; } }

The next step is to have the ability for any class within our domain model to raise such an event, which is easily accomplished with the following static class that makes use of a container like Unity, Castle, or Spring.NET:

public static class DomainEvents { public IContainer Container { get; set; } public static void Raise<T>(T args) where T : IDomainEvent { foreach(var handler in Container.ResolveAll<Handles<T>>()) handler.Handle(args); } }

Now, any class in our domain model can raise a domain event, with entity classes usually raising the events like so:

public class Customer { public void DoSomething() { // regular logic (that also makes IsPreferred = true) DomainEvents.Raise(new CustomerBecamePreferred() { Customer = this }); } }

Testability

While the DomainEvents class shown is functional, it can make unit testing a domain model somewhat cumbersome as we'd need to make use of a container to check that domain events were raised. Some additions to the DomainEvents class can sidestep the issue, as shown in Figure 1.

Figure 1 Additions to the DomainEvents Class

public static class DomainEvents { [ThreadStatic] //so that each thread has its own callbacks private static List<Delegate> actions; public IContainer Container { get; set; } //as before //Registers a callback for the given domain event public static void Register<T>(Action<T> callback) where T : IDomainEvent { if (actions == null) actions = new List<Delegate>(); actions.Add(callback); } //Clears callbacks passed to Register on the current thread public static void ClearCallbacks () { actions = null; } //Raises the given domain event public static void Raise<T>(T args) where T : IDomainEvent { foreach(var handler in Container.ResolveAll<Handles<T>>()) handler.Handle(args); if (actions != null) foreach (var action in actions) if (action is Action<T>) ((Action<T>)action)(args); } }

Now a unit test could be entirely self-contained without needing a container, as Figure 2 shows.

Figure 2 Unit Test Without Container

public class UnitTest { public void DoSomethingShouldMakeCustomerPreferred() { var c = new Customer(); Customer preferred = null; DomainEvents.Register<CustomerBecamePreferred>( p => preferred = p.Customer ); c.DoSomething(); Assert(preferred == c && c.IsPreferred); } }

Commands and Queries

The use cases we've been examining so far have all dealt with changing data and the rules around them. Yet in many systems, users will also need to be able to view this data, as well as perform all sorts of searches, sorts, and filters.

I had originally thought that the same entity classes that were in the domain model should be used for showing data to the user. Over the years, I've been getting used to understanding that my original thinking often turns out to be wrong. The domain model is all about encapsulating data with business behaviors.

Showing user information involves no business behavior and is all about opening up that data. Even when we have certain security-related requirements around which users can see what information, that often can be represented as a mandatory filtering of the data.

While I was "successful" in the past in creating a single persistent object model that handled both commands and queries, it was often very difficult to scale it, as each part of the system tugged the model in a different direction.

It turns out that developers often take on more strenuous requirements than the business actually needs. The decision to use the domain model entities for showing information to the user is just such an example.

You see, in a multi-user system, changes made by one user don't necessarily have to be immediately visible to all other users. We all implicitly understand this when we introduce caching to improve performance—but the deeper questions remain: If you don't need the most up-to-date data, why go through the domain model that necessarily works on that data? If you don't need the behavior found on those domain model classes, why plough through them to get at their data?

For those old enough to remember, the best practices around using COM+ guided us to create separate components for read-only and for read-write logic. Here we are, a decade later, with new technologies like the Entity Framework, yet those same principles continue to hold.

Getting data from a database and showing it to a user is a fairly trivial problem to solve these days. This can be as simple as using an ADO.NET data reader or data set.

Figure 3 shows what our "new" architecture might look like.

Model for Getting Data from a Database

Figure 3 Model for Getting Data from a Database

One thing that is different in this model from common approaches based on two-way data binding, is that the structure that is used to show the data isn't used for changes. This makes things like change-tracking not entirely necessary.

In this architecture, data flows up the right side from the database to the user in the form of queries and down the left side from the user back to the database in the form of commands. Choosing to go to a fully separate database used for those queries is a compelling option in terms of performance and scalability, as reads don't interfere with writes in the database (including which pages of data are kept in memory in the database), yet an explicit synchronization mechanism between the two is required. Options for this include ADO.NET Sync Services, SQL Server Integration Services (SSIS), and publish/subscribe messaging. Choosing one of these options is beyond the scope of this article.

Keeping the Business in the Domain

One of the challenges facing developers when designing a domain model is how to ensure that business logic doesn't bleed out of the domain model. There is no silver-bullet solution to this, but one style of working does manage to find a delicate balance between concurrency, correctness, and domain encapsulation that can even be tested for with static analysis tools like FxCop.

Here is an example of the kind of code we wouldn't want to see interacting with a domain model:

public void SubmitOrder(OrderData data) { var customer = GetCustomer(data.CustomerId); var shoppingCart = GetShoppingCart(data.CartId); if (customer.UnpaidOrdersAmount + shoppingCart.Total > Max) // fail (no discussion of exceptions vs returns codes here) else customer.Purchase(shoppingCart); }

Although this code is quite object-oriented, we can see that a certain amount of business logic is being performed here rather than in the domain model. A preferable approach would be this:

public void SubmitOrder(OrderData data) { var customer = GetCustomer(data.CustomerId); var shoppingCart = GetShoppingCart(data.CartId); customer.Purchase(shoppingCart); }

In the case of the new order exceeding the limit of unpaid orders, that would be represented by a domain event, handled by a separate class as demonstrated previously. The purchase method would not cause any data changes in that case, resulting in a technically successful transaction without any business effect.

When inspecting the difference between the two code samples, we can see that calling only a single method on the domain model necessarily means that all business logic has to be encapsulated there. The more focused API of the domain model often further improves testability.

While this is a good step in the right direction, it does open up some questions about concurrency.

Concurrency

You see, in between the time where we get the customer and the time we ask it to perform the purchase, another transaction can come in and change the customer in such a way that its unpaid order amount is updated. That may cause our transaction to perform the purchase (based on previously retrieved data), although it doesn't comply with the updated state.

The simplest way to solve this issue is for us to cause the customer record to be locked when we originally read it—performed by indicating a transaction isolation level of at least repeatable-read (or serializable—which is the default) as follows:

public void SubmitOrder(OrderData data) { using (var scope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.RepeatableRead } )) { // regular code } }

Although this does take a slightly more expensive lock than the read-committed isolation level some high-performance environments have settled on, performance can be maintained at similar levels when the entities involved in a given use case are eagerly loaded and are connected by indexed columns. This is often largely offset by the much simpler applicative coding model, because no code is required for identifying or resolving concurrency issues. When employing a separate database for the query portions of the system and all reads are offloaded from the OLTP database serving the domain model, performance and scalability can be almost identical to read-committed-based solutions.

Finding a Comprehensive Solution

The domain model pattern is indeed a powerful tool in the hands of a skilled craftsman. Like many other developers, the first time I picked up this tool, I over-used it and may even have abused it with less than stellar results. When designing a domain model, spend more time looking at the specifics found in various use cases rather than jumping directly into modeling entity relationships—especially be careful of setting up these relationships for the purposes of showing the user data. That is better served with simple and straightforward database querying, with possibly a thin layer of facade on top of it for some database-provider independence.

When looking at how code outside the domain model interacts with it, look for the agile "simplest thing that could possibly work"—a single method call on a single object from the domain, even in the case when you're working on multiple objects. Domain events can help round out your solution for handling more complex interactions and technological integrations, without introducing any complications.

When starting down this path, it took me some time to adjust my thinking, but the benefits of each pattern were quickly felt. When I began employing all of these patterns together, I found they provided a comprehensive solution to even the most demanding business domains, while keeping all the code in each part of the system small, focused, and testable—everything a developer could want.

Works Cited

Fowler, M. Patterns of Enterprise Application Architecture, (Addison Wesley, 2003).

Udi Dahan Recognized as an MVP, an IASA Master Architect, and Dr. Dobb's SOA Guru, Udi Dahan is The Software Simplist, an independent consultant, speaker, author, and trainer providing high-end services in service-oriented, scalable, and secure enterprise architecture and design. Contact Udi via his blog at UdiDahan.com.