How Domain-Driven Design (DDD) Can Help Your App

What's DDD?

The ELI5 Version

To begin our journey into Domain-Driven Design (DDD): imagine an RC car. If it's built well, it has parts that you can swap in and out as needed in order to maintain it. More importantly, it has parts that people use a common vocabulary to describe:

  • If something goes wrong with a wheel, you replace the wheel, you don't replace the whole RC car.
  • If the battery dies, you swap in a new battery to power it. It's plug-and-play. You don't have to get out a soldering iron, remove solder connections on a bunch of random wires, and then solder a new power source into the wiring of the RC car to get it running again.
  • If the suspension breaks, you can replace it with a new spring or part.

Wouldn't our lives as software development teams be more simple if we had swappable parts like this in our applications? Wouldn't our lives be easier if everyone knew exactly what a wheel, a battery, and a suspension were and we could swap them out whenever we wanted to?

In a nutshell, this is the goal of Domain-Driven Design: ubiquitous language that a team uses to describe a specific bounded context (in this case an RC car) that describes a modular architecture where nothing is hardwired together.

Hopefully the pitfall of DDD begins to come into focus at this point: overengineering. We'll get to that later.

Let's jump in headfirst now and put ourselves in a real enterprise situation.

Enterprise DDD

Or how to tame a monster

You're three months into your new role as the lead Front-End developer on the Orders team at Fab Burger, and things are going pretty great. You have a couple hundred thousand users each day who order their burgers, sides, etc. through your UI across the country when suddenly you see an alarming post on X:

"We're pleased to announce that Google and Meta have merged to form an exciting new company: Quark. All React and Angular support will reach end-of-life (EOL) in three weeks, and we'll be replacing it with our awesome new framework: Boson! Stay tuned for more exciting news on Boson! It's going to change everything in the UI space. You'll love it. Trust us." - a fake X post

You can hear your heartbeat in your ears and your palms are sweating. Can you still go on PTO for a week next week, or will you have to cancel your family trip to make a new production deadline? This is going to take a lot of work to get off of your current UI framework and onto Boson before React and Angular both reach EOL.

"JOHNSON!"

You hear the voice of your CTO ricochet off the glass of the exterior windows on the third floor that overlook the duck pond. It is a nice day outside. Maybe he didn't see you and you can keep pretending to look at the ducks.

"HAVE YOU FINISHED THOSE ESTIMATES FOR THE PICKLER BUILD YET? WE NEED TO RELEASE BY THE END OF THE QUARTER OR OUR STAKEHOLDERS ARE GOING TO RIOT! IT'S GONNA BE A HUGE REVENUE BOOST FOR US SINCE WE CAN CHARGE $0.50 A PICKLE ON ALL OUR BURGERS ONCE WE RELEASE! "

Nope he definitely saw you. Depending on the state of your project, you're probably thinking something in-between one of these two engineering limits:

  • 1) The Pessimist: I have no idea how we're going to get all of this work done in time. And leadership couldn't care less about this Boson thing so now we're really in trouble because we have to do both of them.
  • 2) The Realist: If we focus and don't allow a ton of scope creep we can probably get all of this work done in time. But leadership couldn't care less about this Boson thing so now it's going to be a close one because we have to do both of them.

In software engineering, there are no optimists. They all perish in the first annual review.

Luckily for you, your team has been building with DDD in mind, so your app structure looks like this:

A Hexagonal DDD UI Architecture

You then realize you can probably meet your deadlines by doing the following:

  • Step 1: Migrate to Boson
    • Rip out the OrderView in React. Rewrite it in Boson.
    • And . . . that's it for Step 1. You're done with the Boson migration.
  • Now for Step 2:
    • Add a new PicklerComponent that emits a new event up to the new Boson OrderView. Write a unit test that tests the new updateNumPicklesEvent emitter on the PicklerComponent in the OrderView
    • Have the OrderView call the OrderViewService's updateNumPickles() method, which is written in plain JS. Write a unit test that makes sure the OrderView calls OrderViewService.updateNumPickles() when it's supposed to.
    • From there, handle all the business-logic processing, etc. that needs to happen in your Core Domain. Unit test that.
    • Update the Store via the StateService from the Core with the new numPickles. Unit test the StateService's updateNumPickles() method and makes sure it calls ReduxStore.dispatch('updateNumPickles', numPickles)
    • Once the Store is updated, the Core calls out to the API using Axios via the InfrastructureService in order to persist the order update to the database. Unit test that InfrastructureService.updateOrder() method too. And make sure that the InfrastructureService.updateOrder() method actually calls axios.patch() on the right URL with the right payload.

Wowzers! Ripping out and rewriting your entire UI layer took less time than it did to write your Pickler project logic! And here we see one of the biggest trade-offs of strict DDD:

The Trade-Off: it takes longer to change anything in your app, but when you do change things over time the potential blast radius that bugs can trigger is kept very small.

Assuming that you converted over some simple EventEmitters into the appropriate Boson handlers (hack! - cough! - sorry I meant "converted over some simple setState methods that you passed down into your child component as props". For a second there I walked into Angular territory . . . ), and you got your styling right, you don't really have a ton to rewrite in the UI port itself. You have some basic display logic, and that's it. Everything else gets handled by the Core Domain Service. Massaging the data into the right form for the UI happens in the OrderViewService in plain JS before it ever gets to the view. In this way, your core business logic stays evergreen because it's just vanilla JavaScript. A critical library suddenly going unmaintained will no longer kill your PTO requests or keep you up at night, because your core business logic for the UI is bulletproof.

You later learn that Fab Burger started 15 years ago and was originally written in JQuery. You are glad that JQuery could be removed in 2013 without putting the business at risk in the same way that you just removed React. The OrderApp has been the heart and soul of Fab Burger for a long time, and it will continue to be for the foreseeable future. Because of this, large enterprise companies may be willing to make the trade-off of "changes always take a while" for "less risky maintenance" in return.

Another way to put it is this:

The longer your app is going to serve a critical business function in production, the more important strict DDD and avoiding core third-party package dependencies becomes for long-term health and sane maintenance.

You'll create a monster. But you can at least change its battery without ripping out its motherboard in the process and watching the whole thing go up in flames.

Next time, we'll explore how DDD can hurt your app. Stay tuned. You'll love it! It's going to change everything! Promise!