RESTful services with HATEOAS: Hypermedia, Optional Or Not?
1. Introduction
In this last, conclusive part of the tutorial we are going to compile our thoughts, what we have learnt so far, and come up with the answer to a single question: could we ignore hypermedia and HATEOAS altogether, essentially relaxing (intentionally or not) one of the mandatory constraints of REST architectural style, and still pretend we are building REST web services and APIs? Or should we even bother?
First and foremost, by any means we are not going to challenge Roy Fielding’s excellent Representational State Transfer (REST) dissertation and look for loopholes or vague definitions. From this perspective, you cannot skip over hypermedia and HATEOAS and claim your application is designed and implemented in accordance with the REST architectural style. But … is REST an example of one-size-fits-all architecture?
Table Of Contents
2. REST in the Context
For many it might be an eye-opener but REST was never designed to be the universal architecture and has never pretended to make it so.
The REST interface is designed to be efficient for large-grain hypermedia data transfer, optimizing for the common case of the Web, but resulting in an interface that is not optimal for other forms of architectural interaction.
https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
This quote says it all. It does not mean that REST could not be applied to other types of the software systems, but if those systems are not inherently based on hypermedia, you are shooting yourself in the foot and your best option is to look for different architectures.
3. But REST is everywhere!
Yes, this is true. These days, the fact that REST is not general purpose architecture for building distributed applications is completely forgotten and REST has gained the reputation of the silver bullet capable to solve all sorts of problems in software systems. Mostly any kind of web service or API gets the REST tag by default (so the need to introduce RESTful was imminent at some point). This is somewhat tragic and ironic at the same time.
Are you building large-scale web based application, not necessarily publicly accessible but which is supposed to be used by large number of the different clients and systems you do not have direct control of? If answer is “yes”, then it is worth considering the usage of the hypermedia engine and following REST architectural style as intended.
If you are in doubt, just look around, modern web is an example of the successful story of REST and its foundational principles. Web browsers are playing the role of the universal hypermedia driven clients which could render any web page using its HTML representation. It does not matter what web site you are visiting and where it is located, it just works. RSS and Atom feeds fall into the same bucket.
But truth to be told, most of the software systems are far off from such a scale, so what could be the alternatives in terms of simpler and more suitable architectures?
3.1. Web APIs
Essentially, it means “continue what you are doing” but more formally, it refers to Level 2 APIs outlined by Richardson API Maturity Model. The vast majority of the existing so called REST services and APIs belong to this category.
There is nothing wrong with borrowing some principles and constraints from the REST architectural style if they are proven to be useful and practical. However, we should not refer to these APIs as REST or RESTful, probably calling them JSON over HTTP APIs, or just web (or simply HTTP) APIs would be quite sufficient. The absence of the REST label would not the change the perception of the web services or APIs at all. Empowered by OpenAPI specification or its alternatives, such web services and APIs are the commodity these days.
3.2. gRPC
The RPC mode of communication is probably the oldest architectural style out there, which is still around and unlikely will ever go away. gRPC, a popular, high performance, open-source universal RPC framework from Google, is the one that bridges the RPC semantics with HTTP/2 protocol. By default, gRPC uses protocol buffers as both its Interface Definition Language (IDL) and as its underlying message interchange format. The IDL contains the definitions of all data structures and services and carries on the contract between gRPC server and its clients.
gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.
For example, here is a very simplified attempt to redefine the functional side of the Reservation
web APIs using protocol buffers specification and RPC style.
syntax = "proto3"; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; option java_multiple_files = true; option java_package = "com.javacodegeeks.rentals"; package rentals; service ReservationService { rpc book(ReservationRequest) returns (Reservation); rpc search(FilterRequest) returns (ReservationList); rpc cancel(CancellationRequest) returns (google.protobuf.Empty); } message ReservationRequest { string vehicle = 1; google.protobuf.Timestamp from = 2; google.protobuf.Timestamp to = 3; string customerId = 4; } message Reservation { string reservationId = 1; string vehicle = 2; google.protobuf.Timestamp from = 3; google.protobuf.Timestamp to = 4; string customerId = 5; } message FilterRequest { string customerId = 1; } message ReservationList { repeated Reservation reservations = 1; } message CancellationRequest { string reservationId = 1; }
gRPC provides bindings for many mainstream programming languages (including Java of course) and relies on the protocol buffers tooling, plugins and extensions for code generation. It is an excellent way to establish efficient channels for internal service-to-service or service-to-consumer communication, including even native browsers support.
3.3. RSocket
RSocket is one of the newest application protocols out there which supports reactive streams semantics. In a nutshell, it is a binary protocol for use on byte stream transports such as TCP, WebSockets, and Aeron. It offers multiple symmetric interaction models via asynchronous message passing using just a single connection:
- request/response (stream of 1)
- request/stream (finite stream of many)
- fire-and-forget (no response)
- channel (bi-directional streams)
Among other things, it supports session resumption which allows to resume long-lived streams across different transport connections. This is particularly useful when network connections drop, switch, or reconnect frequently.
3.4. GraphQL
GraphQL has an interesting story. It was originally created at Facebook in 2012 to address the challenges of handling their data models for client / server applications. The development of the GraphQL specification in the open started only in 2015 and since then this pretty much new technology is steadily gaining the popularity and widespread adoption.
GraphQL is not a programming language capable of arbitrary computation, but is instead a language used to query application servers that have capabilities defined in this specification. GraphQL does not mandate a particular programming language or storage system for application servers that implement it. Instead, application servers take their capabilities and map them to a uniform language, type system, and philosophy that GraphQL encodes. This provides a unified interface friendly to product development and a powerful platform for tool‐building.
In the heart of the GraphQL is a set of the core design principles with the strong focus on productivity and type safety.
- It is hierarchical: Most of the data these days is organized into hierarchical structures. To achieve congruence with such reality, a GraphQL query itself is structured hierarchically.
- Strong‐typing: Every application declares its own type system (also known as schema). Each GraphQL query is executed within the context of that type system whereas GraphQL server enforces the validity and correctness of such query before executing it.
- Client‐specified queries: A GraphQL server publishes the capabilities that are available for its clients. It becomes the responsibility of the client to specifying exactly how it is going to consume those published capabilities so the given GraphQL query returns exactly what a client asks for.
- Introspective: The specific type system which is managed by a particular GraphQL server must be queryable by the GraphQL language itself.
Interestingly, in contrast to REST where service providers guide the clients through hypermedia, GraphQL allows clients to have more control over what they want to receive back from the server.
Unsurprisingly, most of the GraphQL implementations are also HTTP-based and for good reasons: to serve as a foundation for building web APIs. Briefly, the GraphQL server should handle only HTTP GET
or/and POST
methods. Since the conceptual model in GraphQL is an entity graph, such entities are not identified by URIs. Instead, a GraphQL server operates on a single endpoint (usually /graphql
) which handles all requests for a given service.
schema { query: Query mutation: Mutation } scalar DateTime type Customer { id: ID! firstName: String! lastName: String! } type Reservation { id: ID! customer: Customer! vehicle: String! to: DateTime! from: DateTime! } type Query { reservations(customerId: ID!): [Reservation] reservation(id: ID!): Reservation } type Mutation { book(customerId: ID!, vehicle: String!, to: DateTime!, from: DateTime!): Reservation cancel(id: ID!): Reservation }
The implementation of GraphQL exists in many programming languages. Since we are scoping ourselves to Java and JVM, it is worth highlighting the graphql-java project.
3.5. Message passing
The request–response is not the only method to structure the communication in distributed and scalable systems. Message passing is another communication style, asynchronous by nature, which revolves around exchanging messages between all the participants. Actor Model is one of the great examples of how successful message passing style could be when applied to the real-world systems.
Messaging is heart and soul of the event-driven applications. In practice, they are implemented primarily on the principles of Event Sourcing or Command Query Responsibility Segregation (CQRS) architectures however the definition of what it means to be event-driven goes broader than that.
There is tremendous amount of different options to pick from, including but not limited to Java Message Service (JMS), Advanced Message Queuing Protocol (AMQP), Simple (or Streaming) Text Orientated Messaging Protocol (STOMP), Apache Kafka, NATS, NSQ, ZeroMQ, Akka toolkit, not to forget about Redis Pub/Sub and Redis Streams.
4. Conclusions
For quite a long time REST has become the subject of the heated discussions in the industry and the community. The practitioners are building JSON over HTTP web APIs and proudly call them REST APIs. As we have seen, not only this is not strictly correct but there is no real need to call them such. REST architectural style has a number of mandatory constraints, has its purpose, it is not universally applicable and never aimed to become such.
Building a truly REST (well, RESTful if you will) distributed system is rewarding but quite challenging task to pull off. To stress this fact, many claim that the death of REST is near while advocating towards using gRPC or/and GraphQL. In fact, nor GraphQL nor gRPC are direct competitors to REST, those are just different styles to architect the software systems.
Hopefully, this tutorial has helped you to understand better what is REST architectural style and what kind of software systems it is supposed to be used for. The industry has come up with its own understanding what is REST and unfortunately it is too late to fix that, the confusion is here to stay.