chore(furniture-bff): merge contract integration branch

This commit is contained in:
José René White Enciso 2026-02-22 04:57:48 -06:00
commit df0b6dc57d
13 changed files with 130 additions and 15 deletions

View File

@ -11,5 +11,6 @@
## Edge Responsibilities ## Edge Responsibilities
- Validate and normalize consumer request inputs. - 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. - Map downstream errors to consistent API error models.

View File

@ -5,9 +5,14 @@ package "furniture-bff" {
class Program class Program
interface IGetFurnitureAvailabilityHandler interface IGetFurnitureAvailabilityHandler
class GetFurnitureAvailabilityHandler class GetFurnitureAvailabilityHandler
interface IFurnitureAvailabilityEdgeContractAdapter
interface IFurnitureAvailabilityEdgeGrpcContractAdapter
class GetFurnitureAvailabilityEdgeGrpcContract
interface IFurnitureServiceClient interface IFurnitureServiceClient
GetFurnitureAvailabilityHandler ..|> IGetFurnitureAvailabilityHandler GetFurnitureAvailabilityHandler ..|> IGetFurnitureAvailabilityHandler
GetFurnitureAvailabilityHandler --> IFurnitureAvailabilityEdgeContractAdapter
IFurnitureAvailabilityEdgeGrpcContractAdapter --> GetFurnitureAvailabilityEdgeGrpcContract
GetFurnitureAvailabilityHandler --> IFurnitureServiceClient GetFurnitureAvailabilityHandler --> IFurnitureServiceClient
} }
@ -16,5 +21,5 @@ package "furniture-service" as FurnitureService
Consumers --> Program : REST Consumers --> Program : REST
Program --> IGetFurnitureAvailabilityHandler Program --> IGetFurnitureAvailabilityHandler
IFurnitureServiceClient ..> FurnitureService : gRPC/internal IFurnitureServiceClient ..> FurnitureService : service contracts
@enduml @enduml

View File

@ -9,6 +9,7 @@
- Preserve correlation headers to downstream service calls. - Preserve correlation headers to downstream service calls.
- Add correlation headers if missing at the edge. - Add correlation headers if missing at the edge.
- Populate furniture-service request contracts with correlation identifiers.
## Observability Baseline ## Observability Baseline

View File

@ -0,0 +1,24 @@
using Furniture.Bff.Contracts.Api;
using Furniture.Service.Contracts.UseCases;
namespace Furniture.Bff.Application.Adapters;
/// <summary>
/// Defines adapter boundary between furniture BFF edge contracts and furniture service contracts.
/// </summary>
public interface IFurnitureAvailabilityEdgeContractAdapter
{
/// <summary>
/// Maps edge API request into furniture service request contract.
/// </summary>
/// <param name="request">Edge API request.</param>
/// <returns>Furniture service request contract.</returns>
GetFurnitureAvailabilityRequest ToServiceRequest(GetFurnitureAvailabilityApiRequest request);
/// <summary>
/// Maps furniture service response contract into edge API response.
/// </summary>
/// <param name="response">Furniture service response contract.</param>
/// <returns>Edge API response.</returns>
GetFurnitureAvailabilityApiResponse ToApiResponse(GetFurnitureAvailabilityResponse response);
}

View File

@ -0,0 +1,24 @@
using Furniture.Bff.Application.Grpc;
using Furniture.Bff.Contracts.Api;
namespace Furniture.Bff.Application.Adapters;
/// <summary>
/// Defines adapter boundary for gRPC translation at the furniture edge.
/// </summary>
public interface IFurnitureAvailabilityEdgeGrpcContractAdapter
{
/// <summary>
/// Maps edge API request into gRPC contract shape.
/// </summary>
/// <param name="request">Edge API request.</param>
/// <returns>gRPC request contract shape.</returns>
GetFurnitureAvailabilityEdgeGrpcContract ToGrpc(GetFurnitureAvailabilityApiRequest request);
/// <summary>
/// Maps gRPC request contract shape back into edge API request.
/// </summary>
/// <param name="contract">gRPC request contract shape.</param>
/// <returns>Edge API request.</returns>
GetFurnitureAvailabilityApiRequest FromGrpc(GetFurnitureAvailabilityEdgeGrpcContract contract);
}

View File

@ -1,4 +1,4 @@
using Furniture.Bff.Contracts.Api; using Furniture.Service.Contracts.UseCases;
namespace Furniture.Bff.Application.Adapters; namespace Furniture.Bff.Application.Adapters;
@ -10,7 +10,7 @@ public interface IFurnitureServiceClient
/// <summary> /// <summary>
/// Retrieves furniture availability from downstream service. /// Retrieves furniture availability from downstream service.
/// </summary> /// </summary>
/// <param name="furnitureId">Furniture identifier.</param> /// <param name="request">Service-layer availability request contract.</param>
/// <returns>Consumer-ready availability contract.</returns> /// <returns>Service-layer availability response contract.</returns>
Task<GetFurnitureAvailabilityApiResponse> GetAvailabilityAsync(string furnitureId); Task<GetFurnitureAvailabilityResponse> GetAvailabilityAsync(GetFurnitureAvailabilityRequest request);
} }

View File

@ -6,5 +6,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Furniture.Bff.Contracts\Furniture.Bff.Contracts.csproj" /> <ProjectReference Include="..\Furniture.Bff.Contracts\Furniture.Bff.Contracts.csproj" />
<ProjectReference Include="..\..\..\furniture-service\src\Furniture.Service.Contracts\Furniture.Service.Contracts.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,8 @@
namespace Furniture.Bff.Application.Grpc;
/// <summary>
/// Defines minimal gRPC contract shape for furniture availability edge translation.
/// </summary>
/// <param name="FurnitureId">Furniture identifier provided by consumers.</param>
/// <param name="CorrelationId">Request correlation identifier.</param>
public sealed record GetFurnitureAvailabilityEdgeGrpcContract(string FurnitureId, string CorrelationId);

View File

@ -6,12 +6,16 @@ namespace Furniture.Bff.Application.Handlers;
/// <summary> /// <summary>
/// Default edge handler implementation for furniture availability. /// Default edge handler implementation for furniture availability.
/// </summary> /// </summary>
public sealed class GetFurnitureAvailabilityHandler(IFurnitureServiceClient serviceClient) public sealed class GetFurnitureAvailabilityHandler(
IFurnitureServiceClient serviceClient,
IFurnitureAvailabilityEdgeContractAdapter contractAdapter)
: IGetFurnitureAvailabilityHandler : IGetFurnitureAvailabilityHandler
{ {
/// <inheritdoc /> /// <inheritdoc />
public Task<GetFurnitureAvailabilityApiResponse> HandleAsync(GetFurnitureAvailabilityApiRequest request) public async Task<GetFurnitureAvailabilityApiResponse> HandleAsync(GetFurnitureAvailabilityApiRequest request)
{ {
return serviceClient.GetAvailabilityAsync(request.FurnitureId); var serviceRequest = contractAdapter.ToServiceRequest(request);
var serviceResponse = await serviceClient.GetAvailabilityAsync(serviceRequest);
return contractAdapter.ToApiResponse(serviceResponse);
} }
} }

View File

@ -4,4 +4,5 @@ namespace Furniture.Bff.Contracts.Api;
/// External REST request contract for furniture availability. /// External REST request contract for furniture availability.
/// </summary> /// </summary>
/// <param name="FurnitureId">Furniture identifier provided by consumers.</param> /// <param name="FurnitureId">Furniture identifier provided by consumers.</param>
public sealed record GetFurnitureAvailabilityApiRequest(string FurnitureId); /// <param name="CorrelationId">Cross-service correlation identifier.</param>
public sealed record GetFurnitureAvailabilityApiRequest(string FurnitureId, string CorrelationId = "");

View File

@ -4,5 +4,6 @@ namespace Furniture.Bff.Contracts.Api;
/// External REST response contract for furniture availability. /// External REST response contract for furniture availability.
/// </summary> /// </summary>
/// <param name="FurnitureId">Requested furniture identifier.</param> /// <param name="FurnitureId">Requested furniture identifier.</param>
/// <param name="DisplayName">Display name to present to consumer clients.</param>
/// <param name="QuantityAvailable">Availability value for client display.</param> /// <param name="QuantityAvailable">Availability value for client display.</param>
public sealed record GetFurnitureAvailabilityApiResponse(string FurnitureId, int QuantityAvailable); public sealed record GetFurnitureAvailabilityApiResponse(string FurnitureId, string DisplayName, int QuantityAvailable);

View File

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

View File

@ -1,6 +1,7 @@
using Furniture.Bff.Application.Adapters; using Furniture.Bff.Application.Adapters;
using Furniture.Bff.Application.Handlers; using Furniture.Bff.Application.Handlers;
using Furniture.Bff.Contracts.Api; using Furniture.Bff.Contracts.Api;
using Furniture.Service.Contracts.UseCases;
namespace Furniture.Bff.Application.UnitTests; namespace Furniture.Bff.Application.UnitTests;
@ -9,19 +10,38 @@ public class GetFurnitureAvailabilityHandlerTests
[Fact] [Fact]
public async Task HandleAsync_WhenCalled_DelegatesToServiceClient() 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("FUR-001", response.FurnitureId);
Assert.Equal("Chair", response.DisplayName);
Assert.Equal(3, response.QuantityAvailable); Assert.Equal(3, response.QuantityAvailable);
} }
private sealed class FakeFurnitureServiceClient : IFurnitureServiceClient private sealed class FakeFurnitureServiceClient : IFurnitureServiceClient
{ {
public Task<GetFurnitureAvailabilityApiResponse> GetAvailabilityAsync(string furnitureId) public Task<GetFurnitureAvailabilityResponse> 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);
} }
} }
} }