adr: jobs replace services#8
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR adds ADR-0001 documenting a framework redesign that replaces ChangesADR-0001: Services to Job-Based Side Effects Redesign
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
How to use the Graphite Merge QueueAdd the label add-to-gt-merge-queue to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has required the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
6be35b6 to
a7950b2
Compare
a7950b2 to
c581197
Compare
c581197 to
b4abd04
Compare
b4abd04 to
be4025d
Compare
f418054 to
cd43e9b
Compare
be4025d to
a000b43
Compare
cd43e9b to
0dd1f45
Compare
a000b43 to
fcfdad7
Compare
4e33364 to
d64da60
Compare
7217caf to
efcba38
Compare
d64da60 to
a8e03bb
Compare
a8e03bb to
2213a7d
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@adrs/0001-jobs-replace-services.md`:
- Around line 79-80: The ADR currently buries a major migration detail: handlers
no longer read runtime context from Services (clock, config, idempotency keys)
and that responsibility moves to Command construction; update the Consequences →
Negative section with a dedicated bullet that explicitly states callers must
gather and embed runtime context when creating Command instances (e.g., include
Instant::now(), config values, and external idempotency keys) and give a short
example sentence describing the caller-side burden (commands must be constructed
with these values before dispatch rather than handlers reading them on-demand);
reference the terms Command, Handlers, and Services in the new bullet so readers
can locate the change in the ADR.
- Around line 27-44: Condense the "Context" inventory by keeping only the
high-level components (Job<Ctx> trait, JobQueue<Task> newtype,
build_supervised_worker! macro, work::<Ctx, J> apalis handler, FailureInjector,
Label) and move the detailed configuration points (poll-interval tuning,
FAIL_STOP_RECOVERY_TIMEOUT, retry/circuit-breaker policy, terminal-failure
notifier, test vs production handler variants, SLO reasoning) into the
"Machinery moved into event-sorcery" / Decision section where implementation
decisions are justified; update the ADR text to remove those specifics from the
Context list and add them as supporting details in the Decision subsection
referencing the same symbols.
- Around line 256-263: Document the task-local buffer's assumptions and
limitations: in the ADR text near the paragraph describing "per-command
task-local buffer" and the flow through Store::send, with_pending_jobs, the
Lifecycle handler and JobQueue draining into the event repository, add a concise
note that this design assumes handlers execute single-threaded per command (no
spawned child tasks), Store::send is not called re-entrantly, and multiple
commands are not processed concurrently on the same task; also state how these
edge cases are prevented or should be handled (e.g., prohibit spawning async
tasks that rely on the buffer, detect/deny re-entrant Store::send, or switch to
an explicit Transaction-bound buffer if needed).
- Around line 228-229: Clarify whether the FailureInjector design choice is
internal or API-affecting: explicitly state if FailureInjector<KIND: &'static
str> will expose consumer-facing registration differences (e.g., requiring
consumers to register by KIND when using a HashMap of mutexes) or if those
details are hidden behind the same public API regardless of the internal
approach; if undecided, add a single sentence deferring the exact implementation
(HashMap of one Mutex per KIND vs. small typestate) to the implementation PR and
confirm that the public contract for registering failure points will remain
unchanged.
- Line 245: Clarify and implement the intended transaction contract by
refactoring persist_events so it no longer opens its own transaction (remove the
self.pool.begin() usage) and instead accepts a &mut Transaction parameter that
callers must pass through; update all call sites that previously relied on
persist_events to thread the existing tx (the same Transaction used for other
CQRS/ES writes) into persist_events, and document this contract change in the
ADR and the event repository interfaces so implementations know they must use
the provided &mut Transaction rather than starting their own or relying on
task-local tx injection.
- Around line 152-154: Clarify how consumers invoke register_jobs!: add a short
usage example in the ADR showing jobs![A, B] -> Cons<A, Cons<B, Nil>> and then
demonstrate whether register_jobs!(Jobs) is called per-aggregate (e.g., inside
the aggregate module where type Jobs = jobs![SendEmail, ChargeCard];
register_jobs!(Jobs);) or globally, and state if it is internal machinery
consumers never touch; reference the macro names register_jobs!, jobs!, and the
trait HasJob (and types Cons/Nil) so readers can locate the implementation and
understand the intended invocation pattern.
- Around line 267-272: Update the ADR to document the six parameters of the
build_supervised_worker! macro used in Monitor::register: explain that index is
the worker numeric index used for naming/metrics, queue is the
JobBackend::<MyJob> instance, input is Arc<MyJob::Input> (already noted),
fail_stop is the Duration used by the circuit-breaker for recovery timeout, and
failure_notify is the callback invoked on terminal failure; add this short
parameter list near the macro example and add a pointer to the macro’s full docs
(or file) for more detail.
- Around line 30-32: The parenthetical in the sentence referencing Job<Ctx>
interrupts flow by previewing a later design decision; edit the sentence that
mentions "`trait Job<Ctx>` with `perform(&self, ctx: &Ctx)`, `WORKER_NAME`,
`label()`, `Output`, `Error`" to remove the parenthetical entirely or replace it
with a brief neutral note such as "using `Job<Ctx>` as written" so the ADR does
not forward-reference the later change to an associated `Input` type; update the
line containing `Job<Ctx>` accordingly.
- Around line 17-21: Rewrite the paragraph in adrs/0001-jobs-replace-services.md
to lead with the concrete crash failure mode: state transitions and side effects
are both computed in the handler that returns Vec<Event>, so if the process
crashes after performing an external side effect but before cqrs-es persists
those returned events, the external action occurred with no event recorded and
restart cannot suppress or compensate; then follow with the explanation that
this tight coupling creates a crash window and motivates separating side effects
from pure event generation. Keep references to the handler, Vec<Event>, and
cqrs-es persistence to make the causality explicit.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: f0593f5e-7fa4-4f80-a7e5-5150ffefa3a1
📒 Files selected for processing (1)
adrs/0001-jobs-replace-services.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.md
⚙️ CodeRabbit configuration file
Focus on the contents of the docs and not on cosmetic things like markdown formatting. We use markdown files for various docs including but not limited to guidelines for AI contributors (AGENTS.md), project overview and instructions for human contributors (README.md), and topic-focused references under docs/ (cqrs.md, sqlx.md, ttdd.md). Think about the target audience of a document when deciding what comment to leave. For instructions, suggest better rules and guidelines and point out missing instructions. For topic references, suggest improvements that would make non-obvious framework behavior or pitfalls easier to discover. In all cases, flag needless bloat, prefer clear concise writing, and consider the structure of the document and order of the sections
Files:
adrs/0001-jobs-replace-services.md
🔇 Additional comments (5)
adrs/0001-jobs-replace-services.md (5)
3-5: LGTM!
98-136: LGTM!
277-318: LGTM!
320-335: LGTM!
337-342: LGTM!
Merge activity
|

Motivation
EventSourcedhandlers compute state transitions and run side effects throughtype Services, all before cqrs-es persists the returned events. A crash betweena side effect and the event write leaves an action in the outside world with no
event to record it — nothing to suppress a retry on restart, nothing to react
to. Handlers need to become pure
(state, command) -> Vec<Event>and sideeffects need to move onto durable, crash-safe machinery. That reshape touches
every
impl EventSourced, so it needs a recorded decision before any codechanges.
Solution
ADR-0001 records the decision to replace
type Serviceswithtype Jobs: JobListand move all side effects into durable, retryableapalis-backed jobs whose enqueue commits in the same SQLite transaction as the
events that trigger them.
st0x.liquidityconsumer(
conductor::job) into event-sorcery, generalizing the consumer-specific bits.Jobcarries its dependency bundle as an associatedInputtype (vs thereference's
Job<Ctx>generic), so a job impl is self-describing.type Jobsis a type-levelJobList(Cons/Nil), so one aggregate candispatch many job types — each with its own worker and retry policy — with
compile-time membership via
HasJob<J>.idempotency keys) move into the
Command.JobQueue; the frameworkdrains the buffer inside the event-commit transaction.
performmust be idempotent.Design doc only; #11-#16 implement it.
Summary by CodeRabbit
Closes RAI-913.