Skip to content

feat: microsvc context#42

Merged
patrickleet merged 4 commits into
feat/asyncfrom
feat/microsvc-context
May 27, 2026
Merged

feat: microsvc context#42
patrickleet merged 4 commits into
feat/asyncfrom
feat/microsvc-context

Conversation

@patrickleet
Copy link
Copy Markdown
Collaborator

@patrickleet patrickleet commented May 27, 2026

Summary by CodeRabbit

  • New Features

    • Introduced dependency-based service architecture with flexible HasRepo and HasReadModelStore traits for cleaner handler context access.
    • Added ReadModelWorkspace API for streamlined read-model operations with improved method naming.
  • Refactor

    • Migrated read-model layer from session-based to write-plan-based architecture.
    • Renamed builder methods: save()upsert(), save_changes()sync() for clarity.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c7a3a5b7-87e6-47b9-94d6-d5c0316f241d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR comprehensively refactors the sourced_rust framework: the Service is generalized to accept a dependency type D instead of a repository R, with new traits (HasRepo, HasReadModelStore) exposing capabilities; the read-model API is migrated from session-based to write-plan-builder and workspace-based patterns; all handlers, transports, tests, and documentation are updated to match. The core motivation is supporting flexible dependency injection and a more composable read-model lifecycle model.

Changes

Dependency-Centric Service and Read-Model Write-Plan Migration

Layer / File(s) Summary
Dependency abstraction layer
src/microsvc/dependencies.rs
Introduces HasRepo and HasReadModelStore traits with associated type accessors; provides RepoDependencies, ReadModelStoreDependencies, and RepoReadModelDependencies wrapper structs to wrap different dependency combinations.
Context refactored to dependency-based generic
src/microsvc/context.rs
Context<R> becomes Context<D> storing dependencies: &D instead of repo: &R; adds accessor methods dependencies(), repo() (gated by HasRepo), and read_model_store() (gated by HasReadModelStore).
Service refactored to generic dependency model
src/microsvc/service.rs, src/microsvc/mod.rs
Service<R> becomes Service<D> with handlers operating on Context<D>; dispatch builds context from self.dependencies; constructors with_repo() and with_read_model_store() use new dependency traits; bus transport functions (listen, subscribe) updated for Service<D>.
Transport layer updates
src/microsvc/http.rs, src/microsvc/grpc.rs
HTTP router/serve and gRPC GrpcHandler/grpc_server/serve_grpc generic parameter renamed RD to align with new Service<D> dependency model.
Read-model session → write-plan/workspace API
src/read_model/session.rs
ReadModelSession replaced by ReadModelWritePlanBuilder for short-lived staging; ReadModelSessionUnitOfWork replaced by ReadModelWorkspace for tracked load/mutate/sync/commit; store contract renamed ReadModelSessionStoreReadModelWritePlanStore; save_changes()sync(); new methods upsert(), upsert_related(), upsert_patch(), delete_model(), document().
Repository adapters updated for write-plan API
src/read_model/in_memory.rs, src/sqlite_repo/mod.rs, src/hashmap_repo/repository.rs, src/read_model/queued.rs, src/repository/async_repository.rs, src/repository/mod.rs
Trait implementations switched from ReadModelSessionStore to ReadModelWritePlanStore; async equivalents updated to AsyncReadModelWritePlanStore; re-exports adjusted.
CommitBuilder integration
src/commit_builder/mod.rs
CommitBuilder::read_models() and StagedCommitBuilder::read_models() now accept ReadModelWritePlanBuilder, converting via into_write_plan(); new ReadModelWritePlanCommitExt trait replaces session-based extension.
Public API exports
src/lib.rs, src/read_model/mod.rs
Removed session-related types; added workspace and write-plan-builder types; updated commit-builder extension trait exports.
Documentation updates
README.md, docs/read-models.md, docs/async-repositories.md, src/read_model/metadata.rs
Service construction examples switched to Service::with_repo(); handler examples use Context<D> with HasRepo bounds; read-model docs migrated to write-plan/workspace flow.
Test suite: Service construction
tests/microsvc/basic.rs, tests/microsvc/convention.rs, tests/microsvc/transport_*.rs, tests/sagas/microsvc_saga.rs, tests/distributed_read_model/*/service.rs, tests/distributed_read_model_board/*/service.rs
Service construction updated from Service::new(repo) to Service::with_repo(repo) across dozens of test helpers and integration points.
Test suite: Read-model API
tests/async_repository/main.rs, tests/read_model_session/main.rs, tests/read_model_document_conformance/main.rs, tests/read_model_relationship_includes/main.rs, tests/read_models/main.rs, tests/postgres_repository/main.rs, tests/sqlite_repository/main.rs
Tests migrated to ReadModelWritePlanBuilder, ReadModelWorkspace, and workspace() API; session-based patterns replaced throughout.
Distributed projection service refactoring
tests/distributed_read_model/projection_service/, tests/distributed_read_model_board/projections_service/, tests/distributed_read_model/query_service/mod.rs, tests/distributed_read_model_board/query_service/mod.rs
Projection handlers use Context<ProjectionDependencies> and workspace API; services wired via microsvc::subscribe; old session-based handlers replaced; board projection adds guard and version monotonicity checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • patrickleet/sourced_rust#35: Introduces relational read-model write-plan and session commit plumbing that this PR builds upon for the full ReadModelWritePlanBuilder/ReadModelWritePlanCommitExt migration.
  • patrickleet/sourced_rust#36: Adds tracked include querying in read-model sessions; this PR substantially refactors that session-based API layer into the write-plan/workspace model.

Poem

🐰 Hops through the dependency garden 🌱
Services now dance with traits so neat,
From Service<R> to Service<D> sweet,
Write-plans build, workspaces sync their beat—
A refactor that makes the code complete! ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/microsvc-context

@patrickleet
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/commit_builder/mod.rs (1)

12-30: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Recreate the builder in each example snippet.

The first repo.read_models(read_models) consumes read_models, so the later examples in this rustdoc block reuse a moved value. Anyone copying the example will hit a compile error immediately.

🤖 Prompt for 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.

In `@src/commit_builder/mod.rs` around lines 12 - 30, The doc example reuses the
moved variable read_models (constructed via ReadModelWritePlanBuilder::new())
across multiple repo.read_models(...) calls which consumes it; fix by recreating
the builder before each example use (or use a Clone if implemented) so each
snippet calls ReadModelWritePlanBuilder::new() to produce a fresh read_models
prior to repo.read_models(...), repo.outbox(...).read_models(...).commit(...),
and repo.aggregate(...).read_models(...).outbox(...).
src/read_model/session.rs (1)

828-868: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Refresh or invalidate the tracked baseline after sync().

sync() stages diffs against the original loaded baseline but leaves that baseline intact. If the same model is mutated and sync() is called again in the same workspace, the second patch is still emitted with the old expected version, so the batch can self-conflict when the first patch has already advanced the row version.

At minimum, reject a second sync() for the same tracked model until it is reloaded; ideally, update the tracked baseline/expected version once staging succeeds.

🤖 Prompt for 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.

In `@src/read_model/session.rs` around lines 828 - 868, sync() currently stages
diffs against an immutable tracked baseline (in ReadModel::sync) so repeated
sync() calls for the same model use a stale baseline/version; update behavior to
prevent self-conflicts by either rejecting a second sync for the same
RowIdentity until the baseline is reloaded or (preferred) update the stored
baseline entry in self.baselines after successful staging: after the calls to
stage_row_diff and stage_include_changes complete, replace or refresh the
matching baseline (the entry found via RowIdentity matching
baseline.root_schema.table_name and key_fingerprint(&baseline.root_key)) with a
new baseline that uses current_row (and its included rows), and set root_version
to the new expected version (or mark it invalid if you choose the reject
approach); adjust ReadModel::sync to perform this update using the same
RowIdentity/baseline variables so subsequent sync() calls use the updated
baseline/version.
🤖 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.

Outside diff comments:
In `@src/commit_builder/mod.rs`:
- Around line 12-30: The doc example reuses the moved variable read_models
(constructed via ReadModelWritePlanBuilder::new()) across multiple
repo.read_models(...) calls which consumes it; fix by recreating the builder
before each example use (or use a Clone if implemented) so each snippet calls
ReadModelWritePlanBuilder::new() to produce a fresh read_models prior to
repo.read_models(...), repo.outbox(...).read_models(...).commit(...), and
repo.aggregate(...).read_models(...).outbox(...).

In `@src/read_model/session.rs`:
- Around line 828-868: sync() currently stages diffs against an immutable
tracked baseline (in ReadModel::sync) so repeated sync() calls for the same
model use a stale baseline/version; update behavior to prevent self-conflicts by
either rejecting a second sync for the same RowIdentity until the baseline is
reloaded or (preferred) update the stored baseline entry in self.baselines after
successful staging: after the calls to stage_row_diff and stage_include_changes
complete, replace or refresh the matching baseline (the entry found via
RowIdentity matching baseline.root_schema.table_name and
key_fingerprint(&baseline.root_key)) with a new baseline that uses current_row
(and its included rows), and set root_version to the new expected version (or
mark it invalid if you choose the reject approach); adjust ReadModel::sync to
perform this update using the same RowIdentity/baseline variables so subsequent
sync() calls use the updated baseline/version.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 79292b23-f77d-43bb-8da8-3c6781f101f6

📥 Commits

Reviewing files that changed from the base of the PR and between 78e480f and 8528669.

📒 Files selected for processing (53)
  • .github/workflows/on-pr-quality.yaml
  • README.md
  • docs/async-repositories.md
  • docs/read-models.md
  • src/commit_builder/mod.rs
  • src/hashmap_repo/repository.rs
  • src/lib.rs
  • src/microsvc/context.rs
  • src/microsvc/dependencies.rs
  • src/microsvc/grpc.rs
  • src/microsvc/http.rs
  • src/microsvc/mod.rs
  • src/microsvc/service.rs
  • src/read_model/in_memory.rs
  • src/read_model/metadata.rs
  • src/read_model/mod.rs
  • src/read_model/queued.rs
  • src/read_model/session.rs
  • src/repository/async_repository.rs
  • src/repository/mod.rs
  • src/sqlite_repo/mod.rs
  • tests/async_repository/main.rs
  • tests/distributed_read_model/checkout_saga_service/service.rs
  • tests/distributed_read_model/main.rs
  • tests/distributed_read_model/projection_service/handlers/checkout.rs
  • tests/distributed_read_model/projection_service/handlers/mod.rs
  • tests/distributed_read_model/projection_service/handlers/seat.rs
  • tests/distributed_read_model/projection_service/mod.rs
  • tests/distributed_read_model/projection_service/service.rs
  • tests/distributed_read_model/query_service/mod.rs
  • tests/distributed_read_model/seat_inventory_service/service.rs
  • tests/distributed_read_model_board/board_service/service.rs
  • tests/distributed_read_model_board/main.rs
  • tests/distributed_read_model_board/projections_service/handlers/board.rs
  • tests/distributed_read_model_board/projections_service/handlers/mod.rs
  • tests/distributed_read_model_board/projections_service/mod.rs
  • tests/distributed_read_model_board/query_service/mod.rs
  • tests/microsvc/basic.rs
  • tests/microsvc/convention.rs
  • tests/microsvc/session.rs
  • tests/microsvc/transport_grpc.rs
  • tests/microsvc/transport_http.rs
  • tests/microsvc/transport_listen.rs
  • tests/microsvc/transport_subscribe.rs
  • tests/postgres_repository/main.rs
  • tests/read_model_commit_bridge/main.rs
  • tests/read_model_distributed_idempotency/main.rs
  • tests/read_model_document_conformance/main.rs
  • tests/read_model_relationship_includes/main.rs
  • tests/read_model_session/main.rs
  • tests/read_models/main.rs
  • tests/sagas/microsvc_saga.rs
  • tests/sqlite_repository/main.rs
💤 Files with no reviewable changes (1)
  • .github/workflows/on-pr-quality.yaml

@patrickleet
Copy link
Copy Markdown
Collaborator Author

@coderabbitai addressed both outside-diff findings:

  • src/commit_builder/mod.rs: recreated ReadModelWritePlanBuilder before each .read_models(...) call in the rustdoc example so it no longer reuses a moved builder. Commit bb854d0.
  • src/read_model/session.rs: refreshed tracked workspace baselines after sync() stages root/include diffs and updated expected root-version tracking, so repeated sync() calls in one workspace use the staged version instead of the original loaded version. Added regressions for repeated root and include syncs. Commit a0d4418.

Validation:

  • cargo test --doc commit_builder
  • cargo test --test read_model_relationship_includes
  • cargo test --test read_model_session
  • cargo test --all-features

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Tip

For best results, initiate chat on the files or code changes.

@patrickleet Thanks for the thorough follow-up! Both fixes look solid:

  • src/commit_builder/mod.rs: Recreating the ReadModelWritePlanBuilder before each .read_models(...) call in the rustdoc example correctly avoids the use-after-move issue.
  • src/read_model/session.rs: Refreshing the tracked baselines post-sync() and pinning version tracking to the staged version (rather than the original loaded version) is exactly the right semantics for repeated sync() calls — and the regression tests will guard against future regressions.

Full test suite passing confirms everything is in order. 🐇✨

@patrickleet patrickleet merged commit 17005c6 into feat/async May 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant