This is the first of a series of articles in which I want share some ideas of how can we extend the benefits of BDD to the development of all the microservices that are part of a complex application. For this we are going to explore a sample application developed using BDD and Consumer Driven Contracts (1).
One of the main advantages of using microservices is that it enables us to divide a complex application in a series of loosly coupled, collaborating services. These services can be kept small and organized around business domains. These microservices can then be developed by different teams, that can build and deploy them independently.
Nevertheless we don't have to forget that in the end all of them belong to the same application and their development should be driven by the business value they can deliver. For frontend services a classical BDD approach can be used by the development team and the business stakeholders to drive out the desired behavior of the application.
In the case of backend services this approach is not so easy to apply. Some teams may say that they are using BDD in an API because they are using cucumber or JBehave and they are writing tests like the following:
Feature: Get the average price of a product As a consumer service I want to get the average price of a product Scenario: Get the average price of a product Given the product 1 had the prices: | price | days | | 100.00 | 30 | | 150.00 | 10 | | 135.00 | 20 | When I get the average price for product 1 Then I should get 120
But we have to ask our selves where is the connection with the business needs in this scenario. Maybe we want to show the user which is the average price of a product, or maybe we want to make the user aware that the product is now 20% cheaper than the average price of the last 60 days. We have to distinguish between using a framework only to automate the acceptance tests of the API and actually driving the development from the desired behavior for the whole application.
With the mainstream adoption of microservices there has been an increasing interest on the Consumer Driven Contract pattern to drive the development of these API providers from the needs of its consumers. Once again we have to remember that consumer driven contracts are, like BDD, more an attitude towards the development of a provider service from the needs of its consumers than only a set of tools we can use to automate our integration tests.
With all this in mind I think that Behavior Driven Development and Consumer Driven Contracts are a good combination with which we can use the business perspective not only in the frontend components but also deeper in our microservices architecture.
The sample application
To illustrate this ideas I'll use a simple application developed applying BDD and CDC. You can find this sample application in https://github.com/jagilpe/bdd-pact-microservices. In the repo you can also find information about how to build the project, so that you can see it in action. In following articles we'll explore in more detail and step by step what happens behind the scenes.
Once built the application allows us to browse a product catalogue and see the details and the different offers available for each of the products in the catalogue.
In order to keep it as simple as possible I have limited the implemented user scenarios to the following three:
- Get a list of the available categories
- Get a list of the products of a category
- Get the details and offers of a product
The application consists of a single page web application that gets it data from a RESTful API. This RESTful interface is backed by two microservices, one responsible for the product information and another for the offers. In order to decouple the frontend application from the internal architecture, and to make it be more similar to a real world scenario, the application uses the API gateway pattern.
The components in which the application is divided correspond to different folders in the project, and they are:
- ng-frontend: A SPA web frontend based on AngularJS 5.
- api-gateway: An API Gateway service built using Spring Boot and Netflix Zuul.
- offers-service: A microservice based in Spring Boot to access the available offers for a product.
- product-catalogue: A microservice based in Spring Boot to access the product catalogue.
To illustrate the whole process we'll take the third of the scenarios as example: "Get the details and offers of a product". Basically the steps we'd have to make to completely implement it would be:
- Implement the scenario in the frontend using BDD
- Implement the required mapping rules in the API Gateway
- Implement in each of the backing microservices their corresponding part of the pact
First of all, the development team, the business analysts and the product owner would come to a common definition of the scenarios to be built. One of which can be something like this (2):
Feature: Get the offers of a product As a user I want to view the offers of a product Scenario: Get a list of offers of a product Given there is a product "iPhone 8" And the "iPhone 8" product has 8 offers When I go to the "iPhone 8" product offers page Then I get the "iPhone 8" product details And I get 8 items in the list of offers
The team would then implement this scenario as they usually do with BDD, with one exception: they'd use a consumer driven contract tool to mock the responses they are expecting from the API. Depending on the CDC framework they'll have to write this contract manually or let the framework generate it.
The team can eventually also the need to talk with the team/s responsible for the provider service/s to agree about technical details and data structure, but this is something that can be refined iteratively later.
As result the team can end up with a contract that expects that:
- when it makes a GET request to the url /api/v1/products/1 of the api-gateway it will become the details of the product 1
- when it makes a GET request to the url /api/v1/offers/product/1 of the api-gateway it will become a list with the offers of the product 1
One thing to note is that the only provider that the ng-frontend knows about is the api-gateway, as we would expect when using the API gateway pattern.
The mission of the API gateway is to map the requests of the client of the API to the right service. From the perspective of the Consumer Driven Contracts, when we verify if the api-gateway service complies with the contract, we should expect:
- that the verification fails if there are some expectation of the consumer that are not mapped to any backend microservice
- that we get as a result an additional contract between the api-gateway and each of the microservices including only the relevant part of the original pact
In our case the mapping that the gateway will do are:
- it will follow the requests to /api/v1/products/* to the url /products/* of the product-catalogue service
- it will follow the requests to /api/v1/offers/products/* to the url /products/* of the offer-service service
Therefore we'll expect that the following contracts are generated:
- one between the api-gateway service and the product-catalogue service that expects that for a GET request to the url /products/1 it will answer with the data of the product 1
- one between the api-gateway service and the offer-service service that expects that for a GET request to the url /product/1 it will answer with a list of offers of the product 1
By now each of the teams responsible for the backend services will become a consumer contract they'll have to comply with, and that have a match with the business requirements.
As well as in the case of the frontend team, in this moment the team can decide to implement the requirements of the contract as they come or to start a conversation with the consumer team to adapt this contract to what they think is possible or simply recommendable. It's clear that in this case, the consumer team will also have to make changes to their application.
You can check out the other three articles of this series to see how this all looks like in practice exploring the implementation details of the sample application.
(1) If you're not familiar with BDD and Consumer Driven Contracts you can find more information about them in the following links:
(2) Someone may say that I'm mixing two different things in this scenario, show the details of the product, and show the available offers. And they'll be right, but I've chosen to mix them here to have an example of an scenario that has to get data from two different backend services