Using Swagger API
Arne Jørgensen
Posted on March 18, 2022
This was originally published in 2015 at reload.dk
Recently, we needed to build a website for a client with the major functionality based on Drupal integrating with an API provided by the client. Our experience has taught us that this usually results in misconceptions, hidden bugs, delays, and refactoring. Add to the uncertainty that the API was not defined or implemented yet and had to expose functionality from and old mainframe-based legacy system.
So, we looked to Swagger API (now the OpenAPI Specification) in our search for something to minimize the uncertainty and misconceptions.
Swagger API is an open-source project for representing and describing your REST API as a formal specification defined in YAML or JSON, and there is a large ecosystem of tools around Swagger API.
A small example would be a specification of an endpoint taking a single parameter, "username", in the URL and returning a JSON-object with a Hello World message.
swagger: '2.0'
info:
version: 1.0.0
title: Simple example
paths:
'/hello/{username}':
get:
summary: Say Hello.
parameters:
- name: username
description: The user to greet
in: path
required: true
type: string
responses:
'200':
description: Greet the user
schema:
$ref: '#/definitions/Greeting'
definitions:
Greeting:
type: object
required:
- message
properties:
message:
type: string
The example specifies a single GET endpoint with a single parameter in the path. It will return a JSON-object with one property "message".
GET /hello/Molly
{ message: "Hello, Molly!" }
The tooling and how to use the specification
The tooling around Swagger offers several ways to work with the specification.
One way is to generate the YAML-specification from annotations on your API-implementation.
Here is an example from Swagger APIs GitHub:
@GET
@Path("/findByStatus")
@ApiOperation(value = "Finds Pets by status",
notes = "Multiple status values can be provided with comma seperated strings",
response = Pet.class,
responseContainer = "List")
public Response findPetsByStatus(...) { ... }
Another one is to write the YAML-specification by hand and using the Swagger Codegen to generate server-side code and/or consumer (client side) code.
Of course, you could also just use a "handwritten" YAML-specification as reference for the developers implementing the server and/or consumer refraining from auto-generating code from the specification.
How did we approach it? And why?
The time frame for the project made it obvious that our clients developers, who had to implement the server side of the API, were going to be the bottleneck. They were understaffed and had to put out fires in the legacy platform as the project moved along.
To not let our developers be stalled implementing the consumer, it was not an option to generate the specification from the server code. Besides, using the Swagger API was a new approach for both our clients developers and us.
So we decided to write a Swagger API-specification by hand as a collaborative task between the two development teams and specifying the needed details as the project moved along. Doing agile development that was not an unfamiliar approach for us. We knew this would lead to some refactoring of the API as we got wiser, but that is just a strength, since that meant we could get a consistent and well-thought-out API as the final result.
In my experience, this turned out to be a very fruitful approach. Discussing both the overall approach and the details of the API in the abstract manner defined by the Swagger API-specification helped us focus on the actual design of the API and not being burden by implementation details in the server implementation. Of course, this was only possible because the API wasn't implemented yet.
In addition, the need to describe the API as a formal specification even made us revisit some of the assumptions, we had made about the business logic.
For future projects, I will definitely prefer the approach of defining the API first whenever possible!
Editor.swagger.ui was a nice editor for writing the API-specification as we moved long. It ensured we stayed within the definition and caught minor inconsistencies as we moved along. Remember to still store the specification in your version control system of choice.
Implementing the consumer
The consumer of the API was a website based on Drupal.
We used Swagger Codegen to generate PHP client code. The generated code contains methods for all endpoints and contains classes for all the data models defined in the specification as well.
Although it would be possible to work directly with the generated model classes in our implementation, we decided to add a more native Drupal layer.
So, we have implemented native Drupal entities for most of the model classes. This we way we can take advantage of doing stuff "the Drupal way" with regards to view modes, field API, panels, etc.
Unfortunately, it is not 100% possible to make a general Drupal module for bridging an entity with an API endpoint and mode. So, our implementation is still based on insight interpretations of the API.
The details in this implementation must wait for another blog post for a detailed description.
Using Swagger Codegen to generate PHP
Basing a project on code generated by a tool can feel a bit daunting. What if the generated code doesn't meet your expectations or even worse doesn't meet your needs?
It is actually not much different from using open source in general (at least not when the generator is open source as well, like Swagger Codegen is).
You have to use the same metrics and benchmarks as you would any other open-source project you depend upon. And if you run into problems, you will be able to fix them and provide your fix in the upstream code base.
We ran into some minor issues (#1201, #1210, #1355, #1356 and #1769) and fixed some stuff we stumbled upon along the way, although it didn't really bother us as well (#1451 and #1769).
The stuff we fixed is mostly the result of Swagger Codegens generated PHP code still in its early days. We have apparently weeded out those minor issues and for the last couple of months the code based has turned out to be stable for our use case.
We had one use case that is still not supported by Swagger Codegen, though. We would really like to use inheritance in our data model. The Swagger specification has properties for specifying inheritance, but it hasn't made its way into Codegen yet and there might still be some unresolved edge cases in the Swagger specification (#403).
For this project, we use our own fork of Swagger Codegen. In the fork, we have implemented support for inheritance in the PHP code generation. We have provided it as a pull request for Codegen, but because of the unresolved edge cases in the Swagger specification we still need to work a bit on this.
It is our belief though that our use of inheritance will not be incompatible with however the specification issue will be resolved.
Working with the Swagger community has been a real pleasure.
Compared to working with other open-source projects (like Drupal itself and Drupals contrib modules) we haven't experienced more issues than usual.
Reporting bugs and patches upstream to the Swagger community has resulted in fast feedback, and all our patches have been applied and made into the stable releases.
Now what?
The project described in this blog post is about to be released any day now. The next phases of the project is already planned and new API-endpoint and data models have been defined. We will definitely continue to use Swagger for this.
For future projects, I will definitely recommend using Swagger, if the project contains important and non-trivial API integrations.
Posted on March 18, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.