My CRUD is better than yours - DDD and programming best practices
#domain-driven-design
#software-architecture
#opinion
Before anything else, forgive me for the clickbait title. I’ll do it again.
After some time building software and with the experience I’ll share in this post, I came to believe there can’t be absolute best practices in a context full of trade-offs, like software development.
This post is about what led me to that belief and the benefits it brought me as a developer.
Task: build a simple financial application
My epiphany started with a programming challenge I joined recently, whose task was to build a financial application with the following minimum requirements: CRUD for bank accounts and financial operations between them.
The prompt was truly open-ended. The use of some libraries and tools was mandatory, but what you built on top of the core idea of a financial application was up to you.
My approach to the problem, however, was conservative, with the only addition to my delivery being a simple authentication system to identify bank account owners. Still, I wanted to use the challenge as an opportunity to apply what I was studying at the time: Domain-Driven Design (DDD).
Even so, I was conservative. I know rigorously applying every DDD practice is costly, so I settled for applying it more strategically, defining a ubiquitous language, bounded contexts (implemented as modules), and a rich domain, but without complex aggregates, domain events in the code, or domain services.
The absence of some practices was also due to the application’s simplicity. DDD isn’t really applicable or a differentiator for such low complexity. The domain, in this case, was very simple.
The result was a well-structured, robust, and decoupled back end, with a well-defined domain, independent modules at the domain level (bounded contexts), and testable code.
Avoid unnecessary complexity, or suffer the bureaucracy you create
The problem starts when I actually begin to code.
It was necessary to build the authentication module and the bank accounts module to realize that the code I had written, even though it followed the so-called “best practices”, was exhausting me.
The truth is that applying all best practices, in all possible instances, generates complexity, which in turn generates bureaucracy, which I had to deal with to finish the back end with the transactions module.
Premature optimization is the root of all evil
That phrase, credited to Donald Knuth, comes from another context, but it makes perfect sense here too.
By trying to design software optimized from a design and architecture standpoint, I spent a lot of time thinking about things irrelevant to delivering the challenge, which had a tight deadline.
Simple is better than complex
The Python folks are right on this one.
That line, which is part of the Zen of Python, aligns with software design principles like KISS (Keep It Simple, Stupid) and YAGNI (You ain’t gonna need it), and I believe it should be every developer’s mantra.
If my only goal had been to win the challenge, I shouldn’t have focused on practices that are so hard to implement, even in such a simple application.
And, despite the fact that applying DDD was my intention from the start, even if in a simplified way, the friction with the bureaucracy that I myself created showed me that adhering to best practices isn’t always the best choice.
My CRUD is NOT better than yours
That’s the truth. Especially when it comes to CRUD (programmer laugh).

The user doesn’t care about the patterns you applied in your code or how decoupled your system is. As long as your system behaves the way the user expects, you or your company will be in the green.
Good code is code that compiles
Hold on, not exactly.
It’s not that you shouldn’t apply as many best practices as possible just because the alternative is writing unreadable code that doesn’t scale or can’t be maintained.
The user cares about your code if it doesn’t scale, but indirectly.
Badly written code shows up to the user as:
- Features delayed
- Low responsiveness or latency
- Little or no reliability
- Fragility
- Other annoying stuff
But sometimes all you need is code that compiles, for example: when it’s a proof of concept, in an agile context (with caveats), or in continuous delivery.
Trade-offs, trade-offs, trade-offs

As a developer and aspiring software architect, this is the most important lesson I’ve learned so far. And it doesn’t apply only to code, but to business and even life.
If you, as a developer, are worried about AI taking your job, don’t panic if you do what only a human can do: make decisions (whether they’re good or bad).
Architecture is what you can’t google
- Mark Richards, in Fundamentals of Software Architecture
And I add: it’s what you can’t outsource to AI.
That’s because making an architectural decision involves knowing a context, or multiple contexts, that don’t fit in a prompt.
Also, coming back to the post’s topic, to analyze trade-offs you need to look for the least bad alternative for the scenarios your software, your company, and your customers are in. The best alternative simply doesn’t exist, or, if it does, it can barely be found.
There is no right or wrong answer in architecture, only trade-offs
- Neal Ford, in Fundamentals of Software Architecture
All decisions have positive and negative points tied to them; we need to know both and make a decision, maximizing the positives and minimizing the negatives.
And that’s why in the end, when it comes to trade-offs, “it depends” is usually the only correct answer.
Free yourself from the chains of software purism
Absorbing these ideas I shared in this post was essential for me.
As I said, it was an epiphany I had while participating in a programming challenge.
I finished in second place in the challenge, which taught me more about what I talked about in this post, for example how to speed up development using tools based on language models.
But these lessons will stay with me forever.
Software purism is that: focusing too much on supposed best practices instead of focusing on solving the real problems in front of you. It’s creating unnecessary complexity that quickly becomes a problem you created yourself.
I’ve been two weeks clean from software purism.
Final thoughts
Before delivery, I rebuilt the application’s back end in order to get rid of unnecessary bureaucracy, and the result left me very satisfied.