The Importance of Domain Models


Disclaimer: This post focuses on theory, so there are some bits that are oversimplified.

My current assignment is consulting a large eCommerce company on their external APIs. For those not well versed in this concept, the short story is I am focused on the architecture of web services exposed to the outside world. One of my primary tasks is determining project layout of the service solutions.

One of the newer services was set up as a pass through from an internal service. The objects exposed by the underlying service were merely bubbled up through the external service and exposed “as is” to the outside world. This post deals with why this is a mistake and how a service should be modeled. While my focus is external services, the methodology presented should work as a reference model for many types of development.

Domain Models

First, we need a working definition of a domain. In Wikipedia, a domain is defined as follows:

Domain A sphere of knowledge, influence, or activity. The subject area to which the user applies a program is the domain of the software

In an eCommerce company, we have many domains. Some deal with knowledge of the product, others with finance and yet others with shipping and receiving. Each of these domains has different needs. As an example, let’s look at a customer as he relates to various applications in an eCommerce scenario:

Ordering – Need full customer information, including address(es), phone number(s)  and payment information. Not all of this information is mandatory in all steps of the process, however, so we might even represent the customer in a variety of domain models. As a real world example, it is unlikely we will need the customer’s payment information anywhere but the payment process.

Warehouse – There is no need for any customer information to pick items. We do need the order represented so the shippers can identify the customer, but the customer is optional

Shipping – A customer, as far as shipping is concerned, is a name, a shipping address and a phone number (so the carrier can call if there are issues).

Reading the examples above, it should be clear that a domain does not equal an application. In the space of browsing and ordering, for example, the application likely crosses multiple domains. This is not important for this article, and we are not going to create a domain model. Now that you understand the domain and the domain model, let’s focus on the core issue for this exercise.

Core Issues

The core of this article is when an internal service is exposed to the outside world without creating a separate domain model. The model looks like the following diagram:

DIAGRAM

On the surface, this does not seem like such a bad idea. After all, why should I not expose the same objects to the outside world? The main reasons are:

  1. The outside clients are not insulated from changes to the internal change. In more OO terms, the inner model is not encapsulated and there is no abstraction. Encapsulation is more important here than abstraction, of course.
  2. Internal services often contain wording that does not apply to the outside world, or ‘companyisms”
  3. Internal needs are different from external needs and there are often internal “secrets” that should be kept internal only.

Insulation (Encapsulation)

When you design a service that passes through objects from an internal service, there is no insulation from change. Every time you change the objects in the internal service, the external service must be changed to match the internal service. Done properly, the service is versioned and the original service is properly deprecated so it is kept alive until external clients can switch their programming to fit the new service.

This does not sound like such a big deal over all, but consider most internal services are not thought through with the same rigor as external services. This means there are generally more changes to an internal service than the outside world will tolerate.

By adding a domain model to the external service, you can protect the external client from excessive versioning. As an example, if the only changes on the internal service are name changes, you can simply adjust the mapping to your domain model and release a new version on top of the old service. The internal service team need not keep an old version of the service alive, as the external client consumes the external domain model (not quite true, but that is a topic for another post).

Companyisms

Every company I have consulted for has some type of verbiage unique to the organization. In eCommerce, for example, I often see {CompanyName}Price for the standard retail price for the company, which is generally lower than the manufacturer’s standard retail price. In more extreme measures, company acronyms become part of the internal service model.

Regardless of the nature of the companyism, the outside consumer should not have to understand the unique vernacular of your company to consume the service. By creating a domain model which is focused on external clients, you eliminate companyisms from the objects served.

Company “Secrets”

Not all of the items served by the internal service are truly company secrets, but there are many things an external consume need not know. These items should “disappear” from the domain model used by the external service.

In an internal cart service, you may pass back private consumer data so the applications can expose this information to employees who need to know. If you expose this same service to the outside world, these “secreats” are also available. If, instead, you create a domain model, you can consciously remove these items from your model and avoid exposure to the outside world.

Pattern for an External Service

Now let’s look at a pattern for an external service. The pattern is modeled using the Core as Application methodology, as shown in the diagram below:

image

The idea here is the business logic is the application and Persistence (where you save data) and Experience (where the user reads or manipulates data) are not part of the application, per se, but extensions so work can get done. As with all analogies, there are some flaws in this view, but it is a very useful paradigm for developing high quality (testable?) applications. I will cover Core as Application, and its benefits, in another post.

As many are not familiar with the Core As Application model above, I will take you through an “n-tier” set up of the diagram. A couple of notes before going this direction.

  • The UI tier has been renamed experience, like the Core as Application model. This is done to illustrate the fact that our user’s are not necessarily human, as in a service. What we are focused on in this layer is the experience of the user, human or computer.When we focus on interface, we often see this as strictly I/O, which is a mistake.
  • The data tier is renamed persistence, like the Core as Application model. The reasoning is the application should not change based on where you are persisting your data. The only thing that should change when you switch from a file based persistent methodology to a database is your access method.
  • The business tier has been renamed core, like the Core as Application model. This is due to the fact that this tier contains the application. Switching out the experience layer does not change the application, nor does switching to another persistence method. Of the two concepts here, separating persistence (database) from core is generally easier than experience.

Now that we have the “rules”, here is a basic service diagram, with a rather simple implementation.

image

Here is an explanation of the parts of the diagram. The items in blue are items created via attributes in WCF and the Service Proxy, in green, is “free”, as it is created by .NET when you create a service reference.

  • Service Proxy – A proxy for a service created when the service is queried. The proxy comes for free in .NET, as it is created when you include a service reference or use the command line tool. NOTE that this assumes a SOAP service in the .NET Framework 4.0 or earlier.
  • Proxy Façade – The façade is utilized as a bit of an insulation against change in the methods of the underlying service.Adding a façade makes it easier to create an interface, which both a) makes it easier to test and b) allows for substitution for future versions of the service. When added to the factory and mapper, you end up with a very flexible model for change.
  • Mapper – The mappers job is to translate from the service model to the domain model. While this functionality can be encapsulated into the façade, keeping it separate makes it easier to unit test the façade. Using generics, the mapper can allow for flexible input while keeping the domain model objects as its primary output. This allows for easy change while adhering to a single interface.
  • Domain model – The model for the business logic, if any, contained in the external service.
  • Core – In a mapping type of service, the core is a pass through only. In an aggregation type of service, the core will “marry” output from multiple services. I find it unwise to have your external service serve as an aggregate. While it adds “weight” to the total solution, it is better to have another internal service handle the aggregation and have the external service focus on simple mapping of objects. This moves the complexity away from the external client and reduces the likelihood of erroring out  your consumers.
  • Data Contract – Standard WCF adorned objects. Per Microsoft’s suggestion, I use the IDataSurrogate interface to further separate the domain model from the representation to the outside world. The contract can be placed in the service layer, rather than core.

    NOTE: I am currently working on using the MVC pattern on the service, which may be more easily accomplished by bubbling up the data contract to this project. I will post findings later.

  • Operation Contract and Service Contract – These describe the behavior of the service and are a standard part of WCF. I will generally separate these into a separate assembly, although it is not mandatory, as changes here will alter the service.
  • The service – Standard WCF service. As a very simple “User Interface’ (experience piece), the WCF service contains very little code. This may be altered slightly if the service project contains the contracts.

If you feel multiple facades are needed throughout the history of the service, you can add a factory in front of the façade and use it to determine which façade (and thereby service) are used in a particular version.

I will cover the solution layout in more detail a future post.

Summary

Internally, most organizations go through a bit of chaos. In an ideal world, the developers in one department would treat the developers in consuming department as clients, this is often the exception in many organizations. Breaking changes in software are all too common and often break the software created by other departments.

The outside world is far less tolerant than your internal departments. When you break software, they may forgive you once, but eventually the decision will be made to go to a competitor. For this reason, you simply cannot alter service contracts without considerable thought.

To insulate the outside world from your changes, you must use a strategy that keeps the outside interfaces as static as possible despite the internal chaos. Using a domain model and data contracts, along with proper facades and factories, you can insulate the outside world from internal chaos and company specific naming. You also have the ability to remove any “secret” information.

The pattern applied in this article is not a “one size fits all” but rather a very good start for most situations. Feel free to alter the pattern to fit your specific needs. Also feel free to comment, as patterns should always be open for improvement and discussion.

Peace and Grace,
Greg

Twitter: @gbworld
Microsoft MVP Page: http://mvp.support.microsoft.com/profile/Beamer

2 Responses to The Importance of Domain Models

  1. Pingback: SOA Lessons: Insulate Your Client « Stop Making Sense

  2. Pingback: A foray into micro services in .NET | Stop Making Sense

Leave a comment