Class: Hecks::Runtime::RemoteDispatcher
- Inherits:
-
Object
- Object
- Hecks::Runtime::RemoteDispatcher
- Defined in:
- lib/hecks/runtime/remote_dispatcher.rb
Overview
THE WRITE-SIDE HALF OF LAMBDA ROUTING — Runtime::Loader.boot
constructs this INSTEAD OF Dispatcher when the booted domain's
own .world declares Lambda routing (see loader.rb's own
dispatcher_class_for). Matches Dispatcher's public shape
closely enough that everything built ON TOP of a dispatcher —
Handle, AggregateDoor, Facade::Surface — needs no changes
at all: Handle#run's entire contract is
@dispatcher.dispatch("#{fqn}.#{command}", **identity, **args).instance.state,
and both classes answer that identically.
READS DELEGATE, WRITES DON'T. query/reference_query hand off
to a REAL Dispatcher built over the SAME registry — since that
registry's own repositories are ALREADY Lambda-backed
(Adapters::Lambda, persisted_by("Lambda"), Phase 2's other
half), the inherited query machinery (QueryInterpreter,
Ports::Query::InMemory.execute) works completely unchanged, no
query logic duplicated here. dispatch can't delegate the same
way: command validation (givens, constraints) has to actually
run in Rust for a Lambda-routed domain, not get pre-checked here
against incomplete local state and then merely persisted.
Defined Under Namespace
Classes: Result
Instance Attribute Summary collapse
-
#registry ⇒ Object
readonly
Returns the value of attribute registry.
Instance Method Summary collapse
- #dispatch(verb, saga_correlation: nil, **args) ⇒ Object
-
#events ⇒ Object
THE FULL DOMAIN'S EVENT HISTORY, on every call —
{"read": true}replays the whole journal (Phase 1'sdispatch::read), so its owneventsarray already IS the complete log, the same thing@registry.event_logwould answer for a local dispatch. -
#initialize(registry, region: "us-east-1") ⇒ RemoteDispatcher
constructor
A new instance of RemoteDispatcher.
- #query(verb, **args) ⇒ Object
- #reference_query(verb, **args) ⇒ Object
Constructor Details
#initialize(registry, region: "us-east-1") ⇒ RemoteDispatcher
Returns a new instance of RemoteDispatcher.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/hecks/runtime/remote_dispatcher.rb', line 40 def initialize(registry, region: "us-east-1") @registry = registry # `File.basename(registry.root)`, not `bluebooks.keys.first` — # matches `Adapters::Lambda`'s own function-name resolution # exactly (see its own comment on why: one merged Lambda per # deploy, not one per attached chapter, and `root` is the one # signal every bluebook in this registry shares regardless of # which one attached it) — INCLUDING that same adapter's own # `ENV["DOMAIN_NAME"]`-first fix: `root` is always `/var/task` # inside a deployed Lambda, giving "task" instead of the real # domain name (a real, live AccessDeniedException on # "hecks-task" caught this). @client = Adapters::Lambda::Client.new(domain: ENV["DOMAIN_NAME"] || File.basename(registry.root), region: region) # READ-SIDE DELEGATE ONLY (see class comment) — never dispatched # through; a real Dispatcher's own `query`/`reference_query` # already resolve generically via `registry.repository(...)`, # so building one here reuses that instead of duplicating it. @local = Dispatcher.new(registry) end |
Instance Attribute Details
#registry ⇒ Object (readonly)
Returns the value of attribute registry.
38 39 40 |
# File 'lib/hecks/runtime/remote_dispatcher.rb', line 38 def registry @registry end |
Instance Method Details
#dispatch(verb, saga_correlation: nil, **args) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/hecks/runtime/remote_dispatcher.rb', line 60 def dispatch(verb, saga_correlation: nil, **args) domain, aggregate_name, = Naming.split_verb(verb) || raise(UnknownVerb, RefusalWording.render("UnknownVerb", "not_fully_qualified", verb: verb.inspect)) aggregate = @registry.bluebook(domain)&.aggregate(aggregate_name) || raise(UnknownVerb, RefusalWording.render("UnknownVerb", "no_aggregate", domain: domain, aggregate: aggregate_name.inspect)) # NOT EVERY AGGREGATE IN A LAMBDA-ROUTED DOMAIN IS ITSELF # LAMBDA-BOUND — Member's real name->email rekey carries a # `compute` rule (era_check.rb's own `check_compute_rules!`), # which can only ever run against Postgres, permanently. Its # OWN `.hecksagon` bind stays "Postgres" even when # `dispatched_by("Lambda")` is on for everything else — checked # here by real CAPABILITY (`Ports::Persistence::RemoteRuntime`, # §1), not by comparing the adapter's own name to the string # "Lambda" — a bind resolves to whatever adapter CLASS actually # backs it, and only a class shaped like "the real interpreter # lives behind a call boundary" forwards here; anything else # (Postgres, Memory, any future local adapter) falls through to # the real local Dispatcher instead of being forwarded to a # Lambda that has no way to represent its lineage history at # all. adapter_name = Ports::Persistence::BindingPolicy.resolve(@registry, domain, aggregate).adapter return @local.dispatch(verb, saga_correlation: saga_correlation, **args) unless @registry.adapter_class(adapter_name) <= Ports::Persistence::RemoteRuntime response = @client.dispatch(verb, args) refusal = response.fetch("refusals", []).find { |r| r["verb"] == verb } raise RemoteRefusal, "#{verb} refused: #{refusal['error']}" if refusal # THIS STEP'S OWN mutations — `mutations` is one entry per # replayed step (rust/host's rehydrate-and-replay design, # Phase 1), so `.last` is exactly the step just dispatched. # Matched by fully-qualified aggregate name, not just "the # first mutation" — a command whose reaction ALSO mutates a # different aggregate (a policy, a saga leg) puts more than # one mutation in the same step, and the direct effect of # THIS verb is the one this dispatch's own caller expects # `.instance` to be. fqn = "#{domain}::#{aggregate.hecks_name}" mutation = response.fetch("mutations", []).last&.find { |m| m["aggregate"] == fqn } || raise(WiringError, "#{verb} was accepted but rust/host reported no mutation for #{fqn} — response: #{response.inspect}") instance = Instance.new(aggregate: aggregate, id: mutation["id"], state: JSON.parse(JSON.generate(mutation["state"]), symbolize_names: true)) Result.new(verb: verb, instance: instance, events: step_events(response)) end |
#events ⇒ Object
THE FULL DOMAIN'S EVENT HISTORY, on every call — {"read": true} replays the whole journal (Phase 1's dispatch::read),
so its own events array already IS the complete log, the
same thing @registry.event_log would answer for a local
dispatch. Not cached: AggregateDoor.events/Handle#events
are not called in this codebase's own hot paths today: if that
changes, caching belongs here, not in every caller.
121 122 123 |
# File 'lib/hecks/runtime/remote_dispatcher.rb', line 121 def events @client.read.fetch("events", []).map { |e| build_event(e) } end |
#query(verb, **args) ⇒ Object
111 |
# File 'lib/hecks/runtime/remote_dispatcher.rb', line 111 def query(verb, **args) = @local.query(verb, **args) |
#reference_query(verb, **args) ⇒ Object
112 |
# File 'lib/hecks/runtime/remote_dispatcher.rb', line 112 def reference_query(verb, **args) = @local.reference_query(verb, **args) |