Why microservices - part 4: the last third of the success triangle: architectural requirements for rapid, frequent, reliable and sustainable development
success triangleThis is part 4 in a series of blog posts about why and when to use the microservice architecture. Other posts in the series are:
- Part 1 - the need to deliver software rapidly, frequently, and reliably
- Part 2 - the need for sustainable development
- Part 3 - two thirds of the success triangle - process and organization
- Part 5 - the monolithic architecture
In this post, I discuss the final third of the software development success triangle: architecture. Let’s start by looking the day to day life of a developer in a high-performance IT organization.
The developer experience in a high-performance IT organization
The research by the authors of Accelerate found that developers in high performing IT organizations can:
- “… make large scale changes to the design of the system without having to depend on other teams to make changes or without creating work for those other teams …”
- ”.. complete their work without communicating and coordinating with people outside their team… ”
- “… do most of our testing without requiring an integrated environment …”
- “… deploy or release our application independently of other applications/services it depends on …”
Architectural requirements for rapid, frequent and reliable software development
If you analyze these aspects of the developer experience, you will discover that a high-performing IT organization needs an architecture with the following quality attributes:
- Loosely coupled and modular
- Testable
- Deployable
Let’s look at each of these attributes in turn and see why they are required.
Loosely coupled and modular
Consider the first two aspects of the developer experience:
- “… make large scale changes to the design of the system without having to depend on other teams to make changes or without creating work for those other teams …”
- ”.. complete their work without communicating and coordinating with people outside their team… ”
On the one hand, having a cross-functional team eliminates some types of coordination. For example, developers no longer have to coordinate with QA or operations. But on the other hand, Conway’s law states that an architecture mirrors the organization that creates it. A loosely coupled organization requires a loosely coupled, modular architecture.
A developer must be able to develop and test their module without regularly requiring other modules owned by other teams to be changed in lock step. A module’s interface should be designed so that it encapsulates (i.e. hides) the module’s implementation. That’s because, decisions that are hidden behind the interface can be changed without affecting other modules. Of course, not all changes can be hidden but it’s a design smell if a module’s interface is unstable.
Moreover, developers should use principles such as DRY/SOCs and the Common Closure Principle to design modules so that they do not share concepts. For example, let’s imagine that a business rule was implemented by multiple different modules. If that business rule changed then those modules would need to be changed in lockstep even though there were no interface changes. To prevent this problem, a business rule should be implemented by a single module. Also, business rules that often change together should also be implemented by a single module.
Testable
The third aspect of the developer experience is “We can do most of our testing without requiring an integrated environment” The ability to test locally requires an architecture that is testable. There are several different aspects to testability:
- It must be straightforward to specify the intended behavior of the SUT (the System-under-test which is a fancy word for the thing you are testing)
- The SUT must be testable in isolation especially it if has dependencies that would otherwise make testing more complicated
- The tests should run on a developer’s laptop
- The tests should execute quickly so that the lead time (time from commit to deploy and describe in part 1) is short
- Once the tests pass you must be confident that the SUT is ready to be released to users
Deployable
The fourth and final aspect of the developer experience is “We can and do deploy or release our application independently of other applications/services it depends on.” The ability to deploy locally requires an architecture that is deployable. There are several different aspects to deployability: First, the architecture must support frequent and reliable deployments. As I described in part 1, high-performance IT organizations deploy many times a day into production with very few outages.
Second, the architecture must enable a team to deploy and release their changes independently of the other teams. Once a module’s tests pass, a team must able to deploy and release that module without any kind of planning or coordination with other teams. Moreover, they need to be confident that it will continue to work in production.
Now that you have seen that rapid, frequent and reliable delivery requires a loosely coupled modular architecture that’s testable and deployable, let’s now look at the architectural requirements for sustainable development.
Architectural requirements for sustainable software delivery
As I described in part 2, sustainable development requires you to regularly upgrade your application’s technology stack. You must replace technologies that are destined for obsolescence with newer technologies. Replacing a library that is used by one module is relatively easy. However, many needed upgrades are potentially global in scope. Over the lifetime of a long-lived application you might, for example, need to change the language, the database, or the application framework.
For example, let’s imagine that around 2007, you had picked Ruby on Rails because it appeared to be competing effectively against Java. Today, you might want to migrate to Rust. The problem is, is that a big bang (total) rewrite of a large application to replace Ruby with Rust would be a massive undertaking. It would not deliver any value until the rewrite was completed. As a result, this kind of rewrite is rarely done and instead organizations choose to continue with an increasingly obsolete technology until it becomes an emergency.
What’s needed is an architecture that lets you incrementally upgrade your application’s technology stack. This concept builds on the idea of modularity that was described earlier. The difference is, however, that a module’s interface must also encapsulate decisions that are traditionally global, such as its language, database, or application framework. Once each module’s technology stack is hidden by the interface, you can upgrade an application’s technology stack one module at a time.
What’s next
The next article looks at the monolithic architecture and how well it meets these requirements.