backend phase 0: foundation, cross-cutting seams & starter cleanup
Remove the Order demo (entity/feature/repo/config/gRPC/proto) and the three pre-marketplace migrations; regenerate a fresh InitialBaseline migration. Stand up the REST surface (PingController + System/Ping CQRS) proving the Mediator -> behaviors -> OperationResult -> ApiResult envelope end to end. Close wiring gaps: register LoggingBehavior (outermost) and add the built-in rate limiter (per-IP global + otp/auth/sensitive policies), placed before authentication. Add current-user + audit plumbing: ICurrentUser (HttpContext + null impls), rename BaseEntity audit fields to CreatedAt/ModifiedAt (DateTimeOffset) + CreatedById/ModifiedById, stamped by a new AuditFieldInterceptor. Introduce five cross-cutting seams (IDateTimeProvider, IFieldEncryptor, ICacheService, IObjectStorage, INotificationDispatcher) with in-memory/local mocks registered via AddCrossCuttingSeams. Add Baya.Test.Foundation (encryptor, audit interceptor, ping handler) and update docs, contracts (swagger.v1.json), handoff, report, and mocks registry.
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
#nullable enable
|
||||
using Baya.Application.Contracts.Common;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Baya.Infrastructure.CrossCutting.Seams;
|
||||
|
||||
/// <summary>
|
||||
/// In-process <see cref="IMemoryCache"/> implementation of <see cref="ICacheService"/> — the mock seam.
|
||||
/// The real implementation swaps to Redis (StackExchange.Redis) while keeping the same key/TTL scheme.
|
||||
/// </summary>
|
||||
public sealed class MemoryCacheService(IMemoryCache cache) : ICacheService
|
||||
{
|
||||
public ValueTask<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return ValueTask.FromResult(cache.TryGetValue(key, out T? value) ? value : default);
|
||||
}
|
||||
|
||||
public ValueTask SetAsync<T>(string key, T value, TimeSpan? ttl = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = new MemoryCacheEntryOptions();
|
||||
if (ttl is { } expiry)
|
||||
options.AbsoluteExpirationRelativeToNow = expiry;
|
||||
|
||||
cache.Set(key, value, options);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask RemoveAsync(string key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cache.Remove(key);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public async ValueTask<T> GetOrCreateAsync<T>(
|
||||
string key,
|
||||
Func<CancellationToken, ValueTask<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cache.TryGetValue(key, out T? cached) && cached is not null)
|
||||
return cached;
|
||||
|
||||
var value = await factory(cancellationToken);
|
||||
await SetAsync(key, value, ttl, cancellationToken);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user