feat: EF Core IIdempotencyStore implementation#374
Conversation
| /// expired keys are logically ignored. | ||
| /// </remarks> | ||
| /// <typeparam name="TContext">The DbContext type that implements <see cref="IIdempotencyStoreDbContext"/>.</typeparam> | ||
| internal sealed class EntityFrameworkIdempotencyStore<TContext> : IIdempotencyStore |
There was a problem hiding this comment.
@copilot I think we shoul implement a central implementation, lile OutboxEventStore.
There was a problem hiding this comment.
Done in b95306a. The implementation now mirrors the OutboxEventStore pattern exactly:
IIdempotencyKeyRepository(Extensibility) — storage abstractionIdempotencyStore(central, inNetEvolve.Pulse) — implementsIIdempotencyStore, delegates toIIdempotencyKeyRepository(handles TTL cutoff logic)EntityFrameworkIdempotencyKeyRepository<TContext>(EF Core pkg) — implementsIIdempotencyKeyRepositoryIdempotencyKeyOptionsmoved toNetEvolve.Pulse(likeOutboxOptions)AddIdempotency()now also registersIdempotencyStoreviaTryAddScopedAddEntityFrameworkIdempotencyStore<TContext>()internally callsAddIdempotency()then registersIIdempotencyKeyRepository
- Add IdempotencyKeySchema constants in NetEvolve.Pulse.Extensibility - Add IdempotencyKey POCO entity in NetEvolve.Pulse.EntityFramework - Add IIdempotencyStoreDbContext interface - Add IdempotencyKeyOptions (Schema, TableName, TimeToLive?) - Add abstract IdempotencyKeyConfigurationBase - Add provider-specific configs: SqlServer, PostgreSql, Sqlite, InMemory - Implement EntityFrameworkIdempotencyStore<TContext> - Add AddEntityFrameworkIdempotencyStore<TContext> extension method - Update ModelBuilderExtensions to apply idempotency config - Add unit tests for extensions and store Agent-Logs-Url: https://github.com/dailydevops/pulse/sessions/5dc3605c-3090-411c-8e88-75cca388557a Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com>
…kIdempotencyStore Agent-Logs-Url: https://github.com/dailydevops/pulse/sessions/5dc3605c-3090-411c-8e88-75cca388557a Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com>
…entStore pattern) - Add IIdempotencyKeyRepository interface in Extensibility (storage abstraction) - Move IdempotencyKeyOptions to NetEvolve.Pulse (like OutboxOptions) - Add central IdempotencyStore in NetEvolve.Pulse (implements IIdempotencyStore via IIdempotencyKeyRepository) - Rename EntityFrameworkIdempotencyStore → EntityFrameworkIdempotencyKeyRepository (implements IIdempotencyKeyRepository) - Update AddIdempotency() to also register IdempotencyStore (via TryAddScoped) - Update AddEntityFrameworkIdempotencyStore() to register IIdempotencyKeyRepository instead of IIdempotencyStore - Add IdempotencyStoreTests for central store (TTL cutoff, timestamp propagation) - Rename EntityFrameworkIdempotencyStoreTests → EntityFrameworkIdempotencyKeyRepositoryTests Agent-Logs-Url: https://github.com/dailydevops/pulse/sessions/9054478d-e37f-4fc1-b30d-88d52bce7f6f Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com>
b95306a to
4ab5f12
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #374 +/- ##
==========================================
+ Coverage 91.16% 91.30% +0.14%
==========================================
Files 106 117 +11
Lines 3916 4061 +145
Branches 357 368 +11
==========================================
+ Hits 3570 3708 +138
- Misses 244 249 +5
- Partials 102 104 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@copilot Implement integration tests based on the outbox tests |
…lServer, PostgreSql, MySql) - Add EntityFrameworkIdempotencyInitializer (mirrors EntityFrameworkInitializer for outbox) - Add IdempotencyTestsBase with 7 test cases: Exists/Store/TTL/Duplicate scenarios - Add per-provider concrete test classes: InMemory, SQLite, SqlServer, PostgreSql, MySql - Add MySqlIdempotencyKeyConfiguration (bigint UTC-ticks for DateTimeOffset, varchar for Key) - Fix ModelBuilderExtensions: use MySqlIdempotencyKeyConfiguration for MySQL (was using SqlServer) - All 267 integration tests pass (net9.0, InMemory + container providers) - All 2238 unit tests pass Agent-Logs-Url: https://github.com/dailydevops/pulse/sessions/f72fbb61-f6f4-418e-a7a6-6e10ffbd56ee Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com>
… DateTimeOffset conversion Agent-Logs-Url: https://github.com/dailydevops/pulse/sessions/f72fbb61-f6f4-418e-a7a6-6e10ffbd56ee Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com>
Done in eaaf790. The integration tests mirror the outbox test structure exactly:
Also added Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
@copilot You missed integration tests with duplicated keys. See method |
Agent-Logs-Url: https://github.com/dailydevops/pulse/sessions/731c080c-4e21-4b0f-aeb7-e5f9247d070b Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
IIdempotencyStorehad no concrete implementation, making the idempotency interceptor a no-op for applications already using EF Core for persistence.Changes
NetEvolve.Pulse.ExtensibilityIdempotencyKeySchema— canonical schema constants (DefaultSchema,DefaultTableName, column names, max lengths) matching all provider configurationsIIdempotencyKeyRepository— storage abstraction interface (ExistsAsync(key, validFrom?, ct)andStoreAsync(key, createdAt, ct)); mirrorsIOutboxRepositoryin the outbox patternNetEvolve.PulseIdempotencyKeyOptions—Schema,TableName, optionalTimeToLive(logical expiry; no background cleanup); lives alongsideOutboxOptionsIdempotencyStore— centralIIdempotencyStoreimplementation that delegates toIIdempotencyKeyRepositoryand applies TTL cutoff logic; mirrorsOutboxEventStoreAddIdempotency()— now also registersIdempotencyStoreas theIIdempotencyStoreimplementation (Scoped, viaTryAddScoped)NetEvolve.Pulse.EntityFrameworkIdempotencyKey— POCO entity (Keystring PK,CreatedAtDateTimeOffset)IIdempotencyStoreDbContext— interface exposingDbSet<IdempotencyKey> IdempotencyKeys; implement on your DbContext alongsideIOutboxDbContextIdempotencyKeyConfigurationBase+ provider configs (SqlServer,PostgreSql,Sqlite,InMemory,MySql) — follow same pattern as outbox message configurations; MySQL usesbigintUTC-ticks forDateTimeOffset(Oracle MySQL provider lacks a nativedatetimeoffsetmapping)EntityFrameworkIdempotencyKeyRepository<TContext>—IIdempotencyKeyRepositoryimpl;ExistsAsyncapplies thevalidFromTTL cutoff passed by the central store;StoreAsynchandles concurrent duplicate-key violations gracefully (local tracker check + database-level constraint catch viaIsDuplicateKeyException)IsDuplicateKeyExceptionwalks the full exception chain and uses correct provider-specific message patterns: SQL Server ("Cannot insert duplicate key","Violation of PRIMARY KEY constraint"), PostgreSQL ("23505"), SQLite ("UNIQUE constraint failed"), MySQL ("Duplicate entry"), InMemory (ArgumentExceptionwith"An item with the same key has already been added")AddEntityFrameworkIdempotencyStore<TContext>()— callsAddIdempotency()internally, then registersEntityFrameworkIdempotencyKeyRepository<TContext>asIIdempotencyKeyRepository(Scoped)ModelBuilderExtensions.ApplyPulseConfiguration— now also applies idempotency key configuration when the context implementsIIdempotencyStoreDbContextIntegration Tests
Mirrors the outbox integration test structure across all five supported providers:
EntityFrameworkIdempotencyInitializer— test initializer using an in-testTestIdempotencyDbContextimplementingIIdempotencyStoreDbContextIdempotencyTestsBase— abstract base with 8 test scenarios: key not found, key found, different key isolation, multiple keys, idempotent duplicate store (same scope), cross-scope duplicate insert (freshDbContext, exercises the database-levelIsDuplicateKeyExceptionpath), TTL within window, TTL expiredInMemoryEntityFrameworkIdempotencyTests,SQLiteEntityFrameworkIdempotencyTests,SqlServerEntityFrameworkIdempotencyTests,PostgreSqlEntityFrameworkIdempotencyTests,MySqlEntityFrameworkIdempotencyTestsUsage
After registering, generate a migration (
dotnet ef migrations add AddIdempotencyKeys) to create thepulse.IdempotencyKeytable.