From 1e192918c312f2ac786af4f0c609e92ec8a233d7 Mon Sep 17 00:00:00 2001 From: Vulthil Date: Mon, 20 Jul 2026 13:21:03 +0200 Subject: [PATCH 1/2] docs: reconcile package docs and articles with current behavior --- docs/articles/cqrs-pipeline.md | 2 +- docs/articles/getting-started.md | 15 +++++-- docs/articles/inbox-pattern.md | 2 +- docs/articles/messaging.md | 27 +++++++++--- docs/articles/outbox-pattern.md | 23 ++++++++-- docs/articles/packages/index.md | 20 +++++++-- docs/articles/packages/toc.yml | 2 + .../packages/vulthil-extensions-hosting.md | 39 ++++++++++++++++ .../packages/vulthil-extensions-testing.md | 20 ++++++--- .../vulthil-messaging-inbox-cosmos.md | 2 +- ...hil-messaging-inbox-entityframeworkcore.md | 2 +- .../vulthil-messaging-inbox-relational.md | 2 +- .../packages/vulthil-messaging-inbox.md | 2 +- .../packages/vulthil-messaging-outbox.md | 2 +- .../packages/vulthil-messaging-testharness.md | 11 +++-- docs/articles/packages/vulthil-messaging.md | 10 ++++- .../packages/vulthil-sharedkernel-api.md | 2 +- ...thil-sharedkernel-infrastructure-cosmos.md | 4 +- ...lthil-sharedkernel-infrastructure-mysql.md | 6 +-- ...thil-sharedkernel-infrastructure-npgsql.md | 6 +-- ...-sharedkernel-infrastructure-relational.md | 28 +++++++----- ...sharedkernel-outbox-entityframeworkcore.md | 2 +- .../packages/vulthil-sharedkernel-outbox.md | 2 +- .../articles/packages/vulthil-sharedkernel.md | 8 ++-- docs/articles/packages/vulthil-xunit.md | 5 +++ docs/articles/result-pattern.md | 11 +++++ docs/articles/testing.md | 17 ++++--- src/Vulthil.Extensions.Hosting/README.md | 4 ++ .../HttpResponseMessageExtensions.cs | 12 ++--- src/Vulthil.Extensions.Testing/README.md | 2 +- src/Vulthil.Messaging.Abstractions/README.md | 2 +- src/Vulthil.Messaging.Inbox.Cosmos/README.md | 6 +-- .../InboxMessage.cs | 2 +- .../IIdempotencyStore.cs | 11 ++++- .../IdempotentConsumeFilter.cs | 6 +-- .../MissingIdempotencyKeyException.cs | 2 +- src/Vulthil.Messaging.Inbox/README.md | 7 +-- src/Vulthil.Messaging.RabbitMq/README.md | 2 +- src/Vulthil.Messaging.TestHarness/README.md | 2 +- src/Vulthil.Messaging/ConsumeFilterOptions.cs | 14 +++--- .../IMessageConfigurationProvider.cs | 6 +-- src/Vulthil.Messaging/README.md | 2 +- src/Vulthil.Results/README.md | 2 +- src/Vulthil.SharedKernel.Api/README.md | 2 +- .../README.md | 2 +- .../README.md | 2 +- .../DependencyInjectionExtensions.cs | 44 ++++++++++++++----- .../README.md | 2 +- .../README.md | 2 +- .../README.md | 2 +- .../README.md | 2 +- .../README.md | 7 +-- .../OutboxProcessingOptions.cs | 3 +- src/Vulthil.SharedKernel.Outbox/README.md | 17 +++---- src/Vulthil.SharedKernel/README.md | 2 +- src/Vulthil.xUnit.Cosmos/README.md | 2 +- src/Vulthil.xUnit/Http/IHttpMock.cs | 3 +- src/Vulthil.xUnit/README.md | 5 ++- 58 files changed, 317 insertions(+), 134 deletions(-) create mode 100644 docs/articles/packages/vulthil-extensions-hosting.md diff --git a/docs/articles/cqrs-pipeline.md b/docs/articles/cqrs-pipeline.md index 83789c97..a235b1d8 100644 --- a/docs/articles/cqrs-pipeline.md +++ b/docs/articles/cqrs-pipeline.md @@ -98,7 +98,7 @@ No additional wiring is needed – the validator is picked up automatically from ### Request Logging -`RequestLoggingPipelineBehavior` logs the start and completion of every request, attaching the serialised error to the logging scope when the result is a failure. +`RequestLoggingPipelineBehavior` logs the start and completion of requests whose response type is `Result` (or `Result`), attaching the serialised error to the logging scope when the result is a failure. Requests with any other response type are not covered by this behavior. ### Transactional diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md index c1657100..b6dcaacb 100644 --- a/docs/articles/getting-started.md +++ b/docs/articles/getting-started.md @@ -12,17 +12,26 @@ The packages are organised into layers so you can adopt only what you need: | Domain | `Vulthil.Results` | Railway-oriented `Result` for explicit error handling | | Application | `Vulthil.SharedKernel.Application` | Commands, queries, handlers, and pipeline behaviors | | Infrastructure | `Vulthil.SharedKernel.Infrastructure` | EF Core base context, generic repository, and outbox processing | -| Infrastructure | `Vulthil.SharedKernel.Infrastructure.Relational` | Shared relational support and the default relational outbox strategy | +| Infrastructure | `Vulthil.SharedKernel.Outbox` | EF-free transactional outbox engine (relay, dispatchers, `IOutboxStore`) | +| Infrastructure | `Vulthil.SharedKernel.Outbox.EntityFrameworkCore` | EF Core implementation of the outbox engine | +| Infrastructure | `Vulthil.SharedKernel.Infrastructure.Relational` | Shared relational support and the relational outbox store base | | Infrastructure | `Vulthil.SharedKernel.Infrastructure.Npgsql` | PostgreSQL provider integration (`UseNpgsql`) | | Infrastructure | `Vulthil.SharedKernel.Infrastructure.MySql` | MySQL provider integration (`UseMySql`) | | Infrastructure | `Vulthil.SharedKernel.Infrastructure.Cosmos` | Azure Cosmos DB provider integration (`UseCosmosDb`) | | API | `Vulthil.SharedKernel.Api` | Minimal API endpoint conventions and `Result` → HTTP mapping | +| Hosting | `Vulthil.Extensions.Hosting` | `IRestartableHostedService` marker for cleanly pausable hosted services | | Messaging | `Vulthil.Messaging.Abstractions` | Transport-agnostic consumer and publisher contracts | -| Messaging | `Vulthil.Messaging` | Queue/consumer registration and hosted service orchestration | +| Messaging | `Vulthil.Messaging` | Queue/consumer registration, hosted orchestration, and the transport SDK | | Messaging | `Vulthil.Messaging.RabbitMq` | RabbitMQ transport implementation | +| Messaging | `Vulthil.Messaging.Outbox` | Transactional bus-publish outbox (captures publishes into the outbox) | +| Messaging | `Vulthil.Messaging.Inbox` | Idempotent-receiver consume filter for exactly/effectively-once processing | +| Messaging | `Vulthil.Messaging.Inbox.EntityFrameworkCore` | Shared EF Core inbox primitives | +| Messaging | `Vulthil.Messaging.Inbox.Relational` | Relational idempotency store (transactional exactly-once) | +| Messaging | `Vulthil.Messaging.Inbox.Cosmos` | Cosmos idempotency store (effectively-once) | | Testing | `Vulthil.xUnit` | Base test classes, auto-mocking, and Testcontainers integration | +| Testing | `Vulthil.xUnit.Cosmos` | Cosmos DB emulator fixture for `Vulthil.xUnit` | | Testing | `Vulthil.Messaging.TestHarness` | In-memory messaging test harness | -| Testing | `Vulthil.Extensions.Testing` | Shared assertion and setup helpers | +| Testing | `Vulthil.Extensions.Testing` | Framework-agnostic polling and HTTP response helpers (no xUnit dependency) | ## Minimal Example diff --git a/docs/articles/inbox-pattern.md b/docs/articles/inbox-pattern.md index e649c683..d4f41446 100644 --- a/docs/articles/inbox-pattern.md +++ b/docs/articles/inbox-pattern.md @@ -4,7 +4,7 @@ Message delivery is **at-least-once**: a broker can redeliver a message (after a The inbox pattern makes consumers **idempotent**: it records which messages have already been processed and skips duplicates. `Vulthil.Messaging.Inbox` provides this as a consume filter, with the processed-marker written in the **same transaction** as the consumer's business changes — so on a relational store, processing is exactly-once. -This is an *idempotent receiver*, not a store-and-forward inbox: it rides on top of the transport's existing retry, fault, and dead-letter machinery rather than re-implementing them. Bounding a consumer that keeps failing is therefore **not** the guard's job — on RabbitMQ a poison delivery is retried up to the queue's `MaxRetryCount`, then a `Fault` is published and the message is nacked to the dead-letter exchange. The filter deliberately ignores `IMessageContext.RetryCount` and adds no max-attempts of its own, keeping retry policy in one place (the transport). +This is an *idempotent receiver*, not a store-and-forward inbox: it rides on top of the transport's existing retry, fault, and dead-letter machinery rather than re-implementing them. Bounding a consumer that keeps failing is therefore **not** the guard's job — on RabbitMQ a persistently-failing delivery is retried per the consumer's [retry policy](messaging.md#retries) (its own registration's, falling back to the queue default), then a `Fault` is published and the delivery is dead-lettered when a dead-letter queue is configured. The filter deliberately ignores `IMessageContext.RetryCount` and adds no max-attempts of its own, keeping retry policy in one place (the transport). ## How It Works diff --git a/docs/articles/messaging.md b/docs/articles/messaging.md index 1efa7b67..1e7f066c 100644 --- a/docs/articles/messaging.md +++ b/docs/articles/messaging.md @@ -305,17 +305,19 @@ Or in code: m.ConfigureMessagingOptions(opts => opts.ConsumeFilters.EnableLogging = false); ``` -The filter stays registered in DI; only its behavior is skipped, so it's still -resolvable in unit tests if you want to assert against it. +The toggle is applied when `AddMessaging` composes the pipeline: a disabled filter is +never registered in DI at all (there is no per-delivery flag check), so flipping the +flag after registration has no effect. ### Idempotent receivers (inbox) `Vulthil.Messaging.Inbox` ships a consume filter that deduplicates redelivered -messages, giving exactly-once processing on top of at-least-once delivery. It is a -transaction-owning filter: the consumer runs inside an `IIdempotencyStore` -transaction so the processed-marker and the consumer's writes commit atomically. -Opt a message type in with `AddIdempotentInbox()`. See the -[Inbox Pattern](inbox-pattern.md) article for the full design. +messages on top of at-least-once delivery. The consumer invocation is handed to an +`IIdempotencyStore`, which owns the unit: with a transactional (relational) store the +processed-marker and the consumer's writes commit atomically — exactly-once +processing — while a store without cross-partition transactions (e.g. Cosmos) is +effectively-once. Opt a message type in with `AddIdempotentInbox()`. See +the [Inbox Pattern](inbox-pattern.md) article for the full design. ## Ordered Processing (per-aggregate) @@ -523,6 +525,17 @@ await publisher.PublishAsync(new OrderCreatedEvent(orderId), ctx => Fault publishing is best-effort: a failure to publish the fault is logged and never prevents the original delivery from being settled (nacked for dead-lettering). +### Poison deliveries + +Deliveries that fail **before** a consumer can run bypass the retry and fault machinery entirely, and +no `Fault` is published for them: + +- **Unknown message type** (no consumer/plan matches the delivery's type identity): the delivery is + **acked and dropped permanently**, with a warning log — it does not reach the dead-letter queue. +- **Undeserializable body** (malformed JSON, or a body that deserializes to `null`): the delivery is + **nacked without requeue**, with an error log. When the queue has a dead-letter queue configured + (`q.UseDeadLetterQueue()`), the broker moves it there; otherwise it is discarded. + ## Routing Keys Routing keys flow through two distinct configuration sites, one on each side of the wire: diff --git a/docs/articles/outbox-pattern.md b/docs/articles/outbox-pattern.md index 67c9422f..505529f8 100644 --- a/docs/articles/outbox-pattern.md +++ b/docs/articles/outbox-pattern.md @@ -4,7 +4,7 @@ The transactional outbox pattern guarantees that domain events raised by aggrega ## How It Works -1. During `SaveChangesAsync`, a `SaveChangesInterceptor` serialises every pending domain event into an `OutboxMessage` row in the same database transaction. +1. During `SaveChanges`/`SaveChangesAsync` (both the synchronous and asynchronous paths are intercepted), a `SaveChangesInterceptor` serialises every pending domain event into an `OutboxMessage` row in the same database transaction. 2. The aggregate root's event collection is cleared. 3. A background service (`OutboxBackgroundService`) relays unprocessed outbox messages — woken immediately once the captured rows are durable (on transaction commit, or right after a non-transactional `SaveChanges` that captured domain events) for low latency, and polling on an interval as the backstop. 4. Each message is routed by its `OutboxDestination` to the registered `IOutboxDispatcher` that handles it (in-process domain events by default, or the broker — see below). @@ -12,6 +12,11 @@ The transactional outbox pattern guarantees that domain events raised by aggrega This guarantees **at-least-once delivery** because the event and the business data are committed atomically. +Relay order is best-effort, not a contract: messages are fetched oldest-first (`OccurredOnUtc`, then `Id`), but a +failed message does not block the rest of its batch — the batch keeps dispatching, so later messages overtake the +failure, which is retried on a later cycle (no head-of-line blocking). With `EnableParallelPublishing`, order +within a batch is not preserved at all. Consumers that need ordering must not rely on the outbox for it. + ## Configuration ### Enable outbox processing during DbContext registration @@ -54,6 +59,11 @@ public sealed class AppDbContext(DbContextOptions options) `BaseDbContext` owns the `OutboxMessages` `DbSet`; apply the provider-optimized mapping in `OnModelCreating` by calling your provider's extension — `ApplyNpgsqlOutbox()`, `ApplyMySqlOutbox()`, or `ApplyCosmosOutbox()` (as shown above). The agnostic `ApplyOutbox()` is available for custom providers. +Because the `DbSet` lives on the base class, EF Core's convention discovery puts the `OutboxMessage` entity into +**every** derived context's model — and its migrations — even when outbox processing is never enabled for that +context. A context that will never use the outbox can opt out with `modelBuilder.Ignore()` in its +`OnModelCreating`; an outbox-enabled context must keep the mapping (the relay store refuses to run without it). + Only one outbox-enabled `DbContext` is supported per host: the relay and retention background services resolve a single `IOutboxStore`, so calling `EnableOutboxProcessing()` for a second context throws an `InvalidOperationException` at startup instead of silently leaving the first context's messages unrelayed. The @@ -71,7 +81,7 @@ running unprotected. | `EnableParallelPublishing` | `false` | Publish messages in parallel within a batch (each dispatch runs in its own DI scope) | | `MaxDegreeOfParallelism` | 4 | Maximum concurrent dispatches when `EnableParallelPublishing` is enabled | | `OutboxProcessingDelaySeconds` | 2 | Base polling delay between processing cycles | -| `MaxDelaySeconds` | 60 | Maximum back-off delay when no messages are found | +| `MaxDelaySeconds` | 60 | Maximum back-off delay when a cycle relays nothing (no pending messages, or every fetched message failed) | | `EnableTracing` | `true` | Carry the originating trace identifier when publishing | ## Observability @@ -105,7 +115,7 @@ Processed and dead-lettered rows remain in the `OutboxMessages` table after rela | `SweepInterval` | 1 hour | Delay between sweeps | | `BatchSize` | 1000 | Rows deleted per batch within a sweep | -The sweep deletes rows whose `ProcessedOnUtc` **or** `FailedOnUtc` is older than `RetentionPeriod`; **pending rows are never touched**. It runs through the registered `IOutboxStore` when it implements `IOutboxRetentionStore` (the EF Core store does) — every store deletes at most `BatchSize` rows per call (relational providers select the oldest eligible keys and delete them set-based with `ExecuteDelete`), so a large backlog is drained in bounded, short-lived batches, and the **same sweep covers Cosmos** (a Cosmos container TTL is not used, because it cannot tell a pending row from a relayed one and could expire an undelivered message). +The sweep deletes rows whose `ProcessedOnUtc` **or** `FailedOnUtc` is older than `RetentionPeriod`; **pending rows are never touched**. It runs through the registered `IOutboxStore` when it implements `IOutboxRetentionStore` (the EF Core store does; a custom store that doesn't gets a one-time warning and the sweep is skipped) — every store deletes at most `BatchSize` rows per call (relational providers select the oldest eligible keys and delete them set-based with `ExecuteDelete`), so a large backlog is drained in bounded, short-lived batches, and the **same sweep covers Cosmos** (a Cosmos container TTL is not used, because it cannot tell a pending row from a relayed one and could expire an undelivered message). ## Custom Outbox Store @@ -156,6 +166,13 @@ Capture is relational-only (it enlists in the ambient transaction); the relay wo the general publish/send **filter pipeline** (`IPublishFilter`, registered via `AddPublishFilter()`), which is the publish-side counterpart to consume filters and can host other cross-cutting concerns. +Two pipeline consequences to know: capturing **short-circuits** the pipeline, so publish filters registered after +`AddTransactionalOutbox()` never see a captured message; and the relay publishes captured messages straight through +the transport, **bypassing the publish-filter pipeline entirely** (nothing runs twice, but nothing else observes the +relayed publish either). Register any filter that must observe every outgoing message *before* +`AddTransactionalOutbox()`. Request/reply is unaffected — requests never flow through the publish filters and are +never captured (a request awaiting a synchronous reply cannot be deferred to a relay). + The capture preserves the publish faithfully: the message id, correlation id, routing key, send destination, custom headers, and the current trace context (`TraceParent`/`TraceState`, when tracing is enabled) are stored with the row and re-applied on relay — the relayed publish runs under an activity linked to the capturing trace. Headers persist diff --git a/docs/articles/packages/index.md b/docs/articles/packages/index.md index 47a8083c..9a94240e 100644 --- a/docs/articles/packages/index.md +++ b/docs/articles/packages/index.md @@ -14,7 +14,9 @@ Focused usage guidance for each package in `src`. Pick the page that matches the ## Infrastructure - [Vulthil.SharedKernel.Infrastructure](vulthil-sharedkernel-infrastructure.md) – `BaseDbContext`, generic repository, and outbox composition entry point. -- [Vulthil.SharedKernel.Infrastructure.Relational](vulthil-sharedkernel-infrastructure-relational.md) – relational outbox strategy and `MigrateAsync` for EF Core providers. +- [Vulthil.SharedKernel.Outbox](vulthil-sharedkernel-outbox.md) – the EF-free transactional outbox engine (relay, dispatchers, `IOutboxStore` seam). +- [Vulthil.SharedKernel.Outbox.EntityFrameworkCore](vulthil-sharedkernel-outbox-entityframeworkcore.md) – the EF Core implementation of the outbox engine. +- [Vulthil.SharedKernel.Infrastructure.Relational](vulthil-sharedkernel-infrastructure-relational.md) – relational outbox store base and `MigrateAsync` for EF Core providers. - [Vulthil.SharedKernel.Infrastructure.Npgsql](vulthil-sharedkernel-infrastructure-npgsql.md) – `UseNpgsql` provider wiring with PostgreSQL-tuned outbox. - [Vulthil.SharedKernel.Infrastructure.MySql](vulthil-sharedkernel-infrastructure-mysql.md) – `UseMySql` provider wiring with MySQL-tuned outbox. - [Vulthil.SharedKernel.Infrastructure.Cosmos](vulthil-sharedkernel-infrastructure-cosmos.md) – `UseCosmosDb` provider wiring with Cosmos-specific outbox. @@ -23,14 +25,24 @@ Focused usage guidance for each package in `src`. Pick the page that matches the - [Vulthil.SharedKernel.Api](vulthil-sharedkernel-api.md) – minimal API endpoint conventions and `Result` → HTTP mapping. +## Hosting + +- [Vulthil.Extensions.Hosting](vulthil-extensions-hosting.md) – `IRestartableHostedService`, the marker for hosted services that can be paused and resumed cleanly. + ## Messaging - [Vulthil.Messaging.Abstractions](vulthil-messaging-abstractions.md) – transport-agnostic consumer and publisher contracts. -- [Vulthil.Messaging](vulthil-messaging.md) – queue/consumer registration and hosted processing. +- [Vulthil.Messaging](vulthil-messaging.md) – queue/consumer registration, hosted processing, and the transport-author SDK. - [Vulthil.Messaging.RabbitMq](vulthil-messaging-rabbitmq.md) – RabbitMQ transport implementation. +- [Vulthil.Messaging.Outbox](vulthil-messaging-outbox.md) – transactional bus-publish outbox bridging the outbox engine to the broker. +- [Vulthil.Messaging.Inbox](vulthil-messaging-inbox.md) – idempotent-receiver consume filter (`IIdempotencyStore` contract). +- [Vulthil.Messaging.Inbox.EntityFrameworkCore](vulthil-messaging-inbox-entityframeworkcore.md) – shared EF Core inbox primitives (`InboxMessage`, `ISaveInboxMessages`). +- [Vulthil.Messaging.Inbox.Relational](vulthil-messaging-inbox-relational.md) – relational idempotency store (transactional exactly-once). +- [Vulthil.Messaging.Inbox.Cosmos](vulthil-messaging-inbox-cosmos.md) – Cosmos idempotency store (effectively-once). ## Testing -- [Vulthil.xUnit](vulthil-xunit.md) – reusable xUnit base classes and auto-mocking. +- [Vulthil.xUnit](vulthil-xunit.md) – reusable xUnit base classes, auto-mocking, containers, and HTTP mocks. +- [Vulthil.xUnit.Cosmos](vulthil-xunit-cosmos.md) – Cosmos DB emulator fixture for `Vulthil.xUnit`. - [Vulthil.Messaging.TestHarness](vulthil-messaging-testharness.md) – in-memory messaging test harness. -- [Vulthil.Extensions.Testing](vulthil-extensions-testing.md) – shared assertion and setup helpers. +- [Vulthil.Extensions.Testing](vulthil-extensions-testing.md) – framework-agnostic polling and HTTP response helpers. diff --git a/docs/articles/packages/toc.yml b/docs/articles/packages/toc.yml index dbca2a39..67e4b48f 100644 --- a/docs/articles/packages/toc.yml +++ b/docs/articles/packages/toc.yml @@ -22,6 +22,8 @@ href: vulthil-sharedkernel-infrastructure-cosmos.md - name: Vulthil.SharedKernel.Api href: vulthil-sharedkernel-api.md +- name: Vulthil.Extensions.Hosting + href: vulthil-extensions-hosting.md - name: Vulthil.Messaging.Abstractions href: vulthil-messaging-abstractions.md - name: Vulthil.Messaging diff --git a/docs/articles/packages/vulthil-extensions-hosting.md b/docs/articles/packages/vulthil-extensions-hosting.md new file mode 100644 index 00000000..7cceb287 --- /dev/null +++ b/docs/articles/packages/vulthil-extensions-hosting.md @@ -0,0 +1,39 @@ +# Vulthil.Extensions.Hosting + +Small hosting abstractions for Vulthil. The package currently contains one contract: +`IRestartableHostedService`. + +## When to use + +- A hosted service (such as a database-polling relay) should be pausable around operations that must not run + concurrently with it +- Test infrastructure needs to stop background work around a reset and resume it afterwards + +## `IRestartableHostedService` + +A marker interface an `IHostedService` implements to declare that its execution can be **stopped and started +again cleanly**. Infrastructure can then pause the service for the duration of an operation that must not run +concurrently with it, and resume it afterwards. + +The primary use is test isolation: `Vulthil.xUnit` stops every registered hosted service implementing this +marker around the per-test database reset and restarts it afterwards, so a live database-polling relay (for +example the `Vulthil.SharedKernel.Outbox` background relay) does not contend with the reset and time it out. +The relay opts in simply by implementing the marker — no test-only code, and the testing library never depends +on the outbox engine. + +```csharp +internal sealed class OutboxBackgroundService(/* ... */) : IRestartableHostedService, IDisposable +{ + // StartAsync/StopAsync manage the execute task so a Stop/Start cycle re-runs it cleanly. +} +``` + +## Implementation guidance + +Implement the marker only on services whose `StartAsync`/`StopAsync` are idempotent across repeated cycles. +Prefer implementing `IHostedService` directly over inheriting `BackgroundService`: the host observes only the +execute task of a `BackgroundService`'s first start and stops the whole host when that task is canceled while +the application is running — and on .NET 10 a stop racing service startup can cancel the task before +`ExecuteAsync` has run at all. + +See [Testing](../testing.md) for how the marker participates in the per-test reset. diff --git a/docs/articles/packages/vulthil-extensions-testing.md b/docs/articles/packages/vulthil-extensions-testing.md index afa0fe46..71e5b2dc 100644 --- a/docs/articles/packages/vulthil-extensions-testing.md +++ b/docs/articles/packages/vulthil-extensions-testing.md @@ -1,17 +1,18 @@ # Vulthil.Extensions.Testing -Use `Vulthil.Extensions.Testing` for shared testing helpers and extension points. +Use `Vulthil.Extensions.Testing` for framework-agnostic test helpers. It has no xUnit dependency — the +xUnit-coupled test stack (base classes, fixtures, containers) lives in [`Vulthil.xUnit`](vulthil-xunit.md). ## When to use -- Reusing assertion and setup helpers across test suites -- Reducing repeated test composition boilerplate -- Polling asynchronous conditions during integration tests +- Polling asynchronous conditions during integration tests (`Polling.WaitAsync`) +- Reading and asserting JSON HTTP responses (`GetResponseAsync`) +- Any test framework — nothing here requires xUnit ## Pattern +- Express the polled condition as a `Result`/`Result` so each failed attempt carries a diagnosable `Error` - Keep helpers small and composable -- Prefer intent-revealing test extensions - Avoid embedding production logic in test helpers ## Polling @@ -36,3 +37,12 @@ result.IsSuccess.ShouldBeTrue(); ``` When polling times out, `PollingResult.PollingError` exposes the individual errors collected from each failed attempt. + +## HTTP responses + +`GetResponseAsync` asserts an `HttpResponseMessage` indicates success and deserializes its JSON body: + +```csharp +var response = await client.GetAsync("/weather/london"); +var forecast = await response.GetResponseAsync(); +``` diff --git a/docs/articles/packages/vulthil-messaging-inbox-cosmos.md b/docs/articles/packages/vulthil-messaging-inbox-cosmos.md index e332565d..a6554905 100644 --- a/docs/articles/packages/vulthil-messaging-inbox-cosmos.md +++ b/docs/articles/packages/vulthil-messaging-inbox-cosmos.md @@ -33,5 +33,5 @@ public sealed class AppDbContext(DbContextOptions options) : DbCon builder.Services.AddCosmosInbox(); ``` -See the [Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md) +See the [Inbox Pattern](../inbox-pattern.md) article for how the guarantees differ from the relational store. diff --git a/docs/articles/packages/vulthil-messaging-inbox-entityframeworkcore.md b/docs/articles/packages/vulthil-messaging-inbox-entityframeworkcore.md index 5459c4e8..20add03f 100644 --- a/docs/articles/packages/vulthil-messaging-inbox-entityframeworkcore.md +++ b/docs/articles/packages/vulthil-messaging-inbox-entityframeworkcore.md @@ -25,5 +25,5 @@ public sealed class AppDbContext(DbContextOptions options) : DbCon } ``` -See the [Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md) +See the [Inbox Pattern](../inbox-pattern.md) article for the full design. diff --git a/docs/articles/packages/vulthil-messaging-inbox-relational.md b/docs/articles/packages/vulthil-messaging-inbox-relational.md index 20556312..a710c9dd 100644 --- a/docs/articles/packages/vulthil-messaging-inbox-relational.md +++ b/docs/articles/packages/vulthil-messaging-inbox-relational.md @@ -36,5 +36,5 @@ builder.Services.AddRelationalInbox(); The consumer and the store must resolve the same scoped `DbContext` instance (the default with `AddDbContext`). Add an EF Core migration for the `InboxMessage` table as you would for any entity. See the -[Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md) article +[Inbox Pattern](../inbox-pattern.md) article for details. diff --git a/docs/articles/packages/vulthil-messaging-inbox.md b/docs/articles/packages/vulthil-messaging-inbox.md index 9a0a3809..aec71d47 100644 --- a/docs/articles/packages/vulthil-messaging-inbox.md +++ b/docs/articles/packages/vulthil-messaging-inbox.md @@ -35,5 +35,5 @@ builder.AddMessaging(messaging => }); ``` -See the [Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md) +See the [Inbox Pattern](../inbox-pattern.md) article for the design, guarantees, and the message-id stability contract. diff --git a/docs/articles/packages/vulthil-messaging-outbox.md b/docs/articles/packages/vulthil-messaging-outbox.md index 3f630082..da31580f 100644 --- a/docs/articles/packages/vulthil-messaging-outbox.md +++ b/docs/articles/packages/vulthil-messaging-outbox.md @@ -37,5 +37,5 @@ builder.AddMessaging(messaging => The application's `DbContext` must implement `ISaveOutboxMessages` (a `BaseDbContext` already does). Capture is relational-only (it enlists in the ambient transaction); the relay works on any provider. -See the [Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md) +See the [Outbox Pattern](../outbox-pattern.md) article for the design, the pluggable-sink model, and the commit-time trigger. diff --git a/docs/articles/packages/vulthil-messaging-testharness.md b/docs/articles/packages/vulthil-messaging-testharness.md index e9fe173b..9122377b 100644 --- a/docs/articles/packages/vulthil-messaging-testharness.md +++ b/docs/articles/packages/vulthil-messaging-testharness.md @@ -2,7 +2,8 @@ An in-memory messaging transport for tests. It runs your consumers with no broker and captures every produced and consumed message for assertion. Built entirely on the public `Vulthil.Messaging.Transport` SDK, so it -mirrors the real consumer topology assembled from your queue configuration. +assembles the same execution plans (consumers, polymorphic dispatch, per-consumer retry resolution) from your +queue configuration that a real transport would. ## When to use @@ -13,7 +14,10 @@ mirrors the real consumer topology assembled from your queue configuration. ## Pattern - Dispatch is synchronous: when a publish/send/request call returns, every consumer it triggered has run — no polling -- A one-way consumer's exception propagates to the publisher/sender; a request consumer's exception becomes a failed result +- A one-way consumer's exception does **not** propagate to the publisher/sender: the consumer is retried per its + resolved retry policy (attempts run back-to-back, without the configured delays), then a `Fault` is published + and captured — assert it via `Published>()`. A request consumer's exception becomes a failed + `Result` on the requesting side - Keep assertions on `ITestHarness` deterministic and explicit; `Clear()` between phases ## Usage @@ -57,5 +61,4 @@ transport with the harness, leaving production code untouched: builder.ConfigureServices(services => services.ReplaceTransportWithTestHarness()); ``` -See the [Testing guide](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/testing.md) for -the full API and details. +See the [Testing guide](../testing.md#messaging-test-harness) for the full API and details. diff --git a/docs/articles/packages/vulthil-messaging.md b/docs/articles/packages/vulthil-messaging.md index 854979d0..60a7e869 100644 --- a/docs/articles/packages/vulthil-messaging.md +++ b/docs/articles/packages/vulthil-messaging.md @@ -48,15 +48,21 @@ messaging.ConfigureMessage(message => }); ``` -### Per-consumer routing overrides +### Per-consumer retry override + +`AddConsumer` accepts a configurator; its knob is the consumer's retry policy, which overrides the queue-level +default for that consumer alone: ```csharp queue.AddConsumer(c => { - c.Bind("order.eu"); + c.UseRetry(r => r.Immediate(5)); }); ``` +Binding patterns are configured per queue, not per consumer — use +`queue.Subscribe("order.eu")` (see [Messaging — Routing Keys](../messaging.md#routing-keys)). + ### Configuration-driven setup Queue and message settings under `Messaging:Queues:*` and `Messaging:Messages:*` diff --git a/docs/articles/packages/vulthil-sharedkernel-api.md b/docs/articles/packages/vulthil-sharedkernel-api.md index 4d870f5d..990328cc 100644 --- a/docs/articles/packages/vulthil-sharedkernel-api.md +++ b/docs/articles/packages/vulthil-sharedkernel-api.md @@ -31,7 +31,7 @@ public sealed class GetUserEndpoint : IEndpoint } ``` -`ToIResult()` returns `Results, ValidationProblem, NotFound, Conflict, ProblemHttpResult>`, so OpenAPI automatically documents all possible response types. +`ToIResult()` returns `Results, ValidationProblem, NotFound, Conflict, ProblemHttpResult>`, so OpenAPI automatically documents all possible response types. At runtime every failure — including `NotFound` and `Conflict` — is returned as a problem response (RFC 7807) with the status mapped from the `ErrorType` and the error's description as `detail`; the `NotFound`/`Conflict` union members exist for the OpenAPI documentation. See [Result Pattern — Mapping to HTTP Responses](../result-pattern.md#mapping-to-http-responses). ### Registration and mapping diff --git a/docs/articles/packages/vulthil-sharedkernel-infrastructure-cosmos.md b/docs/articles/packages/vulthil-sharedkernel-infrastructure-cosmos.md index 45262b0d..b82d3175 100644 --- a/docs/articles/packages/vulthil-sharedkernel-infrastructure-cosmos.md +++ b/docs/articles/packages/vulthil-sharedkernel-infrastructure-cosmos.md @@ -6,11 +6,11 @@ Use `Vulthil.SharedKernel.Infrastructure.Cosmos` to run the shared infrastructur - Cosmos DB is the underlying store for an application's primary `DbContext` - Aspire-wired Cosmos connection via `AddCosmosDbContext` -- Outbox processing should use the Cosmos-specific strategy (best-effort relay without relational locking) +- Outbox processing should use the Cosmos-specific store (best-effort relay without relational locking) ## Pattern -- Call `UseCosmosDb("connectionStringKey")` on the database infrastructure configurator – it both registers the EF Core context and selects the Cosmos outbox strategy +- Call `UseCosmosDb("connectionStringKey")` on the database infrastructure configurator – it both registers the EF Core context and selects the Cosmos outbox store - Configure the Cosmos-specific entity model for `OutboxMessage` via the `ApplyCosmosOutbox()` model-builder extension (call it from your `OnModelCreating`) - Order between `UseCosmosDb`, `EnableOutboxProcessing`, and `UseOutboxStore` does not matter; the configurator defers the underlying registrations until the full chain has executed, and the Cosmos outbox store is applied only as a default – a custom store selected via `UseOutboxStore` is always preserved diff --git a/docs/articles/packages/vulthil-sharedkernel-infrastructure-mysql.md b/docs/articles/packages/vulthil-sharedkernel-infrastructure-mysql.md index fa322e41..2e3eaaaa 100644 --- a/docs/articles/packages/vulthil-sharedkernel-infrastructure-mysql.md +++ b/docs/articles/packages/vulthil-sharedkernel-infrastructure-mysql.md @@ -1,15 +1,15 @@ # Vulthil.SharedKernel.Infrastructure.MySql -Use `Vulthil.SharedKernel.Infrastructure.MySql` to wire a MySQL-backed `DbContext` together with the MySQL-tuned outbox strategy. +Use `Vulthil.SharedKernel.Infrastructure.MySql` to wire a MySQL-backed `DbContext` together with the MySQL-tuned outbox store. ## When to use - MySQL is the underlying database for an application's primary `DbContext` -- Outbox processing should use the MySQL strategy (row-level locking via `FOR UPDATE SKIP LOCKED`) +- Outbox processing should use the MySQL store (row-level locking via `FOR UPDATE SKIP LOCKED`) ## Pattern -- Call `UseMySql("ConnectionStringKey")` on the database infrastructure configurator – it registers a pooled EF Core context for the named connection string, selects the MySQL outbox strategy, and (when outbox processing is enabled) wires the commit-time relay trigger +- Call `UseMySql("ConnectionStringKey")` on the database infrastructure configurator – it registers a pooled EF Core context for the named connection string, selects the MySQL outbox store, and (when outbox processing is enabled) wires the commit-time relay trigger - Order between `UseMySql`, `EnableOutboxProcessing`, and `UseOutboxStore` does not matter; the configurator defers the underlying registrations until the full chain has executed, and the MySQL outbox store is applied only as a default – a custom store selected via `UseOutboxStore` is always preserved - The relay's locking fetch SQL is composed from the model's mapped identifiers, so custom outbox table or column names (a naming convention, `ToTable`, or `HasColumnName`) are supported, and the relay dispatches strictly in `(OccurredOnUtc, Id)` order - Retrying execution strategies are fully supported – the outbox processor runs its transactional unit inside the context's execution strategy (`Database.CreateExecutionStrategy().ExecuteAsync`) diff --git a/docs/articles/packages/vulthil-sharedkernel-infrastructure-npgsql.md b/docs/articles/packages/vulthil-sharedkernel-infrastructure-npgsql.md index 7ef395a0..5a55ca40 100644 --- a/docs/articles/packages/vulthil-sharedkernel-infrastructure-npgsql.md +++ b/docs/articles/packages/vulthil-sharedkernel-infrastructure-npgsql.md @@ -1,16 +1,16 @@ # Vulthil.SharedKernel.Infrastructure.Npgsql -Use `Vulthil.SharedKernel.Infrastructure.Npgsql` to wire a PostgreSQL-backed `DbContext` together with the PostgreSQL-tuned outbox strategy. +Use `Vulthil.SharedKernel.Infrastructure.Npgsql` to wire a PostgreSQL-backed `DbContext` together with the PostgreSQL-tuned outbox store. ## When to use - PostgreSQL is the underlying database for an application's primary `DbContext` - Aspire-wired connection strings via `AddNpgsqlDbContext` -- Outbox processing should use the PostgreSQL strategy (row-level locking via `FOR UPDATE SKIP LOCKED`) +- Outbox processing should use the PostgreSQL store (row-level locking via `FOR UPDATE SKIP LOCKED`) ## Pattern -- Call `UseNpgsql("ConnectionStringKey")` on the database infrastructure configurator – it both registers the EF Core context and selects the Npgsql outbox strategy +- Call `UseNpgsql("ConnectionStringKey")` on the database infrastructure configurator – it both registers the EF Core context and selects the Npgsql outbox store - Order between `UseNpgsql`, `EnableOutboxProcessing`, and `UseOutboxStore` does not matter; the configurator defers the underlying registrations until the full chain has executed, and the Npgsql outbox store is applied only as a default – a custom store selected via `UseOutboxStore` is always preserved - The relay's locking fetch SQL is composed from the model's mapped identifiers, so custom outbox table or column names (a naming convention such as `UseSnakeCaseNamingConvention`, `ToTable`, or `HasColumnName`) are supported, and the relay dispatches strictly in `(OccurredOnUtc, Id)` order - Retrying execution strategies are fully supported – the outbox processor runs its transactional unit inside the context's execution strategy (`Database.CreateExecutionStrategy().ExecuteAsync`), so there is no need to force `DisableRetry` diff --git a/docs/articles/packages/vulthil-sharedkernel-infrastructure-relational.md b/docs/articles/packages/vulthil-sharedkernel-infrastructure-relational.md index b7ed06de..62994add 100644 --- a/docs/articles/packages/vulthil-sharedkernel-infrastructure-relational.md +++ b/docs/articles/packages/vulthil-sharedkernel-infrastructure-relational.md @@ -1,6 +1,6 @@ # Vulthil.SharedKernel.Infrastructure.Relational -Use `Vulthil.SharedKernel.Infrastructure.Relational` as the shared base for relational EF Core providers and as the source of the default relational outbox strategy. +Use `Vulthil.SharedKernel.Infrastructure.Relational` as the shared base for relational EF Core providers: it holds the relational outbox store base class, the commit-time relay trigger, and the `MigrateAsync` startup helper. ## When to use @@ -10,7 +10,7 @@ Use `Vulthil.SharedKernel.Infrastructure.Relational` as the shared base for rela ## Pattern - Pull this package in transitively via the provider package (`Vulthil.SharedKernel.Infrastructure.Npgsql`, `…MySql`, etc.); it is rarely referenced directly from application code -- Derive provider-specific outbox strategies from `RelationalOutboxStrategy` to add row-level locking or other provider-specific tuning +- Derive provider-specific outbox stores from `RelationalOutboxStore` to add row-level locking or other provider-specific tuning - Apply migrations during startup rather than at build time so deployments stay reproducible ## Usage @@ -29,21 +29,27 @@ app.Run(); `MigrateAsync` checks for pending migrations and only invokes `Database.MigrateAsync()` when at least one is pending, so it is safe to call on every startup. -### Reusing the relational outbox strategy +### Reusing the relational outbox store -Provider packages wire `RelationalOutboxStrategy` (or a subclass of it) automatically through their own `Use*` extension. If you are authoring a new provider, inherit from `RelationalOutboxStrategy` and override `FetchMessagesAsync` to add provider-specific row locking: +Provider packages wire `RelationalOutboxStore` (or a subclass of it) automatically through their own `Use*` extension. The base class records relay outcomes with set-based `ExecuteUpdate` calls, deletes retention batches by key, and requires the relay batch to run inside a transaction — it throws at relay time if `TContext` does not implement `IUnitOfWork` (derive from `BaseDbContext`), because without a transaction provider row-locking such as `FOR UPDATE SKIP LOCKED` would release immediately after the fetch and concurrent relay instances could double-dispatch. + +If you are authoring a new provider, inherit from `RelationalOutboxStore` and override the fetch to add provider-specific row locking: ```csharp -public sealed class MyProviderOutboxStrategy : RelationalOutboxStrategy +public sealed class MyProviderOutboxStore( + TContext dbContext, TimeProvider timeProvider, IOptions options) + : RelationalOutboxStore(dbContext, timeProvider, options) + where TContext : DbContext, ISaveOutboxMessages { - public override Task> FetchMessagesAsync( - DbSet outboxMessages, - int batchSize, - int maxRetries, - CancellationToken cancellationToken) + protected override Task> FetchMessagesAsync( + int batchSize, int maxRetries, CancellationToken cancellationToken) { // Add provider-specific FOR UPDATE SKIP LOCKED or equivalent here. - return base.FetchMessagesAsync(outboxMessages, batchSize, maxRetries, cancellationToken); + return base.FetchMessagesAsync(batchSize, maxRetries, cancellationToken); } } ``` + +### Commit-time relay trigger + +`AddRelationalOutboxCommitTrigger()` registers a transaction interceptor (`OutboxCommitInterceptor`) that wakes the outbox relay when an explicit database transaction commits, so captured messages relay promptly instead of waiting for the next poll. Provider `Use*` extensions register it automatically when outbox processing is enabled. diff --git a/docs/articles/packages/vulthil-sharedkernel-outbox-entityframeworkcore.md b/docs/articles/packages/vulthil-sharedkernel-outbox-entityframeworkcore.md index 9a8e28aa..8738144e 100644 --- a/docs/articles/packages/vulthil-sharedkernel-outbox-entityframeworkcore.md +++ b/docs/articles/packages/vulthil-sharedkernel-outbox-entityframeworkcore.md @@ -19,5 +19,5 @@ engine. It isolates all EF Core coupling so the engine package stays persistence - `DomainEventsToOutboxMessageSaveChangesInterceptor` / `IOutboxInterceptor` — domain-event capture. - `ApplyOutbox()` — a `ModelBuilder` extension applying the provider-agnostic `OutboxMessage` mapping. Provider packages offer optimized alternatives (`ApplyNpgsqlOutbox()`, `ApplyMySqlOutbox()`, `ApplyCosmosOutbox()`). -See the [Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md) +See the [Outbox Pattern](../outbox-pattern.md) article for the design. diff --git a/docs/articles/packages/vulthil-sharedkernel-outbox.md b/docs/articles/packages/vulthil-sharedkernel-outbox.md index 43ad7470..fe41e6c1 100644 --- a/docs/articles/packages/vulthil-sharedkernel-outbox.md +++ b/docs/articles/packages/vulthil-sharedkernel-outbox.md @@ -27,5 +27,5 @@ seam. It has **no EF Core dependency**; the EF implementation lives in - Opt into a retention sweep via `EnableOutboxProcessing(o => o.Retention.Enabled = true)` to periodically delete processed and dead-lettered rows older than a window (relational set-based `ExecuteDelete`; the same sweep covers Cosmos). -See the [Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md) +See the [Outbox Pattern](../outbox-pattern.md) article for the design, the pluggable-sink model, and the commit-time trigger. diff --git a/docs/articles/packages/vulthil-sharedkernel.md b/docs/articles/packages/vulthil-sharedkernel.md index 99e5992b..d0ddae19 100644 --- a/docs/articles/packages/vulthil-sharedkernel.md +++ b/docs/articles/packages/vulthil-sharedkernel.md @@ -17,11 +17,11 @@ Use `Vulthil.SharedKernel` for domain primitives reused across bounded contexts. ### Defining an entity +Entities compare by identity, so the identifier type must have value equality — use a `record` (or +`readonly record struct`), not a plain class: + ```csharp -public sealed class UserId(Guid value) -{ - public Guid Value { get; } = value; -} +public sealed record UserId(Guid Value); public sealed class User : AggregateRoot { diff --git a/docs/articles/packages/vulthil-xunit.md b/docs/articles/packages/vulthil-xunit.md index 73022c11..e5f21f6f 100644 --- a/docs/articles/packages/vulthil-xunit.md +++ b/docs/articles/packages/vulthil-xunit.md @@ -12,3 +12,8 @@ Use `Vulthil.xUnit` for reusable xUnit test base infrastructure. - Derive from shared base test classes consistently - Keep fixture setup centralized and reusable - Keep test classes focused on behavior, not plumbing + +This package is xUnit-coupled by design; the framework-agnostic helpers (`Result`-based polling, HTTP response +deserialization) live in [Vulthil.Extensions.Testing](vulthil-extensions-testing.md). + +See [Testing](../testing.md) for the full guide (base classes, containers, `ContainerHost`, HTTP mocks). diff --git a/docs/articles/result-pattern.md b/docs/articles/result-pattern.md index f6400403..d17fea6d 100644 --- a/docs/articles/result-pattern.md +++ b/docs/articles/result-pattern.md @@ -162,3 +162,14 @@ public async Task Get(Guid id) return result.ToActionResult(this); } ``` + +Success maps to `200 Ok` (`Result`), `204 NoContent` (non-generic `Result`), or `201 CreatedAtRoute` +via `ToCreatedAtRouteHttpResult`. Every failure returns an RFC 7807 problem body with the status from the +[table above](#error-classifications): `detail` carries the error's `Description`, and the error's `Code` +appears as a key in the problem `extensions` (mapped to the description). `Validation` failures return a +validation problem whose per-field `errors` dictionary is built from the `ValidationError`'s inner errors. The +minimal-API and MVC paths produce the same status and detail for the same error. + +One nuance of the typed union: at runtime `NotFound` and `Conflict` errors surface as a `ProblemHttpResult` +carrying 404/409 **with that problem body** — the `NotFound`/`Conflict` members of the union exist so OpenAPI +documents those status codes, not as the results actually returned. diff --git a/docs/articles/testing.md b/docs/articles/testing.md index 1aeb4766..eb40cb95 100644 --- a/docs/articles/testing.md +++ b/docs/articles/testing.md @@ -9,7 +9,7 @@ | `Vulthil.xUnit` | Base test classes, auto-mocking, `WebApplicationFactory` support, and Testcontainers integration | | `Vulthil.xUnit.Cosmos` | Azure Cosmos DB emulator fixture with a database per test class | | `Vulthil.Messaging.TestHarness` | In-memory messaging transport for asserting published/consumed messages | -| `Vulthil.Extensions.Testing` | Shared assertion helpers and test composition utilities | +| `Vulthil.Extensions.Testing` | Framework-agnostic helpers — `Result`-based polling and HTTP response deserialization; no xUnit dependency | ## Unit Tests @@ -94,7 +94,7 @@ Use `IClassFixture` so each test class gets its own factory. By d Key features: - **Scoped services** – `ScopedServices` gives you a fresh DI scope per test. -- **Automatic database reset** – the database is reset with Respawn after each test, so tests sharing a factory start from a clean state. +- **Automatic database reset** – the database is reset with Respawn after each test, so tests sharing a factory start from a clean state. Hosted services implementing `IRestartableHostedService` (from `Vulthil.Extensions.Hosting`) are stopped around the reset and restarted afterwards, so a database-polling relay such as the outbox background service never contends with it. - **Log capture** – application logs are routed to the currently running test automatically (via `TestContext`). The `ITestOutputHelper` constructor parameter is for writing test output directly. - **One host per class** – all tests in a class run against the fixture's test host. Override `CreateFactory()` (e.g. `FactoryFixture.WithWebHostBuilder(...)`) when a class needs per-test host configuration; derived factories are disposed after each test. @@ -248,9 +248,16 @@ Mock state is reset after each test (like the database), so stubs and captured r `Vulthil.Messaging.TestHarness` provides an in-memory transport that runs your consumers with no broker and captures every produced and consumed message for assertion. It is built entirely on the public -`Vulthil.Messaging.Transport` SDK, so it mirrors the real consumer topology assembled from your queue -configuration. Dispatch is synchronous — by the time a publish/send/request call returns, every consumer (and -stub) it triggered has run, so assertions need no polling. +`Vulthil.Messaging.Transport` SDK, so it assembles the same execution plans — consumers, polymorphic dispatch, +per-consumer retry resolution — from your queue configuration that a real transport would. Dispatch is +synchronous — by the time a publish/send/request call returns, every consumer (and stub) it triggered has run, +so assertions need no polling. + +Two fidelity limits to keep in mind: the harness dispatches each produced message **once** to all matching +consumers (a real broker delivers a distinct copy per subscribed queue), and partition lanes are not simulated +(dispatch is inline and ordered by call). Retries run back-to-back without the configured delays; a one-way +consumer that exhausts them publishes a `Fault` — observable via `Published>()` — while the +publish call itself completes normally. ### Composing a harness (unit/component tests) diff --git a/src/Vulthil.Extensions.Hosting/README.md b/src/Vulthil.Extensions.Hosting/README.md index 761ce739..eb0db275 100644 --- a/src/Vulthil.Extensions.Hosting/README.md +++ b/src/Vulthil.Extensions.Hosting/README.md @@ -26,3 +26,7 @@ Implement it only on services whose `StartAsync`/`StopAsync` are idempotent acro implementing `IHostedService` directly over inheriting `BackgroundService`: the host observes only the execute task of a `BackgroundService`'s first start and stops the whole host when that task is canceled while the application is running — and on .NET 10 a stop racing service startup can cancel the task before `ExecuteAsync` has run at all. + +## Docs + +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.Extensions.Testing/HttpResponseMessageExtensions.cs b/src/Vulthil.Extensions.Testing/HttpResponseMessageExtensions.cs index 326f1499..f9f24250 100644 --- a/src/Vulthil.Extensions.Testing/HttpResponseMessageExtensions.cs +++ b/src/Vulthil.Extensions.Testing/HttpResponseMessageExtensions.cs @@ -8,12 +8,14 @@ namespace Vulthil.Extensions.Testing; public static class HttpResponseMessageExtensions { /// - /// Reads the JSON content of the HTTP response and deserializes it into the specified type. + /// Asserts the response indicates success, then reads its JSON content and deserializes it into + /// . Throws when the response is , has a + /// non-success status code, or its body is empty or deserializes to . /// - /// - /// - /// - /// + /// The type to deserialize the JSON response body into. + /// The HTTP response to read. + /// A token to observe for cancellation. + /// The deserialized response body. public static async Task GetResponseAsync(this HttpResponseMessage? response, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(response); diff --git a/src/Vulthil.Extensions.Testing/README.md b/src/Vulthil.Extensions.Testing/README.md index 61dd8c52..d8e2664e 100644 --- a/src/Vulthil.Extensions.Testing/README.md +++ b/src/Vulthil.Extensions.Testing/README.md @@ -10,4 +10,4 @@ Testing-oriented extensions for asserting and composing application behaviors. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.Messaging.Abstractions/README.md b/src/Vulthil.Messaging.Abstractions/README.md index 06161a0a..c49bf45d 100644 --- a/src/Vulthil.Messaging.Abstractions/README.md +++ b/src/Vulthil.Messaging.Abstractions/README.md @@ -10,4 +10,4 @@ Messaging contracts for producers/consumers and request/reply boundaries. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.Messaging.Inbox.Cosmos/README.md b/src/Vulthil.Messaging.Inbox.Cosmos/README.md index aa761b0e..edd83023 100644 --- a/src/Vulthil.Messaging.Inbox.Cosmos/README.md +++ b/src/Vulthil.Messaging.Inbox.Cosmos/README.md @@ -35,6 +35,6 @@ builder.AddMessaging(messaging => ``` The marker is a self-contained document keyed and partitioned by `MessageId` (its own container), so duplicate -inserts conflict and are treated as already-processed. See the -[Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md) article -for the guarantees and how this differs from the relational store. +inserts conflict and are treated as already-processed. See the Inbox Pattern article on the +[documentation site](https://vulthil.github.io/Vulthil.SharedKernel/) for the guarantees and how this differs +from the relational store. diff --git a/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/InboxMessage.cs b/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/InboxMessage.cs index 9f55bd32..f0c1c738 100644 --- a/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/InboxMessage.cs +++ b/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/InboxMessage.cs @@ -12,7 +12,7 @@ public sealed record InboxMessage public required string MessageId { get; init; } /// - /// Gets or sets the UTC timestamp at which the message was processed. + /// Gets the UTC timestamp at which the message was processed. /// public DateTimeOffset ProcessedOnUtc { get; init; } } diff --git a/src/Vulthil.Messaging.Inbox/IIdempotencyStore.cs b/src/Vulthil.Messaging.Inbox/IIdempotencyStore.cs index ad246c30..3e654497 100644 --- a/src/Vulthil.Messaging.Inbox/IIdempotencyStore.cs +++ b/src/Vulthil.Messaging.Inbox/IIdempotencyStore.cs @@ -23,8 +23,15 @@ public interface IIdempotencyStore /// skipped. /// /// The idempotency key for the current delivery. - /// The message context for the current delivery. - /// The consumer invocation to run when the delivery is not a duplicate. + /// + /// The message context for the current delivery — optional input for implementations (e.g. to read metadata + /// or headers); the built-in stores do not require it. + /// + /// + /// The consumer invocation to run when the delivery is not a duplicate. The token an implementation passes to + /// this callback does not flow into the consumer: the consumer observes the delivery's own + /// , so the callback token cannot cancel the consumer body. + /// /// A token to observe for cancellation. /// /// if ran and the marker was recorded; diff --git a/src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs b/src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs index 5cf464a8..1c311bc1 100644 --- a/src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs +++ b/src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs @@ -12,9 +12,9 @@ namespace Vulthil.Messaging.Inbox; /// /// /// The guard only deduplicates. Bounding a consumer that keeps failing — retry count, fault emission, and -/// dead-lettering — is delegated to the transport (on RabbitMQ: retry up to the queue's max retry count, then a -/// fault is published and the delivery is nacked to the dead-letter exchange), so this filter deliberately -/// ignores . +/// dead-lettering — is delegated to the transport (on RabbitMQ: retry per the consumer's resolved retry policy, +/// then a fault is published and the delivery is dead-lettered when a dead-letter queue is configured), so this +/// filter deliberately ignores . /// /// The consumed message type. internal sealed class IdempotentConsumeFilter( diff --git a/src/Vulthil.Messaging.Inbox/MissingIdempotencyKeyException.cs b/src/Vulthil.Messaging.Inbox/MissingIdempotencyKeyException.cs index 39213257..0a1a5087 100644 --- a/src/Vulthil.Messaging.Inbox/MissingIdempotencyKeyException.cs +++ b/src/Vulthil.Messaging.Inbox/MissingIdempotencyKeyException.cs @@ -17,7 +17,7 @@ public MissingIdempotencyKeyException(Type messageType) } /// - /// Gets the message type whose delivery lacked an idempotency key, when available. + /// Gets the message type whose delivery lacked an idempotency key. /// public Type MessageType { get; } } diff --git a/src/Vulthil.Messaging.Inbox/README.md b/src/Vulthil.Messaging.Inbox/README.md index 4deda81d..5662277e 100644 --- a/src/Vulthil.Messaging.Inbox/README.md +++ b/src/Vulthil.Messaging.Inbox/README.md @@ -42,10 +42,11 @@ deduplication instead, set it on the store registration: `services.AddRelational ## Scope This package only **deduplicates**. Bounding a persistently-failing consumer is the transport's job: on RabbitMQ -a poison delivery is retried up to the queue's `MaxRetryCount`, then a `Fault` is published and the message is -nacked to the dead-letter exchange — the guard ignores `RetryCount` and adds no max-attempts of its own. It also +a failing delivery is retried per the consumer's retry policy (its registration's, falling back to the queue +default), then a `Fault` is published and the delivery is dead-lettered when a dead-letter queue is +configured — the guard ignores `RetryCount` and adds no max-attempts of its own. It also serializes only the marker *insert*, so two duplicates processed **concurrently** can each run the consumer body once. Keep side effects idempotent if concurrent duplicate delivery is possible. -See the [inbox pattern documentation](https://vulthil.github.io/Vulthil.SharedKernel/articles/inbox-pattern.html) +See the Inbox Pattern article on the [documentation site](https://vulthil.github.io/Vulthil.SharedKernel/) for the full design and the message-id stability contract. diff --git a/src/Vulthil.Messaging.RabbitMq/README.md b/src/Vulthil.Messaging.RabbitMq/README.md index c3e40860..8f000b1e 100644 --- a/src/Vulthil.Messaging.RabbitMq/README.md +++ b/src/Vulthil.Messaging.RabbitMq/README.md @@ -10,4 +10,4 @@ RabbitMQ implementation for the Vulthil messaging abstractions. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.Messaging.TestHarness/README.md b/src/Vulthil.Messaging.TestHarness/README.md index 597ac805..e3ee0df4 100644 --- a/src/Vulthil.Messaging.TestHarness/README.md +++ b/src/Vulthil.Messaging.TestHarness/README.md @@ -28,4 +28,4 @@ For an integration test that keeps the production composition, swap the transpor ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.Messaging/ConsumeFilterOptions.cs b/src/Vulthil.Messaging/ConsumeFilterOptions.cs index 4dce2ac4..00f4fc49 100644 --- a/src/Vulthil.Messaging/ConsumeFilterOptions.cs +++ b/src/Vulthil.Messaging/ConsumeFilterOptions.cs @@ -2,20 +2,22 @@ namespace Vulthil.Messaging; /// /// Configures the built-in consume filters that AddMessaging registers by default. -/// Each flag toggles a specific filter on or off; setting a flag to -/// causes the filter to pass deliveries straight through without performing its work. +/// Each flag toggles a specific filter on or off; a filter whose flag is +/// is never registered in DI, so deliveries do not pass through it at all. /// /// -/// Filters remain registered in DI regardless of these flags so that user code can still -/// resolve them (e.g. for unit tests); the flag is checked at invocation time. To disable -/// every default filter, set each flag explicitly. +/// The flags are read once, when AddMessaging composes the pipeline (after the configurator +/// action has run) — set them via configuration (Messaging:Options:ConsumeFilters) or +/// ConfigureMessagingOptions inside the configurator. There is no runtime toggle: changing +/// a flag after registration has no effect. /// public sealed class ConsumeFilterOptions { /// /// When (the default), /// emits structured Debug logs at the start and end of every consume, plus a Warning log on - /// uncaught exceptions, with timing information. + /// uncaught exceptions, with timing information. When , the filter is + /// not registered at all. /// public bool EnableLogging { get; set; } = true; } diff --git a/src/Vulthil.Messaging/IMessageConfigurationProvider.cs b/src/Vulthil.Messaging/IMessageConfigurationProvider.cs index c435e79b..c2c3f707 100644 --- a/src/Vulthil.Messaging/IMessageConfigurationProvider.cs +++ b/src/Vulthil.Messaging/IMessageConfigurationProvider.cs @@ -55,9 +55,9 @@ public interface IMessageConfigurationProvider IReadOnlyCollection QueueDefinitions { get; } /// - /// Gets the options controlling which built-in consume filters perform their work at runtime. - /// Filters check the appropriate flag on this object on every delivery, so toggles take effect - /// without re-registering the filter. + /// Gets the built-in consume-filter flags as they were configured at AddMessaging time. + /// The flags are applied once, at registration — a disabled filter is never added to DI — so + /// mutating them through this snapshot has no effect on the composed pipeline. /// ConsumeFilterOptions ConsumeFilters { get; } diff --git a/src/Vulthil.Messaging/README.md b/src/Vulthil.Messaging/README.md index 054c8246..e4b7d63b 100644 --- a/src/Vulthil.Messaging/README.md +++ b/src/Vulthil.Messaging/README.md @@ -14,4 +14,4 @@ transport uses. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.Results/README.md b/src/Vulthil.Results/README.md index cb5aeaa1..88db65ff 100644 --- a/src/Vulthil.Results/README.md +++ b/src/Vulthil.Results/README.md @@ -10,4 +10,4 @@ Small result primitives for explicit success/failure flows without exceptions. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.SharedKernel.Api/README.md b/src/Vulthil.SharedKernel.Api/README.md index bde4053a..055a197a 100644 --- a/src/Vulthil.SharedKernel.Api/README.md +++ b/src/Vulthil.SharedKernel.Api/README.md @@ -10,4 +10,4 @@ API-layer helpers for endpoints, controllers, and cross-cutting HTTP concerns. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.SharedKernel.Application/README.md b/src/Vulthil.SharedKernel.Application/README.md index 2f09b329..b7a95434 100644 --- a/src/Vulthil.SharedKernel.Application/README.md +++ b/src/Vulthil.SharedKernel.Application/README.md @@ -10,4 +10,4 @@ Application-layer building blocks such as handlers, pipelines, and validation he ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.SharedKernel.Infrastructure.Cosmos/README.md b/src/Vulthil.SharedKernel.Infrastructure.Cosmos/README.md index 448ec1da..c6278fd9 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.Cosmos/README.md +++ b/src/Vulthil.SharedKernel.Infrastructure.Cosmos/README.md @@ -10,4 +10,4 @@ Provider-specific EF Core mapping and optimizations for Cosmos DB. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.SharedKernel.Infrastructure.MySql/DependencyInjectionExtensions.cs b/src/Vulthil.SharedKernel.Infrastructure.MySql/DependencyInjectionExtensions.cs index 7826e36e..b4be3e59 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.MySql/DependencyInjectionExtensions.cs +++ b/src/Vulthil.SharedKernel.Infrastructure.MySql/DependencyInjectionExtensions.cs @@ -20,12 +20,22 @@ public static class DependencyInjectionExtensions { #if NET10_0_OR_GREATER - /// ... - /// ... - /// ... - /// ... - /// ... - /// ... .NET 10 wording ... + /// + /// Configures the database infrastructure for MySQL: registers a pooled for + /// the named connection string and selects the MySQL outbox store (row-level locking via + /// FOR UPDATE SKIP LOCKED) unless a custom store was chosen via UseOutboxStore. When outbox + /// processing is enabled, the commit-time relay trigger is wired as well. + /// + /// + /// On .NET 10 the Pomelo-compatible EF Core provider is registered directly and the MySQL server version is + /// detected from the connection at startup (ServerVersion.AutoDetect), so the database must be reachable + /// when the host starts. The underlying registrations are deferred until the configuration chain has executed, + /// so the order of UseMySql, EnableOutboxProcessing, and UseOutboxStore does not matter. + /// + /// The application's DbContext; it must expose the outbox set via ISaveOutboxMessages. + /// The database infrastructure configurator. + /// The connection-string name resolved from the host's configuration. + /// An optional action to configure the EF Core/Pomelo options (e.g. command timeout). /// The configurator for chaining. public static IDatabaseInfrastructureConfigurator UseMySql( this IDatabaseInfrastructureConfigurator configurator, @@ -34,12 +44,22 @@ public static IDatabaseInfrastructureConfigurator UseMySql UseMySqlCore(configurator, connectionStringKey, configureSettings); #else - /// ... - /// ... - /// ... - /// ... - /// ... - /// ... .NET 9 wording ... + /// + /// Configures the database infrastructure for MySQL: registers through the + /// Aspire Pomelo integration for the named connection string and selects the MySQL outbox store (row-level + /// locking via FOR UPDATE SKIP LOCKED) unless a custom store was chosen via UseOutboxStore. When + /// outbox processing is enabled, the commit-time relay trigger is wired as well. + /// + /// + /// On .NET 9 the context is registered via the Aspire.Pomelo.EntityFrameworkCore.MySql client + /// integration, which resolves the connection string and adds health checks, telemetry, and connection + /// resiliency. The underlying registrations are deferred until the configuration chain has executed, so the + /// order of UseMySql, EnableOutboxProcessing, and UseOutboxStore does not matter. + /// + /// The application's DbContext; it must expose the outbox set via ISaveOutboxMessages. + /// The database infrastructure configurator. + /// The connection-string name resolved by the Aspire integration. + /// An optional action to configure the Aspire integration settings (health checks, tracing, retries). /// An optional action to configure the DbContext options. /// The configurator for chaining. public static IDatabaseInfrastructureConfigurator UseMySql( diff --git a/src/Vulthil.SharedKernel.Infrastructure.MySql/README.md b/src/Vulthil.SharedKernel.Infrastructure.MySql/README.md index 67bc539c..7d79f60e 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.MySql/README.md +++ b/src/Vulthil.SharedKernel.Infrastructure.MySql/README.md @@ -10,4 +10,4 @@ Provider-specific EF Core mapping and optimizations for MySql. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.SharedKernel.Infrastructure.Npgsql/README.md b/src/Vulthil.SharedKernel.Infrastructure.Npgsql/README.md index 1d96ef2e..fdab44af 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.Npgsql/README.md +++ b/src/Vulthil.SharedKernel.Infrastructure.Npgsql/README.md @@ -10,4 +10,4 @@ Provider-specific EF Core mapping and optimizations for Npgsql/PostgreSQL. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.SharedKernel.Infrastructure.Relational/README.md b/src/Vulthil.SharedKernel.Infrastructure.Relational/README.md index aeedf6b9..9f0828b3 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.Relational/README.md +++ b/src/Vulthil.SharedKernel.Infrastructure.Relational/README.md @@ -10,4 +10,4 @@ Shared relational helpers for provider implementations. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.SharedKernel.Infrastructure/README.md b/src/Vulthil.SharedKernel.Infrastructure/README.md index 2b130ca0..0ec1fe1e 100644 --- a/src/Vulthil.SharedKernel.Infrastructure/README.md +++ b/src/Vulthil.SharedKernel.Infrastructure/README.md @@ -10,4 +10,4 @@ Infrastructure helpers for persistence, transactions, outbox, and EF Core integr ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/README.md b/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/README.md index e1e17070..652e2f4d 100644 --- a/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/README.md +++ b/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/README.md @@ -2,7 +2,8 @@ [![NuGet](https://img.shields.io/nuget/v/Vulthil.SharedKernel.Outbox.EntityFrameworkCore)](https://www.nuget.org/packages/Vulthil.SharedKernel.Outbox.EntityFrameworkCore) -The Entity Framework Core implementation of the [`Vulthil.SharedKernel.Outbox`](../Vulthil.SharedKernel.Outbox) +The Entity Framework Core implementation of the +[`Vulthil.SharedKernel.Outbox`](https://www.nuget.org/packages/Vulthil.SharedKernel.Outbox) engine. It keeps all EF Core coupling out of the engine package: - `ISaveOutboxMessages` — the `DbSet` marker the application's `DbContext` implements. @@ -14,5 +15,5 @@ engine. It keeps all EF Core coupling out of the engine package: - `ApplyOutbox()` — a `ModelBuilder` extension applying the provider-agnostic `OutboxMessage` mapping. Provider packages offer optimized alternatives (`ApplyNpgsqlOutbox()`, `ApplyMySqlOutbox()`, `ApplyCosmosOutbox()`). Most applications consume this transitively via `Vulthil.SharedKernel.Infrastructure` (`EnableOutboxProcessing`) and -a provider package (`UseNpgsql`, `UseMySql`, `UseCosmosDb`). See the -[Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md) article. +a provider package (`UseNpgsql`, `UseMySql`, `UseCosmosDb`). See the Outbox Pattern article on the +[documentation site](https://vulthil.github.io/Vulthil.SharedKernel/). diff --git a/src/Vulthil.SharedKernel.Outbox/OutboxProcessingOptions.cs b/src/Vulthil.SharedKernel.Outbox/OutboxProcessingOptions.cs index 5fd95225..ee50db63 100644 --- a/src/Vulthil.SharedKernel.Outbox/OutboxProcessingOptions.cs +++ b/src/Vulthil.SharedKernel.Outbox/OutboxProcessingOptions.cs @@ -23,7 +23,8 @@ public sealed class OutboxProcessingOptions [Range(1, int.MaxValue)] public int BatchSize { get; set; } = 10; /// - /// Gets the maximum number of delivery attempts before a message is marked as processed. Default is 3. + /// Gets the maximum number of delivery attempts before a message is dead-lettered — its FailedOnUtc + /// is set and it is no longer relayed. Default is 3. /// [Range(1, int.MaxValue)] public int MaxRetries { get; set; } = 3; diff --git a/src/Vulthil.SharedKernel.Outbox/README.md b/src/Vulthil.SharedKernel.Outbox/README.md index c896eb8f..39dcb4a1 100644 --- a/src/Vulthil.SharedKernel.Outbox/README.md +++ b/src/Vulthil.SharedKernel.Outbox/README.md @@ -3,13 +3,14 @@ [![NuGet](https://img.shields.io/nuget/v/Vulthil.SharedKernel.Outbox)](https://www.nuget.org/packages/Vulthil.SharedKernel.Outbox) The transactional **outbox engine** for `Vulthil.SharedKernel`: the message-capture model (`OutboxMessage`), the -relay processor and background service, pluggable sinks (`IOutboxDispatcher`), the commit-time relay signal, and the -provider-agnostic strategy contracts (`IOutboxStrategy` / `BaseOutboxStrategy`). +relay processor and background service, pluggable dispatchers (`IOutboxDispatcher`), the commit-time relay signal, +and the persistence-agnostic `IOutboxStore` seam. -It is intentionally persistence-light — it carries the outbox engine without the rest of -`Vulthil.SharedKernel.Infrastructure` — so a messaging bridge (such as `Vulthil.Messaging.Outbox`) can depend on the -engine alone. The full infrastructure package (`Vulthil.SharedKernel.Infrastructure`) references this engine and adds -the `DbContext` base, the database-infrastructure configurator, and the DI wiring (`EnableOutboxProcessing`). +It is intentionally persistence-light — free of any EF Core dependency — so a messaging bridge (such as +`Vulthil.Messaging.Outbox`) can depend on the engine alone. The EF Core implementation lives in +`Vulthil.SharedKernel.Outbox.EntityFrameworkCore`, and the full infrastructure package +(`Vulthil.SharedKernel.Infrastructure`) references the engine and adds the `DbContext` base, the +database-infrastructure configurator, and the DI wiring (`EnableOutboxProcessing`). -See the [Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md) -article for the design and usage. +See the Outbox Pattern article on the [documentation site](https://vulthil.github.io/Vulthil.SharedKernel/) +for the design and usage. diff --git a/src/Vulthil.SharedKernel/README.md b/src/Vulthil.SharedKernel/README.md index 981bc0fd..2720b411 100644 --- a/src/Vulthil.SharedKernel/README.md +++ b/src/Vulthil.SharedKernel/README.md @@ -10,4 +10,4 @@ Core domain primitives and base abstractions for shared domain logic. ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.xUnit.Cosmos/README.md b/src/Vulthil.xUnit.Cosmos/README.md index 577d4249..064b4b96 100644 --- a/src/Vulthil.xUnit.Cosmos/README.md +++ b/src/Vulthil.xUnit.Cosmos/README.md @@ -20,4 +20,4 @@ internal sealed class CosmosTestContainer(IMessageSink messageSink) ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ diff --git a/src/Vulthil.xUnit/Http/IHttpMock.cs b/src/Vulthil.xUnit/Http/IHttpMock.cs index 05f88310..f131f03f 100644 --- a/src/Vulthil.xUnit/Http/IHttpMock.cs +++ b/src/Vulthil.xUnit/Http/IHttpMock.cs @@ -5,7 +5,8 @@ namespace Vulthil.xUnit.Http; /// /// An in-process fake for an outbound dependency. Configure stubbed responses per test /// and inspect the requests the system under test sent. Register one per typed client via -/// AddHttpMock<TClient, TImplementation>() on the factory and retrieve it with HttpMock<TClient>(). +/// AddHttpMock<TClient>() (or per named client via AddHttpMock("name")) on the factory and +/// retrieve it with HttpMock<TClient>() (or HttpMock("name")). /// public interface IHttpMock : IResettableResource { diff --git a/src/Vulthil.xUnit/README.md b/src/Vulthil.xUnit/README.md index c799887c..9769ddab 100644 --- a/src/Vulthil.xUnit/README.md +++ b/src/Vulthil.xUnit/README.md @@ -4,10 +4,13 @@ Reusable xUnit base infrastructure for integration and unit test composition: auto-mocked unit test base classes, a `WebApplicationFactory` with Testcontainers and HTTP-mock support, and an assembly-wide `ContainerHost` that shares containers across parallel test classes through isolated per-class scopes built into the fixture base classes (a database per class for database containers, a virtual host per class for RabbitMQ; Cosmos lives in `Vulthil.xUnit.Cosmos`). +This package is xUnit-coupled by design; the framework-agnostic helpers (`Result`-based polling, HTTP response +deserialization) live in `Vulthil.Extensions.Testing`. + ## Install `dotnet add package Vulthil.xUnit` ## Docs -Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages +Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/ From 0002938418b39391adf0c06d18a22a498262a6cd Mon Sep 17 00:00:00 2001 From: Vulthil Date: Mon, 20 Jul 2026 13:55:18 +0200 Subject: [PATCH 2/2] chore: give each nuget package content-specific tags --- Directory.Build.props | 2 +- .../Vulthil.Extensions.Hosting.csproj | 1 + .../Vulthil.Extensions.Testing.csproj | 3 ++- .../Vulthil.Messaging.Abstractions.csproj | 1 + .../Vulthil.Messaging.Inbox.Cosmos.csproj | 1 + .../Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj | 1 + .../Vulthil.Messaging.Inbox.Relational.csproj | 1 + src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj | 1 + src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj | 1 + .../Vulthil.Messaging.RabbitMq.csproj | 1 + .../Vulthil.Messaging.TestHarness.csproj | 1 + src/Vulthil.Messaging/Vulthil.Messaging.csproj | 1 + src/Vulthil.Results/Vulthil.Results.csproj | 1 + src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj | 1 + .../Vulthil.SharedKernel.Application.csproj | 1 + .../Vulthil.SharedKernel.Infrastructure.Cosmos.csproj | 1 + .../Vulthil.SharedKernel.Infrastructure.MySql.csproj | 1 + .../Vulthil.SharedKernel.Infrastructure.Npgsql.csproj | 1 + .../Vulthil.SharedKernel.Infrastructure.Relational.csproj | 1 + .../Vulthil.SharedKernel.Infrastructure.csproj | 1 + .../Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj | 1 + .../Vulthil.SharedKernel.Outbox.csproj | 1 + src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj | 1 + src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj | 1 + src/Vulthil.xUnit/Vulthil.xUnit.csproj | 1 + 25 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1ce7f560..e3827962 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -40,7 +40,7 @@ Copyright © Vulthil MIT https://github.com/Vulthil/Vulthil.SharedKernel - shared-kernel;ddd;cqrs;result-pattern;dotnet + git https://github.com/Vulthil/Vulthil.SharedKernel diff --git a/src/Vulthil.Extensions.Hosting/Vulthil.Extensions.Hosting.csproj b/src/Vulthil.Extensions.Hosting/Vulthil.Extensions.Hosting.csproj index 0952ae28..d5613fb4 100644 --- a/src/Vulthil.Extensions.Hosting/Vulthil.Extensions.Hosting.csproj +++ b/src/Vulthil.Extensions.Hosting/Vulthil.Extensions.Hosting.csproj @@ -2,6 +2,7 @@ Hosting abstractions for Vulthil — notably IRestartableHostedService, a marker a background service implements to declare its execution can be stopped and started again cleanly, so infrastructure (such as test harnesses) may safely pause and resume it. + hosting;hosted-service;ihostedservice;background-service;restartable;lifecycle diff --git a/src/Vulthil.Extensions.Testing/Vulthil.Extensions.Testing.csproj b/src/Vulthil.Extensions.Testing/Vulthil.Extensions.Testing.csproj index ee85fda2..d5064944 100644 --- a/src/Vulthil.Extensions.Testing/Vulthil.Extensions.Testing.csproj +++ b/src/Vulthil.Extensions.Testing/Vulthil.Extensions.Testing.csproj @@ -1,7 +1,8 @@  - Testing-oriented extensions for asserting and composing application behaviors. + Framework-agnostic test helpers — Result-based polling (Polling.WaitAsync) for eventually-consistent checks and HTTP response JSON deserialization. No xUnit dependency; the xUnit-coupled test stack lives in Vulthil.xUnit. + testing;polling;eventually-consistent;http;result;helpers diff --git a/src/Vulthil.Messaging.Abstractions/Vulthil.Messaging.Abstractions.csproj b/src/Vulthil.Messaging.Abstractions/Vulthil.Messaging.Abstractions.csproj index f7ef807c..116a4970 100644 --- a/src/Vulthil.Messaging.Abstractions/Vulthil.Messaging.Abstractions.csproj +++ b/src/Vulthil.Messaging.Abstractions/Vulthil.Messaging.Abstractions.csproj @@ -2,6 +2,7 @@ Messaging contracts for producers/consumers and request/reply boundaries. + messaging;abstractions;contracts;consumer;publisher;request-reply;message-bus diff --git a/src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj b/src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj index 682374e2..ebe95af2 100644 --- a/src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj +++ b/src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj @@ -2,6 +2,7 @@ Azure Cosmos DB idempotency store for Vulthil.Messaging.Inbox — effectively-once message processing (best-effort deduplication layered over idempotent-by-design writes). Cosmos has no cross-partition transaction, so it cannot offer the transactional exactly-once the relational store does. + inbox;idempotency;deduplication;cosmosdb;azure-cosmos-db;efcore;messaging diff --git a/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj b/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj index f087e5b0..9e7a7c53 100644 --- a/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj +++ b/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj @@ -2,6 +2,7 @@ Shared Entity Framework Core primitives for Vulthil.Messaging.Inbox — the InboxMessage marker entity and the ISaveInboxMessages context interface, reused by the relational and Cosmos idempotency stores. + inbox;idempotency;deduplication;efcore;entity-framework-core;messaging diff --git a/src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj b/src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj index 250dbac0..4980c6ec 100644 --- a/src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj +++ b/src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj @@ -2,6 +2,7 @@ Relational Entity Framework Core idempotency store for Vulthil.Messaging.Inbox — transactional exactly-once message processing where the inbox marker and the consumer's writes commit in one transaction. Works with any relational EF Core provider (PostgreSQL, SQL Server, MySQL, SQLite); Cosmos requires a separate store. + inbox;idempotency;deduplication;exactly-once;efcore;relational;sql;messaging diff --git a/src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj b/src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj index 79145251..26334cde 100644 --- a/src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj +++ b/src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj @@ -2,6 +2,7 @@ Idempotent-receiver (inbox) consume filter for Vulthil.Messaging. Provides exactly-once message processing on top of at-least-once delivery via a pluggable, persistence-agnostic idempotency store. + inbox;idempotency;idempotent-consumer;deduplication;messaging;exactly-once;consume-filter diff --git a/src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj b/src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj index 6bd0a2fb..81835fe1 100644 --- a/src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj +++ b/src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj @@ -2,6 +2,7 @@ Transactional bus-publish outbox for Vulthil.Messaging. A publish/send filter captures messages published inside a database transaction into the shared outbox table; the outbox relay sends them to the broker after the transaction commits, with a stable message id, eliminating the dual-write problem. + outbox;transactional-outbox;outbox-pattern;messaging;message-bus;dual-write;reliability diff --git a/src/Vulthil.Messaging.RabbitMq/Vulthil.Messaging.RabbitMq.csproj b/src/Vulthil.Messaging.RabbitMq/Vulthil.Messaging.RabbitMq.csproj index 1f14aece..21836e95 100644 --- a/src/Vulthil.Messaging.RabbitMq/Vulthil.Messaging.RabbitMq.csproj +++ b/src/Vulthil.Messaging.RabbitMq/Vulthil.Messaging.RabbitMq.csproj @@ -2,6 +2,7 @@ RabbitMQ implementation for the Vulthil messaging abstractions. + rabbitmq;amqp;messaging;message-bus;pubsub;request-reply;retry;dead-letter diff --git a/src/Vulthil.Messaging.TestHarness/Vulthil.Messaging.TestHarness.csproj b/src/Vulthil.Messaging.TestHarness/Vulthil.Messaging.TestHarness.csproj index eaf9df2f..fa5b678c 100644 --- a/src/Vulthil.Messaging.TestHarness/Vulthil.Messaging.TestHarness.csproj +++ b/src/Vulthil.Messaging.TestHarness/Vulthil.Messaging.TestHarness.csproj @@ -2,6 +2,7 @@ Test utilities for validating messaging flows in integration and component tests. + testing;test-harness;in-memory;messaging;message-bus;consumers;transport diff --git a/src/Vulthil.Messaging/Vulthil.Messaging.csproj b/src/Vulthil.Messaging/Vulthil.Messaging.csproj index efec166a..03b7eaca 100644 --- a/src/Vulthil.Messaging/Vulthil.Messaging.csproj +++ b/src/Vulthil.Messaging/Vulthil.Messaging.csproj @@ -2,6 +2,7 @@ Messaging composition APIs for configuring consumers, queues, and hosted processing. + messaging;message-bus;consumers;queues;consume-filters;retry;partitioning;transport-sdk diff --git a/src/Vulthil.Results/Vulthil.Results.csproj b/src/Vulthil.Results/Vulthil.Results.csproj index 1b54731e..6b3aa1b5 100644 --- a/src/Vulthil.Results/Vulthil.Results.csproj +++ b/src/Vulthil.Results/Vulthil.Results.csproj @@ -2,6 +2,7 @@ Small result primitives for explicit success/failure flows without exceptions. + result;result-pattern;railway-oriented-programming;error-handling;errors;functional diff --git a/src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj b/src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj index cbba3e4c..2f9f0431 100644 --- a/src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj +++ b/src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj @@ -2,6 +2,7 @@ API-layer helpers for endpoints, controllers, and cross-cutting HTTP concerns. + aspnetcore;minimal-api;endpoints;controllers;problemdetails;openapi;result;http diff --git a/src/Vulthil.SharedKernel.Application/Vulthil.SharedKernel.Application.csproj b/src/Vulthil.SharedKernel.Application/Vulthil.SharedKernel.Application.csproj index ad405f5f..01e19627 100644 --- a/src/Vulthil.SharedKernel.Application/Vulthil.SharedKernel.Application.csproj +++ b/src/Vulthil.SharedKernel.Application/Vulthil.SharedKernel.Application.csproj @@ -2,6 +2,7 @@ Application-layer building blocks such as handlers, pipelines, and validation helpers. + cqrs;commands;queries;handlers;pipeline-behaviors;validation;fluentvalidation;application-layer diff --git a/src/Vulthil.SharedKernel.Infrastructure.Cosmos/Vulthil.SharedKernel.Infrastructure.Cosmos.csproj b/src/Vulthil.SharedKernel.Infrastructure.Cosmos/Vulthil.SharedKernel.Infrastructure.Cosmos.csproj index a5689ae5..29556d0a 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.Cosmos/Vulthil.SharedKernel.Infrastructure.Cosmos.csproj +++ b/src/Vulthil.SharedKernel.Infrastructure.Cosmos/Vulthil.SharedKernel.Infrastructure.Cosmos.csproj @@ -2,6 +2,7 @@ Provider-specific EF Core mapping and optimizations for Cosmos DB. + cosmosdb;azure-cosmos-db;cosmos;efcore;entity-framework-core;outbox;aspire diff --git a/src/Vulthil.SharedKernel.Infrastructure.MySql/Vulthil.SharedKernel.Infrastructure.MySql.csproj b/src/Vulthil.SharedKernel.Infrastructure.MySql/Vulthil.SharedKernel.Infrastructure.MySql.csproj index c8b4ca91..a8986db6 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.MySql/Vulthil.SharedKernel.Infrastructure.MySql.csproj +++ b/src/Vulthil.SharedKernel.Infrastructure.MySql/Vulthil.SharedKernel.Infrastructure.MySql.csproj @@ -2,6 +2,7 @@ Provider-specific EF Core mapping and optimizations for MySql. + mysql;pomelo;efcore;entity-framework-core;outbox;aspire diff --git a/src/Vulthil.SharedKernel.Infrastructure.Npgsql/Vulthil.SharedKernel.Infrastructure.Npgsql.csproj b/src/Vulthil.SharedKernel.Infrastructure.Npgsql/Vulthil.SharedKernel.Infrastructure.Npgsql.csproj index 49b2332b..3cc71150 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.Npgsql/Vulthil.SharedKernel.Infrastructure.Npgsql.csproj +++ b/src/Vulthil.SharedKernel.Infrastructure.Npgsql/Vulthil.SharedKernel.Infrastructure.Npgsql.csproj @@ -2,6 +2,7 @@ Provider-specific EF Core mapping and optimizations for Npgsql/PostgreSQL. + postgresql;postgres;npgsql;efcore;entity-framework-core;outbox;aspire diff --git a/src/Vulthil.SharedKernel.Infrastructure.Relational/Vulthil.SharedKernel.Infrastructure.Relational.csproj b/src/Vulthil.SharedKernel.Infrastructure.Relational/Vulthil.SharedKernel.Infrastructure.Relational.csproj index fe827b5e..e87ae0a4 100644 --- a/src/Vulthil.SharedKernel.Infrastructure.Relational/Vulthil.SharedKernel.Infrastructure.Relational.csproj +++ b/src/Vulthil.SharedKernel.Infrastructure.Relational/Vulthil.SharedKernel.Infrastructure.Relational.csproj @@ -2,6 +2,7 @@ Shared relational helpers for provider implementations. + efcore;entity-framework-core;relational;sql;outbox;migrations diff --git a/src/Vulthil.SharedKernel.Infrastructure/Vulthil.SharedKernel.Infrastructure.csproj b/src/Vulthil.SharedKernel.Infrastructure/Vulthil.SharedKernel.Infrastructure.csproj index dc986c78..7c15c0ee 100644 --- a/src/Vulthil.SharedKernel.Infrastructure/Vulthil.SharedKernel.Infrastructure.csproj +++ b/src/Vulthil.SharedKernel.Infrastructure/Vulthil.SharedKernel.Infrastructure.csproj @@ -2,6 +2,7 @@ Infrastructure helpers for persistence, transactions, outbox, and EF Core integration. + efcore;entity-framework-core;dbcontext;unit-of-work;repository;outbox;infrastructure diff --git a/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj b/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj index a82e96dc..a7c8fba2 100644 --- a/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj +++ b/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj @@ -2,6 +2,7 @@ Entity Framework Core implementation of the Vulthil.SharedKernel.Outbox engine — the IOutboxStore (capture + the transactional relay batch unit), the ISaveOutboxMessages context marker, the domain-event capture interceptor, and the OutboxMessage mapping. Referenced by the relational/provider store packages and the infrastructure host; keeps the outbox engine itself free of any EF Core dependency. + outbox;transactional-outbox;outbox-pattern;efcore;entity-framework-core;domain-events diff --git a/src/Vulthil.SharedKernel.Outbox/Vulthil.SharedKernel.Outbox.csproj b/src/Vulthil.SharedKernel.Outbox/Vulthil.SharedKernel.Outbox.csproj index 2a49107a..b6a56d44 100644 --- a/src/Vulthil.SharedKernel.Outbox/Vulthil.SharedKernel.Outbox.csproj +++ b/src/Vulthil.SharedKernel.Outbox/Vulthil.SharedKernel.Outbox.csproj @@ -2,6 +2,7 @@ Transactional outbox engine for Vulthil.SharedKernel — the message-capture model, the relay processor and background service, pluggable dispatchers, the commit-time relay signal, and the persistence-agnostic IOutboxStore seam. Free of any EF Core dependency; an EF implementation lives in Vulthil.SharedKernel.Outbox.EntityFrameworkCore. + outbox;transactional-outbox;outbox-pattern;domain-events;messaging;reliability;background-service diff --git a/src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj b/src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj index 80642e37..e5863bae 100644 --- a/src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj +++ b/src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj @@ -2,6 +2,7 @@ Core domain primitives and base abstractions for shared domain logic. + ddd;domain-driven-design;shared-kernel;entity;aggregate-root;domain-events;building-blocks diff --git a/src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj b/src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj index 8b3db2a5..1c6f32d9 100644 --- a/src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj +++ b/src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj @@ -2,6 +2,7 @@ Azure Cosmos DB emulator fixture for Vulthil.xUnit — wraps the Cosmos emulator Testcontainer, registers the DbContext against it, and gives every consuming factory its own emulator database for parallel-safe integration tests. + xunit;testing;integration-testing;cosmosdb;azure-cosmos-db;cosmos-emulator;testcontainers diff --git a/src/Vulthil.xUnit/Vulthil.xUnit.csproj b/src/Vulthil.xUnit/Vulthil.xUnit.csproj index 41518563..ce7eccaa 100644 --- a/src/Vulthil.xUnit/Vulthil.xUnit.csproj +++ b/src/Vulthil.xUnit/Vulthil.xUnit.csproj @@ -2,6 +2,7 @@ Reusable xUnit base infrastructure for integration and unit test composition. + xunit;testing;integration-testing;testcontainers;webapplicationfactory;automocker;respawn;http-mock