Domain Modeling for Building UIs

A winning strategy for overcoming the fear of staring at a blank canvas.

Issue 25 /

Hey friends 👋 I hope you had a fantastic weekend. Mine was really good—we celebrated my daughter's fifth birthday yesterday with a Bluey-themed party at the local playground and we had a lot of fun. We also now have a ton of leftover cake, cupcakes, and pizza, so the next week is guaranteed to be a massive success (not for my health, though.)

In today's issue, we'll chat about planning new projects with domain modeling, how React bridges the gap between client and server-side development, the importance of fundamentals, and much more.

Let's jump in!

SOFTWARE DESIGN

Domain Modeling for Building UIs

Photo by Christina Morillo on Pexels.com

Ahh, nothing like the smell of a fresh new project in the morning. Excitement is in the air and a world of opportunities lies ahead. No tech debt or legacy code to worry about, only clean code and smooth sailing to success.

There’s just one problem—how do we know where to start?

Starting anything from scratch can feel paralyzing sometimes, doesn’t it? But don’t worry, because today I’ll share with you my winning strategy for overcoming the fear of staring at a blank canvas. It’s only three steps:

  1. Panic,
  2. Try to stop panicking but usually end up crying instead, and
  3. Do some domain modeling.

I can’t say the first two steps help too much, but the last one certainly does, so today I wanted to chat a bit about that.

Now, I can see some of you rolling your eyes at the mention of domain modeling. “Here we go again with the weird stuff,” I can hear you say, “Isn’t this something only backend developers and database people care about?”

But hang on with me for a few minutes. By the end of today’s newsletter I hope to show you that domain modeling isn’t just a useful tool for frontend developers but, perhaps more importantly, that it’s an excellent use of your time.

Entities and Relationships

At its core, domain modeling is about describing the entities of a system and the relationships between them. It might not be immediately obvious what I mean by that, so let’s look at an example.

Imagine we’re building a food delivery application like Uber Eats. Here’s a short description of what the app does:

Uber Eats is a food delivery service where customers can search for nearby restaurants, browse a restaurant’s menu items, add items to their shopping cart, and purchase food by creating and paying for an order. Customers can also add restaurants to their favorites, apply discount codes, and leave customer reviews after making an order.

Certain nouns in this description, like customer and restaurant, are the main entities of our system. And verbs like adding, paying, and applying describe the actions users of the system can take and the relationship between our entities.

Our app's description with its entities (in orange) and relationships (in purple) highlighted

It’s useful sometimes to see a visual representation of our domain, which we can do with a simple, very high-level diagram of our main entities and relationships.

Some of our entities and relationships in diagram form

We haven’t done too much domain modeling at this point yet, but we’ve already accomplished two very important things:

  1. We’ve identified the foundational pieces of our system. The entities and relationships of our system are the things that are least likely to change over time. We’re always going to have customers, restaurants, and food items in our app, so we can safely build the foundation of our codebase around them (folder structure, packages, modules, etc.) without fear of things changing in a drastic way.
  2. We now have a shared vocabulary to talk about the main entities in our system. This is super helpful when dealing with one of the hardest problems in software development—naming things. We can keep our naming consistent by making sure that we always refer to customers as customers (not as users or members), and we can use the names of our entities throughout the codebase with the certainty that everyone will know what we’re talking about.

These are pretty cool benefits for something that only took us a few minutes to figure out, but we’re only scratching the surface of our domain at this point.

The real value of domain modeling comes when we dive a bit deeper into each of our entities to figure out what exactly they’re made of.

Diving Deeper: Attributes and Operations

When we zoom into each entity in our domain, we’ll find that they’re made of essentially two things: attributes (e.g. ID, name, address, etc.) and operations, which are the actions the entities can take.

Developers working at different levels of the stack will reach this step with different objectives in mind. A database developer will be looking to define a database schema, so they’ll be more interested in data types and multiplicity. An API developer, on the other hand, will be more interested in defining the response format of the endpoints they’re working on.

As frontend developers, we’re typically looking to build the user interface of the application, which is why a great place to start looking for the attributes and operations of our various entities is in the UI specs themselves—with a little help from API specs where we can find “invisible” attributes such as IDs.

Back to our Uber Eats example, if we were looking to define the attributes of our Restaurant entity, we could start by looking at the UI spec of the restaurant page:

Looking for attributes and operations on our app's UI spec

We can see that the Restaurant entity has some primitive attributes such as name, logo, address and so on, as well as attributes that represent relationships with other entities. For example, it has a list of Restaurant Categories, as well as a list of Menu Items, which themselves belong to one or more Menu Item Categories.

We also have some operations like “adding a restaurant to favorites”, “adding an item to the shopping cart”, “searching for food”, etc.

I like to create my initial list of attributes and operations in plain text or markdown (here's an example). But if you want a visual representation of your domain, you can put one together fairly quickly using a UML Class Diagram.

(🔥 Hot tip: if you have a list of entities and attributes, you can give it to ChatGPT and ask it to generate a class diagram using mermaid syntax, which would give you something like the diagram below.)

A class diagram with the main entities in our domain (you might want to open it in a new tab to actually read it)

You might be wondering why aren’t we just using the API response to define the attributes of our entities. Wouldn’t that save us a bunch of time?

That’s true if the API was designed with the UI’s needs in mind, but that isn’t always the case.

For instance, if the API was built using the popular JSON:API spec, then modeling your frontend entities following the API response would result in unnecessarily nested data structures—like restaurant.attributes.name, which could have easily just been restaurant.name. Here’s a great video that explains this point in more detail.

You might also be wondering what is the actual value of all the work we just did. After all, domain modeling takes time, so wouldn’t that time be better spent coding instead?

Well, it might seem like we’re wasting precious coding time on the surface, but there are a few ways in which doing a bit of domain modeling upfront can save us a lot of time in the long run.

For starters, if we’re using TypeScript or an Object-Oriented framework, we’ve already done the work of figuring out the data type of our most important entities—which is something we would have had to do anyway.

Also, as we mentioned earlier, domain modeling can help us name things in a more consistent way. For example, take the Restaurant entity, which has two types of categories: the Restaurant Category (Mexican, Sushi, Fast-Food, etc.) and the Menu Item Category (breakfast items, tacos, burritos, drinks, etc.) Had we jumped directly to the code and implemented this from top to bottom, we would have probably named the first one we encountered simply “Category” which, as we now know thanks to our model, is a bit of an ambiguous name that should probably be more specific.

But the more significant benefit of this whole exercise, if you ask me, is that it allows us to have interesting conversations about the different ways in which we could translate our model into actual code.

For instance, while putting together this example model, I had to think about whether the “add item to cart” operation belongs to the menu item, the shopping cart, or the customer entity.

Exploring API options: which entity should be responsible for adding an item to the cart?

At first glance, it might seem that either option would work just fine, but if we look closer into the project requirements (or the actual app in this case) we’ll find that customers can actually have multiple shopping carts—one for each restaurant they’re shopping at.

Aha! This changes things, because now we know there’s going to be some logic around creating a new shopping cart the first time customers add an item from a new restaurant, as well as figuring out which cart an item should go to. This logic has to live somewhere, so who should be responsible for it? Maybe this operation belongs to the customer entity, or perhaps the restaurant itself?

Regardless of the actual answer, having these conversations with your team (or with your rubber duck) at this stage rather than later in the development cycle can save a lot of time, sweat, and tears. Changing our model at this point would only take a few minutes—but changing the actual implementation could take hours or days.

Of course, this doesn’t mean that we have to come up with the most complete model and make all decisions at this point. Spending too much time on domain modeling can lead to analysis paralysis and over-engineering, which is arguably worse than not doing it at all.

So my final advice when it comes to domain modeling for building UIs is to try to keep things light and agile. Don’t think of your model as something that is set in stone but as a starting point for building the foundation of your project.

Your model will likely change after its first encounter with reality, and that’s OK. At least now you’ll have an idea of how the different entities and relationships in your domain are affected by that change, so you can go back to the drawing board and adjust your model accordingly.

And this time, you can jump directly to point #3 in my planning strategy—and skip all the panicking and crying steps.

ARCHITECTURE SNACKS

Links Worth Checking Out

Mind the Gap by Ryan Florence

đź“• READ

  1. Dave Stewart wrote a great article about modular site architecture with Nuxt 3 using one of the framework's latest features—Layers.
  2. Veteran software architect Mark Seemann shared some insightful advice on how to keep our skillset relevant by focusing on fundamentals.
  3. React 19 almost made SPAs a lot slower by changing the behavior of Suspense boundaries in its latest version. Thankfully, "almost" is the key word in that sentence.
  4. Speaking of React, Lazar Nikolov wrote a deep-dive on the Forensics of React Server Components, with a strong focus on how this new model affects page-load performance.

​

🎥 WATCH

​Mind the Gap by Ryan Florence

A lot has been said already about this talk by Ryan Florence at Big Sky Dev Con earlier this month, but I think it’s one of those talks that’s worth watching more than once. The central point in the talk is the tradeoff between the technical cost and the benefit of adding features to a product. Ryan says that the common goal of framework and library authors is to reduce technical cost to zero, so that, as he very well puts it, “only product decisions matter.” But the most interesting aspect of the talk to me is how Ryan demonstrates the way in which React is reducing the technical cost of client-server communication by making it seem like our app’s client and server components are essentially the same thing. Also, Ryan is a hilarious speaker, so this talk is totally worth watching even if it's just for the puns.

​

🎧 LISTEN

​How We Built a Netflix Style “Save for Offline” Feature Into Syntax​

Did you know that you can use the browser’s Cache API to save data on user’s devices (including audio and video) for offline use? I didn’t either! In this short episode of the Syntax podcast, Scott Tolinski and Wes Bos talk about the many different ways to store data in the browser, including the File System API, IndexedDB, and, as I hinted earlier, the Cache API. There’s a video version of the episode on YouTube if you prefer a more visual experience, but the episode works great on audio too.

That’s all for today, friends! Thank you for making it all the way to the end. If you enjoyed the newsletter, it would mean the world to me if you’d share it with your friends and coworkers. (And if you didn't enjoy it, why not share it with an enemy?)

Did someone forward this to you? First of all, tell them how awesome they are, and then consider subscribing to the newsletter to get the next issue right in your inbox.

I read and reply to all of your comments. Feel free to reach out on Twitter or reply to this email directly with any feedback or questions.

Have a great week đź‘‹

– Maxi

Is frontend architecture your cup of tea? 🍵

Level up your skills with Frontend at Scale—your friendly software design and architecture newsletter. Subscribe to get the next issue right in your inbox.

    “Maxi's newsletter is a treasure trove of wisdom for software engineers with a growth mindset. Packed with valuable insights on software design and curated resources that keep you at the forefront of the industry, it's simply a must-read. Elevate your game, one issue at a time.”

    Addy Osmani
    Addy Osmani
    Engineering Lead, Google Chrome