Domain Modeling – Modeling Milk


I recently had a discussion at work about the complexity of modeling data in real world systems. I used the example of a bottle of milk in the discussion, and I really like it, so I thought it would make for a good blog post.

Consider a supermarket that sells milk. In most scenarios, this is not exactly a controversial statement. How would you expect the system to model the concept of milk? The answer turns out to be quite complex, in practice.

To start with, there is no one system here. A supermarket is composed of many different departments that work together to achieve the end goal. Let’s try to list some of the most prominent ones:

  • Cashier
  • Stock
  • Warehouse
  • Product catalog
  • Online

Let’s see how each of these think about milk, shall we?

The cashier rings up a specific bottle of milk, but aside from that, they don’t actually care. Milk is fungible (assuming the same expiry date). The cashier doesn’t care which particular milk cartoon was sold, only that the milk was sold.

The stock clerks care somewhat about the specific milk cartoons, but mostly because they need to make sure that the store doesn’t sell any expired milk. They might also need to remove milk cartoons that don’t look nice (crumpled, etc).

The warehouse care about the number of milk cartoons that are in stock on the shelves and in the warehouse, as well as predicting how much should be ordered.

The product catalog cares about the milk as a concept, the nutritional values, its product picture, etc.

The online team cares about presenting the data to the user, mostly similar to the product catalog, until it hits the shopping cart / actual order. The online team also does prediction, based on past orders, and may suggest shopping carts or items to be purchased.

All of these departments are talking about the same “thing”, or so it appears, but it looks, behaves and acted upon in very different ways.

Your data model is not your domain model

I came across this tweet today:

Here’s the thing. DDD will never be successful if it is applied wholesale to any solution.  

Read that again. When people generate a data model, put it in a folder called “Domain” and call it Domain Driven Design, they have missed the entire point of the exercise. Completely and utterly.

Similarly, when people strive to implement DDD properly to everything, the sheer expense of modeling the crap out of everything results in models which get weaker and weaker until eventually everyone capitulates and we land up with another anemic domain model.

So, what  to do?

For starters, stop looking for an excuse to “do DDD”. 70% or perhaps more of every project is just CRUD. You can dress it up every which way you like, but at the end of the day, if you’re just doing some basic validation around C,U and D, it just ain’t DDD. Apply far simpler methods here to GET SH17 DONE, like transaction scripts. There is nothing wrong with that! It’s the right tool for the right job. When you find that the noise being generated by the procedural nature of something like transaction scripts is feeling off, that’s when one needs to consider something more appropriate for dealing with what may be real complexity.

At this point, it’s important not to go deep into a solo modeling expedition, scaring the crap out of your teammates with some alien artifact called your “domain model”. DDD is hard. It requires a good deal of collaboration and real commitment to get “right”. Always start by involving your team. Call someone over and review how you got here. Then model out loud, using pictures to give ideas shape. What we’re looking for is a sense of justification for the journey we are about to embark on. If the collective feels that it is justified and the weak and probably totally incorrect model you just came up with feels like a leg up, it’s time to do some real modeling. At this point we want to make sure we are having the right conversations with the right stakeholders. We’ll need access to domain experts and perhaps even some users.

The first step is all about establishing boundaries. Listen to the language being used to describe behaviours and the things people are talking about. Be acutely conscious of the fact that language only makes sense in the correct context. We need to start thinking about what the boundary of this context is we’re dealing with. This is really, really hard and critical to the success of our efforts. It takes time for these things to take shape. Once we start with this process, it really challenges so much of our basic understanding that it may seem a little overwhelming. Stick with it! Be committed. At some point, after many iterations we will come up with what we think is representative of the boundary we’re in, given our current understanding, and we will be able to more confidently give shape to the model which represents the domain.

The next trap is to pull up our data model (EF, NH or whatever) and start with that. It’s not about classes and code! It’s about modelling conceptually the things which best represent the reality of the domain. Just forget about the damn code, that comes later. Work through the model constantly, challenge your assumptions, play devil’s advocate, throw silly ideas in there on purpose. At some point it feels like we’ve got something good to start with, and at that point pull up your IDE and code pure POCO’s or POJO’s and get your vanilla framework-less code representing that conceptual model.

The next trap is that we tend to think that whatever  our amazing model is, that it is the final and correct model of this part of the domain. Sadness… it just isn’t. But that’s ok, because you developed a bunch of tests for your model, so that you can evolve it with real confidence as your understanding grows.

Once you’ve done enough for things to feel good and your model to be providing real value to a potentially critical area of development, be sure to remember that you need to go back to the simple life when this process is just not appropriate. It’s always OPT-IN, and when we do, we’re committed to doing it right.

So, basically, keep everything really simple until it just isn’t. Then, approach the DDD process responsibly and remain committed until you have a bounded context and a model which you’re happy with. Never assume you’ve “nailed it”; it’s going to change, and write tests so that you can change it. Remember to default back to a simpler approach when you’re not dealing with real complexity and always look for justification to commit to the DDD journey. Following this guideline will provide you with the value you’re after when you need it.