Software development has always changed dynamically over time. New programming paradigms were created, new types of language, methodologies and processes. The systems architecture could not be left out either, starting with a monolithic architecture, moving to a service-oriented architecture ( SOA ), to the architecture with the greatest prominence at the moment: microservices .
The increased use of architectures based on services and microservices has brought a serious problem into focus: the breach of contract between the service provider and the customer .
When building a system, there are two aspects to the evolution of microservices architecture.
The first strand calls for new systems not to be built directly on this architecture , but for the system to be broken into small services, as the monolith becomes too big (which is currently our case). This pattern is described by Martin Fowler, in the Monolith First article .
The counter-current advocates that systems be designed in microservices from the ground up , as explained in the Don’t start with a monolith article .
The questions to consider initially when building a new system are: is it worth adding complexity and creating a small system with microservices? What if the system is already initially planned to be a small size until the end of its lifecycle?
The creation and evolution of RDStation
RDStation, our main product at Resultados Digitais was born as a monolith (as in the vast majority of cases), mainly due to the urgency of delivering value at the expense of the learning curves of starting directly in a microservices architecture (which was something relatively new in season). Having a product quickly proving value is a much more accurate bet than a system structured on current trends and the bet on microservices could have put the future of the product at risk. (Entering an already saturated market can sometimes mean the end, even before launch).
Currently, the system has become too robust, with thousands of lines of code and dependencies. Some processes became slow because of this, such as deploying, building and reviewing PR’s, slowing down a little of our Agile methodology.
In parallel to the growth of this monolith, RDStation is characterized by being a complete digital marketing platform. To meet the demand needs of all customers and develop our features, we first look for something available on the market (ie, third-party API’s) to help us in the development. In most cases we find some API’s available for use and, consequently, we have considerably increased the amount of third party API’s connected to our system. If there is no other option, we build from scratch.
I work in a team that is responsible for the feature of posts on social media and as we post on three different social networks (Facebook, Twitter and LinkedIn), we have daily contact with three third-party API’s, always worrying about the number of requests that we are effecting, the number of errors in these communications and we also keep an eye on whether any changes were made on the other side. Each of them uses a different standard of updating API’s, ranging from increasing the API version to modifying data in the current API version itself.
A Big New Problem: Contract Inconsistency
Based on these facts, the need to change the architecture of systems to microservices, in addition to communicating with external API’s, made a new type of problem stand out: the breach of contract between the provider and the customer . Contract tests in this context help so that there is no noise about the data that travels on both sides.
A change in structure or data type by the service provider can cause great losses to the customer if the customer is not communicated about the change. Even if this communication has been made, it is possible that the customer does not know the real impact, despite having types of tests focused on communications. A client-side change can also cause data to return in a different way than expected.
Possible Approaches to Work Around Contract Problems
Integration testing, for example, can even solve these problems, but thinking about a test scenario of the entire stack from the customer to the service provider can become time-consuming and take a fair amount of time, causing the impact of modification is still great. Imagine the network latency variable for an API (or a used database) that is located at the other end of the planet, or even the network of thousands of customers competing for a given service.
On the other hand, if this communication is only mocked , it can cause a false positive , which can be even more serious, causing a lack of confidence about the test suite .
The contract testing in the context of services and microservices (or integration contract testing) are effective , since mainly compare the types of communication data from the client endpoints and provider with a contract file, not caring what occurs before and after that. If a contract test is broken, it means that there has been a change on the provider’s side, or the modifications made by the customer have a different result than what was previously expected.
What are contracts?
Contracts are the basis of comparison for integration contract testing. In the form of files (json, xml, yaml, etc), they contain request data such as headers, destination url, HTTP protocol used and sending parameters, as well as return data such as headers and HTTP return code. They also have some sample data and typing of all response data . For our case, the last information mentioned is the most important to verify if there was a breach of contract of any data received.
What are contract tests?
In a unit test context , contract tests describe the programming interface available in an object, that is, they are checked, for example, if the parameters and return of a mocked method have the same type as the parameters and return of the method. original. Furthermore, they also ensure that the original object has the methods that are being simulated (with mocks ) in some test. This is not the main topic of this post, but if you want to dig deeper into this type of contract testing, I recommend reading the Using Contract Testing and Collaboration post .
In a services and microservices context , contract tests verify the validity of mocks that represent simulated communications between service and consumer. Over time, there may be cases where the provider needs to make changes to its service, either by changing, adding or deleting data and depending on the API update process it uses (if it does not use a standard to increase the API version for changes for example), mocks can become obsolete, causing false positives and causing major incidents to be caused in production.
The operation of this type of test is very simple. The consumer makes a call to the service and receives the return data. Typing this data is compared to typing the mock data (via a contract file) and if there is any inconsistency in this comparison, the test will fail, issuing a warning that the API is returning different data than the consumer Is waiting.
There is also a recommendation for these tests to be implemented in a separate suite from the regular build, for two main reasons: use of real requests and frequency of API change. The use of real requests in a regular build is very dangerous, as it can impede the delivery of features because of tests failing due to some API instability (or if it is down). The frequency of API change is different from the frequency of code change, that is, these tests will run excessively and will hardly fail as traditional tests in the test pyramid fail .
The evolution of systems to microservices has resulted in new issues coming into focus, making alternative test methods gain more prominence beyond traditional test pyramid tests. Although many companies have bad experiences in migrating to this type of architecture and the migration risks, there are very relevant advantages in relation to adhering to the use of this type of technology, such as faster builds and deliveries and API’s modularity.
This is an introductory post related to a series of posts that will primarily focus on integration contract testing. The application of this type of test can be very wide and varied, depending on the characteristics of the API’s used. They can also help build and evolve microservices, mitigating change and versioning issues. They also help to make the process run in a smoother and more controlled way.
And you, what is your opinion about microservices? Have you ever used contract testing in this context?