Change Is the Only Constant in a REST API
“It can’t be broken!” I exclaim. “No one has changed a line of code in my service for weeks.”
But the alerts don’t lie. At least not if they’re configured correctly. And the alerts say “we’re hosed.” A look at the logs lets me know that my service is failing when attempting an HTTP call to an upstream service. I try the call manually and confirm the failure. A quick conversation with that team and I understand the problem. They changed an HTTP endpoint that I was consuming. After a small modification to my service everything will work again. While I’m waiting for my fix to go live, I’m wondering why we keep allowing this scenario to occur.
There are a lot of ways to avoid this type of surprise. Most solutions involve keeping the old behavior around in the called service (perhaps through versioning) until everyone is sure nothing uses it any longer. But the called service may not be under your control. And would anyone ever know conclusively that nothing relies on the old behavior? If they did know, would anyone remember to clean up the extraneous details? It would be preferable if you could find out about a REST API change before it affects you directly. Maybe even as early as when the commit happened so that you could discuss it on the pull request.
Last week, Rod blogged about the concept of fingerprints. They are the way that Atomist identifies significant changes to your project and their implications. In this post we are going to discuss a specific type of fingerprint for REST APIs. This fingerprint is like an eclipse in that we are not going to look directly at it. Instead, we are going to see how it can be used to tell you if and how REST endpoints have changed based on the code in a commit. Lets see some examples.
Spring MVC Controller Example
This is a Spring MVC controller. It has one endpoint that you would call like this /add?addend=2&addend2=3
. In case you are new to Spring MVC annotations, the @RequestMapping
value indicates the URL path and the @RequestParam
parameters unsurprisingly indicate request parameters. Now lets make some changes to the controller and see what happens.
Change URL Path
First lets change the URL path by changing @RequestMapping("add")
to @RequestMapping("addition")
. This, of course, changes the way that you would call the endpoint. What is interesting is the opportunity to use the information about the REST API change. Right now Atomist adds a comment to your pull request in GitHub rendering the REST API diff .
The rename is seen as adding an endpoint named /addition
and removing one named /add
. At a glance you can see what changed and that some of the changes were incompatible. We use markdown to indicate addition (bold) and removal (strikethrough). Potential incompatibilities are marked with ‘!’.
Change Request Parameter
You could change the method name, class name, or move the endpoint to a different controller and it would not result in a diff because none of those changes would impact the REST API consumers. However, changing one of the request parameters would alter it. Lets change addend2
to augend
.
The resulting diff shows the change to request parameters for the /addition
endpoint.
Change Response
Calling the endpoint will result in a response that looks like this.
{
"addend": 2,
"addend2": 3,
"sum": 5
}
This is because the controller references AdditionResponse
as the return type.
By default Spring MVC uses Jackson to marshall and unmarshall request and responses to and from JSON. Atomist incorporates the structure of the HTTP request and response into the fingerprint because changes here are a significant cause of API incompatibilities. If we make the naming consistent with the controller by changing addend2
to augend
in AdditionResponse
then we will see a diff that renders the changes to the response.
Change the World
There are a lot of details in Spring MVC and Jackson that can be rendered in a diff to highlight a changed REST API. Response attribute types can change or be objects themselves which results in a nested structure. Request objects are handled similarly to response objects, but may have attributes from an @JsonCreator
constructor or static factory method. HTTP methods like GET
and POST
can be altered. You may have more complex URLs containing path variables or a @RequestMapping
at the class level. The following diff from one of our unit tests renders many types of changes described by the endpoint name.
Change the Future
Right now this works for a Spring MVC app written in Java, but there are a lot of ways to build a REST API. All of them could benefit from this type of fingerprint. In a future post I’ll write more about how the fingerprint itself is calculated and how it could be extended to other languages and frameworks. For now, I’ll just mention that we use microgrammars to scan the code relevant to REST endpoints and put that information into a consistent fingerprint format that minimizes noise.
The diff comment added to a GitHub pull request is just the beginning of what we can do with the information in the REST API fingerprint.
- Documentation (such as Swagger) could be generated from the code and updated on every commit.
- Notifications about upstream API changes could be sent to a Slack channel for calling services prompting discussion before disaster strikes.
- Once similar fingerprints are created from HTTP client code (such as Feign or Retrofit) then new possibilities unfold. Notifications about API changes could be filtered to only the parts that you consume for example.
- It may be possible in some cases to even create pull requests that update the calling code and keep it consistent with upstream endpoint changes.
What ideas do you have for using REST API fingerprints?
It is fun to think about the possibilities. Let us know your thoughts by joining the discussion on the Atomist community Slack.
Note: Fingerprints are a prototype that are not currently shipped in public Atomist