Give the Contract a Working Route
Describe a small order command with RAML, map it through APIkit and test malformed requests before persistence.
The mobile team expects total to be a number and currency to identify its unit. The flow returns "32.00" and omits currency. Both teams can point to an example they were given, and neither example says what an absent order looks like. 32 and 32.00 can represent the same numeric value — if a receipt needs formatted text, give that text its own role.
The small request table in chapter 7 let us agree on behavior before choosing a specification syntax. RAML now gives those decisions a file that tooling can read: one contract and one implementation path here, with the equivalent OpenAPI reference kept in an appendix.
The public command names an order and its items. It does not accept a price. The service will obtain prices from its catalogue in the next checkpoint.

Open this checkpoint in ACB
Stop the previous local application, then use File → Open Folder to open book/checkpoints/13-apikit-contract. Open src/main/mule/app.xml and use Flow List to select the named flow for each example. Choose Run and Debug → Run Mule Application and wait for deployment. Save canvas edits and use Save and Hot-deploy to Local Runtime before repeating a request.
Run python3 book/run.py verify 13 from the companion root to exercise this running checkpoint with its synthetic fixtures. The verifier supplies requests and checks results; it does not start the ACB application. Keep the editor on this checkpoint while reading a failure so an old deployment cannot supply a misleading answer.
Write the command shape
Example 033 — Describe the local order API
Open src/main/resources/api/orders.raml in Explorer. RAML is an API contract document, so edit it in ACB’s text editor rather than the Mule flow canvas. The complete source is in checkpoints/13-apikit-contract/src/main/resources/api/orders.raml.
#%RAML 1.0
title: Local Orders API
version: v1
mediaType: application/json
types:
OrderCommand:
type: object
additionalProperties: false
properties:
orderId:
type: string
pattern: ^[A-Za-z0-9-]{1,40}$
items:
type: array
minItems: 1
maxItems: 20
items:
type: object
additionalProperties: false
properties:
sku:
type: string
enum: [PEN-01, PAD-22, CLP-08]
qty:
type: integer
minimum: 1
maximum: 1000
/orders:
post:
body:
application/json:
type: OrderCommand
responses:
201:
body:
application/json:
type: object
400:
409:
503:
/{orderId}:
uriParameters:
orderId:
type: string
pattern: ^[A-Za-z0-9-]{1,40}$
get:
responses:
200:
body:
application/json:
type: object
404:
OrderCommand is an object with required orderId and items properties — properties declared in a RAML type are required unless marked otherwise, and optionality and nullability are separate decisions, since a field can be omitted, present with a value, or explicitly permitted to contain null. The order identifier has a restricted spelling and length. The items array must contain between one and twenty entries. Each entry has a known SKU and a positive integer quantity no greater than 1000.
additionalProperties: false rejects fields outside the declared shape. At the item level this prevents a caller from adding price and assuming it determines the amount. The enum is suitable for this three-product teaching catalogue. A larger live catalogue would need a product-existence check against its actual source rather than a permanently embedded list in the specification.
The resource tree declares POST /orders and GET /orders/{orderId}. Responses are part of the contract too, and the declared 503 earns its place: a provider that cannot reach its database has no way to know whether A-1001 exists, so turning that timeout into a 404 would make a transient outage look like a factual answer. The current object response declarations are intentionally broad while we assemble the service; tighten them with the final representation and provider tests when making a consumer-facing release.

APIkit connection with Api resource and response-variable names.
Connect the specification to named flows
Example 034 — Map the contract to implementation flows
The complete source is in checkpoints/13-apikit-contract/src/main/mule/app.xml.
Open Global Configurations in the canvas toolbar and inspect the APIkit configuration named Orders_API. Its API resource is api/orders.raml; HTTP Status Variable Name is httpStatus, and Outbound Headers Map Name is outboundHeaders.
The supplied configuration contains these explicit flow mappings:
| Resource | Action | Content type | Implementation flow |
|---|---|---|---|
/orders | post | application/json | create-order |
/orders/{orderId} | get | — | get-order |
Keep the existing mappings while learning the routing path. Select each implementation in Flow List to inspect its behavior. Generating a second scaffold into this project can create competing definitions; use a new project if you want to practice ACB’s Implement an API Specification workflow.
The mappings name create-order and get-order. That makes routing explicit instead of depending on a reader to infer a generated flow name. The configuration also names the variables used for HTTP status and outbound headers. The pinned runtime required the outbound-header-map name during schema parsing, so the complete checkpoint includes it even when a particular example has no custom response headers.
APIkit is a module dependency in this project’s POM. The Router component needs that module installed; a canvas label alone does not supply its implementation. This checkpoint pins APIkit 1.11.17 alongside the existing runtime and HTTP connector.
Example 035 — Route a request through APIkit
The complete source is in checkpoints/13-apikit-contract/src/main/mule/app.xml.
Choose Flow List → orders-api. Select Listener: its Path is /api/*, and it allows GET,POST. In the response settings, inspect normal status vars.httpStatus default 200, error status vars.httpStatus default 500, and error body payload.
Select Router and confirm its Configuration is Orders_API. Expand the flow’s Error handler and inspect Ref API_Errors. The canvas now shows the whole entry boundary: receive, route, and map errors.
The Listener path is /api/*; the API resource paths are relative to that entry point. Send a command to /api/orders, not directly to an assumed implementation-flow URL. APIkit selects the resource/action mapping and applies the configured request validation.
This checkpoint’s create-order returns an explicit PREVIEW state without persistence. The GET implementation returns not found. Those small implementations let us check routing and validation before introducing the database operation in the same flow.
Exercise the contract before saving anything
Use the command fixture:
{
"orderId": "A-1001",
"items": [
{
"sku": "PEN-01",
"qty": 4
},
{
"sku": "PAD-22",
"qty": 2
},
{
"sku": "CLP-08",
"qty": 10
}
]
}
curl --include -H 'Content-Type: application/json' \
--data-binary @book/fixtures/order-command.json \
http://127.0.0.1:18881/api/orders
The preview response uses status 201 and identifies A-1001. Its PREVIEW label prevents this teaching step from being mistaken for a persisted order. The next checkpoint replaces this provisional implementation; a deployed service should not advertise creation semantics for a preview operation.
Now try an empty command, an empty items array, an unknown SKU and an item containing a client-supplied price. The verification command expects each to receive status 400 with INVALID_REQUEST. These tests establish particular rejected shapes; they do not establish authorization or a database rule.
Validation has a boundary
APIkit request validation does not prove that every response the implementation constructs matches a rich response schema. Retain explicit response tests for status, media type and body — and note that a test calling an implementation flow directly bypasses the router, so it cannot establish that the validation happened at all. Likewise, a structurally valid tenant identifier is not proof that the caller owns that tenant’s orders.
Routing absence and business absence are different results. An unmatched resource can produce a routing not-found error; a valid GET resource with an unknown order produces a business not-found error. Declaring 404 in the contract does not make a failed lookup into one either — the implementation has to map genuine absence to that status and map a dependency failure separately. The public envelope may intentionally be similar, but diagnostics should still identify the boundary that failed.
The shared API error handler maps malformed requests, unmatched resources, unsupported methods/media types, dependency failures and unexpected failures to deliberate responses. The named handler contains this policy and is selected by the entry flow. Do not catch every error as “invalid input”: an unavailable catalogue is not something the customer can fix by changing a quantity.
Keep the contract useful to its consumers
A version string does not by itself establish compatibility. Adding a required request field, changing the meaning of an accepted state or returning a new enum value can affect existing clients. An enum cannot say both “this is the only allowed value set” and “these are the currently documented values” at once — which one you mean is an agreement made outside the syntax. Keep representative request and response fixtures with the specification and use them in provider tests. Publishing a new specification version does not automatically upgrade every implementation or client — and that separation is useful, because compatibility decisions need review.
OpenAPI can express the same boundary. Appendix B gives an equivalent reference so you can compare the contract decisions without learning a second notation in the middle of the first implementation. For A-1001 the field names are the easy part — the test is whether every consumer receives the same promised facts. The important continuity is the promised behavior, not loyalty to a file format.
Try it
1. Send a forbidden price. Add price: 0.01 to a command item. Which declared rule rejects it?
Show answer
The item object has additionalProperties: false and declares only sku and qty. The added property is outside that shape. This rejection is different from validating whether an allowed price is numerically positive.
2. Separate routing and data. Compare an unknown resource with /api/orders/ABSENT.
Show answer
The first request can fail before a resource implementation is selected. The second selects the GET implementation and then reports that no visible order exists. Test both cases even if the public error code is the same.
3. Tighten a response. What additional evidence would you want before making the final order response schema stricter?
Show answer
Define required identity, currency, item and state fields, retain representative responses and assert the actual implementation produces them. Request validation alone is not evidence that every response meets the stricter schema.
The contract now rejects malformed commands and routes valid ones to known implementations. We can replace the preview with the pricing and storage operations already understood.
Comments