Service contracts
Contracts are required for entities to interact with each other. They define the message format and medium of communication for the participating entities. In a monolithic environment, it is simpler for components to interact with the target components using the interfaces and functions exposed by them. Since a function clearly defines its message format as input parameters, the message passing between the components can be done by just a function call with the required input parameters. Function calls are further simplified in a monolith due to a common underlying technology stack. Contracts are also easier to maintain for monolithic applications because any change done to the contract is tested and verified across components for compatibility. Such applications are also versioned as a whole and not per-component. The following table compares and contrasts monolithic and microservice architectural styles:
As compared to a monolith, in a microservices-based environment, there is a service deployed for each business capability that may or may not be using the same technology stack. In such cases, service contracts become absolutely mandatory for microservices to understand the message formats and communication medium accepted by other microservices and interact with them. Moreover, these message formats need to be language-agnostic to allow any microservice to communicate irrespective of the technology stack in which they are implemented.
Microservices should never expose their internal data model directly as a part of a message contract to the external world. The internal data model must be decoupled from the external service contract and there should be a way to convert and validate the contract at entry and exit. This helps to evolve the data model of a microservice in isolation without affecting the contract with other microservices. If there is a change required in the service contract, it must be versioned and each version of the contract must be supported by the microservice as long as it is in use by any external service. A version should be discarded only when there are no other services using the obsolete version and there is no longer a need to roll back the service to its previous version.
Microservices that expose REST APIs primarily use the REST API definition and HTTP verbs (https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods) to define well-formed URIs. Service contracts for REST-based APIs are defined using frameworks such as Swagger (https://swagger.io/) and RAML (https://raml.org/). Microservices that use the observer pattern tend to accept messages in Thrift, Avro, or ProtoBuf formats. Each of these frameworks has a way to define language-agnostic specifications and supports most of the popular programming languages.