BLOG/Before You Start Writing Code, Model Your Domain First
15 April 2026engineering

Before You Start Writing Code, Model Your Domain First

BY Afeez Lawal
Before You Start Writing Code, Model Your Domain First

The first thing the new developer noticed was how clean everything was.

90% test coverage. A linter so strict it rejected trailing whitespace. A CI/CD pipeline with six stages. Commit messages written like journal entries, descriptive, intentional, referencing ticket numbers.

Two weeks in, she asked a simple question during a code review: “What exactly is an Order in this system?”

The room went quiet.

One engineer said it was what gets created when a customer checks out. Another said no, it gets created earlier, when items are added to the cart. A third said that wasn’t an Order, that was a Draft. Someone else said Draft didn’t exist in the codebase, it was just an Order with a null confirmed_at field.

The model had 34 attributes. A 400-line service file. Tests for all of it.

Nobody could agree on what it represented.

The tests were passing. The software was wrong.

The Problem Isn’t Your Code Quality

This is the part that’s hard to accept: you can write clean, well-tested, properly formatted code and still build the wrong thing.

Most developers, when they encounter a messy code base, reach for the usual suspects. More tests. Better abstractions. A refactor. A rewrite. A new framework.

What they rarely do is stop and ask the harder question: does our code accurately represent how this business actually works?

That question is the foundation of Domain-Driven Design. Not the patterns, the Repositories, Aggregates, Value Objects, those come later. The foundation is the discipline of understanding your business domain deeply before you write a single class.

If you can’t explain your core concepts clearly in plain language, your code will eventually betray that confusion. Every time.

The Language Problem

Here’s a diagnostic you can run on any code base right now.

Pick a core concept; Order, User, Payment, Booking, and ask five people on your team to define it. Not in code. In plain language.

If you get five different answers, you have a domain modeling problem. No amount of test coverage will fix it.

This is what DDD practitioners call the Ubiquitous Language, a shared vocabulary that the entire team, including non-technical stakeholders, uses consistently. The same words in conversations, documentation, and code.

It sounds deceptively simple. It’s remarkably rare.

Most code bases have a split personality. Product managers talk about “subscriptions.” Designers say “plans.” Developers have a UserAccount model with a tier field and a BillingCycle table loosely joined to it. The same concept has three names and no single owner.

That mismatch doesn’t stay in Slack. It leaks into the code. It becomes the 34-attribute model nobody can explain.

What Modeling Actually Looks Like

You don’t need to read Eric Evans’ Domain-Driven Design to start modeling better. You need two things: the right conversations and the discipline to let them change your code.

Start with your most contested concept. The one that’s been refactored the most. The one where bugs keep appearing. Take it out of the code base entirely and put it in a whiteboard conversation.

Ask: what does this thing do? What can it become? What are the rules around it? What transitions does it go through?

For that Order problem, the conversation might reveal something like:

A customer starts a Cart, When they confirm intent to buy, it becomes a Checkout. When payment clears, it becomes an Order. If it’s fulfilled, it becomes a Shipment.

Those aren’t the same concept at different stages. They’re four distinct domain objects with different responsibilities, different rules, and different lifetimes. Cramming them into one Order model with status flags doesn’t simplify things, it hides the real complexity behind a single God object.

Separate them. Name them precisely. Give each one only the attributes and behaviors that belong to it.

Your code will be longer. It will also be honest.

Bounded Contexts: You Can’t Model Everything Together

Here’s the other thing nobody tells you about domain modeling.

There’s no single correct model for your entire system. The concept of “Customer” in your billing module is not the same “Customer” in your support module. Billing cares about payment methods, invoice history, and tax information. Support cares about tickets, satisfaction scores, and communication preferences.

Forcing one Customer model to serve both is how you end up with a model that serves neither well.

DDD calls these Bounded Contexts, explicit boundaries within which a model is defined and applicable. Inside billing, Customer means one thing. Inside support, it means another. Both are correct within their context.

This is design clarity, not duplication. The boundary is the feature, not the problem.

Where To Start

You don’t need to refactor your entire code base. You need to start having better conversations before you open your editor.

Before writing the next feature, do three things:

  • Define it in plain language first: Write one paragraph explaining what this feature does using words your non-technical stakeholders would recognize. If you can’t, the concept isn’t clear enough to code yet.

  • Find the boundaries: What are the rules? What can change and what can’t? When does this thing transition from one state to another? State transitions are where domain logic lives, they’re also where the most bugs hide.

  • Challenge your names: If your model is called UserAccount, ask whether “user” and “account” are actually the same concept in your domain. Often they aren’t. Users are people. Accounts are billing and access constructs. They have different lifecycles and different owners. Name them separately.

The code follows naturally once the thinking is clear. That’s the point.

A well-modeled domain doesn’t just produce cleaner code. It produces code that your entire team, engineers, product managers, support staff, can reason about together. When everyone uses the same language, bugs stop hiding in translation.

Your next feature isn’t a code problem yet. It’s a thinking problem first.

Solve it in the right order.