Domain Models in React

In September of 2020, I gave a talk about Domain Models to the Downtown ReactJS Meetup in Austin, TX. This post is a retelling of that presentation in blog format. You can also access the slides and the todo list demo application.

Every application has a domain model. It is the Magna Carta that allows the product, design, and engineering teams to communicate effectively. Despite this, many teams don’t take the time to document or maintain their domain model, which will make a product difficult to understand for newcomers. I would like to share a battle-tested method for building a consistent domain model into your front-end applications that is easy to maintain and easy to reason about.

What is the Domain Model?

In the simplest possible terms, a Domain Model is all of the nouns in a system and their relationships to each other. It is often represented by a class diagram, but other types of entity diagrams can be used as well.

The Domain Model for a Simple App
The Domain Model for a More Complex App

As long as your understanding of your industry, your customers, and your company grows and changes - your Domain Model should grow and change with it. It should be constantly iterated upon and refactored as you learn more and more about your space and add to your applications.

Domain Models should be iterated and refactored as understanding changes

Why is it important?

Essentially, the Domain Model is a link between the real world and the code. Every person, place, or thing relevant to your business is given a name that is used by every person at your company. Using that particular noun instantly carries with it a universally understood definition and context for the real-world thing that your business interacts with. This is powerful stuff!

Having this common vocabulary improves communication and efficiency across your organization. If your Product Manager or UX Designer tells you that changes are being considered for "Estimates" in your application, but they're called "Quotes" in your React application and "Sample Pricing" in your Node application - just imagine how much longer it is going to take to get those changes done - especially if a new employee is assigned to the project. By distilling a single set of nouns and using those nouns everywhere, you cut out the need for all of that tribal knowledge. That makes for easier onboarding of new employees, better maintainability, and better efficiency.

On top of that, a Domain Model represented by a diagram is both highly visual and easily understandable by both technical and non-technical people. Imagine being able to put a single image or file directory in front of a new hire that explains exactly what your application does and how it's organized.

How can it be implemented?

Let's go back to the Todo List App example that we've seen a few times now. How can we deeply embed the Domain Model for that application into the code? There is a method that I have used for three large scale production front-end applications that I would like to share. It consists of three simple principles that we'll walk through now.

The Domain Model for a Simple App

Principle # 1: Model Classes

Your application should have a `src/models` directory. Every noun/object in your domain model, in this example "List", "Item", and "Label" should be defined as a class in that directory. Defining each domain model object as a class does three things for us:

  1. Defines the shared name of the object in a concrete way - the name of a class
  2. Defines all of the properties of that object and provides a central place to keep them up to date
  3. Provides the ability to perform `instanceOf` checks for validation and standardization throughout your application

Here are the implementations for each of those three models in the demo application:

NOTE: It is also possible to achieve this same kind of functionality using a "type" or "interface" in TypeScript.

Principle # 2: Component Naming

Now that we have a JavaScript Class for each object in the domain model, we can go a step further. Those concrete nouns that we have defined should carry over into your components. A component that renders an "Item" that belongs to a "List"? That is a "ListItem"! Or, maybe it's a "ListItemCard". Any embellishments that you need to fully define what the component does are great, but at the very minimum, a component that represents one or more objects in your Domain Model (most should!) should contain the names of those objects.

Example: Components related to the "Item" model from the demo application

By following this principle, you'll add a ton of great context to your components and make their purposes much clearer for new employees, and your future self.

Principle # 3: Instance Checking

We mentioned this earlier as benefit 3 of Principle # 1. Essentially, since we have defined a class for each object (and we make sure to instantiate all of those objects in the application!), as we pass those objects around as props, we can perform `instanceOf` checks on them for prop validation. Gone are the days of differing "shape" checks. As long as the object is an instance of your model, you know that all of the properties will be there.

NOTE: If you're using TypeScript with "types" or "interfaces" instead of these Classes, you'll already have the same functionality built in.

Closing Thoughts

If you're starting a project from scratch, I highly recommend using this system from the very beginning. Beyond what we discussed here, there are many other great benefits that we will discuss in future posts (e.g. API definitions for the models).

BUT, if you're not starting from scratch there is always time to start building these concepts into your application. With each new project or sprint, create the model classes that are involved with the new work and use them. Rename the components that you touch to include the domain model nouns, and update them to do instance checking and use instantiated objects. Leave every file that you touch a little better than you found it, and over time your Domain Model will be fully integrated into your application.