Pragmatic Objects
  • Pragmatic Objects
  • What's an Object ?
  • Thinking Object
  • Domain Modeling
  • Information Management
  • Practices
    • The Pragmatic Practices
    • No Getters & Setters
    • Inherit Wisely
    • Wrap Null
    • Wrap Primitives
    • Wrap Collections
    • Expose Snapshots
    • Abandon Composed Names
    • Don't Check Types
    • Minimize Knowledge
    • Immutability
    • Separate Commands & Queries
    • Abandon Statics
    • Invert Dependencies
  • Patterns
    • Always Valid
    • Wrapper
    • Command
    • Procedural Object
    • Compute Object
    • Snapshot
    • Value Object
    • Observability
  • Examples
    • Celsiuses / Fahrenheits
Powered by GitBook
On this page
  • Domain-Driven Design
  • Domain Modeling
  • Bounded Contexts
  • Technically
  • Strategically
  • The Large Model Problem
  • Summary

Domain Modeling

Modeling the domain with objects.

As programmers, our goal is to create programs that serve our customers, right ?

Similarly, our goal is to create software that is aligned with how domain experts think about the problem and its solution, right ?

This one last sentence tends to create a lot of conversations, I don't really know why. If you work in accountants systems, ERPs, trading, banking, medical centers, or almost any area that is even remotely complex, you're gonna have to understand how things work.

Turns out, that's exactly the problem tackled by the Object-Oriented community, more than any other community, for the past 50 years.

Most of the development that led to so-called Enterprise Software, that is, software dedicated to help companies automate their process and serve their customers, was developed by industrial object-oriented programmers.

What am I saying, you say? Am I really glorifying the object community ?

While I could, that's not my intent. The message is that most of the development of object-oriented technology was led by the industry for the industry. Object-orientation isn't some complex, academic-born way of programming needlessly complex programs.

And the culmination of all this work and exploration is called Domain-Driven Design.

Domain-Driven Design

Oh no, what's this again ! A nightmare ! No one understands this domain-driven stuff anyway.

Yeah, I know. Domain-Driven Design has a deserved reputation of feeling complicated, and the blue book by Eric Evans doesn't really help. It takes a few years of experience and 3 reads to barely grasp his ideas. But we're diverging.

At its core, Domain-Driven Design is about designing software guided by the domain, the same way Test-Driven Development is about guiding the development of the software by the tests. The big idea is that our language should shift from technical to domain-driven.

If we take the example of an Auction platform, it's about shifting from "our users can create Auction and other users can put money to win the Auction" to "Bidders can start auction for Buyers to bid until they win the prize". It's about enriching our vocabulary with verbs and nouns (does it remind something ?) directly taken from domain. And it's about enriching our code with objects and methods that reflect these words.

Why is it important ? Because it is our job, as programmers, to create useful software. Software that make money. Software that helps people. Software that is easy to work with and to enhance.

And Domain-Driven Design is really one fantastic approach that tackle this problem, especially on complex domains.

Domain Modeling

Domain Modeling is the act of representing the domain model in the code. And by Domain-Model, we really mean all the verbs and nouns and adjectives that come from the vocabulary of the domain, a shared vocabulary between programmers and non-developers.

The net effect of this approach is that our code become much more expressive and much less technical. As an example, we can have a domain-expert without technical skills open a folder, view all the domain objects and understand how it all works.

Because the code reflects the domain, it's much easier for non-techies to check the code, validate the code, and discuss about the code.

It's about treating code as a communication medium first, and as a software artifact second.

The main reason we use high-level languages like Java, C# or JavaScript is precisely to create a highly expressive code whose intent is easy to understand, for which the complexity remains manageable at scale. This is probably the hardest challenge in software engineering. Definitely.

Bounded Contexts

Domain-Driven Design gives us two tools to manage complexity at scale. The first one is Domain Modeling, as we just saw. But even a domain-model can grow very large at some point.

So the other tool is called Bounded Contexts. It's a fancy name for something we used to call Components for which we have decades of books and tools talking about Component Architecture.

You will see many different definitions of Bounded Contexts, most of them are confusing. We don't need to make things unnecessarily complicated, so I'll explain to you Bounded Contexts in its most technical aspect, before explaining the various aspect of decomposing a software product into Bounded Contexts.

Technically

A Bounded Context really is a Component. Fine, jeez! But then what's a Component ? It really is just a logical grouping of classes (or files, depending on your programming languages, but we're talking object here).

It's been known for decades now that a large, growing software should be decomposed into smaller subsystems. Call these modules, packages, components, whatever floats your boat, but in its essence, a Component is just a subsystem.

Components, Modules, Packages and Subsystems are all used interchangeably to describe the same thing. Don't overthink it.

Robert Martin wrote a lot about Component Architecture in his seminal book Clean Architecture, and that's one aspect that's often looked over. One point he's talking about at length is that depending on what kind of Component you're developing, they might exhibit different forces and weaknesses. He devised 6 more principles for Components specifically.

For example, a very generic, reusable component can be, by definition, used by a lot of other Components. However, that component will have a lot of incoming dependencies and isn't free to change as it wants. One change can potentially impact a variety of users with competing interests.

On the other hand, a very specific component will not be so reusable because it has been developed with a very specific problem in mind. It is tailor-made for that problem, so it's very useful and probably has one or two incoming dependencies at most. It has more space for evolution.

This isn't a course on Component Architecture, but keep in mind that Bounded Contexts really obeys law that are very close to Components.

Strategically

Components are very loosely defined, so it's hard to come with good rules about how to split an application into components. However, Bounded Contexts have a much more detailed definition.

Bounded Contexts is method to slice down an Architecture into domain-relevant components. Each Bounded Context is of significance to the Domain and it has a name that is understandable by the domain. You may have a Warehousing Context or a Shipping Context that describes processes relevant to Warehousing and Shipping.

As a side note, a Bounded Context can itself be subdivided into discrete components ; A very large Warehousing Context can contain an Inventory Module responsible for managing the inventory and another Topology Module responsible for managing a Warehouse and locating assets in the physical space.

When looking at the architecture of a program from afar, Bounded Contexts are the first elements that are architecturally relevant. You can zoom in a specific Bounded Context to see how it's decomposed if necessary, but Bounded Contexts are the highest building block.

One very important trait of Bounded Contexts is that each Bounded Contexts strives to remain independent. You want to minimize dependency between Contexts and keep them under control. Moreover, you want to reduce communication between them and, as much as possible, keep this communication asynchronous.

A lot has been written about Bounded Contexts, their architectural relevance and their relation with the organisational structure of the company. I suggest you do your own research from there.

The Large Model Problem

It has been long believed (and hoped) that an object model should encompass all the features of a software system. For example, it was a common idea to have one single object Product responsible for containing catalog informations, warehousing information and shipping informations.

class Product {
    private id: string;
    private title: string;
    private resume: string;
    private reviews: Review[];
    private averageNote: number;
    private variants: Variant[];
    private price: Price;
    
    private quantityAvailable: number;
    private quantityOrdered: number;
    private quantityInTransit: number;
    private sku: string;
    private warehouseLocationId: string;
    
    private shippableInDays: number;
    private carrier: string;
    private shippingPrices: ShippingPrice[];
}   

Imagine how big this object can be. It will contains probably dozen of methods that are mostly unrelated to one another. It will be maintained by different developers and grow over time until it become unmanageable. Imagine the test suite for this monster !

Moreover, the graph of objects will grow unnecessarily large too, especially at runtime. For one use case, you might need to just change the name of the Product. Yet, you've loaded the product will all the warehousing and shipping informations with it, an arbitrarily deep graph. You're thus locking a large graph of object, with the net effect of reducing the response time of your application as it receive more and more requests.

One possible solution is to (ab)use partial loading and to accept that some properties will be set to null. I'm against this idea, it's awful. For the simple reason that it makes it obscure which properties are loaded at compile time. You can no longer look at your object and know for sure what data is loaded and what isn't.

The other solution is lazy loading, very close to partial loading with the difference that it loads relations à la demande. I don't like it too for very similar reasons, it makes it hard to reason about the code. You're not supposed to have silent database roundtrips in the middle of the code just because some data isn't found in your local ORM's cache / identity map.

So the last solution is to simply split that large model into many submodels, each managing their own part of the product. One possible decomposition is the following.

class CatalogProduct {
    private id: string;
    private title: string;
    private resume: string;
    private reviews: Review[];
    private averageNote: number;
    private variants: Variant[];
    private price: Price;
}   

class WarehouseProduct {
    private id: string;
    private quantityAvailable: number;
    private quantityOrdered: number;
    private quantityInTransit: number;
    private sku: string;
    private warehouseLocationId: string;
}   

class ShippingProduct {
    private id: string;
    private shippableInDays: number;
    private carrier: string;
    private shippingPrices: ShippingPrice[];
}   

We can either prefix or suffix the object with a noun that binds it to some area of our domain, but let's face it : it's not ideal.

Do you see yourself talking to your product owner about ShippingProducts and WarehouseProducts ? This vocabulary is a direct reflection of our incapacity to structure things.

When a client validates an order, we must decrease the quantity of WarehouseProduct and prepare it for shipping

Not great at all, right ? Let's see things differently : how about talking about Products in the Shipping System and Products in the Warehouse System ? Something along the line of...

When a client validates an order, we must decrease the quantity of product available in the Inventory subsystem and prepare it for shipping.

We're moving in the right direction. It seems that Warehousing and Shipping make for great Bounded Contexts into which we can develop WarehouseProduct and ShippingProduct for vastly different use cases. Do we need to prefix them ? We can just keep Product in Warehousing and another class Product in Shipping, referring to the same product but containing features for different actors of the system.

Summary

As object practitioners, we use Domain Modeling to reflect the domain of our software in the code and we use Bounded Contexts to decompose a large software into smaller independant subsystems, each maintaining their own Domain Model, developed and designed for solving a specific problem.

This is the essence of Domain-Driven Design, and the essence of Object-Oriented Design. This article really bridges these disciplines together because they're really meant to be.

For now, this website is in French and my content is mostly dedicated to french-talking communities. However, don't hesitate to drop an e-mail at anthony@ancyracademy.fr and we can talk through your needs.

PreviousThinking ObjectNextInformation Management

Last updated 9 days ago

If you're interested in digging deeper into Domain-Driven Design, Domain Modeling and Object-Orientation, I offer seasonal workshops. You can have more informations here :

https://ancyracademy.fr/workshop-ddd