I certainly love spaghetti.
So, as you can guess, I loved it when my teacher at school gave us a pack of spaghetti and asked us to build a bridge using nothing but hot glue. This bridge had to be as light as possible while still supporting the maximum possible weight.
In Germany, this is a very common exercise that introduces kids to statics. I'm sure you've built a bridge out of spaghetti at some point, too. But did you also notice that the groups of kids building these bridges tended to fall into one of two categories? There were the kids who just glued a block of noodles together and called it a bridge. And then there were the kids who spent the entire class thinking, discussing, and prototyping. Eventually, they glued together an elaborate bridge with arches and cross braces. I don't have to tell you which bridge best met the teacher's stability requirements.
This engineering quote perfectly sums up the lesson's key takeaway:
“Any idiot can build a bridge that stands, but it takes an engineer to build a bridge that barely stands.”
Fast forward to today and I find myself once again tasked with building a bridge out of spaghetti and hot glue. Only this time, the task is a metaphorical one: it's about testing a micro-service architecture. The bridge we want to build should connect the "Idea Island" with the mainland.
Every new feature starts out as an idea that has to find its way into the productive system (the mainland). What our bridge should provide is a safe, reliable, and fast way for this to happen. So, what does the ideal bridge of mircroservice testing look like?
If you search for different methodologies that look at applications' testing architecture, you'll repeatedly stumble across these three models:
That's a very provocative question, and one that's subject to much controversy in the community. It starts with elements that are understood as unit testing and integration testing. For some, a "unit" is a single function or class, while others call an entire micro-service a unit. I say let's keep the discussion of this hot topic on Twitter - at least for today.
For our bridge project, it's actually not that important either because all these models look at individual parts of our software landscape. Kent C. Dodd's Testing Trophy focuses more on the frontend (or one of the micro frontends) testing strategy. Meanwhile, the Honeycomb looks more at the testing approach for a backend micro-service. But for now, let's not get hung up on these individual parts of the bridge. Instead, let's take in the view from a few kilometers away and consider the big picture.
To meet our own quality requirements, we will not build the spaghetti bridge, but instead the well-engineered one. To do this, we will keep things simple by paring down the construction line to four bridge components:
You could say the foundation of a bridge is the unsung hero of the bridge world.
You don't see it, but it's essential for the bridge's stability and structural integrity. Similarly, strong non-functional requirements (NFRs) are the unsung heroes of a micro-service architecture. They are invisible too, but they're also critical for the stability and development of the entire system.
Without strong NFRs, your system could be as shaky as a bridge built on jello (delicious, but not very stable). Just like the foundation of a bridge provides a base for the structure above it, strong NFRs provide a foundation for developing a software system. They ensure that the system can handle the demands placed upon it, such as scalability, security, and performance. Without a solid foundation of strong NFRs, the system could be vulnerable to issues and disruptions.
Similarly to how the foundation of a bridge is carefully planned and constructed to support the structure above it, strong NFRs should be carefully considered and implemented to support the system's development.
By taking some time to address NFRs from the get-go, you can build a system that will be stable and reliable over time. Just don't forget to give your NFRs some love - they may be hidden, but they're essential building blocks.
A bridge can have multiple towers, just as a micro-service architecture typically consists of multiple teams. In our case, those teams own a business domain across the entire vertical stack.
This allows them to make use of the three models mentioned above within their stack and build static code analysis, unit tests, and E2E journey tests for their vertical.
It should be noted that unit tests in the sense of Martin Fowler's description in his post about "The Diverse And Fantastical Shapes of Testing" should be understood here as a mix of sociable and solitary tests of a single service. The E2E journey tests are conventional UI E2E tests, which nevertheless only integrate the services of this vertical.
A horizontal integration to the services of other teams does not take place here.
The suspension cables in a suspension bridge are key as they support the weight of the bridge and ensure its stability. Similarly, contract tests in a micro-service architecture are essential as they support the system's stability and reliability.
Just as suspension cables are responsible for distributing the load of the bridge evenly across its length, contract tests ensure that each micro-service in the system is correctly integrated with the others and that they all work together as intended.
In the same way that suspension cables are inspected regularly to ensure their integrity, contract tests should be run regularly (e.g., before deployments) to ensure that the micro-services in the system are functioning as intended.
This helps identify any potential issues before they become major problems, allowing for timely resolution and circumventing system disruptions.
Overall, the metaphor of suspension cables in a suspension bridge is a useful reminder of the importance of contract tests in a micro-service architecture.
They are crucial in ensuring the system's stability and reliability, and should be treated with the same level of care and attention as any other critical component.
A suspension bridge deck serves as a link between the island and the mainland, allowing people and vehicles to cross the water safely. Similarly, E2E tests in a micro-service architecture constitute the connection between the system's different parts, allowing developers to safely navigate new features into production.
Unlike the E2E Journey tests within a vertical, which only test the user journey in that domain, the full E2E tests that resemble the deck verify the horizontal integration of all domains. In other words, the interaction of one or more users with the complete system without using any mocks or stubs.
If any part of the system is not functioning correctly, it can create obstacles and disrupt the user's journey, much like a pothole in the road surface of the bridge.
By regularly running E2E tests, you ensure that the system is functioning as intended and that the user journey is smooth and seamless.
Overall, testing a micro-service architecture is very much like building a bridge using spaghetti and hot glue. By following a holistic approach and considering all system components, including NFRs, contract tests, and E2E tests, a stable and reliable bridge is well within reach. Unlike a bridge made of a block of noodles glued together, our bridge will elegantly stand up to the challenges of the modern software landscape. So, next time you find yourself tasked with building a micro-service architecture, remember: a little bit of planning and attention to detail can go a long way toward creating a robust and resilient system.