From 479416b9073770015a15723575851a76d92b28be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ren=C3=A9=20White=20Enciso?= Date: Sun, 22 Feb 2026 04:39:56 -0600 Subject: [PATCH] feat(furniture-dal): define dal contracts and adapter boundaries --- docs/architecture/dal-layer-map.puml | 14 +++++++ docs/dal/persistence-cache-policy.md | 2 + docs/dal/provider-boundaries.md | 5 +++ .../ICatalogProjectionContractAdapter.cs | 25 +++++++++++ .../IFurnitureDalGrpcContractAdapter.cs | 38 +++++++++++++++++ .../Caching/ICacheInvalidationPolicy.cs | 15 +++++++ .../Contracts/CatalogProductLookupRequest.cs | 8 ++++ .../CatalogProductProjectionRecord.cs | 12 ++++++ .../FurnitureAvailabilityLookupRequest.cs | 8 ++++ .../Contracts/FurnitureAvailabilityRecord.cs | 12 ++++++ .../FurnitureCacheInvalidationRequest.cs | 12 ++++++ .../Contracts/FurnitureDalContractEnvelope.cs | 8 ++++ .../Contracts/FurnitureDalPackageContract.cs | 15 +++++++ src/Furniture.DAL/Furniture.DAL.csproj | 4 ++ .../Grpc/CatalogProductDalGrpcContract.cs | 7 ++++ .../FurnitureAvailabilityDalGrpcContract.cs | 7 ++++ .../Providers/ICatalogDataProvider.cs | 11 +++++ .../Providers/IFurnitureDataProvider.cs | 11 +++++ .../Repositories/ICatalogRepository.cs | 11 +++++ .../Repositories/IFurnitureRepository.cs | 11 +++++ .../BoundaryShapeTests.cs | 33 +++++++++++++++ .../ContractShapeTests.cs | 41 +++++++++++++++++++ 22 files changed, 310 insertions(+) create mode 100644 src/Furniture.DAL/Adapters/ICatalogProjectionContractAdapter.cs create mode 100644 src/Furniture.DAL/Adapters/IFurnitureDalGrpcContractAdapter.cs create mode 100644 src/Furniture.DAL/Contracts/CatalogProductLookupRequest.cs create mode 100644 src/Furniture.DAL/Contracts/CatalogProductProjectionRecord.cs create mode 100644 src/Furniture.DAL/Contracts/FurnitureAvailabilityLookupRequest.cs create mode 100644 src/Furniture.DAL/Contracts/FurnitureAvailabilityRecord.cs create mode 100644 src/Furniture.DAL/Contracts/FurnitureCacheInvalidationRequest.cs create mode 100644 src/Furniture.DAL/Contracts/FurnitureDalContractEnvelope.cs create mode 100644 src/Furniture.DAL/Contracts/FurnitureDalPackageContract.cs create mode 100644 src/Furniture.DAL/Grpc/CatalogProductDalGrpcContract.cs create mode 100644 src/Furniture.DAL/Grpc/FurnitureAvailabilityDalGrpcContract.cs create mode 100644 tests/Furniture.DAL.UnitTests/ContractShapeTests.cs diff --git a/docs/architecture/dal-layer-map.puml b/docs/architecture/dal-layer-map.puml index fa043d9..8f680b7 100644 --- a/docs/architecture/dal-layer-map.puml +++ b/docs/architecture/dal-layer-map.puml @@ -2,14 +2,28 @@ skinparam packageStyle rectangle package "furniture-dal" { + class FurnitureAvailabilityLookupRequest + class FurnitureAvailabilityRecord + class CatalogProductLookupRequest + class CatalogProductProjectionRecord + interface IFurnitureDalGrpcContractAdapter + interface ICatalogProjectionContractAdapter interface IFurnitureDataProvider interface ICatalogDataProvider interface IFurnitureRepository interface ICatalogRepository interface ICacheInvalidationPolicy + IFurnitureDalGrpcContractAdapter --> FurnitureAvailabilityLookupRequest + IFurnitureDalGrpcContractAdapter --> CatalogProductLookupRequest + ICatalogProjectionContractAdapter --> CatalogProductLookupRequest + ICatalogProjectionContractAdapter --> CatalogProductProjectionRecord IFurnitureRepository --> IFurnitureDataProvider + IFurnitureRepository --> FurnitureAvailabilityLookupRequest + IFurnitureRepository --> FurnitureAvailabilityRecord ICatalogRepository --> ICatalogDataProvider + ICatalogRepository --> CatalogProductLookupRequest + ICatalogRepository --> CatalogProductProjectionRecord IFurnitureRepository --> ICacheInvalidationPolicy ICatalogRepository --> ICacheInvalidationPolicy } diff --git a/docs/dal/persistence-cache-policy.md b/docs/dal/persistence-cache-policy.md index daae1b1..124a6d7 100644 --- a/docs/dal/persistence-cache-policy.md +++ b/docs/dal/persistence-cache-policy.md @@ -5,8 +5,10 @@ - Repositories coordinate provider calls for aggregate persistence. - Cache invalidation policy is owned by DAL. - Service layer does not implement persistence or cache invalidation details. +- Cache invalidation boundaries are expressed through DAL contract interfaces. ## Policy - Persistence writes must define cache invalidation triggers. - Cache keys and invalidation behavior are centralized in DAL policy definitions. +- This stage defines contracts, ports, and adapters only; no persistence implementation is introduced. diff --git a/docs/dal/provider-boundaries.md b/docs/dal/provider-boundaries.md index 75f810c..d5bb15d 100644 --- a/docs/dal/provider-boundaries.md +++ b/docs/dal/provider-boundaries.md @@ -4,9 +4,14 @@ - `IFurnitureDataProvider`: furniture persistence provider boundary. - `ICatalogDataProvider`: catalog persistence provider boundary. +- `IFurnitureRepository`: furniture DAL composition boundary. +- `ICatalogRepository`: catalog DAL composition boundary. +- `ICatalogProjectionContractAdapter`: building-block catalog to DAL contract adapter boundary. +- `IFurnitureDalGrpcContractAdapter`: gRPC translation boundary for DAL contracts. ## Rules - Providers encapsulate datastore-specific access. - Providers do not contain orchestration concerns. - Provider contracts are consumed by DAL repositories. +- Repository and adapter boundaries expose contracts only in this stage. diff --git a/src/Furniture.DAL/Adapters/ICatalogProjectionContractAdapter.cs b/src/Furniture.DAL/Adapters/ICatalogProjectionContractAdapter.cs new file mode 100644 index 0000000..a43d456 --- /dev/null +++ b/src/Furniture.DAL/Adapters/ICatalogProjectionContractAdapter.cs @@ -0,0 +1,25 @@ +using BuildingBlock.Catalog.Contracts.Products; +using BuildingBlock.Catalog.Contracts.Responses; +using Furniture.DAL.Contracts; + +namespace Furniture.DAL.Adapters; + +/// +/// Defines adapter boundary between catalog building block contracts and furniture dal contracts. +/// +public interface ICatalogProjectionContractAdapter +{ + /// + /// Maps catalog capability product contract into dal lookup request. + /// + /// Catalog product contract. + /// Catalog product lookup request contract. + CatalogProductLookupRequest ToDalRequest(ProductContract contract); + + /// + /// Maps dal catalog projection record into catalog capability response contract. + /// + /// Catalog product projection record. + /// Catalog product response contract. + ProductContractResponse ToCatalogResponse(CatalogProductProjectionRecord record); +} diff --git a/src/Furniture.DAL/Adapters/IFurnitureDalGrpcContractAdapter.cs b/src/Furniture.DAL/Adapters/IFurnitureDalGrpcContractAdapter.cs new file mode 100644 index 0000000..d54b800 --- /dev/null +++ b/src/Furniture.DAL/Adapters/IFurnitureDalGrpcContractAdapter.cs @@ -0,0 +1,38 @@ +using Furniture.DAL.Contracts; +using Furniture.DAL.Grpc; + +namespace Furniture.DAL.Adapters; + +/// +/// Defines adapter boundary for furniture dal gRPC contract translation. +/// +public interface IFurnitureDalGrpcContractAdapter +{ + /// + /// Maps transport-neutral availability lookup request to gRPC contract shape. + /// + /// Furniture availability lookup request contract. + /// gRPC availability request contract. + FurnitureAvailabilityDalGrpcContract ToGrpcAvailabilityRequest(FurnitureAvailabilityLookupRequest request); + + /// + /// Maps gRPC availability contract shape to transport-neutral request. + /// + /// gRPC availability request contract. + /// Furniture availability lookup request contract. + FurnitureAvailabilityLookupRequest FromGrpcAvailabilityRequest(FurnitureAvailabilityDalGrpcContract contract); + + /// + /// Maps transport-neutral catalog lookup request to gRPC contract shape. + /// + /// Catalog product lookup request contract. + /// gRPC catalog request contract. + CatalogProductDalGrpcContract ToGrpcCatalogRequest(CatalogProductLookupRequest request); + + /// + /// Maps gRPC catalog contract shape to transport-neutral request. + /// + /// gRPC catalog request contract. + /// Catalog product lookup request contract. + CatalogProductLookupRequest FromGrpcCatalogRequest(CatalogProductDalGrpcContract contract); +} diff --git a/src/Furniture.DAL/Caching/ICacheInvalidationPolicy.cs b/src/Furniture.DAL/Caching/ICacheInvalidationPolicy.cs index f84c871..0409197 100644 --- a/src/Furniture.DAL/Caching/ICacheInvalidationPolicy.cs +++ b/src/Furniture.DAL/Caching/ICacheInvalidationPolicy.cs @@ -1,3 +1,5 @@ +using Furniture.DAL.Contracts; + namespace Furniture.DAL.Caching; /// @@ -5,4 +7,17 @@ namespace Furniture.DAL.Caching; /// public interface ICacheInvalidationPolicy { + /// + /// Builds cache key for a furniture availability request. + /// + /// Furniture availability lookup request contract. + /// Cache key string. + string BuildAvailabilityKey(FurnitureAvailabilityLookupRequest request); + + /// + /// Resolves cache invalidation keys for a mutation request boundary. + /// + /// Cache invalidation request contract. + /// Cache keys to invalidate. + IReadOnlyList ResolveInvalidationKeys(FurnitureCacheInvalidationRequest request); } diff --git a/src/Furniture.DAL/Contracts/CatalogProductLookupRequest.cs b/src/Furniture.DAL/Contracts/CatalogProductLookupRequest.cs new file mode 100644 index 0000000..5479136 --- /dev/null +++ b/src/Furniture.DAL/Contracts/CatalogProductLookupRequest.cs @@ -0,0 +1,8 @@ +namespace Furniture.DAL.Contracts; + +/// +/// Request contract for catalog product lookup. +/// +/// Contract envelope metadata. +/// Catalog product identifier. +public sealed record CatalogProductLookupRequest(FurnitureDalContractEnvelope Envelope, string ProductId); diff --git a/src/Furniture.DAL/Contracts/CatalogProductProjectionRecord.cs b/src/Furniture.DAL/Contracts/CatalogProductProjectionRecord.cs new file mode 100644 index 0000000..94160cf --- /dev/null +++ b/src/Furniture.DAL/Contracts/CatalogProductProjectionRecord.cs @@ -0,0 +1,12 @@ +namespace Furniture.DAL.Contracts; + +/// +/// Response contract representing catalog product projection data. +/// +/// Contract envelope metadata. +/// Catalog product identifier. +/// Catalog display name. +public sealed record CatalogProductProjectionRecord( + FurnitureDalContractEnvelope Envelope, + string ProductId, + string DisplayName); diff --git a/src/Furniture.DAL/Contracts/FurnitureAvailabilityLookupRequest.cs b/src/Furniture.DAL/Contracts/FurnitureAvailabilityLookupRequest.cs new file mode 100644 index 0000000..e5daa6d --- /dev/null +++ b/src/Furniture.DAL/Contracts/FurnitureAvailabilityLookupRequest.cs @@ -0,0 +1,8 @@ +namespace Furniture.DAL.Contracts; + +/// +/// Request contract for furniture availability lookup. +/// +/// Contract envelope metadata. +/// Furniture aggregate identifier. +public sealed record FurnitureAvailabilityLookupRequest(FurnitureDalContractEnvelope Envelope, string FurnitureId); diff --git a/src/Furniture.DAL/Contracts/FurnitureAvailabilityRecord.cs b/src/Furniture.DAL/Contracts/FurnitureAvailabilityRecord.cs new file mode 100644 index 0000000..679ac3f --- /dev/null +++ b/src/Furniture.DAL/Contracts/FurnitureAvailabilityRecord.cs @@ -0,0 +1,12 @@ +namespace Furniture.DAL.Contracts; + +/// +/// Response contract representing persisted furniture availability data. +/// +/// Contract envelope metadata. +/// Furniture aggregate identifier. +/// Current quantity available. +public sealed record FurnitureAvailabilityRecord( + FurnitureDalContractEnvelope Envelope, + string FurnitureId, + int QuantityAvailable); diff --git a/src/Furniture.DAL/Contracts/FurnitureCacheInvalidationRequest.cs b/src/Furniture.DAL/Contracts/FurnitureCacheInvalidationRequest.cs new file mode 100644 index 0000000..98835f8 --- /dev/null +++ b/src/Furniture.DAL/Contracts/FurnitureCacheInvalidationRequest.cs @@ -0,0 +1,12 @@ +namespace Furniture.DAL.Contracts; + +/// +/// Request contract for cache invalidation boundaries. +/// +/// Contract envelope metadata. +/// Furniture aggregate identifier that changed. +/// Invalidation reason code. +public sealed record FurnitureCacheInvalidationRequest( + FurnitureDalContractEnvelope Envelope, + string FurnitureId, + string Reason); diff --git a/src/Furniture.DAL/Contracts/FurnitureDalContractEnvelope.cs b/src/Furniture.DAL/Contracts/FurnitureDalContractEnvelope.cs new file mode 100644 index 0000000..fcaa9a7 --- /dev/null +++ b/src/Furniture.DAL/Contracts/FurnitureDalContractEnvelope.cs @@ -0,0 +1,8 @@ +namespace Furniture.DAL.Contracts; + +/// +/// Defines transport-neutral envelope metadata for furniture dal contract messages. +/// +/// Contract schema version. +/// Correlation identifier for cross-layer tracing. +public sealed record FurnitureDalContractEnvelope(string ContractVersion, string CorrelationId); diff --git a/src/Furniture.DAL/Contracts/FurnitureDalPackageContract.cs b/src/Furniture.DAL/Contracts/FurnitureDalPackageContract.cs new file mode 100644 index 0000000..504a387 --- /dev/null +++ b/src/Furniture.DAL/Contracts/FurnitureDalPackageContract.cs @@ -0,0 +1,15 @@ +using Core.Blueprint.Common.Contracts; + +namespace Furniture.DAL.Contracts; + +/// +/// Defines package descriptor metadata for furniture dal contracts. +/// +public sealed class FurnitureDalPackageContract : IBlueprintPackageContract +{ + /// + public BlueprintPackageDescriptor Descriptor { get; } = new( + "Furniture.DAL.Contracts", + PackageVersionPolicy.Minor, + ["Core.Blueprint.Common", "BuildingBlock.Catalog.Contracts"]); +} diff --git a/src/Furniture.DAL/Furniture.DAL.csproj b/src/Furniture.DAL/Furniture.DAL.csproj index 6c3a887..d824f85 100644 --- a/src/Furniture.DAL/Furniture.DAL.csproj +++ b/src/Furniture.DAL/Furniture.DAL.csproj @@ -4,4 +4,8 @@ enable enable + + + + diff --git a/src/Furniture.DAL/Grpc/CatalogProductDalGrpcContract.cs b/src/Furniture.DAL/Grpc/CatalogProductDalGrpcContract.cs new file mode 100644 index 0000000..c41d2f3 --- /dev/null +++ b/src/Furniture.DAL/Grpc/CatalogProductDalGrpcContract.cs @@ -0,0 +1,7 @@ +namespace Furniture.DAL.Grpc; + +/// +/// Defines minimal gRPC contract shape for catalog projection dal translation. +/// +/// Catalog product identifier. +public sealed record CatalogProductDalGrpcContract(string ProductId); diff --git a/src/Furniture.DAL/Grpc/FurnitureAvailabilityDalGrpcContract.cs b/src/Furniture.DAL/Grpc/FurnitureAvailabilityDalGrpcContract.cs new file mode 100644 index 0000000..f85a937 --- /dev/null +++ b/src/Furniture.DAL/Grpc/FurnitureAvailabilityDalGrpcContract.cs @@ -0,0 +1,7 @@ +namespace Furniture.DAL.Grpc; + +/// +/// Defines minimal gRPC contract shape for furniture availability dal translation. +/// +/// Furniture aggregate identifier. +public sealed record FurnitureAvailabilityDalGrpcContract(string FurnitureId); diff --git a/src/Furniture.DAL/Providers/ICatalogDataProvider.cs b/src/Furniture.DAL/Providers/ICatalogDataProvider.cs index 090a94c..41849cd 100644 --- a/src/Furniture.DAL/Providers/ICatalogDataProvider.cs +++ b/src/Furniture.DAL/Providers/ICatalogDataProvider.cs @@ -1,3 +1,5 @@ +using Furniture.DAL.Contracts; + namespace Furniture.DAL.Providers; /// @@ -5,4 +7,13 @@ namespace Furniture.DAL.Providers; /// public interface ICatalogDataProvider { + /// + /// Reads catalog projection data for furniture scope. + /// + /// Catalog product lookup request contract. + /// Cancellation token. + /// Catalog product projection when found; otherwise null. + Task ReadProductAsync( + CatalogProductLookupRequest request, + CancellationToken cancellationToken = default); } diff --git a/src/Furniture.DAL/Providers/IFurnitureDataProvider.cs b/src/Furniture.DAL/Providers/IFurnitureDataProvider.cs index 66f7e81..7f9ebbe 100644 --- a/src/Furniture.DAL/Providers/IFurnitureDataProvider.cs +++ b/src/Furniture.DAL/Providers/IFurnitureDataProvider.cs @@ -1,3 +1,5 @@ +using Furniture.DAL.Contracts; + namespace Furniture.DAL.Providers; /// @@ -5,4 +7,13 @@ namespace Furniture.DAL.Providers; /// public interface IFurnitureDataProvider { + /// + /// Reads furniture availability persistence data. + /// + /// Furniture availability lookup request contract. + /// Cancellation token. + /// Furniture availability record when found; otherwise null. + Task ReadAvailabilityAsync( + FurnitureAvailabilityLookupRequest request, + CancellationToken cancellationToken = default); } diff --git a/src/Furniture.DAL/Repositories/ICatalogRepository.cs b/src/Furniture.DAL/Repositories/ICatalogRepository.cs index 923d34c..a50b106 100644 --- a/src/Furniture.DAL/Repositories/ICatalogRepository.cs +++ b/src/Furniture.DAL/Repositories/ICatalogRepository.cs @@ -1,3 +1,5 @@ +using Furniture.DAL.Contracts; + namespace Furniture.DAL.Repositories; /// @@ -5,4 +7,13 @@ namespace Furniture.DAL.Repositories; /// public interface ICatalogRepository { + /// + /// Reads catalog product projection for furniture scope. + /// + /// Catalog product lookup request contract. + /// Cancellation token. + /// Catalog product projection when found; otherwise null. + Task ReadProductAsync( + CatalogProductLookupRequest request, + CancellationToken cancellationToken = default); } diff --git a/src/Furniture.DAL/Repositories/IFurnitureRepository.cs b/src/Furniture.DAL/Repositories/IFurnitureRepository.cs index a98ec30..13207d5 100644 --- a/src/Furniture.DAL/Repositories/IFurnitureRepository.cs +++ b/src/Furniture.DAL/Repositories/IFurnitureRepository.cs @@ -1,3 +1,5 @@ +using Furniture.DAL.Contracts; + namespace Furniture.DAL.Repositories; /// @@ -5,4 +7,13 @@ namespace Furniture.DAL.Repositories; /// public interface IFurnitureRepository { + /// + /// Reads availability for a furniture aggregate. + /// + /// Furniture availability lookup request contract. + /// Cancellation token. + /// Furniture availability record when found; otherwise null. + Task ReadAvailabilityAsync( + FurnitureAvailabilityLookupRequest request, + CancellationToken cancellationToken = default); } diff --git a/tests/Furniture.DAL.UnitTests/BoundaryShapeTests.cs b/tests/Furniture.DAL.UnitTests/BoundaryShapeTests.cs index a8923ca..3d47bb6 100644 --- a/tests/Furniture.DAL.UnitTests/BoundaryShapeTests.cs +++ b/tests/Furniture.DAL.UnitTests/BoundaryShapeTests.cs @@ -1,6 +1,8 @@ using Furniture.DAL.Caching; +using Furniture.DAL.Adapters; using Furniture.DAL.Providers; using Furniture.DAL.Repositories; +using System.Reflection; namespace Furniture.DAL.UnitTests; @@ -20,4 +22,35 @@ public class BoundaryShapeTests Assert.True(typeof(ICatalogRepository).IsInterface); Assert.True(typeof(ICacheInvalidationPolicy).IsInterface); } + + [Fact] + public void RepositoryBoundaries_WhenReflected_ExposeReadOnlyContracts() + { + Assert.Equal("ReadAvailabilityAsync", typeof(IFurnitureRepository).GetMethods().Single().Name); + Assert.Equal("ReadProductAsync", typeof(ICatalogRepository).GetMethods().Single().Name); + } + + [Fact] + public void ProviderBoundaries_WhenReflected_ExposeReadOnlyContracts() + { + Assert.Equal("ReadAvailabilityAsync", typeof(IFurnitureDataProvider).GetMethods().Single().Name); + Assert.Equal("ReadProductAsync", typeof(ICatalogDataProvider).GetMethods().Single().Name); + } + + [Fact] + public void AdapterBoundaries_WhenReflected_AreInterfaces() + { + Assert.True(typeof(IFurnitureDalGrpcContractAdapter).IsInterface); + Assert.True(typeof(ICatalogProjectionContractAdapter).IsInterface); + + var grpcMethodNames = typeof(IFurnitureDalGrpcContractAdapter) + .GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Select(method => method.Name) + .OrderBy(name => name) + .ToArray(); + + Assert.Equal( + ["FromGrpcAvailabilityRequest", "FromGrpcCatalogRequest", "ToGrpcAvailabilityRequest", "ToGrpcCatalogRequest"], + grpcMethodNames); + } } diff --git a/tests/Furniture.DAL.UnitTests/ContractShapeTests.cs b/tests/Furniture.DAL.UnitTests/ContractShapeTests.cs new file mode 100644 index 0000000..c62ba6f --- /dev/null +++ b/tests/Furniture.DAL.UnitTests/ContractShapeTests.cs @@ -0,0 +1,41 @@ +using Core.Blueprint.Common.Contracts; +using Furniture.DAL.Contracts; + +namespace Furniture.DAL.UnitTests; + +public class ContractShapeTests +{ + [Fact] + public void FurnitureAvailabilityLookupRequest_WhenCreated_StoresTransportNeutralData() + { + var envelope = new FurnitureDalContractEnvelope("1.0.0", "corr-123"); + var request = new FurnitureAvailabilityLookupRequest(envelope, "FUR-001"); + + Assert.Equal("1.0.0", request.Envelope.ContractVersion); + Assert.Equal("corr-123", request.Envelope.CorrelationId); + Assert.Equal("FUR-001", request.FurnitureId); + } + + [Fact] + public void CatalogProductProjectionRecord_WhenCreated_StoresTransportNeutralData() + { + var envelope = new FurnitureDalContractEnvelope("1.0.0", "corr-123"); + var record = new CatalogProductProjectionRecord(envelope, "PRD-001", "Chair"); + + Assert.Equal("1.0.0", record.Envelope.ContractVersion); + Assert.Equal("corr-123", record.Envelope.CorrelationId); + Assert.Equal("PRD-001", record.ProductId); + Assert.Equal("Chair", record.DisplayName); + } + + [Fact] + public void FurnitureDalPackageContract_WhenCreated_UsesBlueprintDescriptorContract() + { + IBlueprintPackageContract contract = new FurnitureDalPackageContract(); + + Assert.Equal("Furniture.DAL.Contracts", contract.Descriptor.PackageId); + Assert.Equal(PackageVersionPolicy.Minor, contract.Descriptor.VersionPolicy); + Assert.Contains("Core.Blueprint.Common", contract.Descriptor.DependencyPackageIds); + Assert.Contains("BuildingBlock.Catalog.Contracts", contract.Descriptor.DependencyPackageIds); + } +}