diff --git a/docs/application/use-case-boundaries.md b/docs/application/use-case-boundaries.md
index 4d4828e..6f3d865 100644
--- a/docs/application/use-case-boundaries.md
+++ b/docs/application/use-case-boundaries.md
@@ -4,10 +4,14 @@
- Application use cases orchestrate domain workflows.
- Use cases depend on DAL-facing ports, not persistence implementations.
+- Use cases consume BuildingBlock capability contracts through adapter and port boundaries.
- Transport handlers map to use-case contracts and do not own orchestration logic.
## Current Skeleton
- `IGetFurnitureAvailabilityUseCase`: orchestration boundary contract.
- `GetFurnitureAvailabilityUseCase`: orchestration implementation.
-- `IFurnitureAvailabilityReadPort`: DAL-facing port.
+- `IFurnitureAvailabilityContractAdapter`: maps service contracts to inventory and catalog contracts.
+- `ICatalogProductReadPort`: catalog capability read boundary.
+- `IFurnitureAvailabilityReadPort`: inventory capability read boundary.
+- `IFurnitureAvailabilityGrpcContractAdapter`: gRPC translation boundary for service contracts.
diff --git a/docs/architecture/protocol-selection-policy.md b/docs/architecture/protocol-selection-policy.md
index 434eb85..2999f87 100644
--- a/docs/architecture/protocol-selection-policy.md
+++ b/docs/architecture/protocol-selection-policy.md
@@ -5,8 +5,10 @@
- This service deployment uses one active protocol at a time.
- Internal default protocol is gRPC.
- Multi-protocol exposure is not allowed in a single deployment.
+- gRPC message translation is isolated through adapter interfaces.
## Boundary Placement
- Protocol adaptation stays at BFF boundaries.
- Service orchestration remains transport-neutral at application contracts.
+- Service application composes catalog and inventory capability contracts via read ports.
diff --git a/docs/architecture/service-contracts.puml b/docs/architecture/service-contracts.puml
index ec950da..0f9d384 100644
--- a/docs/architecture/service-contracts.puml
+++ b/docs/architecture/service-contracts.puml
@@ -2,22 +2,33 @@
skinparam packageStyle rectangle
package "furniture-service" {
+ interface IFurnitureAvailabilityContractAdapter
interface IGetFurnitureAvailabilityUseCase
class GetFurnitureAvailabilityUseCase
+ interface ICatalogProductReadPort
interface IFurnitureAvailabilityReadPort
+ interface IFurnitureAvailabilityGrpcContractAdapter
+ class GetFurnitureAvailabilityGrpcContract
class GetFurnitureAvailabilityRequest
class GetFurnitureAvailabilityResponse
GetFurnitureAvailabilityUseCase ..|> IGetFurnitureAvailabilityUseCase
+ GetFurnitureAvailabilityUseCase --> IFurnitureAvailabilityContractAdapter
+ GetFurnitureAvailabilityUseCase --> ICatalogProductReadPort
GetFurnitureAvailabilityUseCase --> IFurnitureAvailabilityReadPort
+ IFurnitureAvailabilityGrpcContractAdapter --> GetFurnitureAvailabilityGrpcContract
+ IFurnitureAvailabilityGrpcContractAdapter --> GetFurnitureAvailabilityRequest
IGetFurnitureAvailabilityUseCase --> GetFurnitureAvailabilityRequest
IGetFurnitureAvailabilityUseCase --> GetFurnitureAvailabilityResponse
}
package "furniture-bff" as FurnitureBff
package "furniture-dal" as FurnitureDal
+package "building-block-catalog" as CatalogContracts
+package "building-block-inventory" as InventoryContracts
FurnitureBff --> IGetFurnitureAvailabilityUseCase
-GetFurnitureAvailabilityUseCase --> IFurnitureAvailabilityReadPort
+ICatalogProductReadPort ..> CatalogContracts
IFurnitureAvailabilityReadPort ..> FurnitureDal
+IFurnitureAvailabilityReadPort ..> InventoryContracts
@enduml
diff --git a/src/Furniture.Service.Application/Adapters/IFurnitureAvailabilityContractAdapter.cs b/src/Furniture.Service.Application/Adapters/IFurnitureAvailabilityContractAdapter.cs
new file mode 100644
index 0000000..2f501e5
--- /dev/null
+++ b/src/Furniture.Service.Application/Adapters/IFurnitureAvailabilityContractAdapter.cs
@@ -0,0 +1,39 @@
+using BuildingBlock.Catalog.Contracts.Products;
+using BuildingBlock.Catalog.Contracts.Responses;
+using BuildingBlock.Inventory.Contracts.Requests;
+using BuildingBlock.Inventory.Contracts.Responses;
+using Furniture.Service.Contracts.UseCases;
+
+namespace Furniture.Service.Application.Adapters;
+
+///
+/// Defines adapter boundary for furniture service contract composition.
+///
+public interface IFurnitureAvailabilityContractAdapter
+{
+ ///
+ /// Maps service request into an inventory capability request.
+ ///
+ /// Furniture availability request.
+ /// Inventory lookup request.
+ InventoryItemLookupRequest ToInventoryRequest(GetFurnitureAvailabilityRequest request);
+
+ ///
+ /// Maps service request into a catalog capability request.
+ ///
+ /// Furniture availability request.
+ /// Catalog product request.
+ ProductContract ToCatalogRequest(GetFurnitureAvailabilityRequest request);
+
+ ///
+ /// Maps capability responses back into the service response contract.
+ ///
+ /// Furniture availability request.
+ /// Catalog product response.
+ /// Inventory lookup response.
+ /// Furniture availability response.
+ GetFurnitureAvailabilityResponse ToServiceResponse(
+ GetFurnitureAvailabilityRequest request,
+ ProductContractResponse catalogResponse,
+ InventoryItemLookupResponse inventoryResponse);
+}
diff --git a/src/Furniture.Service.Application/Adapters/IFurnitureAvailabilityGrpcContractAdapter.cs b/src/Furniture.Service.Application/Adapters/IFurnitureAvailabilityGrpcContractAdapter.cs
new file mode 100644
index 0000000..7062948
--- /dev/null
+++ b/src/Furniture.Service.Application/Adapters/IFurnitureAvailabilityGrpcContractAdapter.cs
@@ -0,0 +1,24 @@
+using Furniture.Service.Application.Grpc;
+using Furniture.Service.Contracts.UseCases;
+
+namespace Furniture.Service.Application.Adapters;
+
+///
+/// Defines adapter boundary for gRPC contract translation of furniture availability.
+///
+public interface IFurnitureAvailabilityGrpcContractAdapter
+{
+ ///
+ /// Maps transport-neutral request into gRPC contract shape.
+ ///
+ /// Furniture availability request.
+ /// gRPC availability contract.
+ GetFurnitureAvailabilityGrpcContract ToGrpc(GetFurnitureAvailabilityRequest request);
+
+ ///
+ /// Maps gRPC contract into transport-neutral request.
+ ///
+ /// gRPC availability contract.
+ /// Furniture availability request.
+ GetFurnitureAvailabilityRequest FromGrpc(GetFurnitureAvailabilityGrpcContract contract);
+}
diff --git a/src/Furniture.Service.Application/Furniture.Service.Application.csproj b/src/Furniture.Service.Application/Furniture.Service.Application.csproj
index 9dbacfc..007938b 100644
--- a/src/Furniture.Service.Application/Furniture.Service.Application.csproj
+++ b/src/Furniture.Service.Application/Furniture.Service.Application.csproj
@@ -5,6 +5,8 @@
enable
+
+
diff --git a/src/Furniture.Service.Application/Grpc/GetFurnitureAvailabilityGrpcContract.cs b/src/Furniture.Service.Application/Grpc/GetFurnitureAvailabilityGrpcContract.cs
new file mode 100644
index 0000000..ae6d97f
--- /dev/null
+++ b/src/Furniture.Service.Application/Grpc/GetFurnitureAvailabilityGrpcContract.cs
@@ -0,0 +1,8 @@
+namespace Furniture.Service.Application.Grpc;
+
+///
+/// Defines minimal gRPC contract shape for furniture availability adapter translation.
+///
+/// Furniture aggregate identifier.
+/// Cross-service correlation identifier.
+public sealed record GetFurnitureAvailabilityGrpcContract(string FurnitureId, string CorrelationId);
diff --git a/src/Furniture.Service.Application/Ports/ICatalogProductReadPort.cs b/src/Furniture.Service.Application/Ports/ICatalogProductReadPort.cs
new file mode 100644
index 0000000..7e8e92f
--- /dev/null
+++ b/src/Furniture.Service.Application/Ports/ICatalogProductReadPort.cs
@@ -0,0 +1,17 @@
+using BuildingBlock.Catalog.Contracts.Products;
+using BuildingBlock.Catalog.Contracts.Responses;
+
+namespace Furniture.Service.Application.Ports;
+
+///
+/// Defines read boundary for catalog product contracts.
+///
+public interface ICatalogProductReadPort
+{
+ ///
+ /// Reads product details from the catalog capability contract boundary.
+ ///
+ /// Catalog product request contract.
+ /// Catalog product response contract.
+ Task ReadProductAsync(ProductContract request);
+}
diff --git a/src/Furniture.Service.Application/Ports/IFurnitureAvailabilityReadPort.cs b/src/Furniture.Service.Application/Ports/IFurnitureAvailabilityReadPort.cs
index 56c321d..44756c5 100644
--- a/src/Furniture.Service.Application/Ports/IFurnitureAvailabilityReadPort.cs
+++ b/src/Furniture.Service.Application/Ports/IFurnitureAvailabilityReadPort.cs
@@ -1,16 +1,17 @@
-using Furniture.Service.Contracts.UseCases;
+using BuildingBlock.Inventory.Contracts.Requests;
+using BuildingBlock.Inventory.Contracts.Responses;
namespace Furniture.Service.Application.Ports;
///
-/// Defines DAL-facing read port for furniture availability.
+/// Defines read boundary for inventory availability contracts.
///
public interface IFurnitureAvailabilityReadPort
{
///
- /// Retrieves current availability for a furniture aggregate.
+ /// Reads availability from the inventory capability contract boundary.
///
- /// Furniture aggregate identifier.
- /// Availability response contract.
- Task GetAvailabilityAsync(string furnitureId);
+ /// Inventory lookup request contract.
+ /// Inventory lookup response contract.
+ Task ReadAvailabilityAsync(InventoryItemLookupRequest request);
}
diff --git a/src/Furniture.Service.Application/UseCases/GetFurnitureAvailabilityUseCase.cs b/src/Furniture.Service.Application/UseCases/GetFurnitureAvailabilityUseCase.cs
index 88824b6..52acd3d 100644
--- a/src/Furniture.Service.Application/UseCases/GetFurnitureAvailabilityUseCase.cs
+++ b/src/Furniture.Service.Application/UseCases/GetFurnitureAvailabilityUseCase.cs
@@ -1,3 +1,4 @@
+using Furniture.Service.Application.Adapters;
using Furniture.Service.Application.Ports;
using Furniture.Service.Contracts.UseCases;
@@ -6,12 +7,26 @@ namespace Furniture.Service.Application.UseCases;
///
/// Default orchestration implementation for furniture availability lookup.
///
-public sealed class GetFurnitureAvailabilityUseCase(IFurnitureAvailabilityReadPort readPort)
+public sealed class GetFurnitureAvailabilityUseCase(
+ IFurnitureAvailabilityContractAdapter contractAdapter,
+ ICatalogProductReadPort catalogReadPort,
+ IFurnitureAvailabilityReadPort inventoryReadPort)
: IGetFurnitureAvailabilityUseCase
{
///
- public Task HandleAsync(GetFurnitureAvailabilityRequest request)
+ public async Task HandleAsync(GetFurnitureAvailabilityRequest request)
{
- return readPort.GetAvailabilityAsync(request.FurnitureId);
+ var catalogRequest = contractAdapter.ToCatalogRequest(request);
+ var inventoryRequest = contractAdapter.ToInventoryRequest(request);
+
+ var catalogTask = catalogReadPort.ReadProductAsync(catalogRequest);
+ var inventoryTask = inventoryReadPort.ReadAvailabilityAsync(inventoryRequest);
+
+ await Task.WhenAll(catalogTask, inventoryTask);
+
+ return contractAdapter.ToServiceResponse(
+ request,
+ await catalogTask,
+ await inventoryTask);
}
}
diff --git a/src/Furniture.Service.Contracts/Conventions/FurnitureServicePackageContract.cs b/src/Furniture.Service.Contracts/Conventions/FurnitureServicePackageContract.cs
new file mode 100644
index 0000000..6486e2e
--- /dev/null
+++ b/src/Furniture.Service.Contracts/Conventions/FurnitureServicePackageContract.cs
@@ -0,0 +1,15 @@
+using Core.Blueprint.Common.Contracts;
+
+namespace Furniture.Service.Contracts.Conventions;
+
+///
+/// Defines package descriptor metadata for furniture service contracts.
+///
+public sealed class FurnitureServicePackageContract : IBlueprintPackageContract
+{
+ ///
+ public BlueprintPackageDescriptor Descriptor { get; } = new(
+ "Furniture.Service.Contracts",
+ PackageVersionPolicy.Minor,
+ ["Core.Blueprint.Common", "BuildingBlock.Inventory.Contracts", "BuildingBlock.Catalog.Contracts"]);
+}
diff --git a/src/Furniture.Service.Contracts/Furniture.Service.Contracts.csproj b/src/Furniture.Service.Contracts/Furniture.Service.Contracts.csproj
index 6c3a887..04a4bbc 100644
--- a/src/Furniture.Service.Contracts/Furniture.Service.Contracts.csproj
+++ b/src/Furniture.Service.Contracts/Furniture.Service.Contracts.csproj
@@ -4,4 +4,7 @@
enable
enable
+
+
+
diff --git a/src/Furniture.Service.Contracts/UseCases/GetFurnitureAvailabilityRequest.cs b/src/Furniture.Service.Contracts/UseCases/GetFurnitureAvailabilityRequest.cs
index 044d73a..a1f46dd 100644
--- a/src/Furniture.Service.Contracts/UseCases/GetFurnitureAvailabilityRequest.cs
+++ b/src/Furniture.Service.Contracts/UseCases/GetFurnitureAvailabilityRequest.cs
@@ -4,4 +4,5 @@ namespace Furniture.Service.Contracts.UseCases;
/// Request contract for furniture availability lookup.
///
/// Furniture aggregate identifier.
-public sealed record GetFurnitureAvailabilityRequest(string FurnitureId);
+/// Cross-service correlation identifier.
+public sealed record GetFurnitureAvailabilityRequest(string FurnitureId, string CorrelationId = "");
diff --git a/src/Furniture.Service.Contracts/UseCases/GetFurnitureAvailabilityResponse.cs b/src/Furniture.Service.Contracts/UseCases/GetFurnitureAvailabilityResponse.cs
index abc8ca7..07fc4bd 100644
--- a/src/Furniture.Service.Contracts/UseCases/GetFurnitureAvailabilityResponse.cs
+++ b/src/Furniture.Service.Contracts/UseCases/GetFurnitureAvailabilityResponse.cs
@@ -4,5 +4,6 @@ namespace Furniture.Service.Contracts.UseCases;
/// Response contract for furniture availability lookup.
///
/// Furniture aggregate identifier.
+/// Catalog display name for the requested furniture.
/// Available quantity for the requested furniture.
-public sealed record GetFurnitureAvailabilityResponse(string FurnitureId, int QuantityAvailable);
+public sealed record GetFurnitureAvailabilityResponse(string FurnitureId, string DisplayName, int QuantityAvailable);
diff --git a/tests/Furniture.Service.Application.UnitTests/ContractShapeTests.cs b/tests/Furniture.Service.Application.UnitTests/ContractShapeTests.cs
new file mode 100644
index 0000000..66f41f0
--- /dev/null
+++ b/tests/Furniture.Service.Application.UnitTests/ContractShapeTests.cs
@@ -0,0 +1,29 @@
+using Core.Blueprint.Common.Contracts;
+using Furniture.Service.Contracts.Conventions;
+using Furniture.Service.Contracts.UseCases;
+
+namespace Furniture.Service.Application.UnitTests;
+
+public class ContractShapeTests
+{
+ [Fact]
+ public void GetFurnitureAvailabilityRequest_WhenCreated_StoresCorrelation()
+ {
+ var request = new GetFurnitureAvailabilityRequest("FUR-001", "corr-123");
+
+ Assert.Equal("FUR-001", request.FurnitureId);
+ Assert.Equal("corr-123", request.CorrelationId);
+ }
+
+ [Fact]
+ public void FurnitureServicePackageContract_WhenCreated_UsesBlueprintDescriptorContract()
+ {
+ IBlueprintPackageContract contract = new FurnitureServicePackageContract();
+
+ Assert.Equal("Furniture.Service.Contracts", contract.Descriptor.PackageId);
+ Assert.Equal(PackageVersionPolicy.Minor, contract.Descriptor.VersionPolicy);
+ Assert.Contains("Core.Blueprint.Common", contract.Descriptor.DependencyPackageIds);
+ Assert.Contains("BuildingBlock.Inventory.Contracts", contract.Descriptor.DependencyPackageIds);
+ Assert.Contains("BuildingBlock.Catalog.Contracts", contract.Descriptor.DependencyPackageIds);
+ }
+}
diff --git a/tests/Furniture.Service.Application.UnitTests/GetFurnitureAvailabilityUseCaseTests.cs b/tests/Furniture.Service.Application.UnitTests/GetFurnitureAvailabilityUseCaseTests.cs
index 6ee97f4..3317df7 100644
--- a/tests/Furniture.Service.Application.UnitTests/GetFurnitureAvailabilityUseCaseTests.cs
+++ b/tests/Furniture.Service.Application.UnitTests/GetFurnitureAvailabilityUseCaseTests.cs
@@ -1,3 +1,10 @@
+using BuildingBlock.Catalog.Contracts.Conventions;
+using BuildingBlock.Catalog.Contracts.Products;
+using BuildingBlock.Catalog.Contracts.Responses;
+using BuildingBlock.Inventory.Contracts.Conventions;
+using BuildingBlock.Inventory.Contracts.Requests;
+using BuildingBlock.Inventory.Contracts.Responses;
+using Furniture.Service.Application.Adapters;
using Furniture.Service.Application.Ports;
using Furniture.Service.Application.UseCases;
using Furniture.Service.Contracts.UseCases;
@@ -9,20 +16,68 @@ public class GetFurnitureAvailabilityUseCaseTests
[Fact]
public async Task HandleAsync_WhenCalled_DelegatesToReadPort()
{
- var port = new FakeFurnitureAvailabilityReadPort();
- var useCase = new GetFurnitureAvailabilityUseCase(port);
+ var adapter = new FakeFurnitureAvailabilityContractAdapter();
+ var catalogPort = new FakeCatalogProductReadPort();
+ var inventoryPort = new FakeFurnitureAvailabilityReadPort();
+ var useCase = new GetFurnitureAvailabilityUseCase(adapter, catalogPort, inventoryPort);
- var response = await useCase.HandleAsync(new GetFurnitureAvailabilityRequest("FUR-001"));
+ var response = await useCase.HandleAsync(new GetFurnitureAvailabilityRequest("FUR-001", "corr-123"));
Assert.Equal("FUR-001", response.FurnitureId);
+ Assert.Equal("Chair", response.DisplayName);
Assert.Equal(10, response.QuantityAvailable);
}
+ private sealed class FakeFurnitureAvailabilityContractAdapter : IFurnitureAvailabilityContractAdapter
+ {
+ public ProductContract ToCatalogRequest(GetFurnitureAvailabilityRequest request)
+ {
+ return new ProductContract(
+ new CatalogContractEnvelope("1.0.0", request.CorrelationId),
+ request.FurnitureId,
+ string.Empty);
+ }
+
+ public InventoryItemLookupRequest ToInventoryRequest(GetFurnitureAvailabilityRequest request)
+ {
+ return new InventoryItemLookupRequest(
+ new InventoryContractEnvelope("1.0.0", request.CorrelationId),
+ request.FurnitureId);
+ }
+
+ public GetFurnitureAvailabilityResponse ToServiceResponse(
+ GetFurnitureAvailabilityRequest request,
+ ProductContractResponse catalogResponse,
+ InventoryItemLookupResponse inventoryResponse)
+ {
+ return new GetFurnitureAvailabilityResponse(
+ request.FurnitureId,
+ catalogResponse.DisplayName,
+ inventoryResponse.QuantityAvailable);
+ }
+ }
+
+ private sealed class FakeCatalogProductReadPort : ICatalogProductReadPort
+ {
+ public Task ReadProductAsync(ProductContract request)
+ {
+ return Task.FromResult(
+ new ProductContractResponse(
+ new CatalogContractEnvelope("1.0.0", request.Envelope.CorrelationId),
+ request.ProductId,
+ "Chair"));
+ }
+ }
+
private sealed class FakeFurnitureAvailabilityReadPort : IFurnitureAvailabilityReadPort
{
- public Task GetAvailabilityAsync(string furnitureId)
+ public Task ReadAvailabilityAsync(InventoryItemLookupRequest request)
{
- return Task.FromResult(new GetFurnitureAvailabilityResponse(furnitureId, 10));
+ return Task.FromResult(
+ new InventoryItemLookupResponse(
+ new InventoryContractEnvelope("1.0.0", request.Envelope.CorrelationId),
+ request.ItemCode,
+ 10));
}
}
}