merge(furniture-domain): integrate domain decision baseline

This commit is contained in:
José René White Enciso 2026-02-25 14:27:48 -06:00
commit 05f4ed1d79
11 changed files with 261 additions and 0 deletions

6
.gitignore vendored
View File

@ -1,2 +1,8 @@
.tasks/
.agile/
bin/
obj/
TestResults/
.vs/
*.user
*.suo

8
Furniture.Domain.slnx Normal file
View File

@ -0,0 +1,8 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/Furniture.Domain/Furniture.Domain.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Furniture.Domain.UnitTests/Furniture.Domain.UnitTests.csproj" />
</Folder>
</Solution>

View File

@ -4,6 +4,8 @@
- Availability decision outcome remains unchanged for equivalent inputs.
- Correlation propagation behavior remains unchanged.
- Transport contracts stay stable at service boundary.
- Missing display names map to `Unknown Furniture`.
- Negative inventory quantities are clamped to `0`.
## Validation Approach
- Compare pre/post extraction contract examples.

View File

@ -0,0 +1,8 @@
namespace Furniture.Domain.Contracts;
/// <summary>
/// Domain input for furniture availability decisions.
/// </summary>
/// <param name="FurnitureId">Furniture identifier.</param>
/// <param name="CorrelationId">Correlation identifier.</param>
public sealed record FurnitureAvailabilityDecisionRequest(string FurnitureId, string CorrelationId);

View File

@ -0,0 +1,12 @@
namespace Furniture.Domain.Contracts;
/// <summary>
/// Domain output for furniture availability decisions.
/// </summary>
/// <param name="FurnitureId">Furniture identifier.</param>
/// <param name="DisplayName">Furniture display name.</param>
/// <param name="QuantityAvailable">Quantity available.</param>
public sealed record FurnitureAvailabilityDecisionResponse(
string FurnitureId,
string DisplayName,
int QuantityAvailable);

View File

@ -0,0 +1,8 @@
namespace Furniture.Domain.Conventions;
/// <summary>
/// Marker type for furniture-domain package discovery.
/// </summary>
public sealed class FurnitureDomainPackageContract
{
}

View File

@ -0,0 +1,63 @@
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.Domain.Contracts;
namespace Furniture.Domain.Decisions;
/// <summary>
/// Default domain implementation for furniture availability composition decisions.
/// </summary>
public sealed class FurnitureAvailabilityDecisionService : IFurnitureAvailabilityDecisionService
{
private const string ContractVersion = "1.0.0";
/// <inheritdoc />
public InventoryItemLookupRequest BuildInventoryRequest(FurnitureAvailabilityDecisionRequest request)
{
return new InventoryItemLookupRequest(
new InventoryContractEnvelope(ContractVersion, ResolveCorrelationId(request.CorrelationId)),
request.FurnitureId);
}
/// <inheritdoc />
public ProductContract BuildCatalogRequest(FurnitureAvailabilityDecisionRequest request)
{
return new ProductContract(
new CatalogContractEnvelope(ContractVersion, ResolveCorrelationId(request.CorrelationId)),
request.FurnitureId,
string.Empty);
}
/// <inheritdoc />
public FurnitureAvailabilityDecisionResponse ComposeResponse(
FurnitureAvailabilityDecisionRequest request,
ProductContractResponse catalogResponse,
InventoryItemLookupResponse inventoryResponse)
{
var displayName = string.IsNullOrWhiteSpace(catalogResponse.DisplayName)
? "Unknown Furniture"
: catalogResponse.DisplayName;
var quantityAvailable = inventoryResponse.QuantityAvailable < 0
? 0
: inventoryResponse.QuantityAvailable;
return new FurnitureAvailabilityDecisionResponse(
request.FurnitureId,
displayName,
quantityAvailable);
}
private static string ResolveCorrelationId(string correlationId)
{
if (!string.IsNullOrWhiteSpace(correlationId))
{
return correlationId;
}
return $"corr-{Guid.NewGuid():N}";
}
}

View File

@ -0,0 +1,31 @@
using BuildingBlock.Catalog.Contracts.Products;
using BuildingBlock.Catalog.Contracts.Responses;
using BuildingBlock.Inventory.Contracts.Requests;
using BuildingBlock.Inventory.Contracts.Responses;
using Furniture.Domain.Contracts;
namespace Furniture.Domain.Decisions;
/// <summary>
/// Defines domain decision boundary for furniture availability composition.
/// </summary>
public interface IFurnitureAvailabilityDecisionService
{
/// <summary>
/// Creates inventory capability request from furniture availability input.
/// </summary>
InventoryItemLookupRequest BuildInventoryRequest(FurnitureAvailabilityDecisionRequest request);
/// <summary>
/// Creates catalog capability request from furniture availability input.
/// </summary>
ProductContract BuildCatalogRequest(FurnitureAvailabilityDecisionRequest request);
/// <summary>
/// Composes final furniture availability response from capability responses.
/// </summary>
FurnitureAvailabilityDecisionResponse ComposeResponse(
FurnitureAvailabilityDecisionRequest request,
ProductContractResponse catalogResponse,
InventoryItemLookupResponse inventoryResponse);
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<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" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Furniture.Domain\Furniture.Domain.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,92 @@
using BuildingBlock.Catalog.Contracts.Conventions;
using BuildingBlock.Catalog.Contracts.Responses;
using BuildingBlock.Inventory.Contracts.Conventions;
using BuildingBlock.Inventory.Contracts.Responses;
using Furniture.Domain.Contracts;
using Furniture.Domain.Decisions;
namespace Furniture.Domain.UnitTests;
public class FurnitureAvailabilityDecisionServiceTests
{
[Fact]
public void BuildCatalogRequest_WhenCalled_UsesFurnitureIdAsProductId()
{
var service = new FurnitureAvailabilityDecisionService();
var request = service.BuildCatalogRequest(new FurnitureAvailabilityDecisionRequest("FUR-001", "corr-001"));
Assert.Equal("FUR-001", request.ProductId);
Assert.Equal("corr-001", request.Envelope.CorrelationId);
}
[Fact]
public void BuildInventoryRequest_WhenCorrelationMissing_GeneratesCorrelation()
{
var service = new FurnitureAvailabilityDecisionService();
var request = service.BuildInventoryRequest(new FurnitureAvailabilityDecisionRequest("FUR-001", string.Empty));
Assert.Equal("FUR-001", request.ItemCode);
Assert.NotEmpty(request.Envelope.CorrelationId);
}
[Fact]
public void ComposeResponse_WhenCalled_UsesCatalogAndInventoryValues()
{
var service = new FurnitureAvailabilityDecisionService();
var request = new FurnitureAvailabilityDecisionRequest("FUR-001", "corr-001");
var catalog = new ProductContractResponse(
new CatalogContractEnvelope("1.0.0", "corr-001"),
"FUR-001",
"Chair");
var inventory = new InventoryItemLookupResponse(
new InventoryContractEnvelope("1.0.0", "corr-001"),
"FUR-001",
12);
var response = service.ComposeResponse(request, catalog, inventory);
Assert.Equal("FUR-001", response.FurnitureId);
Assert.Equal("Chair", response.DisplayName);
Assert.Equal(12, response.QuantityAvailable);
}
[Fact]
public void ComposeResponse_WhenCatalogDisplayNameMissing_UsesFallbackName()
{
var service = new FurnitureAvailabilityDecisionService();
var request = new FurnitureAvailabilityDecisionRequest("FUR-004", "corr-004");
var catalog = new ProductContractResponse(
new CatalogContractEnvelope("1.0.0", "corr-004"),
"FUR-004",
string.Empty);
var inventory = new InventoryItemLookupResponse(
new InventoryContractEnvelope("1.0.0", "corr-004"),
"FUR-004",
5);
var response = service.ComposeResponse(request, catalog, inventory);
Assert.Equal("Unknown Furniture", response.DisplayName);
}
[Fact]
public void ComposeResponse_WhenInventoryNegative_ClampsQuantityToZero()
{
var service = new FurnitureAvailabilityDecisionService();
var request = new FurnitureAvailabilityDecisionRequest("FUR-005", "corr-005");
var catalog = new ProductContractResponse(
new CatalogContractEnvelope("1.0.0", "corr-005"),
"FUR-005",
"Shelf");
var inventory = new InventoryItemLookupResponse(
new InventoryContractEnvelope("1.0.0", "corr-005"),
"FUR-005",
-3);
var response = service.ComposeResponse(request, catalog, inventory);
Assert.Equal(0, response.QuantityAvailable);
}
}