Rest Rest Rest
Near every web service, we develop is following the rest paradigm. But from an architectural point of view, it is totally necessary to define and follow some conventions.
This article shall explain how we formalized conventions and best practices to increase efficiency when developing and consuming rest web services within LV 1871.
The common conventions and rules for rest services are not enough to provide the boundaries for efficient provisioning and consumption.
Further rules have to be established beside the common conventions, to document services, keep them stable, reduce boilerplate and ease inter-team communication.
By definition, rest services do have some disadvantages over soap services. These disadvantages have to be picked up by guidelines helping developers to provide solid, easily consumable services.
After all, we don’t dictate the use of rest. The decision if soap or rest services are implemented is mainly a use-case oriented decision.
This series of articles is covered by a GitHub repository showing example implementations of some of the described topics:
The following topics will be published week by week, starting now with the first.
- Documentation (this article)
- Resources vs. RPC semantic
- Error handling (coming soon)
- Reducing server-side boilerplate in Java (coming soon)
- What is a robust service (coming soon)
- Reducing client-side boilerplate in Java (coming soon)
- Unit-testing APIs (coming soon)
Documentation
About how to create a schema file for any REST service.
Rest is deeply associated with Swagger by now. Swagger is a framework available for many languages and frameworks, providing in-depth documentation of rest APIs through code annotation and analyzation.
Swagger allows us to annotate model properties to provide further description. Since normally no one get’s your model classes one by one when consuming your service (maybe this would be an anti-pattern), you can help any consumer by documenting your models to ease consumption.
You can define a brief description or label, allowable values, datatype and many more to define any field.
Further, it is possible to annotate any operation to give your consumer a chance to know about every state and return value. With a description, it is possible to describe the functionality in-depth.
Since there is no possibility to transport information of error states, at least it is totally necessary to document any API in this way to enable consumption. Path- or Request-Parameters can be documented, too. Maybe you want to provide a test case to show how an API works.
And finally, it is also possible to write some documenting notes about the service itself, describing general usage or purpose.
In many programming languages, swagger will retrieve the most of this information by analyzing code at runtime. Basically, it will scan datatypes of parameters, fields and operations. Even values of enumerations will be used as “allowable values” automatically in the docs.
After implementing swagger and documenting operations and models your Service will provide a JSON schema fully describing your service.
This schema file can be used to generate proxy classes or even complete clients. Plugins to generate those proxies are available for many build-systems and many languages like maven for example.
Swagger UI
After all, there is the Swagger UI project.
To visualize your documentation you can add Swagger UI in many forms. Swagger UI can be deployed standalone on any server which can reach the schema from your service and accessed by your consumers. For sure, Swagger UI can also be part of your Spring, dotnet core or nodejs project.
Swagger UI is just an HTML/js based web application visualizing the JSON schema file of any swagger documented service.
You can even trigger and test services by just using this web application. It is the most convenient way to analyze any REST web service before starting implementation. We are also using Swagger UI to provide a fast and easy way letting domain specialists or stakeholders test the services before they get integrated.
We implemented a tiny integration to ease access and usage of Swagger UI. Every single service will redirect any call to “…/api” to a Swagger UI showing the corresponding schema file.
This convention makes it easy to discover our services for any employee or potential consumer in our company. Everybody can always and everywhere discover, access, test and implement any service.
The only requirement to do that is that obfuscation is not your only form of authentication!!
My team defined some best-practices and must-have documentation to provide a convenient way to consume our APIs.
- Every API itself should have a human-readable label and a description in a human-readable, domain-oriented language.
- Every field should have a defined value property within swaggers @ApiModelProperty annotation defining the corresponding label.
- Every field should further define a brief description in a human-readable business-oriented language, to provide further information about the meaning of the data field.
- Every API operation should at least be documented with any possible return code and state, the operation can provide. Every state should be described in a domain-oriented language. Especially circumstances which will enforce this state.
- Every operation parameter should be annotated with it’s required state and a human-readable name which can be used as a label.
- Every service should provide a convenient way to access a swagger-UI with the documentation of the service itself. The swagger-UI should always be reachable by a defined URL pattern:
http(s)://<service_base_url>/api - Every service should provide a way to query all the labels and descriptions in a flattened data format. This API should be searchable.
[ “vertrag.beitrag.label”: “Beitrag”, “vertrag.beitrag.description”: “Der Zahlbeitrag eines spezifischen Vertrags in Euro”]
Recently I heard a sentence to think about when designing or implementing an API:
Eat your own dog-shit!
In this case, this was meant to say, that you always should implement and document any API the way you would like to have it to consume any API.