Knowledge Crunching & Refactoring in a Domain-Driven Design

Minhaj U. Khan
OneFootball Tech
Published in
7 min readSep 6, 2024
Photo by Shayne Inc Photography on Unsplash

Our team was tasked to develop an application for our in-house news team where they can reliably write and publish articles.

We call this application, the Content Studio.

At this point, we were dealing with a legacy system — spaghetti code, little to no abstraction, and information shared across distant modules — database objects floating in HTTP request/response handlers, for instance. It wouldn’t be incorrect to state that the system was a living example of a Big Ball of Mud.

Starting from a clean slate was all we desired — so we did.

From the get-go, we agreed not to repeat the mistakes made before and committed ourselves to a layered design — where application, business functions, and database needs are separated from each other.

I read the Domain-Driven Design book a few years ago, and some teachings of the book, though a bit blurry, stuck with me for good. I thought this would be a good time to revisit, refresh, and apply the teachings to this clean slate of ours.

Language

Photo by Assad Tanoli on Unsplash

So, how did it go?

My friend had an important interview scheduled on Friday this week, he told me two days ahead.

When I met him on Friday evening; I immediately asked — “So, how did it go?” to which he responded, “Really good! I have the next round tomorrow”

As humans, when we communicate, we automatically infer the context, either from our surroundings, from historical knowledge, or the significance of a future event. Computers, however, are not as good as us at this.

One of the core teachings of the book is to observe the language used by the domain experts when they communicate in a certain context.

In our legacy systems, an article was called a Post, however, the newsroom never referred to the article as a post, rather, they called it an Article. It was wrong of the legacy system to invent its terminology which to the domain expert is a synonym — which is just as bad.

This notion of having the model express itself as close to how the domain expert talks about it is referred to by the book as Ubiquitous Language and is one of the core principles in DDD.

In the new system, we created our first model of the Article, exactly how the newsroom talks about it.

The initial model of the Article

Domain Events

Photo by John Oswald on Unsplash

Events are things that happen, and once they do, they cannot be changed.

Each run the batsmen make in a cricket match, is accumulated to evaluate the total score at the end of the innings. If not for the recording of each run scored, it would be impossible to know how many runs the team managed to score.

Our system was expected to raise an event as soon as an article was published. This event is subscribed to by other external systems to carry out their work.

We added a Publish method to our initial model having implementation details like setting the published date, etc. The next job for us was to raise this event when the Publish() method was invoked.

Publish Behavior on Article

Taking a peek at the event schema,

{
article: {},
provider: {},
}

We realized that the event not only has details about the Article but also asks for the Provider of the article.

When the New York Times publishes an article — the New York Times is the provider — Domain Expert

Here, we can take the obvious approach and pass in the Provider to the Publish method,

article.Publish(provider)

but the readability of the code gives it away. It reads as if an article publishes a provider, which is not correct, not in the current domain, at least.

At this point, we see the first defect in our model. An article cannot publish itself. It is time for “Refactoring” as we “Crunched” this new “Knowledge”.

A provider entity is introduced into the domain model that publishes the Article.

Initial Provider Model

Continuous Critique

Kintsugi, or Kintsukuroi, is the centuries-old Japanese art of fixing broken pottery. Rather than rejoin ceramic pieces with a camouflaged adhesive, the kintsugi technique employs a special tree sap lacquer dusted with powdered gold, silver, or platinum. Once completed, beautiful seams of gold glint in the conspicuous cracks of ceramic wares, giving a one-of-a-kind appearance to each “repaired” piece

https://mymodernmet.com/kintsugi-kintsukuroi/

My colleague Ofonime Usoro pointed out that in Content Studio, it is not the provider that publishes the article, but rather an Author publishes it for the Provider.

Most of the time, these discoveries of the shortcomings of the model do not mean that the model is wrong to begin with. Most of the time is an important declaration here. Sometimes, the model may only need a bit of Kintsukuroi.

Associations

In the domain, an author can write for multiple providers. Our database schema may treat Authors and Providers as having a many-to-many relationship.

  • Authors can write for many providers
  • Providers can have many authors

Constraining Associations

The Blue Book suggests putting constraints on your associations in a boundary — so that the problem is easier to model.

When an author is publishing an article, it will always be the case that the author is associated with a single Provider. For example, in Content Studio, it is not the New York Times that publishes the article, rather, it is John Doe, publishing an article for the New York Times.

We reduced our relationship from many-to-many to one-to-one by introducing a constraint in the context of publishing articles.

Abstracting Associations

Photo by Mike Hindle on Unsplash

For a piece of machinery to work, the cogs need to be put in precise associations with one another. All of the cogs only have importance if they are seen in the context of the working machine. The cogs may only have importance individually when one is trying to find the right cog that the machine requires. The usefulness of something is always contextual.

The authors and providers are two distinct entities in the system. However, in isolation, they may not make the most sense in the context of publishing articles.

In Content Studio, an author cannot publish an article without being associated with a provider. On the contrary, the provider needs an author to publish an article for them.

Aggregates

Introducing aggregates — a cluster of entities tied together under a well-defined boundary with one entity as its root.

An authorForProvider object is an Aggregate where the author is the root entity. It has all the attributes and behaviors of the author. Additionally, it also now can Publish the article since it is now associated with a provider.

authorForProvider.Publish(article)

Reconstructing Domain Objects

As more and more aggregates spawn into the code, it becomes harder for the repositories to reconstruct these different entities into an aggregate.

A design pattern from the Blue Book — when writing our repositories to return domain objects from the database using Factories as a dependency to the repository.

Factories

Photo by xyzcharlize on Unsplash

When the creation of an object, or an entire aggregate becomes complicated or reveals too much of the internal structure, factories provide encapsulation — Eric Evans

Below is a simple implementation of the factory, where the factory takes in raw SQL results, and returns the entity based using ORMs.

Figure from the Domain-Driven Design book

Below is another diagram from the book where the Factory is used as a dependency of the Repository.

Lessons & Learnings

The blue book is a big complicated book, but we took a bite at a time and tried to apply the small building blocks in our codebase. We feel that the code is becoming easier to read through.

We are still in the very early stage of adopting the learnings and are still questioning the models that we have created, and that’s the thing that matters.

--

--