API Riffs
API Riffs: before an API ships, sketch it three ways and READ the call sites out loud - the design work happens in the comparing, not the committing. Subject: the journal's group-commit knob (which shipped this round as fsync_every:). Here are the three riffs that could have been, each runnable, each judged at its call site.
Observability & Ops
Round 13
Kasper Timm Hansen
exit 0
bundle exec ruby examples/api_riffs.rb
a real captured run
API RIFFS: three shapes for one durability knob
riff 1 - constructor kwarg:
journal = ExecutionJournal.new(path:, fsync_every: 20)
+ the trade is visible at construction, greppable in the diff
that chose it, and IMMUTABLE - nobody weakens durability
mid-flight three files away.
- it's a magic integer; 20 of WHAT is one docs-lookup away.
riff 2 - a named policy object:
journal = ExecutionJournal.new(path:, durability: Durability.grouped(20))
+ Durability.strict reads as a SENTENCE; new policies (time-
based flushing) get names without new kwargs; the docs live
on the object.
- a whole constant surface for one integer today - the wardrobe
is bigger than the costume. YAGNI has a case here.
riff 3 - per-call override:
journal.record(event, payload, durable: false)
+ maximal flexibility: hot loops opt out, milestones opt in.
- and that's the indictment: durability becomes a per-CALL-SITE
opinion. the invariant 'this journal survives crashes' stops
being a property of the OBJECT and starts being a property of
every author's judgment forever. flexibility is where
invariants go to die.
the riff verdict: shape 1 shipped, and the reading explains why -
a durability contract belongs to the OBJECT (riff 3 dissolves
it), and one integer doesn't yet earn a policy wardrobe (riff 2
can arrive later, wrapping the kwarg, if flush-after-100ms ever
becomes real). but note what the exercise cost: forty lines and
ten minutes, versus the years a shipped API lives. riff BEFORE
you commit - call sites read differently than class definitions,
and the call site is where your users actually live.
source
# frozen_string_literal: true # API Riffs: before an API ships, sketch it three ways and READ the # call sites out loud - the design work happens in the comparing, not # the committing. Subject: the journal's group-commit knob (which # shipped this round as fsync_every:). Here are the three riffs that # could have been, each runnable, each judged at its call site. # # bundle exec ruby examples/api_riffs.rb # # Runs offline; every riff executes against the real journal. require class="s">"bundler/setup" require class="s">"agentic" require class="s">"tmpdir" Agentic.logger.level = class="y">:fatal def fresh_path(name) = File.join(Dir.tmpdir, class="s">"agentic_riff_#{name}.jsonl").tap { |p| File.delete(p) if File.exist?(p) } def write_events(journal, n = 5) n.times { |i| journal.record(class="y">:task_succeeded, task_id: class="s">"t#{i}", description: class="s">"t#{i}", duration: 0.01, output: nil) } end puts class="s">"API RIFFS: three shapes for one durability knob" puts # --- riff 1: the constructor kwarg (what shipped) -------------------------------- puts class="s">" riff 1 - constructor kwarg:" puts class="s">" journal = ExecutionJournal.new(path:, fsync_every: 20)" journal = Agentic:class="y">:ExecutionJournal.new(path: fresh_path(1), fsync_every: 20) write_events(journal) journal.sync puts class="s">" + the trade is visible at construction, greppable in the diff" puts class="s">" that chose it, and IMMUTABLE - nobody weakens durability" puts class="s">" mid-flight three files away." puts class="s">" - it's a magic integer; 20 of WHAT is one docs-lookup away." puts # --- riff 2: the policy object ---------------------------------------------------- puts class="s">" riff 2 - a named policy object:" puts class="s">" journal = ExecutionJournal.new(path:, durability: Durability.grouped(20))" module Durability Every = Struct.new(class="y">:n) do def to_fsync_every = n end def self.grouped(n) = Every.new(n) def self.strict = Every.new(1) end journal = Agentic:class="y">:ExecutionJournal.new(path: fresh_path(2), fsync_every: Durability.grouped(20).to_fsync_every) write_events(journal) journal.sync puts class="s">" + Durability.strict reads as a SENTENCE; new policies (time-" puts class="s">" based flushing) get names without new kwargs; the docs live" puts class="s">" on the object." puts class="s">" - a whole constant surface for one integer today - the wardrobe" puts class="s">" is bigger than the costume. YAGNI has a case here." puts # --- riff 3: the per-call escape hatch -------------------------------------------- puts class="s">" riff 3 - per-call override:" puts class="s">" journal.record(event, payload, durable: false)" class LeakyJournal < Agentic:class="y">:ExecutionJournal def record(event, payload = {}, durable: true, **rest) super(event, payload.merge(rest)) # (sketch: durable: false would skip the fsync) end end journal = LeakyJournal.new(path: fresh_path(3)) write_events(journal) puts class="s">" + maximal flexibility: hot loops opt out, milestones opt in." puts class="s">" - and that's the indictment: durability becomes a per-CALL-SITE" puts class="s">" opinion. the invariant 'this journal survives crashes' stops" puts class="s">" being a property of the OBJECT and starts being a property of" puts class="s">" every author's judgment forever. flexibility is where" puts class="s">" invariants go to die." puts puts class="s">" the riff verdict: shape 1 shipped, and the reading explains why -" puts class="s">" a durability contract belongs to the OBJECT (riff 3 dissolves" puts class="s">" it), and one integer doesn't yet earn a policy wardrobe (riff 2" puts class="s">" can arrive later, wrapping the kwarg, if flush-after-100ms ever" puts class="s">" becomes real). but note what the exercise cost: forty lines and" puts class="s">" ten minutes, versus the years a shipped API lives. riff BEFORE" puts class="s">" you commit - call sites read differently than class definitions," puts class="s">" and the call site is where your users actually live."