Feature/ddd#159
Merged
jasonmwebb-lv merged 50 commits intomainfrom Mar 24, 2026
Merged
Conversation
Defines the design for AggregateRoot, DomainEntity, ValueObject, and IDomainEvent types to be added to RCommon.Entities. Extends existing BusinessEntity hierarchy with zero breaking changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address spec review findings: add [Serializable] and [ConcurrencyCheck], use block-scoped namespaces, add non-generic IAggregateRoot marker, fix DomainEvents allocation via dual-list approach, thread-safe GetHashCode, document graph walker behavior and known limitations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add IEquatable<DomainEntity<TKey>> implementation, default! initializer for Id, expand known-limitation documentation for dual-list sync bypass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TDD implementation plan for AggregateRoot, DomainEntity, ValueObject, and IDomainEvent. 5 chunks, 25 steps, 69 tests covering all spec scenarios including integration with IEntityEventTracker. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
IDomainEvent extends ISerializableEvent with EventId and OccurredOn. DomainEvent is an abstract record with sensible defaults. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C# record-based value object with automatic structural equality, immutability, and with-expression support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lightweight entity with identity-based equality and IEquatable support. No event tracking — child entities raise events through aggregate root.
Extends BusinessEntity<TKey> with domain event management, versioning, and optimistic concurrency. Domain events flow through existing IEntityEventTracker pipeline via AddLocalEvent delegation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 'new' keyword hiding of EntityEquals created polymorphic inconsistency — callers through IBusinessEntity would get binary comparison while callers through AggregateRoot got Id comparison. Better tracked as a future improvement making EntityEquals virtual on BusinessEntity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n, InMemoryEventBus memory leak - Add ConfigureAwait(false) to ~150 await calls across all library projects - Add CancellationToken to IEventBus.PublishAsync, IEventRouter.RouteEventsAsync, and IEmailService.SendEmailAsync interfaces - Propagate CancellationToken through DapperRepository.OpenAsync (13 sites), Linq2DbRepository.DeleteAsync, all MediatR/Wolverine/MassTransit handler bridges, and the event routing pipeline - Refactor InMemoryEventBus to not capture IServiceCollection (memory leak), track dynamic subscriptions internally and resolve via ActivatorUtilities - Fix SendGridEmailService.SendEmailAsync missing return on empty recipients guard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds three new design sections to the aggregate repository spec: - Part 2: Automatic domain event dispatch via UnitOfWork post-commit hook - Part 3: Read-model repositories (IReadModelRepository<T>, IPagedResult<T>) - Part 4: Saga/process manager patterns (ISaga, IStateMachine, ISagaStore) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…as it has gone commercial.
…tboxSerializer, OutboxOptions)
… safety Implement JsonOutboxSerializer as IOutboxSerializer with: - Serialize: Convert ISerializableEvent to JSON using System.Text.Json - GetEventTypeName: Return short assembly-qualified type name for deserialization - Deserialize: Reconstruct ISerializableEvent from type name and payload with validation All 5 tests pass with coverage of happy path and error cases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ationToken propagation - Add PersistEventsAsync(CT) to IEntityEventTracker for outbox phase-1 write - Add CancellationToken to EmitTransactionalEventsAsync signature - Implement PersistEventsAsync as no-op in InMemoryEntityEventTracker - Pass CancellationToken through to IEventRouter.RouteEventsAsync - Refactor UnitOfWork.CommitAsync to three-phase: persist events, commit tx, dispatch events - Add Microsoft.Extensions.Hosting.Abstractions package refs to RCommon.Persistence.csproj - Update all test mocks/verifies to match new interface signatures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements OutboxEventRouter as the transactional outbox IEventRouter: buffers events in-memory via AddTransactionalEvent(s), persists them as OutboxMessage rows via PersistBufferedEventsAsync (Phase 1, within active transaction), and dispatches pending messages via RouteEventsAsync (Phase 3, post-commit). Includes 6 unit tests covering buffering, persistence, field correctness, dispatch, and failure marking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…istence Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ad-letter support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… concurrency tests Implements the AddOutbox<TOutboxStore>() extension on IPersistenceBuilder that wires all outbox services (store, serializer, router, entity tracker, background processor) into the DI container with correct lifetimes. Adds UnitOfWork integration test verifying the two-phase PersistEventsAsync flow through OutboxEntityEventTracker, plus edge-case concurrency tests for empty buffers, no-pending routing, and dead-letter exclusion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, and SQLite tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces RCommon.MassTransit.Outbox project with IMassTransitOutboxBuilder, MassTransitOutboxBuilder, and AddOutbox<TDbContext> extension method that wraps MassTransit's IEntityFrameworkOutboxConfigurator into RCommon's builder pattern. Includes test project with 4 passing unit tests covering UseSqlServer, UsePostgres, UseBusOutbox delegation, and null-guard constructor validation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps Wolverine's WolverineFx.EntityFrameworkCore durable messaging with RCommon's builder pattern via IWolverineOutboxBuilder, WolverineOutboxBuilder, and WolverineOutboxBuilderExtensions. Includes test project with 8 passing unit tests achieving 100% line/branch/method coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add CleanupInterval option (default 1h) to avoid running DELETE queries on every polling cycle - Add clear comment explaining why EFCoreOutboxStore uses client-side OrderBy (DateTimeOffset not supported in ORDER BY by all providers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The RepositoryQuery getter always replaced _repositoryQuery with _includableQueryable, discarding any ThenInclude results. Fixed by nulling _includableQueryable after ThenInclude consumes it, and having Include chain from _repositoryQuery when _includableQueryable is null (preserving prior ThenInclude chains). Affects both EFCoreRepository and EFCoreAggregateRepository. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers exponential backoff, distributed locking (SQL Server + PostgreSQL), dead letter replay, and inbox/idempotency with standalone and auto-check modes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use CTE instead of UPDATE TOP for ordered claims - Add UPDLOCK, ROWLOCK, READPAST hints for concurrent safety - Clarify ConfigureAwait(false) requirement in pseudocode - Fix inbox ConsumerType nullability description - Add missing Dapper/Linq2Db builder extension files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Section 4.4 documenting OutboxEventRouter V2 behavior - RouteEventsAsync() no longer reads from store; dispatches retained events - Failures left for background processor (no MarkFailedAsync call) - Add missing test files to Section 8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add implementation sketch for OutboxEventRouter retained-event pattern - Fix Mode 1 inbox example: use domain-specific ID, not @event.Id - Clarify that ISerializableEvent has no Id property - Document that Mode 1 requires consumer-chosen deduplication key Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12-task plan covering backoff strategy, interface changes, inbox abstractions, OutboxEventRouter V2, OutboxProcessingService V2, EF Core/Dapper/Linq2Db store implementations, inbox stores, and full verification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Options Introduces IBackoffStrategy interface and ExponentialBackoffStrategy implementation for computing retry delays with configurable base delay, max delay, and multiplier. Extends OutboxOptions with V2 properties: LockDuration, BackoffBaseDelay, BackoffMaxDelay, BackoffMultiplier, and InboxTableName. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r replay Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ClaimAsync with optimistic LINQ fallback (works with SQLite in tests) - Remove GetPendingAsync, replaced by ClaimAsync with locking semantics - Update MarkFailedAsync to accept nextRetryAtUtc, clear lock fields - Add GetDeadLettersAsync with offset/batchSize pagination - Add ReplayDeadLetterAsync to reset all fields on dead-lettered messages - Update SaveAsync to copy NextRetryAtUtc, LockedByInstanceId, LockedUntilUtc - Add _tableName field from OutboxOptions - Update OutboxMessageConfiguration with new columns and two new indexes - Rewrite EFCoreOutboxStoreTests with 12 tests covering V2 behaviors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds EFCoreInboxStore (IInboxStore), InboxMessageConfiguration (composite PK on MessageId+ConsumerType), AddInboxMessages ModelBuilder extension, and 4 passing tests. TestOutboxDbContext updated to include inbox schema. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ILockStatementProvider to constructor with null guard - Remove GetPendingAsync; add ClaimAsync with PostgreSQL/SQL Server raw SQL - Update SaveAsync to copy NextRetryAtUtc, LockedByInstanceId, LockedUntilUtc - Update MarkFailedAsync signature to include nextRetryAtUtc and clear locks - Add GetDeadLettersAsync (LINQ) and ReplayDeadLetterAsync (LINQ update) - Add UseLockStatementProvider<TProvider> to ILinq2DbPersistenceBuilder and Linq2DbPersistenceBuilder - Update tests: add lock provider mock and NullLockStatementProvider test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implemented full featured DDD implementation. This is fairly experimental in nature and will need some E2E testing.