Microservices offer a range of benefits, from scalability to fault isolation. However, they also introduce complexities, especially when it comes to development and debugging. This post aims to address some of these challenges
Microservices offer a range of benefits, from scalability to fault isolation. However, they also introduce complexities, especially when it comes to development and debugging. This post aims to address some of these challenges, inspired by a Reddit discussion on the topic.
The services have their own dependencies like hydra, postgresql and so on. So in this case dependencies of services consume much more memory on my local PC. I need something very small and simple.
I have a theory that part of this pain is about the practices we’re used to: as someone who started with small local projects and monoliths that were easy to mock locally, I got used to jus ‘trying stuff’ with my code, making changes and seeing the effects. I’ve often thought, as I once again tapped my desk waiting for my code to compile and deploy, that if I was an old-school programmer who planned her program on a legal pad, I might be better suited to an environment where it took 10 minutes to see the results of code changes.Part of the ethos of platform engineering, though, is to make our process work well with the developers we currently have, rather than hoping we’ll all suddenly change the way we work.
if the number of services is low, you could spin up a dedicated namespace in K8s to run the services you need. But you’ll need to spin up everything in this namespace which could be slow depending upon how many entities you need to spin up. In this approach, you can use tools like skaffold to run the services you are changing locally and “sync” that code into the cluster to get the “hot reload” functionality without having to rebuild docker images.
Mocking is a part of the standard advice for developers trying to replicate their environments, this generally has two components. Contract Testing uses contract tests to ensure that your mocks are behaving as the real services would. Service Virtualization uses tools like WireMock to simulate the behavior of dependent services.
This is worth its own section since we often here ‘Just mock everything else’ as a solution from people who don’t have much experience doing platform engineering for a large team.
Maintenance Overhead
One of the key challenges with mocking is keeping the mocks up-to-date as microservices often evolve rapidly. As services grow more complex, the mocks can become almost as difficult to understand and maintain as the service they’re emulating.
Incomplete Simulation
Mocks might not fully replicate the behavior of a service, especially if that service is stateful or has complex interactions. They often use static or generated data that might not reflect the current state of a real service, leading to false positives or negatives during testing.
Inter-Service Dependencies
Mocks can’t simulate the network latency or failures that might occur in a real microservices environment. If multiple services are involved in a transaction, mocking one without the others can lead to an incomplete test scenario.
Responsibility Sharing
Notable in the discussion on reddit and in the Rands Slack where a similar discussion occurred recently: it’s not always clear who should be creating mocks. It might make sense for team A to make the mocks to be used by others relying on team A, but this isn’t the general practice, meaning that mocks often most accurately reflect ‘this is what we expect the other service to do’ rather than a real understanding of the way team A’s service fulfills its contract
One theme that comes up repeatedly when trying to test with microservices is the realization that separations of services don’t reflect separation of workloads and domains, meaning that interdependence is so significant it makes separate testing impossible. As reddit user ghostsquad4 put it
Microservices should not just “be small”. In fact, maybe we need to start a name change, it should be called “single-domain-service”. It can be helpful to think of a domain in the same way as a team of people, specifically though an autonomous team (one who doesn’t need to coordinate with others to get their job done). Maybe an example of this is when your app needs to “get data” (from other teams/services), but does not rely on the behavior of how that data is compiled. (The difference between GET and POST/PUT).
In a recent talk from the Lyft engineering team, about the implementation of testing on a shared cluster by Matthew Grossman of the Lyft Platform Engineering group, we see a new path to a solution:
We fundamentally shifted our approach for the isolation model: instead of providing fully isolated environments, we isolated requests within a shared environment. At its core, we enable users to override how their request flows through the staging environment to conditionally exercise their experimental code.
Such a solution has its own engineering overhead for setup, but offers a workable way to test microservices in a shared environment, where the requests to and from this service under test are isolated, without needing to create a whole separate cluster or mocking a large number of services.
While microservices introduce complexity, especially in debugging and dependency management, several strategies and tools can mitigate these challenges. By implementing new team practices, and doing the work of providing appropriate tools and tests to your organization, you can approach the ease that developers felt emulating a monolith within their laptop.
Get the latest updates from Signadot