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. In this post, we explore one of those factors, independent lifecycles, in more detail. Independent lifecycles 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 lifecycle. This will help us evolve Project X as we learn more about the business opportunity.
Independent lifecycles boost developer productivity
But speed to market isn’t the only reason we might want an independent lifecycle 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 lifecycles
Let’s compare monoliths and microservices as they relate to the lifecycle 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 lifecycle 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.
Hypothesis-driven development
Independent lifecycles 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 lifecycles make it possible!
Taking its cue from a traditional user story, we can formulate something like this:
We believe <this change>
Will result in <this outcome>
We will know we have succeeded when <we see a X change in this metric>
For example:
We believe adding a distributed cache
Will result in faster startup times
We will know we have succeeded if startup time is less than 15 seconds
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.
Wrapping up
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, we now have the proven patterns and practices to help you accelerate time to market!
Given the need to iterate quickly, independent lifecycles 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 lifecycles are just one piece of the microservice puzzle! Read up the rest of this series:
Part 1: Should that be a Microservice? Keep These 6 Factors in Mind
Part 2: Multiple Rates of Change
Part 4: Independent Scalability
Part 5: Failure Isolation
Part 6: Simplify External Dependencies
Part 7: The Freedom to Choose the Right Tech for the Job
Want to learn more about microservices? Join us at the next SpringOne!
Want more architectural guidance for your modern apps? Be sure to download Nathaniel's eBook Thinking Architecturally.
Want to learn more about deployment pipelines? Check out Continuous Deployment to the Cloud and Scaling the Build Pipeline at Home Depot.