Class: Hecks::Runtime::CommandInterpreter
- Inherits:
-
Object
- Object
- Hecks::Runtime::CommandInterpreter
- 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 owngivenrefuses for real, raisingGivenNotMet, notStaleWrite) — this cap exists for pathological contention (many concurrent writers on one hot aggregate), not the two-writer case. 5
Instance Attribute Summary collapse
-
#registry ⇒ Object
readonly
Returns the value of attribute registry.
Instance Method Summary collapse
-
#call(domain, aggregate, command, args, correlation = nil, route: nil, dry_run: false) ⇒ Object
dry_run:— Dispatcher#dry_run?'s own entry point. -
#initialize(registry, rules:) ⇒ CommandInterpreter
constructor
A new instance of CommandInterpreter.
Methods included from Interpreting
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
#registry ⇒ Object (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 |