In the first part of this series, we laid out a set of principles to help you understand when microservices can be a useful architectural choice. We promised follow-up pieces describing each of the factors in more detail. In the third post of the series, we explore independent life cycles. Independent life cycles are a larger concept. Multiple rates of change, discussed previously, are a subset of this idea. For a variety of reasons, we often find parts of the code that need their own commit to production flow. Let’s consider this dynamic and how microservices help.
A monolithic approach usually hinders our ability to deliver quickly, run A/B tests, and learn from users. Recall our Widget.io example, a prototypical shopping app, shown below.
In our hypothetical scenario, our business leadership identified a new opportunity that requires speed to market. A typical monolith wouldn’t allow us to iterate fast enough, so we made Project X its own microservice. Project X has its own code repository and deployment pipeline - and therefore an independent life cycle. This will help us evolve Project X as we learn more about the business opportunity.
Independent Life Cycles Boost Developer Productivity
But speed to market isn’t the only reason we might want an independent life cycle for a module. We can increase developer productivity too!
Very few developers would say monoliths help them be productive. They slog through dictionary-length developer setup guides. Build times are measured with a sundial. It can take months for a developer to get up to speed on a project. With a smaller scope, a developer can get their head wrapped around a microservice in a day or two. Builds finish in a few minutes (or less). If the build gets broken, engineers know right away and they can take immediate action to fix something.
Smaller code bases also mean testing a microservice can be far simpler too. Tools like Spring Cloud Contract can help us ensure our services are good citizens and play well with others. Don’t bother with the monolith’s 80-hour regression suite. Instead, build a set of fine-grained tests against a microservice that can be executed on every commit. Rather than a “one size fits none” approach to testing, we can bring the right tools and techniques to bear on the individual circumstances of a given microservice. We can subject our microservices to constant scrutiny, instead of a one-off performance test. Imagine how this boosts code quality!
Getting From Code to Prod: A Tale of Two Life Cycles
Let’s compare monoliths and microservices as it relates to the life cycle more specifically - how new code goes from a developer’s laptop to production.
In the not too distant past, many IT organizations took a singular approach to software development. Projects plodded along in typical waterfall fashion, with quarterly or annual releases. Perhaps a review board (or two) had to sign off before code could go to production. Seems logical enough, right? But it often led to sleepless nights, long weekends, and war rooms filled with anxious people. A shared life cycle meant every module was constrained by whichever one had the longest commit-to-production flow. It also meant every line went through the same process regardless of what stages were most applicable.
Microservices are all about flexibility, including customized deployment pipelines. We are no longer forced to push every line of code through the exact same sieve. In the same way microservices allow us to choose the best technology for the job, we also have the freedom to use the right mix of tests, linters, and code quality scans for each microservice.
Fine-grained components - microservices - also make it simpler for us to adhere to our architectural goals. As we refactor our code, it is important that we don’t violate a key aspect of our architecture. But how do we ensure that across multiple developers, working in small, independent teams? Arising out of evolutionary computing, fitness functions allow us to essentially test our architecture. For each microservice, we can select the proper set of fitness functions to ensure our design evolves in a way that supports key quality attributes.
Independent life cycles make our lives better. They also allow us to make better decisions about how our software should evolve. Throughout my career, I have had countless debates with fellow software engineers and customers about possible solutions for a given scenario. And while there were always strong opinions, data was hard to come by. We had to make a decision based on what little we knew and hope for the best.
Of course even if we were wrong, lengthy deployment cycles meant it would be months before we could alter course. These constraints forced us to be conservative. We couldn’t afford to try something unconventional, lest it alienate our users.
Prediction is very difficult, especially if it's about the future.
-Niels Bohr (attributed)
The scientific method is straightforward. Form a hypothesis based on your observations, then design an experiment to test that theory. What if we could apply a bit of high school science to our software? By using hypothesis driven development, we can make far better decisions about our software. Independent life cycles make it possible!
Taking its cue from a traditional user story, we can formulate something like this:
And, we can often turn that structure into a fitness function that we regularly execute against our code!
When a given service has its own commit-to-production flow, we can run multiple experiments reacting to actual results instead of spending countless hours arguing about the future. Today, companies like Google and Amazon run multiple experiments daily. They constantly A/B test. The result: hard data about the impact of a given design on key metrics. What customer doesn’t want constantly improving products aligned ever more closely with their needs? More practically, what company doesn’t want to deliver this kind of service? This is another reason why microservices are so popular!
Mastering Independent Deployment Pipelines for Microservices
Your microservices still have to work of course. That raises a few interesting questions:
When we refer to an application or microservice as “production-ready,” we confer a great deal of trust upon it: we trust it to behave reasonably, we trust it to perform reliably, we trust it to get the job done.
-Susan J. Fowler
How do we keep our services healthy? How do we know we can trust them? The key is deployment pipelines.
Deployment pipelines give us a well-worn path to production. You can’t become an expert at a given task when you only do it once or twice. Expertise grows with repetition. Deploy often, and you develop a kind of digital muscle memory. If we only randomly expose our code to unit tests or linting, we can’t expect much improvement. But if we subject our code to the same procedures on each and every commit, we develop a process we can trust.
To ensure the code we deploy to production meets our expectations, it should pass through a rigorous process. Tools like Concourse, Visual Studio Team Services, and Jenkins help you create robust pipelines. We can craft the proper “gates,” and gain confidence that our code can pass the proverbial gauntlet.
These pipelines were once bespoke one-off endeavors. Today we can leverage projects like Spring Cloud Pipelines or dotnet-pipelines as a starting point. Following an opinionated build/test/stage/prod flow, we can be up and running in our own environment quickly. And we can be sure that our code does what we say it does, thanks to a shortened “idea to production” cycle.
Speed to market is the make or break attribute for your company. Clinging to decades-old processes because “that’s how we’ve always done it” is a recipe for failure. Thankfully, in 2018, we have the proven patterns and practices to help you accelerate time to market!
Given the need to iterate quickly, independent life cycles may be one of the least appreciated benefits of a microservice architecture. Looking for parts of our code base that need their own commit-to-production flow can be an invaluable learning tool.
As you already know, independent life cycles are just one piece of the microservice puzzle! Please join us for the next part of this ongoing series where we’ll tackle “Independent Scalability”.
About the Author
Nathaniel T. Schutta is a software architect focused on cloud computing and building usable applications. A proponent of polyglot programming, Nate has written multiple books and appeared in various videos. Nate is a seasoned speaker regularly presenting at conferences worldwide, No Fluff Just Stuff symposia, meetups, universities, and user groups. Driven to rid the world of bad presentations, Nate coauthored the book Presentation Patterns with Neal Ford and Matthew McCullough.Follow on Twitter More Content by Nathaniel Schutta