Your business is unique, and few APIs will offer the exact functionality required by every client. Whether provided by a third-party software vendor, a systems integrator, or an in-house team, you’ll often have to extend the APIs you consume. Common extensions in the commerce space include:
Sending notifications when an event has occurred, like sending an email when an order has been shipped
Capturing additional properties on resources, like capturing a customer’s shoe size at registration
Validating data, like checking user-submitted data for SQL injection attacks
Performing real-time data checks, like making sure inventory is available during checkout
Adjusting the behavior of the API, like changing how prices are calculated
In this chapter, I’ll explain the three different approaches to extending APIs, highlighting which approach is best for which type of extensions.
If you were consuming a legacy commerce platform, you’d essentially be getting two things:
A framework
A bunch of libraries
The framework, platform, or whatever you want to call it often includes some type of extensibility mechanism, allowing you to plug your custom code inside the framework. This is often implemented with IoC.
Libraries are immutable, precompiled pieces of functionality, like JAR files and NPM packages. Libraries are similar to APIs, with the only difference being how the functionality is consumed. With a library, you’re embedding the vendor’s code in your application. With an API, you’re still embedding the vendor’s code in your application but rather than executing locally, it’s executing somewhere else.
Martin Fowler draws the distinction between frameworks and libraries as follows:
Inversion of Control is a key part of what makes a framework different to a library. A library is essentially a set of functions that you can call, these days usually organized into classes. Each call does some work and returns control to the client.
A framework embodies some abstract design, with more behavior built in. In order to use it you need to insert your behavior into various places in the framework either by subclassing or by plugging in your own classes. The framework’s code then calls your code at these points.
Commerce platforms are no longer just something you deploy off to the side of your business. Commerce is your business. Small, vertical teams are building and exposing granular pieces of functionality to the rest of your business, often as microservices.
In this model, there is no longer a single packaged commerce solution providing both the framework and the libraries with a vendor telling you how to extend out-of-the-box functionality.
Let’s explore four different approaches to extending API-based commerce platforms.
Many customizations are simply a matter of collecting additional attributes or defining custom objects. If you sell shoes, you’ll want to capture the shoe size of your customer. If you sell auto parts, you’ll want to capture the make/model/year of the customer’s car. These are all fairly standard requirements that any API-based commerce platform should be able to easily support.
Many extension use cases can be solved through the use of events. An event is essentially a message with a payload—often a JSON or XML-based representation of an object—like an order or a customer profile. What differentiates an event from a message is volume. Traditionally, messaging was limited to passing important bits of data (orders, customer profiles, etc.) between applications. Messaging often used heavyweight protocols like JMS and relied on expensive commercial products.
Eventing is a central characteristic of modern software development, especially microservice-based development. Everything is represented as an event. Individual lines in log files; small changes to orders, customer profiles, and products; container instantiations, API calls, and so on—all are represented as unique events. It’s not uncommon to have millions or tens of millions of events per second in a microservice-based ecosystem.
Once an event is emitted, it must be consumed and passed to custom code that can process it. Here, you have two options.
The first option is to write a small application. Using a small framework like Spring Boot, Play, or Node.js, you can quickly write a small application whose sole responsibility is to pull events and do something. The “something” may be connecting to your backend CRM system and updating a customer record. It may be sending an email to a customer. It may be charging a credit card. While these applications can be easily built, they must be maintained over years or even decades. The development framework you use will need to be upgraded. You’ll inevitibly have to upgrade your continuous delivery pipeline. Docker will continue to change as it matures. Maintaining applications is difficult.
The second option is to use a function-as-a-service/serverless framework. AWS Lambda, Google Cloud Functions, Azure Functions, and others allow you to essentially route specific types of events back to arbitrary functions/methods.
Let’s take an example. A fairly routine requirement is to send an email to the customer when the order has been successfully sent to the OMS. You’d start by defining your AWS Lambda function, as shown in Figure 5-1.
Then you’d write your code, as shown in Figure 5-2.
Then, you’d bind your function to your event (Figure 5-3) so that your code is executed whenever a message is published.
You’d follow a similar approach with the other cloud vendors and their function-as-a-service offerings.
Function-as-a-service works great because it’s simple and it allows different pieces of the application to be changed and deployed at different times. You can change your order confirmation email template without redeploying your entire monolithic application, which is revolutionary for enterprise-level commerce.
To summarize, it’s best to use event-based extensions for:
Asynchronously synchronizing data between systems, often from your commerce platform to legacy backend systems
Sending notification emails
Logging important data for audit purposes
Computationally heavy activities, like generating product recommendations
Rather than the provider of the API publishing events, they may additionally or instead offer webhooks. Webhooks are URLs that the vendor of the API posts data to.
For example, you can often register webhooks for when an order is placed, a product is added, or a customer record is updated. If you register a webhook when an order is placed, the vendor of the API will post the entire order and maybe the customer’s profile to the URL that you define. It’s essentially the same model as events, with the following exceptions:
HTTP requests are often made synchronously, whereas events are often posted asynchronously.
Rather than pulling messages, you have to provide your vendor with a URL to which they can post data.
An event-based model allows any number of consumers to consume an event; webhooks often allow just one URL.
The vendor has to explicitly provide hook points. This often results in fewer hooks being defined. Events are just emitted from the application.
One advantage of webhooks is that it is theoretically possible to post data to backend systems without having to write an intermediary application or serverless function. The problem is that these back-end systems have different authentication and authorization schemes. Sometimes they require VPNs. It’s hard to do a direct post from a third-party vendor’s API to an application that’s not meant to be public.
Webhooks are difficult for API vendors to properly implement. What happens if the callback fails? How often should retries be made? Are calls idempotent? How can these callbacks be monitored by third parties? It’s hard. Vendors are increasingly exposing events, which do not suffer from many of the same problems as webhooks.
Going back to the previous example of using events, you can also define URLs that trigger serverless functions. Figure 5-4 shows an example of how you’d do it with AWS Lambda and AWS API Gateway:
In this example, you’d register https://xatp7l47qh.execute-api.us-west-2.amazonaws.com/prod/SendOrderConfirmationEmail as the webhook URL for the "placeOrderWebhookURL"
property or whatever your vendor defines.
An issue to be aware of is that there is little in terms of standards. If you switch providers, you’ll have to rewrite your functions.
While extending the default object model, events and webhooks solve a wide range of use cases, there is a class of extensions that are inherently harder to implement. Examples include:
Validating data, like checking user-submitted data for SQL injection attacks
Performing real-time data checks, like making sure inventory is available during checkout
Adjusting the behavior of the API, like changing how prices are calculated
All these examples require synchronously executing custom code before or after an API call is made. The process for this flavor of extensions is exactly the same as you’re used to with old third party commerce platforms.
Let’s take inventory as an example. Let’s say you want to perform a real-time inventory check when inventory dips below 100 units.
With a traditional commerce platform, you’d use their IoC framework to extend the out-of-the-box inventory code. Out of the box, a client calling the inventory resource through an SDK would return an instance of com.bigcorp.inventory.Inventory
or whatever the out-of-the-box class from your vendor is. You’d then create com.yourcorp.inventory.CustomInventory
with a method as follows:
public
class
CustomInventory
extends
Inventory
{
public
int
queryInventory
(
String
productId
,
String
skuId
)
{
int
inventory
=
super
.
queryInventory
(
productId
,
skuId
);
if
(
inventory
<
100
)
{
inventory
=
SAPConnection
.
realTimeInventoryLookup
(
productId
,
skuId
);
}
return
inventory
;
}
}
Now, the Inventory
resource resolves back to instantiations of com.yourcorp.inventory.CustomInventory
. Simple.
The difference is that API-based commerce platforms don’t have an IoC framework. You’re consuming APIs from your commerce platform vendor, from third-party vendors (payment, tax, product recommendations, etc.), and from custom applications/microservices that you build in house. There is no longer a single platform—it’s just a bunch of APIs from different sources.
To perform this simple extension, you’d start by using your API vendor’s SDK. Then, pick a small framework like Spring Boot, Play, or Node.js and build a standalone application that queries the API using your vendor’s SDK:
public
class
InventoryService
{
@RequestMapping
(
value
=
"/Inventory/{productId}/{skuId}"
,
method
=
RequestMethod
.
GET
)
public
int
queryInventory
(
@PathVariable
(
"productId"
)
String
productId
,
@PathVariable
(
"skuId"
)
String
skuId
)
{
int
inventory
=
MyVendorSDK
.
getInventoryService
().
queryInventory
(
productId
,
skuId
);
if
(
inventory
<
100
)
{
inventory
=
SAPConnection
.
realTimeInventoryLookup
(
productId
,
skuId
);
}
return
inventory
;
}
}
This example is based on Spring Boot.
Once you’ve defined your application, deploy it behind your API gateway. Clients would then query inventory by accessing https://api.yourcompany.com/Inventory and passing productId
and skuId
as HTTP GET
arguments.
Using these three approaches, you can implement just about any requirement you can think of. And if you can’t, just build a brand-new API backed by a new microservice to accomplish your requirement.
Stepping back, I hope this introduction to APIs gives you enough guidance to get started on your journey. There are a lot of things to think about; but with proper planning, I have no doubt you’ll be able to quickly get started and realize some quick wins.
98.82.120.188