Diving Deep Into REST API Channels
This article provides a deep dive into how developers can work with REST API services, cache, message logs, and more by using a single platform.
Join the DZone community and get the full member experience.
Join For FreeLet's begin in 2021 with a deep dive into Zato REST API channels. What are they? How can we use them efficiently? How can they be configured for maximum flexibility? Read on to learn all the details.
A Sample Service
First, let's have a look at a sample service that we want to make available to API clients.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class GetUserDetails(Service):
""" Returns details of a selected user.
"""
name = 'api.user.get-details'
class SimpleIO:
input_required = 'user_name'
output_required = 'email', 'user_type'
def handle(self):
# Log what we are about to do.
self.logger.info('Returning details of `%s`', self.request.input.user_name)
# In real code, we would look up the details in a database,
# but not necessarily in a cache - read the article why it is not needed.
details = {
'email': 'my.user@example.com',
'user_type': 'ABC'
}
# Return the response now.
self.response.payload = details
The first thing that may strike you is that the code is very high level – it just has access to a user_name
and some data is returned but there is no mention of REST, no data serialization, caching, or anything that is not involved in the business functionality of returning user details.
This is by design. In Zato, services focus on what they actually need to do and the lower-level details are left to the platform. In other words, the service does not need to be concerned with the peculiarities of a given transport method, it just has its input to process and output to produce – it is channeled in front of a service that deals with all such aspects and the service concentrates on higher-level logic.
This makes it possible to employ the same service in other contexts – for instance, we are describing REST channels today, but the very same service could be invoked from the scheduler, through AMQP, IBM MQ, or via other channels, including multiple REST channels, helping you to design a reusable Service-Oriented Architecture (SOA).
With that in mind, let's move on to REST channels.
REST API Channels
In the Zato web-admin, a definition of a sample REST channel making use of our service may look like the below screenshots:
Now that we have created a new channel, we can invoke it to confirm that it works as expected.
xxxxxxxxxx
% curl localhost:17010/api/v1/user/my.username ; echo
{"email": "my.user@example.com", "user_type": "ABC"}
%
We should see the following message in the server logs:
xxxxxxxxxx
INFO - api.user.get-details - Returning details of `my.username`
Great! Everything is fine and we can proceed.
Just one note about channels: remember that there can be many channels pointing to the same service. This lets you reuse the services in various scenarios, e.g. you can have a separate REST channel with its own security definition for each external application connecting to your APIs.
Whenever you add, modify or delete a channel, the service as such is unchanged, including any other channels. Conversely, when you update service, for instance adding new functionality, all channels using it will automatically invoke the new version of the service.
Channels come with good defaults when you create them, but it is always possible to customize them to one's particular needs. Let's go step-by-step through each attribute of a channel's definition.
Basic Information
- Name: Each channel has a unique name which can be arbitrary, depending on your naming conventions.
- Active: An inactive channel cannot be invoked; doing so will yield a 404 error.
URL Path and Query String
- URL path: URL paths need to be unique. Each path can contain one or more patterns to match input parameters. This is why in our service we were able to reference
self.request.input.user_name
– it was extracted from the URL path. - Match slash: Sometimes, URL path parameters sent from API clients will contain the slash character which normally is used to separate path components. This checkbox controls whether the path patterns should match a slash or not.
- URL params: QS over path vs. Path over QS. Usually, applications will send parameters in one place only, e.g. only in the URL path or in query string parameters. But what if an application has good reasons to send parameters in both the query string and URL path, for instance
/api/v1/user/my.username?user_name=my.other.username
– this happens from time to time and this setting lets you control which of the two will take precedence. - Merge to request: This is used if parameters are sent via a query string in addition to the URL path. If checked, such query string parameters will be accessible through
self.request.input
, otherwise, they will exist only inself.request.http.GET
. - Params priority: This is similar to the options above but it will control the behavior if parameters are sent in both the JSON message body as well as in the URL path or query string – it lets one decide which will have higher priority and which will become available in
self.request.input
.
HTTP Metadata and Data Format
- Method - it is possible to specify that only specific HTTP methods can be used to invoke the channel. This is not always needed. For instance, the service above can work just fine with either
GET
orPOST
, depending on what the caller prefers. Conceptually, this is aGET
request, but some API clients will always usePOST
, which is why filling out this field is not always needed or advised.
A service can handle multiple methods, either as a whole or a channel, and can dispatch requests to specific parts of the service depending on which method is used, i.e. you can addhandle_GET
,handle_POST
, and other such methods independently, resulting, for instance, in a user service that handles CRUD via respectivehandle_*
methods. - Encoding: Can be set to "gzip" to enable compression of responses from the channel.
- Data format: Can code in JSON, XML, or HL7 to enable auto de-serialization of requests and responses. Note that the original request, prior to any processing, is always available in
self.request.raw_request
. - Accept header: Decides to which Accept HTTP headers the channel should react. Note that, just like with methods, it is possible for the service to handle each header separately, which lets one have the same service produce different responses depending on what the client's Accept header dictates.
API Service and Security
- Service: The service that is mounted on this channel will be given incoming requests on input, as in the code example above. The same service may be mounted on multiple channels.
- Security definition: Determine the kind of authentication mechanism this channel requires. This can be basic auth, an API key, JWT, Vault, WS-Security, or XPath. Each channel may have a different security definition assigned and the same definition can be assigned to multiple channels. Note that, to avoid any misunderstandings, you need to explicitly choose "No security definition" if the channel should not handle authentication itself, perhaps because the service should do it on its own, i.e. it is not possible to forget to choose something here and leave a channel unsecured by mistake.
- RBAC: A channel can also use Role-Based Access Control definitions, where each API client is assigned fine-grained permissions to individual methods and roles, possibly hierarchical ones, which control if a particular client can invoke the channel's service. For instance, some API clients may be allowed to only read (
GET
) data whereas different ones will be able to create, update, and delete it too (POST
,PATCH
andDELETE
).
Cache
- Cache type: Optionally, this can be set to be either built-in or Memcached. Requests matching previous ones will be automatically served from a specific cache assigned to the channel. With the built-in cache, the response is served from RAM directly, resulting in very fast responses. It is possible to browse, modify, or delete the cached data, as shown below:
Rate Limiting
The rate-limiting stanza was not expanded upon previously, so let's do it now to add a rate-limiting the definition to our channel. As usual, the limits can be set separately for each channel.
In this particular case, clients connecting from the internal network will be allowed to issue 30 requests per minute but connections from localhost can invoke it 50k times an hour. And everyone else is limited to 100 requests a day.
Message Log
Just like with rate limiting, these options were not expanded upon in the initial screenshot so here they are. Each channel can keep N's last messages received, sent, or both. It is also possible to say that only the first X kilobytes of a given message is to be stored – some messages may be too large for it to be practical to keep them in their entirety. When the log becomes full, the oldest messages are discarded, making room for newer ones.
Stored messages can be browsed in the web admin console, immediately letting one know what a given channel receives and sends.
Such a message log is a feature common to other elements of Zato, e.g. HL7 or WebSocket connections have their own message logs too.
Documentation and OpenAPI (WSDL Too)
It is good that we have a REST channel, but how do we let others know about it? On the one hand, you can simply notify your partners and clients through documentation, wiki pages, or other non-automated means.
On the other hand, you can also supply auto-generated API documentation and OpenAPI definitions, like below. This means that API clients matching your services can be generated automatically.
And by the way, if you need a WSDL for SOAP clients, the generated documentation includes it too.
Invoking REST APIs
This is everything about REST channels today but one question may be still open. If channels are means for handling incoming connections then how does one make requests to REST API endpoints external to Zato? How can we invoke other people's microservices?
The answer is that this is what Zato's outgoing connections are for and we will cover REST outgoing connections in a future post.
Learn More
If you are interested in building scalable and reusable API systems, you can start now by visiting the Zato main page, familiarize yourself with the extensive documentation, or jumping straight into the first part of the tutorial.
Be sure to visit our Twitter, GitHub, and Gitter communities too!
Opinions expressed by DZone contributors are their own.
Comments