A ticketing diagram has an arrow from Booking to Inventory labeled "reserve seat." During a busy sale, Booking sends the request, waits, and times out. Can it try again? Can it tell the customer the seat is theirs? The arrow gives no answer, even though both services could be built exactly as drawn.
Consider a fictional venue where customers select a specific seat before paying. Booking handles the customer request. Inventory owns the seats that are available or temporarily held. The arrow represents a synchronous request to hold one seat while checkout continues. It does not represent payment or a completed sale. Those are separate agreements.
Say what "reserve" means
The first draft of the interface says reserve(showId, seatId, bookingId). The names help, but two teams could implement them differently. One might treat a successful reply as a momentary availability check. The other might assume it has an exclusive hold until checkout finishes. Both interpretations fit the arrow and the method name.
For this design, Booking sends a stable bookingId for this customer's attempt, plus the show and seat identifiers. Inventory validates that the seat belongs to the show and has no active hold or sale. If it accepts the request, it returns a hold identifier and an expiry time set by Inventory. Until that time, Inventory will not grant another active hold or sale for the same seat. A successful reply means the seat is held; it does not mean the booking is confirmed or paid. Booking can show "seat held while you finish checkout," with the returned expiry, but it cannot show "ticket confirmed."
The distinction shapes the unsuccessful replies too. "Unavailable" means Inventory did not create a hold for this booking attempt because another hold or sale prevents it. "Invalid seat" means the identifiers do not name a reservable seat. A request that reuses bookingId with a different show or seat is a conflict, not a new attempt. Booking can display a different outcome for each case. It should not convert a conflict into "sold out."
This is a proposed contract, not a claim that the fictional services already meet it. In particular, the exclusivity promise needs an implementation and concurrent-request tests. Drawing a single arrow cannot establish that two simultaneous calls will not both get a successful reply.
A timeout is an unknown result
Suppose Inventory creates the hold but its reply disappears. Booking sees a timeout. If Booking treats that timeout as "no hold" and submits a new request under a new bookingId, it may strand the first hold. If it tells the customer the seat is held, it may be wrong for the opposite reason: Inventory may never have received the request.
The contract therefore gives bookingId one more job. Repeating the same request with the same ID returns the existing hold or its terminal result; it cannot create a second hold. Inventory retains that result while the show is open for booking. Booking can also ask Inventory for the attempt's status by bookingId. After a timeout, Booking looks up the status or retries with the same ID. It tells the customer the outcome only after it has a definite response. If Inventory remains unreachable, checkout stays unresolved rather than becoming a confirmed ticket or an automatic rejection.
An expired hold is a terminal result for that bookingId. A fresh attempt to hold the seat uses a new ID and must compete for availability again. That rule prevents an old retry from quietly recreating a hold after Booking has moved on. It also means the expiry time matters to the caller: a hold that was valid when Inventory replied may have expired by the time Booking next acts.
Inventory owns the hold and the decision to release it at expiry. Booking owns the customer's checkout state and the decision to continue or abandon payment. If Booking abandons checkout early, it may request release, but the expiry remains the backstop if that request fails. The later step that turns a hold into a sold seat needs its own contract; this one cannot promise that payment and seat sale happen together.
Keep the contract beside the arrow
The diagram can retain the short label "hold selected seat." Beside it, link a compact interface note with the request identity, the meaning of a successful hold, the distinct rejection outcomes, the expiry rule, and the timeout recovery path. The important test is whether Booking's engineer can decide what to do after each observable outcome without guessing what Inventory meant.
You can draw the Booking-to-Inventory relationship in Lycana and label it "hold selected seat" while the team works through the timeout path. Keep the contract in a separate note the implementers can check; the arrow cannot encode every response rule.
SEI's Documenting Software Architecture: Documenting Interfaces separates an interface's syntax from its semantics and error behavior. It also advises documenting externally visible promises while avoiding internal details that callers should not rely on. That balance applies here. The caller needs to know that two active holds cannot claim one seat and that a repeated bookingId will not make a second hold. It does not need Inventory's table layout, lock choice, or cache topology. Putting those details in the contract would make an internal change look like a breaking interface change.
This note is still deliberately narrower than a production API specification. Before implementation, the teams would settle authentication, field formats, response codes, the allowed booking window, operational limits, and how long status remains queryable after sales close. The design discussion has already resolved the dangerous ambiguity in the arrow: what Booking may promise when it gets success, failure, silence, or an expired hold. The remaining details can be added where the engineers implementing and operating this interface will use them.


