Host the KurrentDB Bookings sample in Aspire with blob storage projections - #574
Host the KurrentDB Bookings sample in Aspire with blob storage projections#574alexeyzimarev wants to merge 1 commit into
Conversation
… projections Supersedes #556: instead of adding a third Bookings clone under samples/azure, the existing KurrentDB sample gains an Aspire AppHost and the Azure pieces worth keeping. - Add Bookings.AppHost orchestrating KurrentDB, MongoDB 7.0, the Azurite blob emulator, both services, and a Scalar API reference; service telemetry flows to the Aspire dashboard via OTLP - Add BookingStateBlobProjection: booking state projected to Azure Blob Storage from the same all-stream subscription as the Mongo projections, with ByGlobalPosition idempotency and race retries, exposed via GET /bookings/{id}/view and readable next to the event-store fold - Fix latent Payments sample breakage: set the default event serializer (required since #524), reference the AspNetCore command mapping generator, and bind RecordPayment with HttpCommand<PaymentState> so MapDiscoveredCommands actually maps the route - Add health endpoints and Scalar-compatible OpenAPI document routes to both services; keep fixed ports for standalone runs while letting Aspire assign URLs - Add kurrentdb and azurite services to docker-compose for standalone runs; add a sample README covering both run modes - Pin the sample (and the Spyglass tests that use its apps as fixtures) to net10.0, since Aspire cannot run multi-targeted projects; Spyglass tests move to a net10-only CI step - Verified end to end under Aspire: book, pay via the Payments service, integration events through the gateway, and all three read paths agree Co-authored-by: Quezlatch <quezlatch@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR Summary by QodoHost KurrentDB Bookings in Aspire with blob projections
AI Description
Diagram
High-Level Assessment
Files changed (23)
|
Code Review by Qodo
1. CreateIfNotExists blocks startup
|
| app.Services.GetRequiredService<BlobServiceClient>() | ||
| .GetBlobContainerClient(BookingStateBlobProjection.ContainerName) | ||
| .CreateIfNotExists(); |
There was a problem hiding this comment.
1. createifnotexists blocks startup 📘 Rule violation ➹ Performance
The application synchronously creates the blob container during startup even though Azure Storage provides an asynchronous alternative. This blocks a thread on network I/O and violates the requirement that all I/O be asynchronous.
Agent Prompt
## Issue description
Blob-container creation performs synchronous network I/O during application startup.
## Issue Context
Replace `CreateIfNotExists()` with `CreateIfNotExistsAsync()` and await it using the repository's `.NoContext()` convention.
## Fix Focus Areas
- samples/kurrentdb/Bookings/Program.cs[70-73]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 960ac17803
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .UseCheckpointStore<MongoCheckpointStore>() | ||
| .AddEventHandler<BookingStateProjection>() | ||
| .AddEventHandler<MyBookingsProjection>() | ||
| .AddEventHandler<BookingStateBlobProjection>() |
There was a problem hiding this comment.
Give the new blob projector its own checkpoint
When this version starts against MongoDB containing the pre-existing BookingsProjections checkpoint, the all-stream subscription resumes from that saved position, so the newly added handler never receives historical booking events. Existing bookings therefore return 404 from the new blob endpoint, and a later payment event can create an incomplete BookingView lacking the original room and guest data. Run this projector under an independent checkpoint that can replay from the beginning, or provide an explicit backfill/reset migration.
Useful? React with 👍 / 👎.
| if (otelEnabled) | ||
| builder.AddOtlpExporter(); | ||
| else | ||
| builder.AddZipkinExporter(); |
Test Results 44 files + 21 44 suites +21 11m 57s ⏱️ - 3m 27s Results for commit 960ac17. ± Comparison against base commit 0f19628. This pull request removes 26 and adds 9 tests. Note that renamed tests count towards both. |
Aspire hosting for the Bookings sample
Supersedes #556. Instead of adding a third Bookings clone under
samples/azure, the existing KurrentDB sample gains a .NET Aspire AppHost and the Azure pieces worth demonstrating — one sample to maintain instead of two, exercising the newEventuous.Azure.Storage.Blobspackage (#550) against a KurrentDB event store.What's included
Bookings.AppHostorchestrates KurrentDB (same image as the test fixtures, arm64-aware), MongoDB 7.0, the Azurite blob emulator, both services, and a Scalar API reference. Service telemetry flows to the Aspire dashboard via OTLP; the debugger works across both services. Run withaspire runordotnet run --project Bookings.AppHost.BookingStateBlobProjectionprojects booking state to Azure Blob Storage from the same all-stream subscription as the Mongo projections — one subscription, multiple projection targets. It usesByGlobalPositionidempotency (valid here: the all-stream subscription provides real global positions) and race retries.GET /bookings/{id}/viewserves the blob read model next to the event-store fold (GET /bookings/{id}) and the Mongo view (GET /bookings/my/{userId}).docker compose up -dnow also starts KurrentDB and Azurite; a new README covers both run modes.Latent sample bugs fixed (found by actually running it)
MapDiscoveredCommandsmapped zero routes: the sample never referenced theEventuous.Extensions.AspNetCore.Generatorsanalyzer (analyzers don't flow acrossProjectReference, unlike the packagedanalyzers/dotnet/cspath), and the[HttpCommand]command wasn't bound to a state — commands without a state land in the registry's never-readWithoutStatelist. Now bound via[HttpCommand<PaymentState>]./healthendpoints and serve their OpenAPI document atopenapi/{documentName}.jsonso the Scalar reference finds it.Trade-off to be aware of
Aspire cannot run multi-targeted projects, so the sample is pinned to net10.0 — and
Eventuous.Tests.Spyglassuses the sample apps as fixtures, so it moves from the multi-framework CI matrix to a net10-only step. The clean long-term fix is dedicated multi-TFM fixture apps for the Spyglass tests.Verification
POST /recordPayment), integration events flowed through the gateway → KurrentDB → persistent subscription, and all three read paths agree — the blob view ends atoutstanding: 0, paid: true.The AppHost structure, Scalar wiring, and blob projection approach are salvaged from @quezlatch's work in #556 (credited via
Co-authored-by).🤖 Generated with Claude Code