In this article, I’m sharing a few lessons about software design approach and patterns, which I learned from industry experts, teammates and personal experiences as we build enterprise applications across different platforms.
I’ve been in the Software Engineering industry for a decade and this is just one of the few times (and I hope I could do this more often from now on) that I get to write about one of my favorite topics. I hope you will learn from it too.
A little about how I started
I became an OOP (object-oriented programming) enthusiast years ago when I read an e-book from my former colleague about my favorite programming language: C#. A year before that, I was already using it without the knowledge of how powerful the language was. All I knew was a simple MVC pattern out of a scaffolded ASP.NET MVC 4 web application and a basic understanding of the syntax. I can even recall how clueless I was when resolving a simple NullReferenceException back then.
When I came across SOLID, and other principles as such, I knew I found my cup of tea. It was the answer to my unknowns as someone who had just began his programming journey. But like a kid full of excitement to play with his new toys, I’ve also had my fair share of over-engineering projects in the past – breaking things unintentionally and having a hard time fixing it. “You don’t call yourself a carpenter after using a hammer once”, they say.
Since then, I never looked back to making convoluted code again. On the other hand, I also try to make sure that what I’m writing is not too complex to comprehend. That’s essentially finding the balance between speed and quality (aka ‘the sweet spot’), which turned out to be the hardest part.
Fortunately for our team, we began paving the way to that goal by repeatedly using all the things we learned from each other’s experiences, and so far this has been effective for majority of us.
The pattern
Disclaimer: The following isn’t a silver bullet to every software problems. It just happens that this fits the solutions that we’re building, which are mostly mobile/web-base business applications and backend APIs.
Among others, we lean towards using the Vertical-sliced Clean Architecture (DDD) and CQRS.
DDD is an abbreviation for Domain-Driven Design. The name suggests that the center of the application lies in its specific domain. There’s a lot of resources about DDD on the web, but you can also read this blue book by Eric Evans to understand DDD deeply.
CQRS stands for Command and Query Responsibility Segregation. This is a pattern we use to separate read and write operations on the application level. There are also a lot of debates on how to implement this in different ways and even this topic alone is hefty enough for us to deep dive in this article so I’ll leave that up to you as an assignment.
Vertical slice simply means dividing the functionalities by features on top of the usual Clean Architecture layering. (See below image for reference)
The arrow signifies the dependency flow and the boxes represent each feature of the system. This also translates to how the folders/projects and files is structured in the codebase for ease of maintainability and readability.
The only exemption in this approach is how the infrastructure is managed and utilized. For instance, a single service is used across different features of the system and the most practical way is to declare it as a shared component rather than repeating it in each feature. This is generally acceptable as long as we keep the abstraction (i.e. dependency inversion) from the actual application logic (i.e. use-cases).
In layman’s term I’d like to use the analogy of a supplier – middleman – consumer relationship to this architecture. Think of each layer as one of these characters:
- Supplier – this is your system’s entry point: the user interface. It can be the actual application loaded from the end-user’s PC/laptop browser, mobile phones, or simply a backend endpoint. This carries the raw materials (i.e. requested data) passed to the middleman.
- Middleman – this handles your system’s use-cases or we can say pretty much orchestrates everything. As a middleman you don’t need to know where your supplier got those raw materials. Same thing as your consumer can use the product you sold them however they see fit. You just have to produce something that’s valuable for the system thus your responsibilities revolve around:
- Properly responding from consumer reviews whether they’re satisfied or have received a rejected product.
- Advertise the product to different business entities (i.e. external system) for wider reach thru your skillset (i.e. interfaces).
- Store the product earnings in your wallet, vault or bank (i.e. database).
- Consumer – this is your system’s core, your domain, which keeps it beating. Simply put suppliers and middlemen are useless without it. Consumers have the right to give whatever feedback they have for a certain product and decides whether something is valuable or not. It takes care of ensuring that the system works correctly as expected and by design.
Why do we care about this?
If you’re one of those people like me, who wants things as simple as possible most of the time, you may find yourself drifting away from this approach – you would think it’s a lot of work – and you’re partly right. However, it’s also not a secret that bringing in the hard work upfront gives meaningful returns overtime. It’s just like in fitness. The first time you go to the gym, the weight will seem too heavy in the beginning. By training constantly and using the principle of progressive overload, you’ll notice the strength you gain from that hard work. It’s the same with learning to play an instrument. It isn’t a walk in the park, but a lot of musicians started from being a novice until becoming a superstar who kept the world in harmony.
For us, by using this pattern and putting our efforts in building such architecture, our lives become easier. We can read and understand each others and our own code. It helped us spot bugs and fix it quickly. It keeps our team synchronized – Any one can work with someone else’s code which comes handy when one of our teammates is not around.
A sound design approach and pattern when building software products do not guarantee a perfect system (which doesn’t exist by the way). But do the advantages of using these patterns far outweigh its cons, such as the steep learning curve and additional complexity? I definitely say yes.
How about you? Tell us the way your team uses patterns and practices in building software and how it helped you achieve your product development goals. We’ll be happy to hear more about it.