Skip to content

Azure Blob Storage projections page, corrected against the shipped API - #10

Merged
alexeyzimarev merged 3 commits into
mainfrom
docs/azure-blob-storage-projections
Aug 21, 2026
Merged

Azure Blob Storage projections page, corrected against the shipped API#10
alexeyzimarev merged 3 commits into
mainfrom
docs/azure-blob-storage-projections

Conversation

@alexeyzimarev

Copy link
Copy Markdown
Contributor

Supersedes #4 — this branch carries @quezlatch's original commits plus a correction pass, so their authorship is preserved. Close#4 in favour of this one if it looks right.

The page was written against the pre-review API of Eventuous/eventuous#550. That PR's review corrected the package README, but those corrections never made it to the docs page, so it shipped with samples that don't compile and without the two warnings that matter operationally.

Samples that didn't compile

  • The third constructor argument is BlobStorageProjectorOptions, not JsonSerializerOptions. The IOptions<JsonSerializerOptions> example was invalid — serializerOptions was dropped from the constructor during #550 and folded into BlobStorageProjectorOptions.JsonOptions. Also, ASP.NET Core doesn't register JsonSerializerOptions as an options type; the example now uses Microsoft.AspNetCore.Http.Json.JsonOptions.
  • typeof(T) isn't in scope in a concrete projector deriving from BlobStorageProjector<T>.

Wrong

  • The intro said Eventuous supports "Azure Service Bus" for projections.
  • getBlobId returns a blob ID, not a full name, and the result still goes through GetBlobName. The example returning payments/{id}.json actually wrote to payments/{id}.json/BookingState.json.
  • Overriding both GetBlobName overloads is misleading: the default two-argument implementation delegates to the one-argument one, so overriding the two-argument version makes a one-argument override dead code.

Missing

All of this is in the package README but wasn't on the page:

  • The blob container must exist — the projector doesn't create it. Host the KurrentDB Bookings sample in Aspire with blob storage projections eventuous#574 had to add CreateIfNotExistsAsync() to the sample's startup because of this.
  • ByGlobalPosition requires real global positions. On a subscription where the global position is always zero, the first event stores 0 and every subsequent event is silently ignored. The projector logs a runtime warning for this.
  • Both constructor overloads.
  • Stream name and message ID are stored percent-encoded, because Azure requires ASCII metadata values.
  • RaceRetries exhausted returns EventHandlingStatus.Failure.

Added

  • A Registration section in the style of azure-service-bus.md, covering BlobServiceClient in DI and wiring the projection into a subscription.
  • A note on giving the projection its own subscription and checkpoint so it backfills when added to a system with existing data — the thing Host the KurrentDB Bookings sample in Aspire with blob storage projections eventuous#574 had to fix in the sample.
  • The projector is now listed on Supported projectors, which it was missing from.
  • Typo, missing blank line before ## Features, trailing newline.

The two warnings use :::caution asides, matching mssql.md and kafka.md.

Verification

astro build passes, 157 pages. Both asides render, and all three relative links (read-models/rm-concept, subscriptions/subs-concept, subscriptions/checkpoint) resolve to pages present in dist/.

Follow-up, not in this PR

src/Azure/src/Eventuous.Azure.Storage.Blobs/README.md in the library repo has the same two code bugs — the out-of-scope typeof(T) at line 87 and the double-override example. They weren't caught in the #550 review and need a separate fix there.

🤖 Generated with Claude Code

quezlatchand others added 3 commits July 6, 2026 08:56
The page was written against the pre-review API of Eventuous/eventuous#550,
so it carried mistakes that the package README had already been corrected for.
Fixes that make the samples compile:
- the third constructor argument is BlobStorageProjectorOptions, not
JsonSerializerOptions, so the IOptions<JsonSerializerOptions> example was
invalid; show BlobStorageProjectorOptions.JsonOptions instead
- typeof(T) is not in scope in a concrete projector deriving from
BlobStorageProjector<T>; drop it from the GetBlobName example
Corrections:
- the intro said Eventuous supports "Azure Service Bus" for projections
- getBlobId returns a blob ID, not a full name, so the example producing
"payments/{id}.json" actually wrote payments/{id}.json/BookingState.json
- overriding both GetBlobName overloads is misleading, because the default
two-argument implementation delegates to the one-argument one
Content missing from the page but present in the README:
- the container must exist; the projector doesn't create it
- ByGlobalPosition silently ignores everything after the first event on
subscriptions whose global position is always zero
- both constructor overloads
- stream name and message ID are stored percent-encoded, as Azure requires
ASCII metadata values
Also add a registration section matching the other infra pages, list the
projector on the supported projectors page, and fix a typo, a missing blank
line before a heading, and the missing trailing newline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
eventuous-docsf9a0eb9Commit Preview URL

Branch Preview URL
Aug 21 2026, 11:17 AM

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix and expand Azure Blob Storage projection docs to match shipped API

📝 Documentation🕐 10-20 Minutes

Grey Divider

AI Description

• Add a full Azure Blob Storage projections guide with compiling, shipped-API examples
• Document operational cautions (container must exist, global-position idempotency pitfalls)
• List Azure Blob Storage as a supported projector target
Diagram

graph TD
A["supported-projectors.md"] --> B["azure-blob-storage.md"] --> C["rm-concept"] --> D["subs-concept"] --> E["checkpoint"] --> F["Astro build/site"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Single source of truth: generate docs from package README
  • ➕ Prevents drift between shipped API/README and site documentation
  • ➕ Reduces duplicated maintenance across repos
  • ➖ Requires tooling and a publishing workflow between repos
  • ➖ README structure may not match site information architecture
2. Compile-check documentation samples in CI
  • ➕ Catches non-compiling code snippets before publishing
  • ➕ Improves confidence when APIs evolve
  • ➖ Adds CI complexity (snippet extraction, harness projects, stubs/mocks)
  • ➖ Not all snippets are easily compilable without additional context

Recommendation: The PR’s approach (correct and expand the page to match the shipped API and add operational cautions) is the right immediate fix and restores usability. For longer-term maintenance, consider either generating the page from the library README or adding CI compilation checks for snippets to prevent future API drift.

Files changed (2) +169 / -0

Documentation (2) +169 / -0
azure-blob-storage.mdAdd Azure Blob Storage projections guide with corrected API samples+168/-0

Add Azure Blob Storage projections guide with corrected API samples

• Introduces a new documentation page describing Blob Storage as a projection target, including constructor usage, registration via DI/subscriptions, and JSON option wiring via BlobStorageProjectorOptions. Adds operational cautions (container must pre-exist; ByGlobalPosition behavior on zero global positions), clarifies blob ID vs blob name semantics, and documents naming/idempotency behavior and metadata encoding details.

src/content/docs/dotnet-next/infra/azure-blob-storage.md

supported-projectors.mdList Azure Blob Storage as a supported projector target+1/-0

List Azure Blob Storage as a supported projector target

• Adds a link to the new Azure Blob Storage projections page in the supported projectors list so it’s discoverable alongside existing database-backed targets.

src/content/docs/dotnet-next/read-models/supported-projectors.md

alexeyzimarev added a commit to Eventuous/eventuous that referenced this pull request Aug 21, 2026
Two problems in the "Custom blob naming" section, both mirrored in the
docs site page (Eventuous/eventuous-docs#10):
- `typeof(T)` doesn't compile in the example's context: `T` is the base
class's type parameter and isn't in scope inside a concrete projector
deriving from `BlobStorageProjector<T>`
- overriding both `GetBlobName` overloads is misleading, because the
default two-argument implementation delegates to the one-argument one,
so a one-argument override is never called once the two-argument one is
replaced
Show the two overrides as the alternatives they are, and say which one
wins when both are present.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1)📘 Rule violations (0)📜 Skill insights (0)

Grey Divider


Informational

1. Missing trailing newline 🐞 Bug⚙ Maintainability
Description
The modified Markdown file still has no trailing newline, which can cause noisy diffs and formatting
issues in some tooling. Since this PR touched the file, it should be fixed as part of this change.
Code

src/content/docs/dotnet-next/read-models/supported-projectors.md[R12-14]

+- [Azure Blob Storage projections](../../infra/azure-blob-storage)
You can project to any other database using a custom projector, which can be built as a [custom event handler](../../subscriptions/eventhandler#custom-handlers).
Evidence
The PR modifies the file and the patch indicates it ends without a newline; the cited region is the
file end where the newline should exist.

src/content/docs/dotnet-next/read-models/supported-projectors.md[1-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
`src/content/docs/dotnet-next/read-models/supported-projectors.md` is missing a trailing newline (`\ No newline at end of file` in the diff). This commonly causes unnecessary future diffs and can trip formatting/lint tooling.
## Issue Context
The PR updates this file to add the Azure Blob Storage projector link, and the patch indicates the file still does not end with a newline.
## Fix Focus Areas
- src/content/docs/dotnet-next/read-models/supported-projectors.md[12-14]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can tweak Display preferences with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +12 to 14
- [Azure Blob Storage projections](../../infra/azure-blob-storage)

You can project to any other database using a custom projector, which can be built as a [custom event handler](../../subscriptions/eventhandler#custom-handlers). No newline at end of file

Choose a reason for hiding this comment

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

Informational

1. Missing trailing newline 🐞 Bug⚙ Maintainability

The modified Markdown file still has no trailing newline, which can cause noisy diffs and formatting
issues in some tooling. Since this PR touched the file, it should be fixed as part of this change.
Agent Prompt
## Issue description
`src/content/docs/dotnet-next/read-models/supported-projectors.md` is missing a trailing newline (`\ No newline at end of file` in the diff). This commonly causes unnecessary future diffs and can trip formatting/lint tooling.
## Issue Context
The PR updates this file to add the Azure Blob Storage projector link, and the patch indicates the file still does not end with a newline.
## Fix Focus Areas
- src/content/docs/dotnet-next/read-models/supported-projectors.md[12-14]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@alexeyzimarev
alexeyzimarev merged commit 3674288 into mainAug 21, 2026
1 check passed
alexeyzimarev added a commit to Eventuous/eventuous that referenced this pull request Aug 21, 2026
…#578)
* fix: dispose locals flagged by CodeQL cs/local-not-disposed
- Blob projector tests: hoist the upload MemoryStreams into `using var`,
matching what BlobStorageProjector itself already does. The Azure SDK
does not dispose caller-supplied streams, so ownership was ours.
- ElasticSerializer: dispose the Utf8JsonWriter. It rents buffers from
ArrayPool and only Dispose returns them. Disposal flushes and does not
close the caller's stream, so it is safe on a serializer contract.
- ElasticSerializer: drop the BinaryReader and deserialize straight from
the stream. Adding `using` there would have closed the caller's stream,
and the reader only added a full copy of the payload. This also removes
the stream.Length call, so non-seekable streams now work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: fix the blob projector README custom naming example
Two problems in the "Custom blob naming" section, both mirrored in the
docs site page (Eventuous/eventuous-docs#10):
- `typeof(T)` doesn't compile in the example's context: `T` is the base
class's type parameter and isn't in scope inside a concrete projector
deriving from `BlobStorageProjector<T>`
- overriding both `GetBlobName` overloads is misleading, because the
default two-argument implementation delegates to the one-argument one,
so a one-argument override is never called once the two-argument one is
replaced
Show the two overrides as the alternatives they are, and say which one
wins when both are present.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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.

2 participants

@alexeyzimarev@quezlatch