Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
Software architecture is about making critical decisions that will impact how you can make decisions in the future. It’s about giving yourself options at a relatively low cost early on so your system can evolve without a high cost. Software architecture is about options.
YouTube
Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.
Cost of Options
Software should be malleable. It shouldn’t be so rigid that you can’t change it because of new requirements or insights about the domain or the model you’ve built. You want to be able to evolve your system over time as all these emerge. You don’t want to have to pay a high price (complete rewrite) because you’re system is hard to change. Giving yourself low-cost options means making decisions that allow you to evolve your system over time but don’t come at a high cost (time/effort/etc.)
This means you have to pay the price initially to give yourself these options, usually early on in a project/product. These are critical decisions in choosing which options are low-cost and high-value.
I’m not talking about “what if” scenarios. Developers tend to make assumptions, often related to technical and business requirements. I’m not referring to all kinds of edge cases or technical concerns like scaling, which developers love to focus on.
The options I’m talking about are fundamental to your architecture. How you develop the system allows you to evolve it over time.
Coupling & Cohesion
A lot of software design comes down to understanding and making decisions based on coupling & cohesion.
To me, coupling & cohesion are the yin-yangs of software design. They are a push & pull against each other. You’re trying to increase functional cohesion and lower coupling.
There are different forms of coupling, but to give a definition:
“degree of interdependence between software modules”
ISO/IEC/IEEE 24765:2010 Systems and software engineering — Vocabulary
And for cohesion:
“degree to which the elements inside a module belong together”
Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design
Why do coupling & cohesion matter? Because ultimately, a lot of the decisions made are rooted in one or both of them, even if you don’t realize it.
If you do understand coupling and cohesion, it can better help you make decisions that provide options.
The 3 concrete examples I will provide in this post are all rooted in coupling & cohesion.
For more on coupling & cohesion, check out my post: SOLID? Nope, just Coupling and Cohesion
Logical Boundaries
The first way to give yourself options within your architecture is to define logical boundaries. Grouping related behaviors and functionality (capabilities) of what your system provides. Having groupings of capabilities that are functionality cohesive.
Not having a system that is free for all of functionality without any boundaries.
A piece of functionality shouldn’t be intertwined with other unrelated functionality. In other words, the dependencies for one piece of functionality shouldn’t affect another. An example of this is a database. A set/grouping of features should own and be responsible for the underlying data for that feature set.
Define logical boundaries where you’re grouping functionality that works together on a set of underlying data. Focus on the capabilities and behaviors of your system. Group those capabilities into logical boundaries.
Within a logical boundary, you can make decisions that are isolated within it. How should you perform data access? Which type of database is best for the data set of that logical boundary? How should we model within the given context? Different boundaries will have different models. By defining logical boundaries (cohesion), you can make all kinds of decisions that are best for the feature set within that boundary. This gives you options.
Boundaries are one of the hardest things to define correctly, yet the most important things to do. Check out a whole series and talk: Context is King: Finding Service Boundaries
Loose Coupling
If you’re defining boundaries, how do you communicate between them?
In a free for all, there is coupling everywhere. You have different parts of the system that are directly coupled to other parts of the system. This could be coupling between classes/modules or, generally, at it’s worse, via the database.
I often refer to this as a turd pile. But it’s just a system that’s lost control of coupling.
If you’ve defined logical boundaries, as explained earlier, you’ll likely need to communicate between them. Any system will have long-running business processes and workflows that span many logical boundaries.
To remove tight coupling, we can leverage asynchronous messaging. Removing direct communication between boundaries means we are also removing temporal coupling. In other words, you’re not bound by time.
This means that one boundary can send a message to a queue for another and can be processed independently.
Because you have logical boundaries (cohesion), this works best with long-running business processes or workflows. So often, we model our workflows as being synchronous requests/responses when in reality, we could be building a much more resilient system by making them asynchronous.
Asynchronous messaging and event-driven architectures give you options by loose coupling! Check out my Real-World Event Driven Architecture! 4 Practical Examples
CQRS
Unfortunately, CQRS is a buzzword (acronym) that is widely misunderstood.
Command Query Responsibility Segregation is often conflated with Event Sourcing, Asynchronous Communication, Domain Driven Design, Multiple Databases, and more. If you search and read enough posts, you’re bound to find a similar diagram.
Sadly, while this is CQRS, as mentioned, it’s also conflating a bunch of other patterns or concepts. CQRS is nothing more than separating reads and writes even from a service layer.
Yes, really. It’s that simple. Still don’t believe me? Check out my post CQRS Myths: 3 Most Common Misconceptions, where I reference many of the early blog posts from Greg Young.
So why is it important? Because it gives you options. Defining separate paths for reads and writes allows you to make decisions for each path and each occurrence.
If you look at the first diagram illustrates a Command Bus, a Domain model, Event Sourcing, and a projection (multiple databases). All of that is facilitated by the decision to separate commands and queries.
CQRS is a gateway to other patterns and concepts, but at its core, it’s pretty trivial but it gives you options!
What is Software Architecture?
To me, software architecture is about critical decisions, usually early on within a product/project, that give you future options. Options that allow you to evolve your system over time. The cost is usually relatively low when making decisions and giving yourself options at the very beginning. Defining logical boundaries, loose coupling between boundaries with a message/event-driven architecture, and CQRS.
As always, it’s rooted in coupling and cohesion.
Join!
Developer-level members of my YouTube channel or Patreon get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.