Cloud native application development can be intimidating sometimes! Architecting distributed applications that need to access and connect to complex infrastructure requires developers to learn different technologies and cloud services, which slows down their development tasks. It does get more challenging if developers need to deploy, debug, and test their applications on a Kubernetes cluster, as now they need to master not only Kubernetes but also how to containerize and distribute their applications. On the testing front, if your applications are composed of multiple services, it becomes challenging to start them all for development and testing scenarios. Wouldn’t it be great if we could simplify these complexities while promoting best practices, so applications run smoothly on our Kubernetes clusters?
In this blog post, we will look at how two CNCF projects can help us to simplify cloud native development and testing, both on Kubernetes and outside Kubernetes clusters. We will start by looking at how Dapr, a graduated CNCF project, decouples the access to complex infrastructure from application code by giving application developers APIs that promote best practices and abstract away cloud services and complex infrastructure that platform teams can configure.
When adding extensions to our Kubernetes clusters this can affect how applications are run and behave, pushing development teams to figure out how to replicate these setups for development tasks. How do we enable developers’ productivity (and joy!) without running their applications on Kubernetes clusters? That’s where Microcks, a sandbox CNCF project, shines. It eases the pain of setting up complex environments by providing mocks for your dependencies.
This post details our experiment introducing Microcks into the famous Dapr Salaboy‘s pizza demo application (see https://github.com/salaboy/pizza). You’ll learn how to use Microcks with Dapr for an unprecedented developer experience of a lightweight environment that allows shifting left the detection of most integration issues upfront!
Dapr + Microcks: Sidecars, Mocks and Contract Testing For The Win!
Application Overview
The Pizza Store application simulates placing a Pizza Order that different services will process. The application is composed of the `Store` Service, which serves as the front end and back end for placing the order. The order is sent to the `Kitchen` Service for preparation, and once it is ready to be delivered, the `Delivery` Service takes it to your door. For a full description of synchronous and asynchronous flows, I encourage you to look at the README.
The Pizza Application Overview
Adding Dapr to this application helps developers abstract complex concerns about infrastructure, service-to-service communications, security, observability, and resiliency while simplifying the application logic. For each Dapr-enabled service, Dapr injects a sidecar that exposes application-level APIs when running on a Kubernetes cluster. The Dapr sidecar is now responsible for understanding how to route requests across services, but also provides access to application dependencies such as Kafka, a message broker, without the need to add any dependency to the application code.
The Pizza Application implemented with Dapr on Kubernetes
The `Store`, `Kitchen`, and `Delivery` services can now rely on the Dapr APIs to implement common distributed application logic from any programming language, reusing hundreds of implementations, allowing them to move their applications across different environments without changing, repackaging, or re-releasing their source code. Check the Dapr official documentation page for a full list of APIs and implementations.
Caption needed
But as mentioned before, working on Kubernetes with projects like Dapr, which rely on the sidecar pattern, can complicate local development and testing. So, let’s take a quick look at some of the challenges that developers will face when working with these distributed applications.
Challenges
Distributed applications, like the Pizza Store, present challenges for developers in fixing bugs or adding new features to the application services:
- To confidently test a change, all components should be present in the environment. Developers must master tasks such as creating a Kubernetes cluster, installing Dapr, and containerizing their services. Running all these components might require a fair amount of resources!
- To test any changes to the services, all dependencies must be built and run locally. Depending on your technology stack’s diversity, this may require advanced knowledge!
- May you choose not to deploy everything; you expose yourself to integration issues. Deploying everything still exposes you to configuration drift risks!
All those concerns and challenges require teams to spend time to mitigate them, slowing down the development’s feedback loop, and finally impacting developers’ velocity!
Team scopes & responsibilities
For this example, imagine that a different team of developers is responsible for each of the application’s services. As service owners, each team is interested in improving the service with consistent interfaces that other services can rely on—its synchronous and asynchronous APIs.
Let’s look at the `Store` Service. We will notice that the team is responsible for exposing one REST API endpoint for placing pizza orders and creating two downstream calls to the `Kitchen` and `Delivery` Services to process the order.
This means that the team responsible for the `Store` Service needs both the `Kitchen` and `Delivery` Services to validate that their changes will not break other services. One good practice to codify these contracts is to use standard specifications like OpenAPI and AsyncAPI to describe those interactions.
If we add Dapr to the mix, developers in charge of testing the Store Service need to not only set up the downstream services but also their Dapr configurations.
Caption needed
Before jumping into how Microcks can help us here, let’s look at the `Kitchen` Service, which presents a different scenario.
The `Kitchen` Service is in charge of preparing the order and notifying the store asynchronously about its progress. The application architect has decided to use Kafka as transport for exchanging messages across applications; hence, every time the `Kitchen` wants to notify the store about progress, a new Kafka message is placed in a topic.
> By using Dapr PubSub APIs, the Kitchen Service team doesn’t need to worry about Kafka at all; they just use the Dapr PubSub API (`publishEvent(event)`) to emit an event about the pizza preparation progress.
> The team has chosen a Kafka Topic as the medium from which consumers can get messages (whether using Dapr or not), ensuring that this component receives correctly formed messages, which is undoubtedly part of the owner’s contract promise.
For the `Store` and `Kitchen` services, application developers need downstream services and application dependencies such as Kafka to validate changes. Using the services contracts, their configurations, and dependencies, we can define each of these services’ boundaries. Relying on these service boundaries and contracts, we can use Microcks to:
- Build simulations of your dependencies from their contracts to allow each team to work in isolation—this is the mocking part.
- Build complete test suites from your contracts to allow you to verify that you’re not drifting from your service’s contract—this is the testing part.
This approach will allow us to define isolated units to which we will apply our Dapr + Microcks combo. But enough theory! Now, let’s dive in the details!
Simplifying distributed application testing experience with Microcks
How can Microcks help to locally develop and test complex distributed Dapr-enabled applications without pushing developers to learn about Kubernetes?
Given the `Store` Service component boundaries, we’ll achieve this local configuration where the Dapr sidecar will be started right beside the service, and Microcks will simulate the `Kitchen` and `Delivery` Service components for both pub/sub and service invocations interactions:
Caption needed
Microcks creates mocks for Kitchen and Delivery Services, based on their contracts, with associated endpoints to make them available for the Store Service to run its tests. Microcks also simulates sending messages to Kafka, so the application can validate its behaviour.
For this example, the configuration to create these tests includes:
- A reference to the contracts for the Kitchen and Delivery Services REST APIs (OpenAPI),
- A reference to the AsyncAPI contract used to exchange messages via Kafka,
- The configuration needed to connect the Microcks simulations of Kitchen and Delivery Service contracts and Dapr to Kafka,
Let’s see how the components can be wired together from the `Store` Service team’s perspective.
Microcks uses service contracts to simulate dependency endpoints
As we can see in the diagram, instead of starting the `Kitchen` and `Delivery` Services, we rely on Microcks to run a simulation of these services based on their OpenAPI contracts. Notice that all service-to-service communications will happen via the Dapr sidecars, leveraging in this way resiliency, security, and observability features, but this requires us to set up Dapr sidecars also for the mocks.
For testing the synchronous interactions across systems, this setup enables the team working on the `Store` Service to make progress without waiting on the `Kitchen` and `Delivery` Services teams. As long as they keep their contract in sync, the `Store` Service team can be confident that their changes are not breaking other services.
For asynchronous interactions, the team can rely on AsyncAPI definitions to validate that the messages exchanged follow a previously defined contract.
Microcks uses event contracts to simulate event producers
Microcks can simulate message producers and deliver to Kafka following the Async APIs contracts of these services. The `Store` Service can test the expected behavior when receiving async messages via Dapr Subscriptions.
With this configuration, teams have a full working application in which the Dapr container acts like it’s connected to real endpoints. They can make progress using a contract-first approach, where Microcks provides all the simulations from the provided contracts. This configuration is lightweight, fast to start, and doesn’t require knowledge of the other components’ technologies. However, all this wiring may need to be streamlined. That’s where Platform Engineers can help!
What does this mean for Platform Engineers?
As mentioned in the previous section, we need contracts, sidecars, simulation servers, and a bunch of configuration files to connect all the required components for these tests. To build this example, we focused on two key aspects:
- Local development experience
- Relied on containers to create libraries of reproducible configurations and scenarios that development teams can use to test their services
We wanted to ensure that developers could run the application locally for development purposes and run tests without running inside Kubernetes. To achieve this, we used Testcontainers, a library that allows us to configure how containers are started and wired up together to create the previously described scenarios.
Writing these scenarios was straightforward, as both projects (Dapr and Microcks) integrate with Testcontainers (now part of Docker).
Dapr + Microcks for lightweight dependency simulations
With these configurations, platform engineering teams can create libraries that teams can use to avoid recreating these configurations in each service. Facilitating the initial wiring of all these components and contracts to other teams enables developers to focus on writing tests instead of figuring out how all these pieces fit together.
Using these tools also enables developers to run, debug, and test without running a local Kubernetes cluster, which further reduces the cognitive load imposed on developers.
> If you are wondering if this approach of using simulations also makes sense on Kubernetes, for example, to lower the cost of building environments, then Microcks has your back! Many Microcks adopters are also using it through its GitOps-compliant Operator to simulate third-party dependencies like CRM or Payment providers on their central environments!
We believe that the techniques and approach that we have taken to combine Dapr and Microcks can be leveraged by multiple projects in the CNCF, as this example demonstrates how developers can keep using the tools that they are comfortable with, instead of spending time trying to figure out how other tools work.
Wrap-up
Combining Dapr and Microcks offers a powerful solution for simplifying cloud-native application development. By leveraging Dapr’s sidecar architecture for seamless service sync and async communications and Microcks for mocking dependencies and testing, developers can efficiently simulate complex distributed systems without the need to deploy every service to a Kubernetes cluster.
This approach streamlines the development process, accelerates feedback loops, and helps detect integration issues early, significantly improving productivity and confidence in application reliability. Whether you’re mocking APIs or testing event-driven workflows, this combination provides a lightweight, quick-to-set-up, and highly effective development environment that allows teams to focus more on building features and less on managing infrastructure.
If you want to learn more about Dapr or Microcks, or how to combine them, you can check out the Dapr Website and the Microcks Website; or join their vibrant communities on both the Dapr Discord server and the Microcks Discord server!