Stock availability lookup
A shopper is on Acme’s website about to click "Add to basket", and before that button shows, the storefront wants a straight answer: how many of this item are in stock right now? It asks Acme, waits a moment, and gets back a number from the warehouse’s records. The catch is that hundreds of shoppers can be asking at the same instant, so each answer has to find its way back to the exact shopper who asked, never to the next one in line. And because the shopper is waiting, a slow or broken lookup cannot hang the page: if no answer comes back in time, the site says "check availability later" and moves on.
So this is a question-and-answer over a single open line. The storefront sends its question and holds on. Acme looks the number up in the stock records and sends it straight back down the same line, to the right shopper. The whole point is that the caller is waiting for the reply, which is different from most of the flows in these tutorials, where work is handed off and the sender walks away.
Here is how Art2link ESB builds it. Everything runs on the bus, the shared message backbone that flows publish to and subscribe from. The storefront posts a small lookup request carrying a request id (the tag that names this shopper’s question) and a SKU (the item code). A two-way API Listener receive port takes the call (a receive port is the entry point that brings work onto the bus; an adapter is the connector for one kind of system, here an inbound web call, and "two-way" means it keeps the connection open to send a reply back). It publishes the question as a StockQuery, a message type, which is simply a name plus a format, and remembers the request id in a Variable, a named slot that holds a value for this one request, before holding the line open. A two-way SQL Caller send port (a send port delivers a message out, here to the database that holds stock levels) subscribes to the query, runs dbo.GetAvailability, and gets back one row with the request id, SKU, and quantity. A map (a small transform that reshapes one message into another) turns that row into a StockAnswer, still carrying the tag, and publishes it. The Listener’s Response Subscription Expression closes the loop: it is the rule that picks the matching answer off the bus and sends it back to the waiting shopper.
{{Message.MessageType}} == "StockQuery"
{{Promoted.StockAnswer.RequestId}} == {{Variable.RequestId}}
The correlation token matters because the bus is shared: ten storefronts can be asking at once, and ten StockAnswer messages will be in flight. The expression's reference to the request's own {{Variable.RequestId}} is what guarantees each caller gets their answer and not the next one off the bus.
When it fails. The caller is waiting, so failure must be fast and explicit. If no response matches before the Listener’s wait window lapses, it releases the connection with a timeout status (typically 504), the storefront degrades to "check availability later" instead of hanging. The SQL port carries exception type StockQueryFailed with the standard O365 Mail operations subscription, so a dead inventory database pages someone the first minute, not after the bounce rate spikes. The wait window is platform-controlled today, a configurable per-port request timeout is planned; either way, a lookup the storefront abandoned is not worth retrying for five minutes.
{{Message.MessageType}} == "StockQueryFailed"
Build it, step by step. The steps run in dependency order: every object is created before the object that selects it.
Under the application’s Message types, create the three types this flow routes on. A message type is a name plus a format, no schema required:
| Name | Format | Purpose |
|---|---|---|
| StockQuery | JSON | the question, published on arrival |
| StockAnswer | JSON | the answer the lookup republishes (Step 6) |
| StockQueryFailed | JSON | the exception type the failure path publishes under, payload intact (Step 7) |
Promotions live on the message type, so add them while you are here. The lookup port’s parameters (Step 6) and the Listener’s response matching (Step 5) bind these values, and adapter parameters are plain strings: they bind {{…}} tokens but never evaluate a body path.
| Promotion | Path |
|---|---|
| RequestId | $.requestId |
| Sku | $.sku |
The answer carries the token back; this is what the response matching will route on:
| Promotion | Path |
|---|---|
| RequestId | $.requestId |
The lookup request the storefront POSTs, a StockQuery:
{
"requestId": "9f4c1e8a",
"sku": "TP-4501"
}The answer that comes back, a StockAnswer:
{
"requestId": "9f4c1e8a",
"sku": "TP-4501",
"quantity": 106
}Three external parties, three credentials. Under the application’s Authentications, the dialog asks for a Name, the Adapter it pairs with, and a Definition, the credential shape that adapter offers, with the Application preset; the Definition decides the config section that follows.
| Setting | Value |
|---|---|
| Name | StorefrontToken |
| Adapter | API Listener |
| Definition | API Listener Token |
| Token (Partner Config) | the secret the storefront will present on every call |
| Setting | Value |
|---|---|
| Name | InventoryDb |
| Adapter | SQL Caller |
| Definition | SQL Server Connection |
| Connection String (Database Config) | the inventory database’s connection string, credentials included |
| Setting | Value |
|---|---|
| Name | O365Ops |
| Adapter | O365 Mail Sender |
| Definition | Microsoft Graph |
| Tenant Id / Client Id / Client Secret (Graph AuthConfig) | an app registration with Mail.Send granted |
Create the table and procedure in your inventory database; the lookup port (Step 6) calls the procedure. It returns the request id alongside the quantity; that echoed RequestId is what the Response Subscription matches on. The SELECT ends in FOR JSON PATH, WITHOUT_ARRAY_WRAPPER so the single row comes back as one JSON object, the shape the response map (Step 4) consumes.
CREATE TABLE dbo.Inventory ( Sku VARCHAR(40) NOT NULL PRIMARY KEY, Available INT NOT NULL ); CREATE OR ALTER PROCEDURE dbo.GetAvailability @RequestId VARCHAR(40), @Sku VARCHAR(40) AS BEGIN SET NOCOUNT ON; SELECT @RequestId AS RequestId, @Sku AS Sku, ISNULL((SELECT Available FROM dbo.Inventory WHERE Sku = @Sku), 0) AS Quantity FOR JSON PATH, WITHOUT_ARRAY_WRAPPER; END;
Under the application’s Maps, create the map the lookup port (Step 6) selects on its response side:
| Map setting | Value |
|---|---|
| Name | ResultToStockAnswer |
| Source | SqlCallerResult (JSON), the SQL Caller’s result; this query ends in FOR JSON (Step 3), so the result is JSON |
| Target | StockAnswer, from Step 1 |
Because the procedure’s SELECT ends in FOR JSON PATH, WITHOUT_ARRAY_WRAPPER, the single row comes back as one JSON object:
{ "RequestId": "9f4c1e8a", "Sku": "TP-4501", "Quantity": 106 }Maps are authored in XSLT 3.0 and run on Saxon HE 12.9. JSON in, JSON out: parse-json(.) reads the result, the ?key operator pulls each field, and the answer carries the request id so the Listener can match it to the waiting caller:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/"> <xsl:variable name="r" select="parse-json(.)"/> <xsl:value-of select="serialize( map { 'requestId': string($r?RequestId), 'sku': string($r?Sku), 'quantity': number($r?Quantity) }, map { 'method': 'json', 'indent': true() })"/> </xsl:template> </xsl:stylesheet>
Create a receive port StockLookup on the API Listener, two-way mode, paired with StorefrontToken, from Step 2. (The dropdowns can also create types and maps inline; building the dependencies first keeps each dialog a selection.)
| Header | Value | Why |
|---|---|---|
| App | AcmeOrders | the Application’s namespace, narrows the request to this Application |
| Purpose | StockLookup | keeps this Listener unique within the Application |
The shared API ingress routes on these plus the Authentication; the Purpose value keeps this Listener distinct from any other in the same Application, see order intake for the full rule. Note it can share the App namespace with order intake precisely because its Purpose differs.
On the Publish step under the port’s Solicit stages, set Message Type Fallback to StockQuery, from Step 1, so the posted body classifies at the start of the inbound flow.
Assign the correlation variable, Variable.RequestId ← {{Message.Body}}.JPath("$.requestId").
Set the Response Subscription Expression on the response side’s Subscription tab: {{Promoted.StockAnswer.RequestId}} == {{Variable.RequestId}}.
Create a send port StockAvailability on the SQL Caller, two-way, subscribing on {{Message.MessageType}} == "StockQuery".
| Setting | Value |
|---|---|
| Adapter | SQL Caller |
| Way | Two |
| Auth Config | InventoryDb, from Step 2 |
| Command Type | StoredProcedure |
| Command Text | dbo.GetAvailability |
The procedure from Step 3 takes the request id and the SKU, binding the promotions defined on StockQuery in Step 1, one key/value row each:
| Parameter | Value |
|---|---|
| RequestId | {{Promoted.StockQuery.RequestId}} |
| Sku | {{Promoted.StockQuery.Sku}} |
On the response side, select ResultToStockAnswer, from Step 4.
On StockAvailability, set Exception Message Type, on the port’s General step, to StockQueryFailed, from Step 1, and keep retries minimal, the caller is waiting.
Create the OpsAlert O365 Mail port subscribing on {{Message.MessageType}} == "StockQueryFailed":
| Setting | Value |
|---|---|
| Authentication | O365Ops, from Step 2 |
| From | noreply@acme.example |
| To | ops@acme.example |
| Subject | Stock query failed |
Everything you configure is live the moment you save it, there is nothing to deploy. Start the send ports first, then the receive ports.
Set Tracking to Enabled + Body on every port the flow touches, so you can walk each run step by step, with its message body, in tracking.
Call the endpoint with a known SKU, and confirm the quantity comes back on the same connection.
Fire several concurrent requests with distinct request ids and verify each caller receives its own answer.
Stop the database: the caller should get the timeout status (typically 504) once the wait window lapses, and operations should get the StockQueryFailed alert.