From 97324b367acb19e6b311a65967ca424be7e148c1 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:18:32 -0600 Subject: [PATCH] feat(furniture-bff): integrate service contract edge adapters --- docs/api/external-api-surface.md | 3 +- docs/architecture/protocol-adaptation.puml | 7 ++++- docs/operations/tracing-and-headers.md | 1 + ...urnitureAvailabilityEdgeContractAdapter.cs | 24 ++++++++++++++++ ...tureAvailabilityEdgeGrpcContractAdapter.cs | 24 ++++++++++++++++ .../Adapters/IFurnitureServiceClient.cs | 8 +++--- .../Furniture.Bff.Application.csproj | 1 + ...etFurnitureAvailabilityEdgeGrpcContract.cs | 8 ++++++ .../GetFurnitureAvailabilityHandler.cs | 10 +++++-- .../Api/GetFurnitureAvailabilityApiRequest.cs | 3 +- .../GetFurnitureAvailabilityApiResponse.cs | 3 +- .../ContractShapeTests.cs | 25 +++++++++++++++++ .../GetFurnitureAvailabilityHandlerTests.cs | 28 ++++++++++++++++--- 13 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 src/Furniture.Bff.Application/Adapters/IFurnitureAvailabilityEdgeContractAdapter.cs create mode 100644 src/Furniture.Bff.Application/Adapters/IFurnitureAvailabilityEdgeGrpcContractAdapter.cs create mode 100644 src/Furniture.Bff.Application/Grpc/GetFurnitureAvailabilityEdgeGrpcContract.cs create mode 100644 tests/Furniture.Bff.Application.UnitTests/ContractShapeTests.cs diff --git a/docs/api/external-api-surface.md b/docs/api/external-api-surface.md index b0b5433..7dd8ed2 100644 --- a/docs/api/external-api-surface.md +++ b/docs/api/external-api-surface.md @@ -11,5 +11,6 @@ ## Edge Responsibilities - Validate and normalize consumer request inputs. -- Map downstream service responses to consumer-facing shapes. +- Map edge requests to furniture-service transport-neutral contracts. +- Map downstream furniture-service responses to consumer-facing shapes. - Map downstream errors to consistent API error models. diff --git a/docs/architecture/protocol-adaptation.puml b/docs/architecture/protocol-adaptation.puml index 7d2790b..a962ebf 100644 --- a/docs/architecture/protocol-adaptation.puml +++ b/docs/architecture/protocol-adaptation.puml @@ -5,9 +5,14 @@ package "furniture-bff" { class Program interface IGetFurnitureAvailabilityHandler class GetFurnitureAvailabilityHandler + interface IFurnitureAvailabilityEdgeContractAdapter + interface IFurnitureAvailabilityEdgeGrpcContractAdapter + class GetFurnitureAvailabilityEdgeGrpcContract interface IFurnitureServiceClient GetFurnitureAvailabilityHandler ..|> IGetFurnitureAvailabilityHandler + GetFurnitureAvailabilityHandler --> IFurnitureAvailabilityEdgeContractAdapter + IFurnitureAvailabilityEdgeGrpcContractAdapter --> GetFurnitureAvailabilityEdgeGrpcContract GetFurnitureAvailabilityHandler --> IFurnitureServiceClient } @@ -16,5 +21,5 @@ package "furniture-service" as FurnitureService Consumers --> Program : REST Program --> IGetFurnitureAvailabilityHandler -IFurnitureServiceClient ..> FurnitureService : gRPC/internal +IFurnitureServiceClient ..> FurnitureService : service contracts @enduml diff --git a/docs/operations/tracing-and-headers.md b/docs/operations/tracing-and-headers.md index b159fa0..1acf164 100644 --- a/docs/operations/tracing-and-headers.md +++ b/docs/operations/tracing-and-headers.md @@ -9,6 +9,7 @@ - Preserve correlation headers to downstream service calls. - Add correlation headers if missing at the edge. +- Populate furniture-service request contracts with correlation identifiers. ## Observability Baseline diff --git a/src/Furniture.Bff.Application/Adapters/IFurnitureAvailabilityEdgeContractAdapter.cs b/src/Furniture.Bff.Application/Adapters/IFurnitureAvailabilityEdgeContractAdapter.cs new file mode 100644 index 0000000..1eb23dd --- /dev/null +++ b/src/Furniture.Bff.Application/Adapters/IFurnitureAvailabilityEdgeContractAdapter.cs @@ -0,0 +1,24 @@ +using Furniture.Bff.Contracts.Api; +using Furniture.Service.Contracts.UseCases; + +namespace Furniture.Bff.Application.Adapters; + +/// +/// Defines adapter boundary between furniture BFF edge contracts and furniture service contracts. +/// +public interface IFurnitureAvailabilityEdgeContractAdapter +{ + /// + /// Maps edge API request into furniture service request contract. + /// + /// Edge API request. + /// Furniture service request contract. + GetFurnitureAvailabilityRequest ToServiceRequest(GetFurnitureAvailabilityApiRequest request); + + /// + /// Maps furniture service response contract into edge API response. + /// + /// Furniture service response contract. + /// Edge API response. + GetFurnitureAvailabilityApiResponse ToApiResponse(GetFurnitureAvailabilityResponse response); +} diff --git a/src/Furniture.Bff.Application/Adapters/IFurnitureAvailabilityEdgeGrpcContractAdapter.cs b/src/Furniture.Bff.Application/Adapters/IFurnitureAvailabilityEdgeGrpcContractAdapter.cs new file mode 100644 index 0000000..bede2c8 --- /dev/null +++ b/src/Furniture.Bff.Application/Adapters/IFurnitureAvailabilityEdgeGrpcContractAdapter.cs @@ -0,0 +1,24 @@ +using Furniture.Bff.Application.Grpc; +using Furniture.Bff.Contracts.Api; + +namespace Furniture.Bff.Application.Adapters; + +/// +/// Defines adapter boundary for gRPC translation at the furniture edge. +/// +public interface IFurnitureAvailabilityEdgeGrpcContractAdapter +{ + /// + /// Maps edge API request into gRPC contract shape. + /// + /// Edge API request. + /// gRPC request contract shape. + GetFurnitureAvailabilityEdgeGrpcContract ToGrpc(GetFurnitureAvailabilityApiRequest request); + + /// + /// Maps gRPC request contract shape back into edge API request. + /// + /// gRPC request contract shape. + /// Edge API request. + GetFurnitureAvailabilityApiRequest FromGrpc(GetFurnitureAvailabilityEdgeGrpcContract contract); +} diff --git a/src/Furniture.Bff.Application/Adapters/IFurnitureServiceClient.cs b/src/Furniture.Bff.Application/Adapters/IFurnitureServiceClient.cs index 1e118a1..d9ba0e4 100644 --- a/src/Furniture.Bff.Application/Adapters/IFurnitureServiceClient.cs +++ b/src/Furniture.Bff.Application/Adapters/IFurnitureServiceClient.cs @@ -1,4 +1,4 @@ -using Furniture.Bff.Contracts.Api; +using Furniture.Service.Contracts.UseCases; namespace Furniture.Bff.Application.Adapters; @@ -10,7 +10,7 @@ public interface IFurnitureServiceClient /// /// Retrieves furniture availability from downstream service. /// - /// Furniture identifier. - /// Consumer-ready availability contract. - Task GetAvailabilityAsync(string furnitureId); + /// Service-layer availability request contract. + /// Service-layer availability response contract. + Task GetAvailabilityAsync(GetFurnitureAvailabilityRequest request); } diff --git a/src/Furniture.Bff.Application/Furniture.Bff.Application.csproj b/src/Furniture.Bff.Application/Furniture.Bff.Application.csproj index c75bc4d..41b768c 100644 --- a/src/Furniture.Bff.Application/Furniture.Bff.Application.csproj +++ b/src/Furniture.Bff.Application/Furniture.Bff.Application.csproj @@ -6,5 +6,6 @@ + diff --git a/src/Furniture.Bff.Application/Grpc/GetFurnitureAvailabilityEdgeGrpcContract.cs b/src/Furniture.Bff.Application/Grpc/GetFurnitureAvailabilityEdgeGrpcContract.cs new file mode 100644 index 0000000..4d95b1a --- /dev/null +++ b/src/Furniture.Bff.Application/Grpc/GetFurnitureAvailabilityEdgeGrpcContract.cs @@ -0,0 +1,8 @@ +namespace Furniture.Bff.Application.Grpc; + +/// +/// Defines minimal gRPC contract shape for furniture availability edge translation. +/// +/// Furniture identifier provided by consumers. +/// Request correlation identifier. +public sealed record GetFurnitureAvailabilityEdgeGrpcContract(string FurnitureId, string CorrelationId); diff --git a/src/Furniture.Bff.Application/Handlers/GetFurnitureAvailabilityHandler.cs b/src/Furniture.Bff.Application/Handlers/GetFurnitureAvailabilityHandler.cs index 7781661..c0da525 100644 --- a/src/Furniture.Bff.Application/Handlers/GetFurnitureAvailabilityHandler.cs +++ b/src/Furniture.Bff.Application/Handlers/GetFurnitureAvailabilityHandler.cs @@ -6,12 +6,16 @@ namespace Furniture.Bff.Application.Handlers; /// /// Default edge handler implementation for furniture availability. /// -public sealed class GetFurnitureAvailabilityHandler(IFurnitureServiceClient serviceClient) +public sealed class GetFurnitureAvailabilityHandler( + IFurnitureServiceClient serviceClient, + IFurnitureAvailabilityEdgeContractAdapter contractAdapter) : IGetFurnitureAvailabilityHandler { /// - public Task HandleAsync(GetFurnitureAvailabilityApiRequest request) + public async Task HandleAsync(GetFurnitureAvailabilityApiRequest request) { - return serviceClient.GetAvailabilityAsync(request.FurnitureId); + var serviceRequest = contractAdapter.ToServiceRequest(request); + var serviceResponse = await serviceClient.GetAvailabilityAsync(serviceRequest); + return contractAdapter.ToApiResponse(serviceResponse); } } diff --git a/src/Furniture.Bff.Contracts/Api/GetFurnitureAvailabilityApiRequest.cs b/src/Furniture.Bff.Contracts/Api/GetFurnitureAvailabilityApiRequest.cs index 42b9d38..2de74e4 100644 --- a/src/Furniture.Bff.Contracts/Api/GetFurnitureAvailabilityApiRequest.cs +++ b/src/Furniture.Bff.Contracts/Api/GetFurnitureAvailabilityApiRequest.cs @@ -4,4 +4,5 @@ namespace Furniture.Bff.Contracts.Api; /// External REST request contract for furniture availability. /// /// Furniture identifier provided by consumers. -public sealed record GetFurnitureAvailabilityApiRequest(string FurnitureId); +/// Cross-service correlation identifier. +public sealed record GetFurnitureAvailabilityApiRequest(string FurnitureId, string CorrelationId = ""); diff --git a/src/Furniture.Bff.Contracts/Api/GetFurnitureAvailabilityApiResponse.cs b/src/Furniture.Bff.Contracts/Api/GetFurnitureAvailabilityApiResponse.cs index c6068c8..2df63b8 100644 --- a/src/Furniture.Bff.Contracts/Api/GetFurnitureAvailabilityApiResponse.cs +++ b/src/Furniture.Bff.Contracts/Api/GetFurnitureAvailabilityApiResponse.cs @@ -4,5 +4,6 @@ namespace Furniture.Bff.Contracts.Api; /// External REST response contract for furniture availability. /// /// Requested furniture identifier. +/// Display name to present to consumer clients. /// Availability value for client display. -public sealed record GetFurnitureAvailabilityApiResponse(string FurnitureId, int QuantityAvailable); +public sealed record GetFurnitureAvailabilityApiResponse(string FurnitureId, string DisplayName, int QuantityAvailable); diff --git a/tests/Furniture.Bff.Application.UnitTests/ContractShapeTests.cs b/tests/Furniture.Bff.Application.UnitTests/ContractShapeTests.cs new file mode 100644 index 0000000..23703b8 --- /dev/null +++ b/tests/Furniture.Bff.Application.UnitTests/ContractShapeTests.cs @@ -0,0 +1,25 @@ +using Furniture.Bff.Contracts.Api; + +namespace Furniture.Bff.Application.UnitTests; + +public class ContractShapeTests +{ + [Fact] + public void GetFurnitureAvailabilityApiRequest_WhenCreated_StoresCorrelationId() + { + var request = new GetFurnitureAvailabilityApiRequest("FUR-001", "corr-123"); + + Assert.Equal("FUR-001", request.FurnitureId); + Assert.Equal("corr-123", request.CorrelationId); + } + + [Fact] + public void GetFurnitureAvailabilityApiResponse_WhenCreated_StoresDisplayName() + { + var response = new GetFurnitureAvailabilityApiResponse("FUR-001", "Chair", 8); + + Assert.Equal("FUR-001", response.FurnitureId); + Assert.Equal("Chair", response.DisplayName); + Assert.Equal(8, response.QuantityAvailable); + } +} diff --git a/tests/Furniture.Bff.Application.UnitTests/GetFurnitureAvailabilityHandlerTests.cs b/tests/Furniture.Bff.Application.UnitTests/GetFurnitureAvailabilityHandlerTests.cs index 8eabf74..93daf0a 100644 --- a/tests/Furniture.Bff.Application.UnitTests/GetFurnitureAvailabilityHandlerTests.cs +++ b/tests/Furniture.Bff.Application.UnitTests/GetFurnitureAvailabilityHandlerTests.cs @@ -1,6 +1,7 @@ using Furniture.Bff.Application.Adapters; using Furniture.Bff.Application.Handlers; using Furniture.Bff.Contracts.Api; +using Furniture.Service.Contracts.UseCases; namespace Furniture.Bff.Application.UnitTests; @@ -9,19 +10,38 @@ public class GetFurnitureAvailabilityHandlerTests [Fact] public async Task HandleAsync_WhenCalled_DelegatesToServiceClient() { - var handler = new GetFurnitureAvailabilityHandler(new FakeFurnitureServiceClient()); + var handler = new GetFurnitureAvailabilityHandler( + new FakeFurnitureServiceClient(), + new FakeFurnitureAvailabilityEdgeContractAdapter()); - var response = await handler.HandleAsync(new GetFurnitureAvailabilityApiRequest("FUR-001")); + var response = await handler.HandleAsync(new GetFurnitureAvailabilityApiRequest("FUR-001", "corr-123")); Assert.Equal("FUR-001", response.FurnitureId); + Assert.Equal("Chair", response.DisplayName); Assert.Equal(3, response.QuantityAvailable); } private sealed class FakeFurnitureServiceClient : IFurnitureServiceClient { - public Task GetAvailabilityAsync(string furnitureId) + public Task GetAvailabilityAsync(GetFurnitureAvailabilityRequest request) { - return Task.FromResult(new GetFurnitureAvailabilityApiResponse(furnitureId, 3)); + return Task.FromResult(new GetFurnitureAvailabilityResponse(request.FurnitureId, "Chair", 3)); + } + } + + private sealed class FakeFurnitureAvailabilityEdgeContractAdapter : IFurnitureAvailabilityEdgeContractAdapter + { + public GetFurnitureAvailabilityRequest ToServiceRequest(GetFurnitureAvailabilityApiRequest request) + { + return new GetFurnitureAvailabilityRequest(request.FurnitureId, request.CorrelationId); + } + + public GetFurnitureAvailabilityApiResponse ToApiResponse(GetFurnitureAvailabilityResponse response) + { + return new GetFurnitureAvailabilityApiResponse( + response.FurnitureId, + response.DisplayName, + response.QuantityAvailable); } } }