00:00:27.760
Hello everyone! My name is Marcos Castilho, and you can find me online as marcus at pretty much any platform. I work for a great company called Totrex, who is kindly sponsoring my trip here. I'm coming all the way from Scotland, although I'm originally from Brazil. And just to clear the air, I actually like microservices! But throughout this talk, it might seem like I don't. So let's talk about the mythical monolith.
00:00:47.840
Consider the monolith: it's like a billion lines of code that does everything your organization needs. It seems like a common scenario in Ruby: we’ve moved past the initial excitement phase, and now we're just focused on getting things done. Startups begin small, building services that grow as they add more functionalities. Eventually, you reach a point where you have a massive codebase that does everything—something we can probably agree isn’t ideal. I'm sure everyone here has horror stories about dealing with such codebases. You touch one line, and suddenly you break ten classes nearby, all without knowing what you've done. The term 'monolith' genuinely fits, similar to finding something massive and bewildering in the woods—who built this? So, clearly, this isn't the best approach.
00:02:00.880
To escape from this dilemma, many companies are turning to microservices. So what exactly is a microservice? It's a small codebase designed to handle a single, well-defined functionality. The idea is to apply the Unix philosophy of small utilities working together. The term is relatively recent; it gained traction around 2011, and while it's often discussed in the context of Java, I'm starting to hear more about it in Ruby conferences.
00:02:43.360
In a microservices architecture, services usually communicate through a RESTful interface, leveraging HTTP. Unfortunately, in our industry, that often means adhering to the limitations of HTTP. The goal is to implement all the good practices of single responsibility principles within a service-oriented architecture. Microservices aren't entirely novel; it's about services being constructed appropriately.
00:03:06.560
Transitioning from a monolith to multiple microservices presents various benefits. These services are easily replaceable due to their small size, allowing you to discard a service you don't like and create a new one without worrying about the entire ecosystem. This enables technology flexibility; a Ruby service can communicate with a Java service, which, in turn, talks to a Go service, etc. Additionally, their simplicity ensures the entire codebase fits in your head. They offer a natural separation of workflows, allowing companies to allocate teams to specific services without stepping on each other's toes too often. They communicate via defined interfaces, streamlining the organization.
00:03:40.640
However, as with all trends, adopting microservices doesn't come without its complexities. Deploying a single app is hard enough; imagine deploying 30 to 50. Performance might take a hit, and navigating the intricacies of networking among numerous microservices can become sluggish. Security also becomes an issue, as you can't just shove data into a session anymore; you need robust methods of authentication and proper monitoring. These challenges can quickly complicate the landscape once you enter the microservices realm.
00:04:20.160
When it comes to testing, especially acceptance or integration tests, many issues arise. These tests check how well one service integrates with another. I believe that tests are only as good as their failure messages. When testing microservices, especially through a UI, you might encounter vague messages like 'expected to find element item but couldn't.' This isn’t helpful, and after running tests in your CI pipeline for a while, you could still end up frustrated with unclear failures. Such shortcomings lead to a significant challenge.
00:05:06.240
Now, let’s address a critical question: how do you ensure you’re not disrupting someone else's day? When defining APIs for services, they need to be well-structured, but in practice, they can often change rapidly. If the customer of those services is your own company, we might not maintain the same professional standards as we would with a public API. This can complicate testing, especially when different teams are working with different technologies.
00:05:44.560
Over time, I’ve encountered this problem frequently while working with microservices, particularly in creating acceptance tests. One approach I tried was running all services in your ecosystem on your local machine before testing. While this can work, managing various technologies and ensuring everything runs smoothly on a local machine can become complex, especially as your CI pipeline grows.
00:06:10.000
Another approach is to run tests against a shared environment, like a development or QA environment where services are continuously running. However, many companies struggle to maintain a consistent production environment, leading to flakiness in the tests. Flaky tests can erode the confidence of a team. You might find that a test passes one moment and fails the next without any clear reason why.
00:06:44.000
A third approach might involve creating mocks or stubs for each service, allowing you to test against those stubs. This could be done using automated tools or custom code. While this can work, it often leads to a situation where you're trapped in a 'fantasy stub land,' where everything appears to function flawlessly, but in reality, you need to keep these stubs in sync with any service changes.
00:07:44.360
Another method is to utilize VCR, a technique that records HTTP interactions and allows you to replay them for tests. This can work well when talking to stable APIs like GitHub or Facebook. However, with numerous services in play, the recorded interactions can quickly become noisy and challenging to interpret. Each commit might create several changes that could confuse those working on the project.
00:08:25.280
Here's a sad story: while working on a microservices project, my team had to make changes to a rapidly evolving API without proper versioning. Every time we wanted to change something, it involved searching for dependencies and asking various teams if they were using specific functionalities. This ad hoc approach seemed really inefficient, and ultimately it felt ridiculous. I went on a quest to find better solutions.
00:09:07.680
I stumbled upon the idea of using executable contracts. A contract in this context refers to the specific functionality one service needs from another. It differs from the service API, focusing only on what's necessary for that interaction. For instance, if service A needs data from service B, the contract could specify a single field in an HTTP call that service A requires. It’s all about defining what’s necessary without overwhelming the service.
00:09:37.040
The process begins with service A, the consumer, defining the required functionality from service B, the provider. This involves drafting a contract that outlines the needed specifications. Both sides can engage in discussions to refine this contract before implementation. The beauty of consumer-driven contracts lies in their mutual benefits: both parties can evolve their services while staying aligned with the established agreements, maintaining important communication throughout.
00:10:36.720
By using contracts, you create a clearer understanding of dependencies, which is crucial when changes occur. For the provider team, knowing who uses their services can facilitate easier updates or even eliminate unused endpoints. The same benefits apply from the consumer’s perspective: clear contracts lead to well-defined expectations for the functionality they depend on. If something changes unexpectedly, the explicit nature of the contract provides a specific error message.
00:11:25.520
Consumer-driven contracts encourage teams to prioritize the actual needs of the users of the services. Instead of focusing solely on what cool capability a service can provide, the emphasis shifts to understanding what consumers truly need. This not only enhances the quality of communication between teams but invites increased collaboration and discussion.
00:12:00.640
In conclusion, microservices offer many advantages but come with their own set of challenges, particularly around testing. Executable contracts open up the possibility for effective testing in isolation, while consumer-driven contracts facilitate harmonious service evolution and communication among teams within the organization. If you wish to learn more about this approach, I'll be sharing additional resources and links in the presentation later.
00:12:36.480
Thank you for your attention!
00:12:39.000
You are welcome!