Class: Hecks::Runtime::CommandInterpreter

Inherits:
Object
  • Object
show all
Includes:
ArgumentGate, MutationApplier, Interpreting
Defined in:
lib/hecks/runtime/command_interpreter.rb,
lib/hecks/runtime/command_interpreter/argument_gate.rb,
lib/hecks/runtime/command_interpreter/mutation_applier.rb

Overview

The dispatch pipeline for a command on an aggregate head. The payload gate lives in command_interpreter/argument_gate.rb, the mutation walk in command_interpreter/mutation_applier.rb; what stays here is the order of the steps and how a record is addressed.

Defined Under Namespace

Modules: ArgumentGate, MutationApplier Classes: Context

Constant Summary collapse

DISPATCH_ORDER =

THE DECLARED ORDER, HAND-TYPED — mirrors Vocabulary::AggregateDispatchOrder (language/bluebook/vocabulary.bluebook:188-205), held equal to it by spec/vocabulary_conformance_spec.rb the same way every other vocabulary in that file is (RefusalWording::TEMPLATES, CommandRules::MUTATION_OPS, ...) rather than read live off the meta-domain at every dispatch — Runtime::RefusalWording's own doc comment gives the same reason.

Hecks::Vocabulary.symbols("AggregateDispatchOrder")
MAX_STALE_WRITE_RETRIES =

A LAST-RESORT SAFETY VALVE, NOT THE NORMAL OUTCOME PATH — see Runtime::StaleWrite's own comment. Two concurrent writers against one aggregate resolve through exactly one retry in the ordinary case (the loser's retried hydrate reads the winner's now- committed state and its own given refuses for real, raising GivenNotMet, not StaleWrite) — this cap exists for pathological contention (many concurrent writers on one hot aggregate), not the two-writer case.

5

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Interpreting

included

Constructor Details

#initialize(registry, rules:) ⇒ CommandInterpreter

Returns a new instance of CommandInterpreter.



53
54
55
56
# File 'lib/hecks/runtime/command_interpreter.rb', line 53

def initialize(registry, rules:)
  @registry = registry
  @rules    = rules
end

Instance Attribute Details

#registryObject (readonly)

Returns the value of attribute registry.



25
26
27
# File 'lib/hecks/runtime/command_interpreter.rb', line 25

def registry
  @registry
end

Instance Method Details

#call(domain, aggregate, command, args, correlation = nil, route: nil, dry_run: false) ⇒ Object

dry_run: — Dispatcher#dry_run?'s own entry point. Every step up through enforce_ensures/enforce_invariants runs exactly as a real dispatch would (givens checked, mutations applied to ctx.instance in memory); step_save/step_emit are the only two that read this flag, each skipping its own real work — see their own comments. RETRIES THE WHOLE METHOD BODY on StaleWrite — a fresh ctx, a fresh step_hydrate re-reading current state, so enforce_givens re-evaluates against reality rather than the snapshot that just went stale. See MAX_STALE_WRITE_RETRIES/Runtime::StaleWrite for why exhaustion is a pathological-contention signal, not the expected shape of a two-writer race.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/hecks/runtime/command_interpreter.rb', line 69

def call(domain, aggregate, command, args, correlation = nil, route: nil, dry_run: false)
  attempt = 0
  begin
    ctx = Context.new(domain, aggregate, command, args)
    ctx.correlation = correlation
    ctx.route = route
    ctx.dry_run = dry_run
    ctx.plan = DependencyPlanning::Analyzer.call(aggregate: aggregate, command: command)
    # RESOLVED HERE, ONCE, BEFORE HYDRATION — `Registry#repository`
    # memoizes, so this and `step_hydrate`'s own read of `ctx.repository`
    # (no second fetch there any more) always name the same instance;
    # the isolation decision below (lock vs. CAS+retry) needs the
    # repository's capabilities before a single step runs.
    ctx.repository = @registry.repository(domain, aggregate)
    lock_id = Identity.best_effort(aggregate, args, route, reference_key: reference_key(command))
    run_dispatch_order_with_isolation(DISPATCH_ORDER, ctx, lock_key_id: lock_id)
    [ctx.instance, ctx.result, ctx.plan, ctx.persistence_outcome]
  rescue StaleWrite
    attempt += 1
    retry if attempt < MAX_STALE_WRITE_RETRIES
    raise
  end
end