Skip to content

Use cases, surfaces and extension points

Yoyaku is a generic booking engine. The booking on a resource page with a calendar is one surface, not the model. This page maps the full space of booking use cases onto the engine and names the seam that absorbs each axis of variation, so any use case is a configuration or a submodule on top of an unchanged core.

The one rule

The core never assumes how a booking is reached or what kind of thing is booked. Every axis of variation lives behind a named seam with a shipped default. A new use case is a new surface (and sometimes a new availability query), never a change to the core.

The neutral spine

Whatever the use case, every booking flows through the same path:

flowchart LR
    SEL[Selection<br/>a surface] --> HOLD[holdGroup<br/>capacity lines]
    HOLD --> TX[Transaction<br/>the order]
    TX --> EV[HELD event]
    EV --> WF[Workflow]
    WF --> PAY[Payment]

Each step is entry-agnostic:

  • holdGroup() takes {target, quantity} lines; it does not care how they were chosen.
  • the transaction is the order: the unit of payment and of lifecycle.
  • the HELD event starts the workflow regardless of which surface held.
  • payment and gateway resolve at the order level, never on the resource.

A surface's only job is to help the user produce selection lines and hand them to holdGroup. Everything after that is identical for every use case.

The axes of variation

A booking use case is a point in this space. Each axis is absorbed by a seam, with a shipped default:

1. How the customer selects (the surface)

Resource-first calendar; date or criteria search; service or category listing; appointment / next-available; event / ticket quantity; map or location; cart / multi-item; operator / back-office; remote API; embedded widget.

  • Seam: surface submodules that produce selection lines and call holdGroup.
  • Default: yoyaku_node + yoyaku_calendar (resource-first calendar).

2. How availability is queried

Per-resource month grid; cross-resource criteria search; next-available; location radius.

  • Seam: a first-class, criteria-based availability read in the engine, that surfaces consume.
  • Default today: a per-resource month feed (AvailabilityFeed). See the note below: generalizing this read is the main thing that keeps the engine surface-free, because a search front door inverts the query (dates and criteria to resources) and cannot reuse a per-resource month feed.

3. What is booked (the bookable target shape)

Time slot with shared capacity (a class, a table); a whole unit booked exclusively (a cabin, capacity 1); a multi-day range (a stay); a quantity of identical units (tickets, N seats); an appointment (a point in time plus a duration); an interchangeable pool (any of N rooms); a recurring series.

  • Seam: all are configurations of the slot + capacity primitive, plus category tiers and the calendar selection settings.
  • Status: whole-unit, multi-day, tiers and quantity already exist; pools and recurrence are additive.

4. The lifecycle (workflow)

Instant confirm; pay then confirm; request then operator approval; deposit or caution held near the date; waitlist then promote; cancellation and no-show policy.

  • Seam: the Orchestra workflow, resolved at the order level (not pinned to the resource), so each deployment chooses its lifecycle.
  • Default: the example yoyaku_booking workflow.

5. Pricing and payment

Free; fixed price; tiered (adult / child); deposit or caution; pay on site; pay online; per-unit or per-booking.

  • Seam: the payment policy resolver, the gateway resolver, and gateway plugins.
  • Default: the simulator gateway, with a manual fallback.

6. Who and when can book (constraints)

Minimum lead time; booking cutoff (at the slot start or end); an opening date; capacity; arbitrary site rules.

  • Seam: the BookingConstraint plugin type.
  • Default: the shipped lead-time, cutoff and opening constraints.

7. Tenancy and governance

A single operator; multi-tenant (many operators on one site); per-resource managers; shared versus private configuration.

  • Seam: the Tenant config entity and the manager / resource-access layer.

What is already neutral, and what to generalize

Already entry-agnostic, so any surface can be built on it safely:

  • holdGroup (selection to hold), the transaction, the HELD to workflow trigger, the payment and gateway resolvers, the constraint plugin type, and the tenant model.

To make fully generic (the work that keeps the core surface-free):

  • Availability read. Introduce a criteria-based query as the engine primitive and rebuild the month feed on top of it. Do this early: surfaces will depend on the availability API, and a per-resource forMonth bakes in the resource-first assumption that a generic module must not impose.
  • Selection-to-hold contract. Formalize the seam (a surface produces lines plus a target context) so every front door funnels through it. SelectionBooker is almost this already.
  • Workflow association. Resolve it at the order level (by resource type or tenant), not as a per-resource field, so deployments choose their model. See resource type design.

The maintenance rule

The core never references a surface or a specific use case. Review every change against one question: does this assume a calendar, a single resource, or one lifecycle? If it does, it belongs in a surface submodule, not the core. A new use case is a new surface (plus, at most, a new availability query), never a core edit.

Open questions

Tracked in resource type design: the workflow anchor (tenant versus resource type), multi-resource and cross-type carts, and how much configuration lives on the resource type versus the resource.