If you don’t know Swagger, Swagger is a tool for documenting REST APIs. The documents are written in YAML, which I call XML for lazy people 😉
Before we continue I want to clarify that this post is based on personal experiences with Swagger. Some of the problems presented here are known Swagger’s limitations, some other are more related to how people use Swagger. However, I have observed that the usage is a common pattern, so although it is not a proper Swagger limitation, the tool tends to promote that kind of usage. The problems I am going to describe in this post happen in the context of documenting REST API using JSON and Java.
YAML is the new XSD
This is a Swagger misusage problem, not a problem from Swagger per sé. Nevertheless I have seen it in several projects. People think the YAML file is the new XSD, which means they treat the YAML file as a schema and just want it to generate the client model. However there are many problems that arise with this behavior.
Code generation and deserialization.
At first glance there is nothing wrong with code generation. It is actually seen as a productivity boost. Here is the thing though: When consuming a REST API you want to be as tolerant as possible. This means, you should only consider, or read, the elements in the payload that you need and ignore anything else you don’t. It also means, that you should not make any assumption regarding the structure of the payload. This is known as the Tolerant Reader Pattern . If you implement the tolerant reader pattern correctly, modifying the payload – i.e adding a new element – should not break the client code.
The problem of code generation with Swagger makes itself more evident with the enum type. In JSON there are no enums. That’s it. However you can define enums in Swagger. When I started using Swagger I thought the purpose of this was that you can see all possible values an enum has in the Swagger UI. However, when you generate the Java code out of the YAML, it generates the corresponding Java enums. In this way you end up with hard-coded values of what it was supposed to be a dynamic list. If the service provider adds a new element to the list, which means adding a new element to the enum, your code will break when deserializing the payload if you don’t re-generate the client model. So you see here how fragile your code becomes when it is generated. Adding a new element to the structure of the payload should NEVER break your code.
Again, this is not a Swagger problem, but a problem that arises from its usage. I think this behaviour comes from the SOAP/XSD world, where adding a new element to the payload meant a completely new version of the API.
To mitigate this problem just stop generating code and doing automatic deserialisation. Sadly, I have observed that’s a very hard habit to break.
Nobody reads the documentation anymore.
In one particular project we have experienced a lot of misunderstandings when a team tries to consume an API by blindly generating the client code and not reading what the APIs does or how it behaves or , worse, completely ignoring how REST works. The only interesting bits are the structure of the model, the URL and the HTTP method. These are just API details. The true value of an API is what it does, what it enables, but nobody seems to care.
No, this is not a swagger problem. However, I have experienced that many people just want to use swagger to quickly generate code and implement the client’s API. Swagger is like an enabler of this behaviour.
No Hypermedia support
Swagger does not support Hypermedia. It has been considered but as of now Hypermedia is still not supported. When I mention the lack of Hypermedia support in my projects the normal reaction is more or less a “so what?”. Well, as Roy Fielding says
Hypermedia is a constraint. As in, you either do it or you aren’t doing REST. You can’t have evolvability if clients have their controls baked into their design at deployment. Controls have to be learned on the fly. That’s what hypermedia enables.
In my opinion you want to have hypermedia just because of evolvability alone. The fact that using hypermedia means you are really doing REST is a nice secondary effect. Let me explain that a little bit further.
Suppose you have the following resource being called by a GET request.
GET http://myapi.com/orders/123
The service will return the payload plus a set of links.
"links": [ { "href": "http://myapi.com/orders/123", "rel": "cancel", "type" : "DELETE" } ]
That may look simple but it is actually quite powerful because:
1. Links are dynamic and the service provider decides when to send those links. The client just reacts to them – i.e. it may show a button to cancel the order.
2. what the URL looks like is irrelevant.
How do the above points contribute to evolvability?
In the above example, the business logic that decides whether an order may be canceled or not resides on the server, not on the client. Which means, you can change that business logic whenever you want without having to change the clients.
Since the links are dynamically provided by the server, they may be added or changed at any given point in time without having to change the clients as the URLs are not hard-coded.
I hope at this point I was able to explain that you can change the business logic of your API and even how a URL looks like without modifying the clients that use your APIs and that’s the key aspect of evolvability: The possibility to adapt your API without having to change (compile and redeploy) the clients.
Without hypermedia, the clients would probably have to parse the payload to see if there are some kind of status and then evaluate that status to take a decision whether the order may or not be canceled. It might seem harmless but that usually leads to distributed business logic baked in every consumer.
So…
if you have evolvable APIs and clients that implement the tolerant reader pattern, you never need to version your APIs!
Swagger is URI Centric
As you may be aware now, the URI is a detail of the API and most of them are dynamically created by the service provider via Hypermedia. Swagger focueses heavily on the URIs. Take a look at the following image:
You see a list of all possible URIs an API has whereas the only URI that should be of your interest is the entry URI of your API. The rest of the URIs are provided by Hypermedia.
YAML generation.
If you are doing API first, there are several frameworks to generate YAML for Swagger. In Java many of these frameworks rely heavily on annotations. The official swagger library works that way. Let’s take a look at it:
@Path("/{username}") @ApiOperation(value = "Updated user", notes = "This can only be done by the logged in user.") public Response updateUser( @ApiParam(value = "name that need to be updated", required = true) @PathParam("username") String username, @ApiParam(value = "Updated user object", required = true) User user) {...}
You can see that, apart from the fact that swagger repeats itself a lot with redundant annotations, it also leads to having more annotations than actual code. You also have to be aware that, normally, an API description is more complex than This operation updates a user. In our current project we even have complex tables describing use cases. Try to write that inside an annotation.
Fortunately there are other frameworks that generate YAML from the Javadoc. If you have to use Swagger, I strongly recommend using one of those frameworks.
Regardless of the framework you use, annotation or Javadoc based, you will probably leak implementation details in your documentation. When generating the YAML, those frameworks use the name of the class you use as REST DTO to generate the model definition. So, if you use something like UserResponseBody, this name will be later baked in every client that uses code generation. If you later decide to rename your class, you will cause a lot compile errors in those clients. I have seen that happen and I think that is an absurd consequence of generating documentation.
If you happen to do contract first, I can tell you it is not a pleasant experience to write the YAML yourself for non-trivial APIs even with the swagger editor.
Swagger makes a very good first impression
Yes, that is a problem. When people start evaluating tools for documenting REST APIs, they eventually find Swagger, give it a try and are amazed by how easy and fast you can get your first documentation up and running. Besides, many people think that the possibility to generate code out of the documentation is a good idea and Swagger provides that as well.
So the fact that swagger makes a great first impression, makes its limitations and constraints less apparent. Personally, I don’t like a tool that keeps me from using an essential and powerful feature like Hypermedia. I don’t like this kind of constraint to be imposed by a tool. You may use hypermedia or not, but don’t let the documentation tool take that decision for you.
I think the fact that Swagger makes a good first impression and that people see Swagger as a kind of a REST schema, has made Swagger very popular.
What are the alternatives?
The original problem swagger tries to solve is: API documentation. So, if you need to document an API, use a format that is created for that purpose. I highly recommend using Asciidoctor for writing documentation. There are some tools for Java that help you with this. However they are somehow technology specific. For instance Spring REST Docs works very well if you are using Spring / Spring Boot. If you use JAX-RS, the JAX-RS Analyzer might be a good option.
Conclusion
As I mentioned at the beginning of this post, some of the Swagger’s drawbacks are actually a usage problem. However, Swagger imposes some constraints, like the lack of hypermedia, which I personally think are show stoppers. I’m not saying you should immediately stop using swagger or never use it, but I hope you are now aware of its constraints and limitations. I hope you now understand that, if you are using swagger, you are probably giving up one of the most powerful feature of RESTful APIs. You are giving up evolvability!