An Introduction to Contract-First Development

An Introduction to Contract-First Development

A better way of developing microservices

Have you ever started a job where they told you your contract would be determined 'on the fly'? Or bought a car without knowing what color it is? Or planned a meet-up with friends without picking a location? You could say that in our daily life, we live contract-first: we establish clear expectations before a commitment between two parties. In software development, starting with defining contracts is not as self-evident for everyone. This article explains the basics of working contract-first in software development and why you should start adopting it.

What is contract-first development?

Contract-first or API-first development is about defining the interface of a service before starting to build that service.

That means before writing the code of your application, you first define the interface of your service with the outside world. What endpoints are exposed, what is the schema of the requests and responses and what security schemas are available?

Why develop contract-first?

The short answer is: having clear expectations for a product from the start avoids having to change it in the future. But, let us go over its importance in a bit more detail.

Contract-first development is a must when working in a microservices ecosystem. When companies started moving away from monoliths to microservices, they were partly motivated by the leanness of microservices. They are simple units that can be created and maintained independently. This also means that multiple microservices can be developed in parallel by different teams, which increases productivity. That means both teams can focus on their own service without stepping on each other's toes, right? No, it does not if there is communication happening between the two services.

The terms microservices and decoupling go hand in hand for many people, while in many cases this is not entirely correct. Let me demonstrate this with a short story.

Let's say you are working for a food-delivery start-up, as a developer. The IT services are in the process of being built. Your team is responsible for building a service allowing users to order from nearby restaurants. Another team manages a service providing information about participating restaurants and another manages a service for user data.

FoodDeliveryCF1.drawio.png

The three teams start working on their service independently. Once your team finishes the inner workings of the ordering service, it is time to connect with the restaurant service. You contact the restaurant team and explain you want to query restaurants based on a location. Furthermore, you would like to receive discount deals for those restaurants to show to the users. The restaurant team tells you the endpoint to use and shows an example response:

[{
  id: 1,
  name: "PizzaPlace",
  discountDeals: [56,67,98],
  ...
]}

After seeing this example response, you ask the team how to interpret the discount deals. They tell you they decided to make a separate service for this: a discount service. The identifiers from the response should be looked up in this new service:

FoodDeliveryCF2.drawio.png

This is a setback for your team. You have to refactor your application to ask the discount service for information on discount deals received in the restaurant application. Even with a clean architecture, this requires additional work. Moreover, a new service account/token might be required for communication with the discount service. It can take days before that account is made. Maybe the ordering service needs to be whitelisted in the discount service as a valid consumer as well, requiring more work and time, etcetera.

All this extra delay could have been avoided by developing contract-first. If the teams started by defining contracts of the services they were about to make, the delivery service would have become known from the start of the project, instead of at the end. The ordering team can ask for permissions and tokens in time and can structure their application in a way that accommodates communicating with the discount service.

Advantages of working contract-first

Here is a brief overview of the biggest advantages of working contract-first.

  • Limiting avoidable reiterations during development - if you discuss contracts between services from the start, you have a detailed idea of what you need to develop and it will require little rework. There will be no big surprises during development like having to connect to an extra service, as you know what you can expect.
  • Code generation from contracts - another timesaver is that you can generate clients/servers from API contracts. This means you do not have to write that code yourself and that you cannot make mistakes while writing the client/server.
  • No room for mistakes like wrong capitalization - When it comes to contracts the devil is in the details. When teams explore APIs through example requests, you are never sure which fields can be null, empty or which have different subtypes. Another common pitfall is assuming the wrong capitalization of properties (camelCase, snake_case, PascalCase). A pitfall that is also avoided by working contract-first.
  • Accountability and transparency - Once a contract has been established, it will require the provider to stick to it or at least remind consumers whenever a change is made in the contract. The notification of this can even be automated!

Criticism of contract-first development

  • Contract-first development does not sound agile - trying to design a contract before starting does sound like waterfall development. However, even a contract is not set in stone. The goal of contract-first development is to establish realistic expectations between two parties and to start a discussion on what information is required in what form. Changes might still be desired by the contract provider later, but they should be considered with more care, as it affects the consumers of the contract.
  • The contract of the service is not yet known at the start - this just shows an underlying problem in most cases. Either the responsibilities of the service are not yet clear, the domain of it is still unclear, or there are no guidelines in the company on how to format a microservice API.

Tools and frameworks

OpenAPI

OpenAPI has become the standard way of describing REST APIs. Its description language supports all relevant HTTP methods, types, parameters, textual descriptions, and even inheritance of data structures. Furthermore, an interactive web page of the contract can be generated using swagger and you can deploy that page along with you service so users can try out your API.

image.png

Code generation of servers and clients based on OpenAPI contracts is also easy to do in many different programming languages. An overview of possibilities can be found here.

AsyncAPI

AsyncAPI is like the younger brother of OpenAPI. It aims to provide a description language for asynchronous communication. Examples of use-cases are messages sent over WebSockets or via message brokers.

Although the language that AsyncAPI provides is already rather complete, the code generation from AsyncAPI contracts is still in the early stages. The so-called 'templates', of generators, are still being developed by the community and an overview can be found here and on Github. Personally, I still find the code generation too unstable to use but I have used the contracts on their own.

Conclusion

Developing contract-first should be the industry standard when working in microservice ecosystems.

  • This way of working will prevent miscommunications and setbacks caused by the independence of teams, without sacrificing their leanness and flexibility.
  • Servers and clients can be generated based on contracts, saving development time and preventing mistakes.
  • Contracts promote responsibility for APIs and increase transparency.
  • OpenAPI and Swagger provide all you need to quickly start developing contract-first.

Try experimenting with this way of working at your company and see if it benefits your team. If you already have experience with it, I am interested to hear about it in the comments!

Did you find this article valuable?

Support Ruben Scheedler's blog by becoming a sponsor. Any amount is appreciated!