chore(furniture-service): merge contract integration branch
This commit is contained in:
commit
c94b6cc56f
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Defines adapter boundary for furniture service contract composition.
|
||||
/// </summary>
|
||||
public interface IFurnitureAvailabilityContractAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps service request into an inventory capability request.
|
||||
/// </summary>
|
||||
/// <param name="request">Furniture availability request.</param>
|
||||
/// <returns>Inventory lookup request.</returns>
|
||||
InventoryItemLookupRequest ToInventoryRequest(GetFurnitureAvailabilityRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Maps service request into a catalog capability request.
|
||||
/// </summary>
|
||||
/// <param name="request">Furniture availability request.</param>
|
||||
/// <returns>Catalog product request.</returns>
|
||||
ProductContract ToCatalogRequest(GetFurnitureAvailabilityRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Maps capability responses back into the service response contract.
|
||||
/// </summary>
|
||||
/// <param name="request">Furniture availability request.</param>
|
||||
/// <param name="catalogResponse">Catalog product response.</param>
|
||||
/// <param name="inventoryResponse">Inventory lookup response.</param>
|
||||
/// <returns>Furniture availability response.</returns>
|
||||
GetFurnitureAvailabilityResponse ToServiceResponse(
|
||||
GetFurnitureAvailabilityRequest request,
|
||||
ProductContractResponse catalogResponse,
|
||||
InventoryItemLookupResponse inventoryResponse);
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
using Furniture.Service.Application.Grpc;
|
||||
using Furniture.Service.Contracts.UseCases;
|
||||
|
||||
namespace Furniture.Service.Application.Adapters;
|
||||
|
||||
/// <summary>
|
||||
/// Defines adapter boundary for gRPC contract translation of furniture availability.
|
||||
/// </summary>
|
||||
public interface IFurnitureAvailabilityGrpcContractAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps transport-neutral request into gRPC contract shape.
|
||||
/// </summary>
|
||||
/// <param name="request">Furniture availability request.</param>
|
||||
/// <returns>gRPC availability contract.</returns>
|
||||
GetFurnitureAvailabilityGrpcContract ToGrpc(GetFurnitureAvailabilityRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Maps gRPC contract into transport-neutral request.
|
||||
/// </summary>
|
||||
/// <param name="contract">gRPC availability contract.</param>
|
||||
/// <returns>Furniture availability request.</returns>
|
||||
GetFurnitureAvailabilityRequest FromGrpc(GetFurnitureAvailabilityGrpcContract contract);
|
||||
}
|
||||
@ -5,6 +5,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\building-block-catalog\src\BuildingBlock.Catalog.Contracts\BuildingBlock.Catalog.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\..\building-block-inventory\src\BuildingBlock.Inventory.Contracts\BuildingBlock.Inventory.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Furniture.Service.Contracts\Furniture.Service.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Furniture.Service.Application.Grpc;
|
||||
|
||||
/// <summary>
|
||||
/// Defines minimal gRPC contract shape for furniture availability adapter translation.
|
||||
/// </summary>
|
||||
/// <param name="FurnitureId">Furniture aggregate identifier.</param>
|
||||
/// <param name="CorrelationId">Cross-service correlation identifier.</param>
|
||||
public sealed record GetFurnitureAvailabilityGrpcContract(string FurnitureId, string CorrelationId);
|
||||
@ -0,0 +1,17 @@
|
||||
using BuildingBlock.Catalog.Contracts.Products;
|
||||
using BuildingBlock.Catalog.Contracts.Responses;
|
||||
|
||||
namespace Furniture.Service.Application.Ports;
|
||||
|
||||
/// <summary>
|
||||
/// Defines read boundary for catalog product contracts.
|
||||
/// </summary>
|
||||
public interface ICatalogProductReadPort
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads product details from the catalog capability contract boundary.
|
||||
/// </summary>
|
||||
/// <param name="request">Catalog product request contract.</param>
|
||||
/// <returns>Catalog product response contract.</returns>
|
||||
Task<ProductContractResponse> ReadProductAsync(ProductContract request);
|
||||
}
|
||||
@ -1,16 +1,17 @@
|
||||
using Furniture.Service.Contracts.UseCases;
|
||||
using BuildingBlock.Inventory.Contracts.Requests;
|
||||
using BuildingBlock.Inventory.Contracts.Responses;
|
||||
|
||||
namespace Furniture.Service.Application.Ports;
|
||||
|
||||
/// <summary>
|
||||
/// Defines DAL-facing read port for furniture availability.
|
||||
/// Defines read boundary for inventory availability contracts.
|
||||
/// </summary>
|
||||
public interface IFurnitureAvailabilityReadPort
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves current availability for a furniture aggregate.
|
||||
/// Reads availability from the inventory capability contract boundary.
|
||||
/// </summary>
|
||||
/// <param name="furnitureId">Furniture aggregate identifier.</param>
|
||||
/// <returns>Availability response contract.</returns>
|
||||
Task<GetFurnitureAvailabilityResponse> GetAvailabilityAsync(string furnitureId);
|
||||
/// <param name="request">Inventory lookup request contract.</param>
|
||||
/// <returns>Inventory lookup response contract.</returns>
|
||||
Task<InventoryItemLookupResponse> ReadAvailabilityAsync(InventoryItemLookupRequest request);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
/// <summary>
|
||||
/// Default orchestration implementation for furniture availability lookup.
|
||||
/// </summary>
|
||||
public sealed class GetFurnitureAvailabilityUseCase(IFurnitureAvailabilityReadPort readPort)
|
||||
public sealed class GetFurnitureAvailabilityUseCase(
|
||||
IFurnitureAvailabilityContractAdapter contractAdapter,
|
||||
ICatalogProductReadPort catalogReadPort,
|
||||
IFurnitureAvailabilityReadPort inventoryReadPort)
|
||||
: IGetFurnitureAvailabilityUseCase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<GetFurnitureAvailabilityResponse> HandleAsync(GetFurnitureAvailabilityRequest request)
|
||||
public async Task<GetFurnitureAvailabilityResponse> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
using Core.Blueprint.Common.Contracts;
|
||||
|
||||
namespace Furniture.Service.Contracts.Conventions;
|
||||
|
||||
/// <summary>
|
||||
/// Defines package descriptor metadata for furniture service contracts.
|
||||
/// </summary>
|
||||
public sealed class FurnitureServicePackageContract : IBlueprintPackageContract
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public BlueprintPackageDescriptor Descriptor { get; } = new(
|
||||
"Furniture.Service.Contracts",
|
||||
PackageVersionPolicy.Minor,
|
||||
["Core.Blueprint.Common", "BuildingBlock.Inventory.Contracts", "BuildingBlock.Catalog.Contracts"]);
|
||||
}
|
||||
@ -4,4 +4,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\blueprint-platform\src\Core.Blueprint.Common\Core.Blueprint.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -4,4 +4,5 @@ namespace Furniture.Service.Contracts.UseCases;
|
||||
/// Request contract for furniture availability lookup.
|
||||
/// </summary>
|
||||
/// <param name="FurnitureId">Furniture aggregate identifier.</param>
|
||||
public sealed record GetFurnitureAvailabilityRequest(string FurnitureId);
|
||||
/// <param name="CorrelationId">Cross-service correlation identifier.</param>
|
||||
public sealed record GetFurnitureAvailabilityRequest(string FurnitureId, string CorrelationId = "");
|
||||
|
||||
@ -4,5 +4,6 @@ namespace Furniture.Service.Contracts.UseCases;
|
||||
/// Response contract for furniture availability lookup.
|
||||
/// </summary>
|
||||
/// <param name="FurnitureId">Furniture aggregate identifier.</param>
|
||||
/// <param name="DisplayName">Catalog display name for the requested furniture.</param>
|
||||
/// <param name="QuantityAvailable">Available quantity for the requested furniture.</param>
|
||||
public sealed record GetFurnitureAvailabilityResponse(string FurnitureId, int QuantityAvailable);
|
||||
public sealed record GetFurnitureAvailabilityResponse(string FurnitureId, string DisplayName, int QuantityAvailable);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<ProductContractResponse> 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<GetFurnitureAvailabilityResponse> GetAvailabilityAsync(string furnitureId)
|
||||
public Task<InventoryItemLookupResponse> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user