25
Backend
04
Security
08
iOS
07
Infra

Event-source trips as an append-only log of effects

ADR-0033 ACCEPTED · 2025-09-03
Event-source trips as an append-only log of effects

Decision

Store a trip as an append-only log of modifications and derive the trip by replaying it. Each modification, starting with the one that creates the trip, holds a list of effects. The derived trip is an ordered sequence of destinations, each a continuous stay in one place.

Effects are events. The model or the UI resolves dates, places and references before anything is stored. Applying an effect reads only the trip and the effect, with no outside lookups, so replay always yields the same trip. Entity ids derive from the modification that created them, so replay reproduces those too (ADR-0049).

The effect vocabulary only grows. A new optional field gets a serde default. A change in meaning is a new effect kind, and the old kind stays in the vocabulary for good.

A tap, a drag or a single-purpose form expresses one unambiguous change and writes its effects without the model. Natural language goes through the model (ADR-0032), as do generating alternatives and turning one phrase into several effects. Ambiguity decides the route, whatever the input device.

Removing a destination discards its planning and keeps its bookings. Accommodations nested in the destination go with it. Reservations and journey legs that point at it stay, and the discrepancy check (ADR-0056) shows them to the user as dangling. Every path that removes a destination must give this result.

Why

Most trip planners are forms, and the trip is whatever the user typed in. People start with "Vienna birthday weekend" and refine it. A log keeps the history that versions branch from (ADR-0046), and edits from the model, the UI and MCP land in the same log. A booking records a real commitment, a seat or a confirmation number, which is too much to lose because the plan around it moved. Planned stays and notes are a draft, and they can go with the destination that holds them.

Rejected alternatives

  • Rows edited in place. The history is lost, and conversational refinement has nothing to branch from.
  • Sending every edit through the model. A gesture whose meaning is already clear gains nothing from interpretation.
  • Storing commands and resolving them at replay. Replay would then depend on outside state such as a geocoder or today's date, and could build a different trip each time.
  • Deleting a destination's bookings with it. A plan edit would throw away a booked seat the user never asked to cancel.

Consequences

Replay treats every effect the same whatever produced it, and the same replay code runs on the server and on the device (ADR-0073). The grammar has to track the vocabulary as it grows. Rebuilding a trip costs time linear in the length of its history.

See Martin Fowler on event sourcing and Greg Young on versioning an event-sourced system.