In the book we are at pains to point out that each pattern is a trade-off, and comes with costs. But just on the offchance that anyone was still missing the message and thinking we were saying that all apps should be built like this, I thought I’d write a small blog post just to reinforce the message about costs. If you’ve been feeling tempted to cargo-cult every single pattern into every single app from now on, this should put you off.
Each time you add a layer, you buy yourself some decoupling, but it comes at the cost of an extra moving part. In the simplest terms, there’s an extra file you have to maintain.
So. Once upon a time, early in my time at MADE, I remember having to make a simple change to an app that the buying team uses. We needed to record an extra piece of information for each shipment, an optional “delay” field to be used in some ETA calculations. This is a nice illustration of a trip all the way through the stack, because things have to change all the way from the frontend/UI, all the way down to the database.
If you’re using a framework like Django, you might be used to thinking of a
change like this, in a perfect world, as a change you can make to just one file.
You would change
models.py, and then your
ModelForm will be updated automatically,
and maybe even the frontend will “just work” too, if you’re using the form’s
autogenerated HTML. That’s one of the reasons that Django is so good as a
rapid application development framework: by closely coupling its various parts,
it saves you a lot of messing about with database tables, html forms,
validation, and so on. And if those are the main things you spend your time on,
then Django is going to save you a lot of time.
But in our world (at least in theory *), database tables and html forms are not where we spend our time. Instead, we want to optimise for capturing and understand business logic, and as a result we want to decouple things.
What does it cost? Well, let’s take a trip through each file I had to touch, when I was making my very minor change to the data model in our app.
- An end-to-end / API test for the create and edit use cases for the objects in question.
- The Command classes that capture those write interactions a user can have with this model.
- The Command schema which we use to validate incoming requests.
- The Service-Layer tests which instantiates those commands to test their handlers
- The Handlers at the Service Layer that orchestrate these use cases.
- The Domain Model tests that were affected. Although not every domain model needs low-level unit tests as well as service-layer tests, so if I was being indulgent I might not count this. But we did happen to have a few low-level tests in this case.
- The Domain Model itself.
- The Repository integration test (repo and DB stuff is in chapter 3)
- The Repository and ORM config
- The database schema
- A migration file (admittedly autogenerated by Alembic, but we like to just give them a bit of a tidy-up before committing).
- The Event classes that capture ongoing internal / external consequences of the various affected use cases
- The Event schema files we use for (outbound) validation.
- And that’s not all! Because this app uses CQRS, the read-side is separate from the write side, so I also had to change some API JSON view tests
- And the CQRS JSON views code
So that’s fifteen files. Fifteen! To add one field!
Now I should add that each change was very simple. Most were a matter of copy-pasting a line and some find+replace. The whole job might have taken an hour or so. But if you’re used to this sort of thing taking five minutes and happening in a single file, or at most a couple, then when first confronted with all these layers, you are definitely going to start questioning the sanity of the entire endeavour. I know I certainly did.
We think the cost we impose on ourselves here is worth it, because we believe that the main thing we want to make easy is not adding database fields and html forms. We want to make it easy to capture complex and evolving business requirements in a domain model. But, as we try to say in each chapter, your mileage may vary!