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);
+ }
+}