What Are Asynchronous APIs_ (fo - Mike Amundsen
What Are Asynchronous APIs_ (fo - Mike Amundsen
The views expressed in this work are those of the authors and
do not represent the publisher’s views. While the publisher and
the authors have used good faith efforts to ensure that the
information and instructions contained in this work are
accurate, the publisher and the authors disclaim all
responsibility for errors or omissions, including without
limitation responsibility for damages resulting from the use of
or reliance on this work. Use of the information and
instructions contained in this work is at your own risk. If any
code samples or other technology this work contains or
describes is subject to open source licenses or the intellectual
property rights of others, it is your responsibility to ensure that
your use thereof complies with such licenses and/or rights.
978-1-098-14746-4
[LSI]
Preface
Welcome to What Are Asynchronous APIs?
This report gives you all the tools you need to successfully
introduce the powerful asynchronous API pattern to your
organization. More and more applications today offer “instant
feedback,” push notifications, and streaming data. A key
component of these systems is support for asynchronous APIs—
API calls that don’t rely on the RESTful client-server model but,
instead, support servers that push notifications and data
directly to the client as soon as it is available. This push-style
interaction is at the heart of asynchronous APIs.
Acknowledgments
We want to thank everyone who read early copies of this report
and provided valuable feedback. We especially want to thank
Fran Mendez and Lukasz Dynowski for their help in improving
the text. Thanks also to two great O’Reilly editors, Rita Fernando
and Louise Corrigan, for all their assistance in bringing this
report to life.
Chapter 1. Understanding
Asynchronous APIs
To start, this chapter will cover the basics of Application
Programming Interfaces (APIs) in general and specifically
asynchronous APIs. We’ll touch on the difference between
synchronous protocols like Hypertext Transfer Protocol (HTTP)
and asynchronous protocols like MQ Telemetry Transport
(MQTT), and we’ll introduce the idea of them representing
either RESTful (HTTP) or EVENTful (MQTT) types of
implementations.
EVENTFUL?
TIP
It is worth noting that the first element of the Ajax pattern is the use of asynchronous
requests. Ajax was used to connect with RESTful APIs initially, but soon people got
the idea that services could be designed to support asynchronous APIs, too.
Fifteen years ago, the initial use of APIs in browsers (via Ajax)
assumed the use of asynchronous interactions. However, today
most API calls are still designed and implemented using
another pattern—the synchronous REST (REpresentational
State Transfer) pattern defined by Chapter 5 of Roy Fielding’s
PhD dissertation a few years earlier (2000). However, the use of
asynchronous API implementations has grown steadily over the
years and is now just as prevalent as synchronous APIs.
RESTful Messaging
At the HTTP level, the response could look something like this:
200 OK HTTP/1.1
Content-Length: XXX
Content-Type: application/json
Date: Tue, 04 Apr 2023 17:38:41 GMT
{
"total_made" : 23,
"shoe_list" : [
{
"shoe_id" : "q1w2e3r4",
"customer_id" : "j_smith",
"store_id" : "y67ut5",
"store_name" : "South Jersey Feet",
"date_time" : "2023-03-13 12:00:00"
},
{
"shoe_id" : "p0o9i8u7",
"customer_id" : "m_dingle",
"store_id" : "r45tef",
"store_name" : "East North Winglet Footwear",
"date_time" : "2023-03-13 13:00:00"
},
...
]
}
Now, any client application (for example, the client app running
at the “South Jersey Feet” store) can initiate a request to the
home office server and get an immediate response. Although
not covered here, there is probably an option that allows the
store to send a request that returns only information about the
day’s production for that particular store. To get the status of all
the orders for just the “South Jersey Feet” store, they might send
an API request like this:
http://sportasua.example.com/shoes-made/?store_id=q1w
This works well when all the information you need is in a single
location (the home office server) and you can build and run
client applications that know where the server is
( http://sportasua.example.com ) and are able to make
repeated requests throughout the day to get updates. And it is
important that you run the client application frequently if you
want the most recent information on the shoe production for
the day. You won’t know how things are going unless you “ask
the server.” In this case, even if the custom order for j_smith
was completed early in the day, the local store will not know
that until they send a request to the server for an updated list.
Of course, if it is not a very busy day at the factory, you might be
making lots of requests that keep returning the same data over
and over again.
EVENTful Messaging
{
"metadata" : {
"message_source" : "factory_1",
"event-type" : "shoe_completed"
},
"message" : {
"shoe_id" : "q1w2e3r4",
"customer_id" : "j_smith",
"store_id" : "y67ut5",
"store_name" : "South Jersey Feet",
"date_time" : "2023-03-13 12:00:00"
}
}
TIP
RESTful systems generate messages when clients ask for data. EVENTful systems
generate messages when the data changes.
The example message we just saw is typically referred to as an
“object message.” This is not the only type of message EVENTful
systems can generate. And we’ll cover that next.
NOTE
To learn more, check out the excellent article “What Do You Mean by ‘Event-
Driven’?” by Martin Fowler.
{
"metadata" : {
"event-type" : "user_login",
"message_source" : "website",
},
"message" : {
"user_id" : "j_smith",
"date_time" : "2023-03-13 12:00:00",
"details" :
"https://sportasua.example.com/notifications/p1
}
}
Now, when a user logs into the site, this notification can alert
different parts of the organization (sales, marketing, security)
and, if appropriate, take some follow-up action. If they wish,
they can follow the details link to find all the related data for
this event.
TIP
Notification messages are handy when you want to know what just happened but
may not need all the details right away.
Event-Carried State
Notification messages are fine for cases where you just need the
bare minimum of information to appear in a kind of alert
status. But what about cases where you want to actually pass a
block of data in order to write it to a data store or update an
existing data collection? For that, you can use the Event-Carried
State Transfer (ECS) type messages.
{
"metadata" : {
"event-type" : "order_summary",
"message_source" : "storage",
"date_time" : "2023-03-13 14:13:12"
},
"message" : {
"customer" : {
"user_id" : "j_smith",
"user_email" : "[email protected]",
"user_voice" : "123-456-7890"
},
"order" : {
"order_id" : "p0o9i8",
"order_date" : "2023-03-11 13:13:13",
"order_type" : "Executive Trainer",
"order_size" : "12D"
},
"progress" : {
"tracking_id" : "a1w2d3",
"start_date" : "2023-03-12 10:11:12",
"stage" : "cutting",
"status" : "working"
}
}
}
TIP
The ECS pattern works for cases where you need to send a large collection of
properties and want to carry all the related information in a single call.
Event-Source
...
{"order_id" : "p0o9i8", "stage" : "cutting" ,
"status":"started", "time" : "2023-03-13 10:11:12"}
{"order_id" : "p0o9i8", "stage" : "cutting",
"status":"completed", "time" : "2023-03-13 10:14:13
{" d id" " 0 9i8" " t " " lt" " t t " "
{"order_id" : "p0o9i8", "stage" : "welt", "status":"s
"time" : "2023-03-13 10:14:15"}
{"order_id" : "p0o9i8", "stage" : "welt", "status":"c
"time" : "2023-03-13 10:16:15"}
...
TIP
The streaming message approach works well when you have lots of events to keep
track of and you want to create a log of all those events for later reference.
There are also times when event messages are sent without
being caused by a command. For example, when a shoebox on
our Sportasua assembly line has crossed an assembly line
sensor on its way to shipping, that might trigger an event
message (e.g., “order p0o9i8 has been sent to shipping”). Or an
event message might be triggered when a customer walks into
the shop. In a way, you might consider these external actions
(walking into the shop, passing an assembly line sensor) as
commands, too.
When you are designing your EVENTful API system, you need to
identify the events you wish to keep track of and also define the
commands you want to initiate.
Identifying Events
You can also identify events that indicate there is some problem
in your processes. For example:
TIP
Notice that events are always described as happening in the past. The above
examples use words such as placed, logged in, and was quite a bit. It is a good habit to
always write your event descriptions as something that has already happened.
You can put together a simple table that will list the key
information about each activity including eventName ,
messageType , the conditions that cause that event to occur,
as well as an example message and any other notes . This can
be collected in a spreadsheet as part of your design process and
shared with designers, developers, and architects as needed.
Defining Commands
As mentioned earlier, In EVENTful systems, events tell you what
has already happened in the system and commands are a way
to tell parts of the system to do something in the future. That
future might be what we think of as “right now.” For example, a
command message that changes the status of an order from
unpaid to paid might be recorded almost instantly. However,
if there is quite a bit of payment processing going on at the
moment, the command might take a few seconds or more to
complete. And, if there was a problem verifying payment with
associated banks, the status change for some orders might take
an hour or even longer.
NOTE
In EVENTful systems, we can’t be sure when some future event will happen, but we
can be sure it will happen. And when it does happen, there will likely be an event that
tells us the action has occurred.
Summary
In this chapter, we’ve covered the basics of an EVENTful API
system, one that is designed to pass messages between
machines based on events that happened in the past and
commands that will happen in the future. You learned that
there are three types of messages (notifications, Event-Carried
State, and Event-Source) and when it is appropriate to use each
one. You also learned how to identify and define events and
commands and record them in a spreadsheet to make it easy
for others on the team to know what is needed to build your
EVENTful system.
Why AsyncAPI?
APIs enable software to perform jobs and access data by
providing an interface. Well-designed APIs make it easier for
developers to write code that uses that interface. Good API
design includes using words that developers understand and
describing functions that fit the developer’s jobs to be done. But,
along with these design concerns, designers need a way to
clearly describe what their API does in a meaningful, easy-to-
consume manner. That’s where API description languages help.
But, OAS isn’t a great fit for the EVENTful style of APIs we
described in the previous chapter. As of version 3.0, one of the
biggest limitations of OAS for event-based integration is that it
is very tightly bound to the HTTP protocol. When you describe
an API in OAS, you are expected to use URIs, HTTP methods,
and HTTP status codes. This works very well for RESTful web
APIs, but as we discovered in Chapter 1, event implementations
are often run in local networks and HTTP is only one of the
protocols that are commonly chosen.
NOTE
To help bring our tour of the AsyncAPI specification to life, we’ll use an example
from our shoe store application throughout this chapter. You can find the full source
for this AsyncAPI description at https://oreil.ly/dz_2F.
TIP
To learn more about the AsyncAPI specification and its parts, we recommend that
you start with the excellent AsyncAPI documentation at https://oreil.ly/33KDG.
With the target flow for our ordering process in hand, let’s
define who will use the interface and what we think they’ll
want to achieve.
Domain-Centric
When you take a domain-centric API perspective, you describe
all of the related events within a single logical boundary (for
example, sales order fulfillment). But, you describe the events
without tying those descriptions to a particular application or
system. For example, we could describe our sales order
fulfillment API in terms of the events for order placement and
order bidding generally, without any reference to the web
applications, sales systems, or shipping systems that we expect
to use them. This is useful for situations where we want to build
a centralized, shared, domain-based view of our world, with
less dependence on the software systems that need to operate
within it.
Application-Centric
TIP
If you want to get hands-on with AsyncAPI development as we walk through these
sections, the AsyncAPI team provides an online editor that you can use.
Our first step will be to define the version of the AsyncAPI
specification we are using by populating a required asyncapi
field. We’ll also define the metadata for our API design using an
object called info , as shown in the following example:
asyncapi: '2.6.0'
info:
title: Shoe Sales System Order Fulfillment API
version: '1.0.0'
description: |
Describes purchase order and supply chain events
Sales System.
In this example, you can see that we’ve identified the AsyncAPI
version as 2.6.0. We’ve also populated the info object with the
scope and purpose of the API. These metadata details are useful
for people reading the AsyncAPI document and can be used by
document generation tools and API portals. We’ve used a very
simple example here, but the more descriptive you can be in
this section, the better your EVENTful reference documentation
will become.
Now that we have the context and scope of our API defined, we
can move onto the next big decision: choosing our transport
protocol and system.
servers:
dev:
url: test.mykafkacluster.org:8092
protocol: kafka-secure
description: Test broker
production:
url: mykafkacluster.org:8092
protocol: kafka-secure
description: Production broker
NOTE
JSON Schema is a standardized way of describing structure and data types for JSON
objects, using the JSON language. You can read more about JSON Schema at
https://oreil.ly/WRgs3.
asyncapi: "2.6.0"
info: ... info details ...
servers: ... server details ...
components:
schemas:
...
orderReceivedPayload:
description: A sales order event that can be us
trigger sourcing and procurement processes
type: object
properties:
salesOrder:
$ref: "#/components/schemas/salesOrder"
description: A sales order that contains th
customer's identifier, the items they hav
payment status, and shipping details.
sourcingPreferences:
type: object
properties:
preferredSupplierIds:
description: A list of supplier IDs tha
identified as preferred suppliers for
type: array
items:
type: string
deadline:
description: The deadline that supplier
meet before a sourcing decision is ma
type: string
format: date-time
sourcingBidPayload:
description: A bid request made by a supplier a
the sourcing process
type: object
properties:
orderId:
type: string
supplierId:
type: string
bidExpiry:
type: string
format: date-time
bidPrice:
$ref: "#/components/schemas/money"
conditions:
type: string
...
As you can see from this snippet, we’ve defined two payload
components: orderReceivedPayload and
sourcingBidPayload . We haven’t yet defined how these
payloads will be used, but by defining them as components, we
can make that decision later. The payloads have been defined
using a structure that is inspired by JSON Schema and is fairly
easy to understand. For example, we can see that the order
received message will be represented as a JSON object and will
have salesOrder and sourcingPreferences properties. But,
notice that the salesOrder property contains a slightly cryptic
$ref property within it.
asyncapi: "2.6.0"
info: ... info details ...
servers: ... server details ...
components:
messages:
orderReceived:
name: orderReceived
summary: Sales order details for an order tha
bidOnOrder:
name: bidOnOrder
summary: A supplier bid message to fulfill an
awaiting fulfillment
contentType: application/json
payload:
$ref: "#/components/schemas/sourcingBidPayl
$ref: #/components/schemas/sourcingBidPayl
schemas:
orderReceivedPayload: ...
sourcingBidPayload: ...
NOTE
To save space, we haven’t included the payload examples in the text of this report.
But, you can view our example payloads in the reference example.
TIP
This language of channels is changing in the upcoming version 3.0 of the AsyncAPI
specification, including a redefinition of the publish and subscribe operations.
asyncapi: '2.6.0'
info: .... info details ...
info: .... info details ...
servers: ... server details ...
channels:
shoesales/orders:
subscribe:
message:
$ref: '#/components/messages/orderReceived'
shoesales/bids:
publish:
message:
$ref: '#/components/messages/bidOnOrder'
Summary
In this chapter, we designed an async API for an example order
fulfillment process for our fictional Sportasua store. We started
by identifying the scope for our API based on the teams that
would use it and their jobs to be done. We introduced the
AsyncAPI description language and started to use it to
document our API design. Then, we defined the data, messages,
and channel binding for the API design using the AsyncAPI
format.
This shift of focus from future tense to past tense can be jarring
for RESTful API designers, but it is the key to EVENTful
architecture thinking. Event messages reflect things that have
already happened and they cannot be changed. Another way of
saying this is that events are immutable.
Over the last 15 years, there has been an explosion of tools for
the RESTful API ecosystem. Now, as asynchronous APIs gain
popularity, we are seeing a similar growth and evolution in the
tooling system. An important first task for any asynchronous
company is to choose the right set of tools that can drive the
right set of outputs. Asynchronous API tools usually align with
the stages and focus areas of software development, such as
design, engineering, testing, governance, security, and
observation.
The challenge with tooling is that you may end up with too
many of them. Tools are becoming increasingly specialized but
can cause your teams big problems if they don’t work well
together. Thankfully, specification formats like OpenAPI and
AsyncAPI create a common, standardized language that helps
tools integrate with each other. As the AsyncAPI specification
continues to gain popularity, we expect more tool developers to
adopt it as a universal language for describing asynchronous
APIs. This will in turn accelerate the growth of the EVENTful
tooling ecosystem. We expect the EVENTful tooling landscape to
evolve dramatically over the coming years.
For as long as there has been APIs, there has been a problem of
finding the right API to do a specific job. This problem persists
in the EVENTful world—as adoption of asynchronous APIs
increases in your organization, you’ll soon find that
implementers are unable to discover the events that could help
them. This discovery need happens at design time, when teams
are building software, as well as at run time, when software is
responding to events. For incumbent organizations, the main
challenge here is that API catalog, portals, and discovery tools
usually already exist, but they lack the ability to serve metadata
that is unique to events. Some API management vendors are
starting to incorporate EVENTful APIs in their turnkey portal
solutions. Other vendors are focusing on the event space and
will sell you specialized asynchronous API portals. You’ll need
to make a decision on whether to implement a holistic, wide-
ranging portal or provide a set of specialized catalogues. The
good news is that the switching costs are not usually too high,
so you can always change your mind down the road and
migrate your API catalogue data into a better solution when it
arises. We’ll highlight an example of an event discovery
approach later in this chapter when we talk about asset
catalogues.
Adding Elements
Often you can define new messages and events and wait for
messaging clients to start consuming them. However, there may
be cases where you want to make sure some new events are not
ignored by client applications. That means you need to
coordinate releases a bit, but you can at least add the events
before the clients start to subscribe to them.
Handling Modifications
{
"context": {
"source" : "shop_floor",
"type" : "shoeDetail"
},
"event": {
"shoe_id" : "p0o9i8u7",
"customer_id" : "jsmith",
"size" : "12.5",
"model" : "Executive Trainer",
"color" : "maroon",
"status" : "welt_station",
"cobbler" : "mgeppetto"
}
}
{
"context": {
"source" : "shop_floor",
"type" : "shoeDetail_2"
},
"event": {
"shoe" : {
"shoe_id" : "p0o9i8u7",
"size" : "12.5",
"model" : "Executive Trainer",
"color" : "maroon"
},
"customer_id" : "jsmith",
"status" : "welt_station",
"boot_maker" : "mgeppetto"
}
}
This asset catalog works well for small collections but can get a
bit hard to maintain as your system grows over time. Another
handy approach is to create a more detailed, searchable
document that makes it easier to navigate from messages to
events to commands and so forth in a single interface.
Note that the asset catalog is not the same thing as the AsyncAPI document. The
catalog is a free-standing list of EVENTful assets (messages, events, and commands).
The AsyncAPI document is a design document that details how some of those assets
are arranged into a solution. You’ll need to maintain updated versions of both kinds
of documentation throughout the life of your system.
Summary
In this chapter, we’ve explored some of the design and
governance details of supporting a robust EVENTful API
architecture. We reviewed how to properly integrate
asynchronous APIs into an existing infrastructure (see
“Integrating Async APIs into Your Company”) by adopting a
kind of “event-oriented” thinking and introducing tooling as an
enabler.