Don’t Put All Your Models in One Dumpster
By: Boris Cherkasky
Tags: Domain Driven Design, Clean Code, Software Development, Software Engineering, Programming

Image by David Libeert on unsplash

Code readability and structure evolves. What was once a small, readable, neat codebase, within a few weeks can become a messy repository with many objects, where it’s hard to navigate the relationships between them

In the short article below, I’d like to share with you, one, very common pattern, that exists in many repositories. A Pattern, that if avoided can increase the readability and reduce the complexity of your codebase.

The “Model Dumpster”

The “Model Dumpster” is the simple, widely used, one, notoriously big and cluttered package called models.

You probably have one in your codebase too. It’s the one you need to scroll through for a very long time to navigate to that one class you’re looking for.

Let’s go through a made-up example to express the problem of such code structure.

A real-world example

Let’s say we’re working on the billing part of your application, and we’re assigned a new task:

Change our premium account’s service fee to be calculated weekly and not monthly.

Easy enough, but…let’s assume we don’t know anything about it, we’re quite new to the team, and we were mostly working on the analytics part of the system. We never even seen how the premium account looks like, and how the fees are calculated.

So, where do we begin? We do what programmers do 90% of their time — we start reading code. Obviously, we start by searching the repo for the nouns in the task’s description: we search for the PremiumAccount and get:

It’s obvious that our PremiumAccount model is surrounded by many other, irrelevant model classes. We have no context where it is being used and how. We now need to keep on digging and looking for usages until we find the code we need to change…

Going through the code from the basic building block- the model, to the business logic where it’s being used can be avoided in some cases. I consider it a code structure and packaging manifestation of non-information.

Non-information

Non Information can be summed up shortly:

not disclosing information available during code writing, with the future maintainer.

The best simple example of non-information is a simple function that copies the head of one Array to another (Ruby code):

def cp(a, b)

a << b.first

end

When I’ve written this function I knew that the first argument is the target array, and the second argument is the source array, but I’ve chosen not to disclose this information with the (possibly miserable) future maintainer.

This is non-information.

We can revise the function and avoid non-information with more indicative parameter names:

def cp(to, from)

to << from.first

end

Taking out the trash — Resolving the model dumpster

Back to our premium account example — non-information applies here too! When developing the original feature, I had the context and all moving parts of the Billing system and the premium account, but I’ve chosen to spread them across different packages and domains, rather than grouping the model of the feature I’m developing with its services, and Data access objects and creating a wholesome context for the future maintainer of all (or most) parts of the feature, within a single package

The Models package became such a standard in many codebases and frameworks that we sacrifice readability, context, and feature locality just to put models all in one place.

It sometimes does make sense, but in most others, it’s just a dumpster for unrelated files put all together for no real reason.

If I were to do it with the previous example, the package would have been something like:

Where when you get to your model, the business logic that uses it is nearby, and at sight.

Cohesion

Now, when we can see our original packaging and the one where models are packaged close to their usage site, we can finally see cohesion! Our packages have a common ground — not a bunch of unrelated models.

Bounded contexting

But what if our Accounts models are being used all over our system? Putting it in a specific package may cause wired dependencies, for example, A RecommendationService depending on billing.accounts . Should the RecommendationService really be dependent on Billing? Moreover, What if the BillingService were to be refactored to a completely separated service?

This is where Bounded context comes in handy — we can have multiple PremiumAccounts in our system — each having a different set of properties, relevant to different services in our system.

We can have a PremiumAccount in the context of the BillingService with fees and billing dates and details, while also having a PremiumAccount in the context of RecommendationService with settings for user preferences and social profiles.

Final thoughts and Frameworks

There are some very smart people who created frameworks and patterns for us to use, and make our lives awesome! Like Ruby On Rails, MVVM, MVC, and many others.

I always thought of it as an antipattern of sorts…It’s only in very rare cases that we’re working in the context of “all controllers” or “all models” and need to see them all in a single package. Most of the time we’re more feature-oriented in our work and need to see the full flow: Controller →Service → Model → DB, and the common package structure of those patterns and frameworks usually guide us to do the opposite.

This post was about explaining WHY I consider the “model dumpster” such an antipattern, and I hope I managed to convince you that packaging models together just because they are all models is meaningless, and actually reduces the readability and maintainability of your code.

As always, comments and responses are welcome @cherkaskyb