Skip to content
Art2link ESB v2.02 LTS HomeDocumentationBlogContact
Tutorials/API scenarios/Stock availability lookup

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.

Ask the stock count, wait for the answer Shopper’s page waiting to add how many in stock? tag: this shopper Look up the count in the stock records Stock records 106 available, to this shopper

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.

EXPRESSIONsubscription expression
{{Message.MessageType}} == "StockQuery"
RESPONSE SUBSCRIPTIONresponse match
{{Promoted.StockAnswer.RequestId}} == {{Variable.RequestId}}
Storefront POST {sku} 200 qty API Listener (two-way) holds the connection, RequestId → variable StockQuery StockAnswer BUS SQL Caller (two-way) availability query Inventory DB

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.

EXPRESSIONfailure subscription
{{Message.MessageType}} == "StockQueryFailed"
Reserve the synchronous shape for questions. A lookup is a natural fit: small, fast, and the caller genuinely cannot proceed without the answer. Order placement is not, accept it one-way as in order intake and let delivery happen on the bus's time.

Build it, step by step. The steps run in dependency order: every object is created before the object that selects it.

Before you start. Create the Application this flow lives in, a Name plus a code-safe Namespace, here AcmeOrders, under Applications, and select it, so every artifact you create below lands inside it. Create the Variable RequestId on it as well; Step 5 assigns it per request.
About SqlCallerResult. The SQL Caller hands the response map a result under the standard type name SqlCallerResult. Its format is whatever the procedure’s final SELECT emits, not a fixed envelope: a plain SELECT or FOR JSON returns JSON, FOR XML returns XML. The lookup procedure (Step 3) ends in FOR JSON, so the result is JSON and the map’s source is JSON (Step 4). You do not create this type by hand; the SQL Caller provides it. If you prefer it listed, add a message type SqlCallerResult with format JSON.
1
Step One
Create the message types
The three types

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:

NameFormatPurpose
StockQueryJSONthe question, published on arrival
StockAnswerJSONthe answer the lookup republishes (Step 6)
StockQueryFailedJSONthe 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.

Promotions on StockQuery
PromotionPath
RequestId$.requestId
Sku$.sku
Promotion on StockAnswer

The answer carries the token back; this is what the response matching will route on:

PromotionPath
RequestId$.requestId
The request and the answer

The lookup request the storefront POSTs, a StockQuery:

JSONstock query request
{
  "requestId": "9f4c1e8a",
  "sku": "TP-4501"
}

The answer that comes back, a StockAnswer:

JSONstock answer
{
  "requestId": "9f4c1e8a",
  "sku": "TP-4501",
  "quantity": 106
}

2
Step Two
Create the Authentications

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.

Storefront credential, for the API Listener
SettingValue
NameStorefrontToken
AdapterAPI Listener
DefinitionAPI Listener Token
Token (Partner Config)the secret the storefront will present on every call
Inventory database, for the SQL Caller
SettingValue
NameInventoryDb
AdapterSQL Caller
DefinitionSQL Server Connection
Connection String (Database Config)the inventory database’s connection string, credentials included
Alert mailbox, for O365 Mail
SettingValue
NameO365Ops
AdapterO365 Mail Sender
DefinitionMicrosoft Graph
Tenant Id / Client Id / Client Secret (Graph AuthConfig)an app registration with Mail.Send granted

3
Step Three
Create the database objects

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.

The inventory table and lookup procedure
SQLinventory schema
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;

4
Step Four
Create the response map
Create the map

Under the application’s Maps, create the map the lookup port (Step 6) selects on its response side:

Map settingValue
NameResultToStockAnswer
SourceSqlCallerResult (JSON), the SQL Caller’s result; this query ends in FOR JSON (Step 3), so the result is JSON
TargetStockAnswer, from Step 1
The result

Because the procedure’s SELECT ends in FOR JSON PATH, WITHOUT_ARRAY_WRAPPER, the single row comes back as one JSON object:

JSONsql caller result
{ "RequestId": "9f4c1e8a", "Sku": "TP-4501", "Quantity": 106 }
The SQL Caller returns what the SELECT asks for. This query ends in FOR JSON, so the result is JSON, and the map’s source is JSON to match. A plain SELECT would also return JSON; FOR XML would return XML. If the map’s source format disagrees with what the SELECT emits, the map matches nothing and the message republishes empty.
Author the XSLT

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:

XSLT 3.0response map
<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>

5
Step Five
Create the two-way Listener
General

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.)

Declare the routing headers
HeaderValueWhy
AppAcmeOrdersthe Application’s namespace, narrows the request to this Application
PurposeStockLookupkeeps 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.

Set the fallback type

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.

Open Variables

Assign the correlation variable, Variable.RequestId ← {{Message.Body}}.JPath("$.requestId").

Response side

Set the Response Subscription Expression on the response side’s Subscription tab: {{Promoted.StockAnswer.RequestId}} == {{Variable.RequestId}}.


6
Step Six
Create the lookup port
General

Create a send port StockAvailability on the SQL Caller, two-way, subscribing on {{Message.MessageType}} == "StockQuery".

SettingValue
AdapterSQL Caller
WayTwo
Auth ConfigInventoryDb, from Step 2
Command TypeStoredProcedure
Command Textdbo.GetAvailability
Input Parameters

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:

ParameterValue
RequestId{{Promoted.StockQuery.RequestId}}
Sku{{Promoted.StockQuery.Sku}}
Response side

On the response side, select ResultToStockAnswer, from Step 4.


7
Step Seven
Wire the failure path
Set the Exception Message Type

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 send port

Create the OpsAlert O365 Mail port subscribing on {{Message.MessageType}} == "StockQueryFailed":

SettingValue
AuthenticationO365Ops, from Step 2
Fromnoreply@acme.example
Toops@acme.example
SubjectStock query failed

8
Step Eight
Start the ports and test
Start in dependency order

Everything you configure is live the moment you save it, there is nothing to deploy. Start the send ports first, then the receive ports.

Turn on tracking

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

Call the endpoint with a known SKU, and confirm the quantity comes back on the same connection.

Fire concurrent requests

Fire several concurrent requests with distinct request ids and verify each caller receives its own answer.

Break it on purpose

Stop the database: the caller should get the timeout status (typically 504) once the wait window lapses, and operations should get the StockQueryFailed alert.

Turn bodies off in production. Enabled + Body is the most expensive tracking level, extra processing and database space, and it records every payload including successful runs. Once the flow is proven, set the ports to Only on Error instead: failures still capture the full body for diagnosis, while healthy runs are not recorded.