Skip to main content

The Standard Implementation Guide

Quick Reference for RFC 2003: The Standard Implementation

This guide provides practical examples for implementing Hassan Habib's "The Standard" in the .NET backend.

Quick Start

1. Understand the Five Layers

Exposers (Controllers) → Orchestration → Processing → Foundation → Brokers

2. Follow Business Language Mapping

Technical OperationBusiness Language
InsertAdd
SelectRetrieve
UpdateModify
DeleteRemove

3. Apply the Florance Pattern

Maximum 2-3 dependencies per service

Core Patterns

Brokers Layer

Purpose: Thin abstraction over external dependencies (database, APIs, time, logging)

Rules:

  • Zero business logic
  • No validation
  • No exception classification
  • Only native/primitive types where possible

Database Broker Interface

/// <summary>
/// Low-level persistence contract for invoice aggregates backed by Azure Cosmos DB.
/// </summary>
/// <remarks>
/// <para><b>Role (The Standard):</b> A broker is a thin abstraction over an external
/// dependency. It exposes primitive CRUD operations with minimal translation.</para>
/// </remarks>
public interface IInvoiceNoSqlBroker
{
/// <summary>Persists a new invoice document.</summary>
ValueTask<Invoice> CreateInvoiceAsync(Invoice invoice);

/// <summary>Retrieves a single invoice by identifier.</summary>
ValueTask<Invoice?> ReadInvoiceAsync(Guid invoiceIdentifier);

/// <summary>Lists all non soft-deleted invoices.</summary>
ValueTask<IEnumerable<Invoice>> ReadInvoicesAsync();

/// <summary>Replaces (upserts) an invoice by identifier.</summary>
ValueTask<Invoice?> UpdateInvoiceAsync(Guid invoiceIdentifier, Invoice updatedInvoice);

/// <summary>Soft-deletes an invoice (sets IsSoftDeleted flag).</summary>
ValueTask DeleteInvoiceAsync(Guid invoiceIdentifier);
}

Database Broker Implementation

public sealed partial class InvoiceNoSqlBroker : DbContext, IInvoiceNoSqlBroker
{
private CosmosClient CosmosClient { get; }

public InvoiceNoSqlBroker(
CosmosClient client,
DbContextOptions<InvoiceNoSqlBroker> options)
: base(options)
{
ArgumentNullException.ThrowIfNull(client);
ArgumentNullException.ThrowIfNull(options);
CosmosClient = client;
}

// DbSet properties
public DbSet<Invoice> Invoices { get; set; } = default!;
public DbSet<Merchant> Merchants { get; set; } = default!;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure Cosmos containers, partition keys, JSON mappings
modelBuilder.Entity<Invoice>()
.ToContainer("invoices")
.HasPartitionKey(i => i.UserIdentifier);
}
}

Foundation Services

Purpose: Validation + CRUD operations (broker-neighboring layer)

Rules:

  • Single dependency: one broker
  • Business language method names (Add, Retrieve, Modify, Remove)
  • Structural and logical validation
  • Exception classification (Validation, Dependency, DependencyValidation)

Foundation Service Interface

/// <summary>
/// Foundation (broker-neighboring) service for invoice storage operations.
/// Validates invoice entities and delegates persistence to the database broker.
/// </summary>
/// <remarks>
/// <para><b>The Standard Layer:</b> Foundation. Single dependency on <see cref="IInvoiceNoSqlBroker"/>.
/// Performs structural and logical validation before delegating CRUD to the broker.</para>
/// </remarks>
public interface IInvoiceStorageFoundationService
{
/// <summary>Adds a new invoice to persistent storage after validation.</summary>
ValueTask<Invoice> AddInvoiceAsync(Invoice invoice);

/// <summary>Retrieves a single invoice by identifier.</summary>
ValueTask<Invoice?> RetrieveInvoiceByIdAsync(Guid invoiceId);

/// <summary>Retrieves all invoices.</summary>
ValueTask<IEnumerable<Invoice>> RetrieveAllInvoicesAsync();

/// <summary>Modifies an existing invoice after validation.</summary>
ValueTask<Invoice?> ModifyInvoiceAsync(Invoice invoice);

/// <summary>Removes an invoice from storage (soft delete).</summary>
ValueTask RemoveInvoiceAsync(Guid invoiceId);
}

Foundation Service Implementation

public sealed partial class InvoiceStorageFoundationService : IInvoiceStorageFoundationService
{
private readonly IInvoiceNoSqlBroker invoiceNoSqlBroker;

public InvoiceStorageFoundationService(IInvoiceNoSqlBroker invoiceNoSqlBroker)
{
this.invoiceNoSqlBroker = invoiceNoSqlBroker;
}

public async ValueTask<Invoice> AddInvoiceAsync(Invoice invoice) =>
await TryCatchAsync(async () =>
{
ValidateInvoiceOnAdd(invoice);

return await invoiceNoSqlBroker
.CreateInvoiceAsync(invoice)
.ConfigureAwait(false);
}).ConfigureAwait(false);

public async ValueTask<Invoice?> RetrieveInvoiceByIdAsync(Guid invoiceId) =>
await TryCatchAsync(async () =>
{
ValidateInvoiceId(invoiceId);

return await invoiceNoSqlBroker
.ReadInvoiceAsync(invoiceId)
.ConfigureAwait(false);
}).ConfigureAwait(false);

// Validation methods in partial class: InvoiceStorageFoundationService.Validations.cs
}

Processing Services

Purpose: Higher-order business logic combining multiple operations

Rules:

  • 2-3 dependencies (typically multiple foundation services)
  • Business language method names
  • Exception classification
  • No direct broker access

Processing Service Interface

/// <summary>
/// Processing service for invoice business operations.
/// Combines invoice storage, merchant storage, and analysis services.
/// </summary>
/// <remarks>
/// <para><b>The Standard Layer:</b> Processing. Dependencies on multiple foundation services
/// to perform higher-order invoice operations.</para>
/// </remarks>
public interface IInvoiceProcessingService
{
/// <summary>Creates a complete invoice with merchant and analysis.</summary>
Task CreateInvoice(Invoice invoice, Guid? userIdentifier = null);

/// <summary>Analyzes an invoice with specified options.</summary>
Task AnalyzeInvoice(
InvoiceAnalysisOptions options,
Guid identifier,
Guid? userIdentifier = null);

/// <summary>Retrieves an invoice by identifier.</summary>
Task<Invoice?> RetrieveInvoice(Guid invoiceIdentifier, Guid? userIdentifier = null);

/// <summary>Modifies an existing invoice.</summary>
Task ModifyInvoice(Invoice invoice, Guid? userIdentifier = null);

/// <summary>Removes an invoice.</summary>
Task DeleteInvoice(Guid invoiceIdentifier, Guid? userIdentifier = null);
}

Processing Service Implementation

public sealed partial class InvoiceProcessingService : IInvoiceProcessingService
{
private readonly IInvoiceOrchestrationService invoiceOrchestrationService;

public InvoiceProcessingService(
IInvoiceOrchestrationService invoiceOrchestrationService)
{
this.invoiceOrchestrationService = invoiceOrchestrationService;
}

public async Task CreateInvoice(Invoice invoice, Guid? userIdentifier = null) =>
await TryCatchAsync(async () =>
{
using var activity = InvoicePackageTracing.StartActivity(nameof(CreateInvoice));

await invoiceOrchestrationService
.CreateInvoiceObject(invoice)
.ConfigureAwait(false);
}).ConfigureAwait(false);

public async Task AnalyzeInvoice(
InvoiceAnalysisOptions options,
Guid identifier,
Guid? userIdentifier = null) =>
await TryCatchAsync(async () =>
{
using var activity = InvoicePackageTracing.StartActivity(nameof(AnalyzeInvoice));

await invoiceOrchestrationService
.AnalyzeInvoiceWithOptions(options, identifier, userIdentifier)
.ConfigureAwait(false);
}).ConfigureAwait(false);

// Exception handling in partial class: InvoiceProcessingService.Exceptions.cs
}

Orchestration Services

Purpose: Complex multi-entity workflows and coordination

Rules:

  • 2-3 dependencies (multiple foundation/processing services)
  • Business language method names
  • Coordinate cross-aggregate operations
  • Exception classification

Orchestration Service Interface

/// <summary>
/// Orchestration service coordinating invoice, merchant, and analysis operations.
/// </summary>
/// <remarks>
/// <para><b>The Standard Layer:</b> Orchestration. Coordinates multiple foundation services
/// for complex invoice workflows.</para>
/// </remarks>
public interface IInvoiceOrchestrationService
{
/// <summary>Creates a complete invoice object with all related entities.</summary>
Task<Invoice> CreateInvoiceObject(Invoice invoice);

/// <summary>Retrieves a complete invoice with all related data.</summary>
Task<Invoice?> RetrieveInvoiceObject(Guid invoiceId);

/// <summary>Analyzes an invoice with specified options and enrichment.</summary>
Task AnalyzeInvoiceWithOptions(
InvoiceAnalysisOptions options,
Guid invoiceId,
Guid? userId = null);
}

Orchestration Service Implementation

public sealed partial class InvoiceOrchestrationService : IInvoiceOrchestrationService
{
private readonly IInvoiceStorageFoundationService invoiceStorageFoundationService;
private readonly IMerchantStorageFoundationService merchantStorageFoundationService;
private readonly IInvoiceAnalysisFoundationService invoiceAnalysisFoundationService;

public InvoiceOrchestrationService(
IInvoiceStorageFoundationService invoiceStorageFoundationService,
IMerchantStorageFoundationService merchantStorageFoundationService,
IInvoiceAnalysisFoundationService invoiceAnalysisFoundationService)
{
this.invoiceStorageFoundationService = invoiceStorageFoundationService;
this.merchantStorageFoundationService = merchantStorageFoundationService;
this.invoiceAnalysisFoundationService = invoiceAnalysisFoundationService;
}

public async Task<Invoice> CreateInvoiceObject(Invoice invoice) =>
await TryCatchAsync(async () =>
{
// Step 1: Ensure merchant exists
if (invoice.Merchant is not null)
{
var existingMerchant = await merchantStorageFoundationService
.RetrieveMerchantByIdAsync(invoice.MerchantId)
.ConfigureAwait(false);

if (existingMerchant is null)
{
await merchantStorageFoundationService
.AddMerchantAsync(invoice.Merchant)
.ConfigureAwait(false);
}
}

// Step 2: Create invoice
return await invoiceStorageFoundationService
.AddInvoiceAsync(invoice)
.ConfigureAwait(false);
}).ConfigureAwait(false);
}

Exposers Layer

Purpose: Protocol mapping (HTTP, gRPC) to service calls

Rules:

  • Minimal API endpoints (ASP.NET Core)
  • Business language route names
  • Single dependency: one processing/orchestration service
  • Exception to HTTP status code mapping

Minimal API Endpoint

internal static partial class InvoiceEndpoints
{
internal static async partial Task<IResult> CreateNewInvoiceAsync(
[FromServices] IInvoiceProcessingService invoiceProcessingService,
[FromBody] InvoiceDto invoiceDto)
{
try
{
using var activity = InvoicePackageTracing.StartActivity(
nameof(CreateNewInvoiceAsync),
ActivityKind.Server
);

var invoice = invoiceDto.ToInvoice();

await invoiceProcessingService
.CreateInvoice(invoice)
.ConfigureAwait(false);

return TypedResults.Created($"/rest/v1/invoices/{invoice.Id}", invoice);
}
catch (InvoiceProcessingServiceValidationException exception)
{
return TypedResults.Problem(
detail: exception.Message,
statusCode: StatusCodes.Status400BadRequest,
title: "Validation error"
);
}
catch (InvoiceProcessingServiceDependencyException exception)
{
return TypedResults.Problem(
detail: exception.Message,
statusCode: StatusCodes.Status500InternalServerError,
title: "Dependency error"
);
}
}
}

Exception Classification

Three Exception Types

Exception TypeMeaningHTTP Status
ValidationExceptionInvalid input from caller400 Bad Request
DependencyExceptionExternal dependency failed500 Internal Server Error
DependencyValidationExceptionDependency rejected input424 Failed Dependency

Exception Hierarchy

// Foundation Service Exceptions
public class InvoiceStorageFoundationServiceValidationException : Exception { }
public class InvoiceStorageFoundationServiceDependencyException : Exception { }
public class InvoiceStorageFoundationServiceDependencyValidationException : Exception { }

// Processing Service Exceptions
public class InvoiceProcessingServiceValidationException : Exception { }
public class InvoiceProcessingServiceDependencyException : Exception { }
public class InvoiceProcessingServiceDependencyValidationException : Exception { }

Try-Catch Pattern

private async ValueTask<Invoice> TryCatchAsync(
ReturningInvoiceFunction returningInvoiceFunction)
{
try
{
return await returningInvoiceFunction().ConfigureAwait(false);
}
catch (NullInvoiceException nullInvoiceException)
{
throw CreateAndLogValidationException(nullInvoiceException);
}
catch (InvalidInvoiceException invalidInvoiceException)
{
throw CreateAndLogValidationException(invalidInvoiceException);
}
catch (CosmosException cosmosException)
{
throw CreateAndLogDependencyException(cosmosException);
}
catch (Exception exception)
{
throw CreateAndLogServiceException(exception);
}
}

Validation Strategy

Structural Validation

Check: Object nullability, required properties

private static void ValidateInvoiceOnAdd(Invoice invoice)
{
ValidateInvoiceIsNotNull(invoice);

Validate(
(Rule: IsInvalid(invoice.Id), Parameter: nameof(Invoice.Id)),
(Rule: IsInvalid(invoice.Name), Parameter: nameof(Invoice.Name)),
(Rule: IsInvalid(invoice.MerchantId), Parameter: nameof(Invoice.MerchantId)),
(Rule: IsInvalid(invoice.CreatedDate), Parameter: nameof(Invoice.CreatedDate))
);
}

private static dynamic IsInvalid(Guid id) => new
{
Condition = id == Guid.Empty,
Message = "Id is required"
};

private static dynamic IsInvalid(string text) => new
{
Condition = string.IsNullOrWhiteSpace(text),
Message = "Text is required"
};

Logical Validation

Check: Business rules, domain invariants

private static void ValidateInvoiceOnModify(Invoice invoice)
{
ValidateInvoiceIsNotNull(invoice);

Validate(
(Rule: IsInvalid(invoice.Id), Parameter: nameof(Invoice.Id)),
(Rule: IsInvalid(invoice.UpdatedDate), Parameter: nameof(Invoice.UpdatedDate)),
(Rule: IsNotRecent(invoice.UpdatedDate), Parameter: nameof(Invoice.UpdatedDate)),
(Rule: IsSame(
firstDate: invoice.UpdatedDate,
secondDate: invoice.CreatedDate,
secondDateName: nameof(Invoice.CreatedDate)),
Parameter: nameof(Invoice.UpdatedDate))
);
}

private static dynamic IsNotRecent(DateTimeOffset date) => new
{
Condition = IsDateNotRecent(date),
Message = $"Date is not recent. It was {date}"
};

Dependency Injection Setup

Service Registration by Layer

// Program.cs or extension methods

// Brokers
builder.Services.AddDbContext<IInvoiceNoSqlBroker, InvoiceNoSqlBroker>();
builder.Services.AddSingleton<IInvoiceAnalysisBroker, InvoiceAnalysisBroker>();

// Foundation Services
builder.Services.AddScoped<IInvoiceStorageFoundationService, InvoiceStorageFoundationService>();
builder.Services.AddScoped<IMerchantStorageFoundationService, MerchantStorageFoundationService>();
builder.Services.AddScoped<IInvoiceAnalysisFoundationService, InvoiceAnalysisFoundationService>();

// Processing Services
builder.Services.AddScoped<IInvoiceProcessingService, InvoiceProcessingService>();

// Orchestration Services
builder.Services.AddScoped<IInvoiceOrchestrationService, InvoiceOrchestrationService>();
builder.Services.AddScoped<IMerchantOrchestrationService, MerchantOrchestrationService>();

Best Practices

✅ Do: Use Business Language

// ✅ Good - business language
public interface IInvoiceStorageFoundationService
{
ValueTask<Invoice> AddInvoiceAsync(Invoice invoice);
ValueTask<Invoice?> RetrieveInvoiceByIdAsync(Guid id);
ValueTask<Invoice?> ModifyInvoiceAsync(Invoice invoice);
ValueTask RemoveInvoiceAsync(Guid id);
}

// ❌ Bad - technical language
public interface IInvoiceStorageFoundationService
{
ValueTask<Invoice> InsertInvoiceAsync(Invoice invoice);
ValueTask<Invoice?> SelectInvoiceByIdAsync(Guid id);
ValueTask<Invoice?> UpdateInvoiceAsync(Invoice invoice);
ValueTask DeleteInvoiceAsync(Guid id);
}

✅ Do: Follow the Florance Pattern

// ✅ Good - 1 dependency (Foundation Service)
public class InvoiceStorageFoundationService
{
private readonly IInvoiceNoSqlBroker invoiceNoSqlBroker;

public InvoiceStorageFoundationService(IInvoiceNoSqlBroker invoiceNoSqlBroker)
{
this.invoiceNoSqlBroker = invoiceNoSqlBroker;
}
}

// ✅ Good - 3 dependencies (Orchestration Service)
public class InvoiceOrchestrationService
{
private readonly IInvoiceStorageFoundationService invoiceStorageFoundationService;
private readonly IMerchantStorageFoundationService merchantStorageFoundationService;
private readonly IInvoiceAnalysisFoundationService invoiceAnalysisFoundationService;
}

// ❌ Bad - too many dependencies
public class InvoiceService
{
private readonly IBroker1 broker1;
private readonly IBroker2 broker2;
private readonly IService1 service1;
private readonly IService2 service2;
private readonly IService3 service3;
// ... 5+ dependencies violates Florance Pattern
}

✅ Do: Pure Contracting

// ✅ Good - interface-driven
public interface IInvoiceProcessingService
{
Task CreateInvoice(Invoice invoice);
}

public class InvoiceProcessingService : IInvoiceProcessingService
{
public async Task CreateInvoice(Invoice invoice) { }
}

// ❌ Bad - concrete class without interface
public class InvoiceProcessingService
{
public async Task CreateInvoice(Invoice invoice) { }
}

✅ Do: Validate at Every Layer

// ✅ Good - each layer validates
public class InvoiceStorageFoundationService
{
public async ValueTask<Invoice> AddInvoiceAsync(Invoice invoice)
{
ValidateInvoiceOnAdd(invoice); // ← Foundation validates
return await broker.CreateInvoiceAsync(invoice);
}
}

public class InvoiceProcessingService
{
public async Task CreateInvoice(Invoice invoice)
{
ValidateInvoiceForCreation(invoice); // ← Processing validates
await foundationService.AddInvoiceAsync(invoice);
}
}

// ❌ Bad - only top layer validates
public class InvoiceProcessingService
{
public async Task CreateInvoice(Invoice invoice)
{
ValidateInvoice(invoice);
await foundationService.AddInvoiceAsync(invoice); // ← No validation
}
}

❌ Don't: Put Business Logic in Brokers

// ❌ Bad - broker contains validation
public class InvoiceNoSqlBroker : IInvoiceNoSqlBroker
{
public async ValueTask<Invoice> CreateInvoiceAsync(Invoice invoice)
{
if (invoice.TotalAmount < 0) // ← Business logic in broker!
throw new InvalidOperationException();

return await dbContext.Invoices.AddAsync(invoice);
}
}

// ✅ Good - broker is thin
public class InvoiceNoSqlBroker : IInvoiceNoSqlBroker
{
public async ValueTask<Invoice> CreateInvoiceAsync(Invoice invoice)
{
return await dbContext.Invoices.AddAsync(invoice);
}
}

❌ Don't: Skip Layers

// ❌ Bad - processing service directly calls broker
public class InvoiceProcessingService
{
private readonly IInvoiceNoSqlBroker broker; // ← Should not inject broker

public async Task CreateInvoice(Invoice invoice)
{
await broker.CreateInvoiceAsync(invoice); // ← Skips foundation layer
}
}

// ✅ Good - processing service calls foundation service
public class InvoiceProcessingService
{
private readonly IInvoiceStorageFoundationService foundationService;

public async Task CreateInvoice(Invoice invoice)
{
await foundationService.AddInvoiceAsync(invoice);
}
}

Testing Patterns

Foundation Service Tests

[Fact]
public async Task ShouldAddInvoiceAsync()
{
// given
Invoice randomInvoice = CreateRandomInvoice();
Invoice inputInvoice = randomInvoice;
Invoice expectedInvoice = inputInvoice;

this.invoiceNoSqlBrokerMock.Setup(broker =>
broker.CreateInvoiceAsync(inputInvoice))
.ReturnsAsync(expectedInvoice);

// when
Invoice actualInvoice = await this.invoiceStorageFoundationService
.AddInvoiceAsync(inputInvoice);

// then
actualInvoice.Should().BeEquivalentTo(expectedInvoice);

this.invoiceNoSqlBrokerMock.Verify(broker =>
broker.CreateInvoiceAsync(inputInvoice),
Times.Once);
}

Exception Classification Tests

[Fact]
public async Task ShouldThrowValidationExceptionOnAddIfInvoiceIsNullAndLogItAsync()
{
// given
Invoice nullInvoice = null;

var nullInvoiceException = new NullInvoiceException();

var expectedInvoiceStorageFoundationServiceValidationException =
new InvoiceStorageFoundationServiceValidationException(nullInvoiceException);

// when
ValueTask<Invoice> addInvoiceTask =
this.invoiceStorageFoundationService.AddInvoiceAsync(nullInvoice);

// then
await Assert.ThrowsAsync<InvoiceStorageFoundationServiceValidationException>(
() => addInvoiceTask.AsTask());
}

Quick Reference

LayerPurposeDependenciesMethod Prefix
BrokersExternal dependency abstractionNoneCreate/Read/Update/Delete
FoundationValidation + CRUD1 brokerAdd/Retrieve/Modify/Remove
ProcessingHigher-order business logic2-3 foundation servicesBusiness verbs
OrchestrationMulti-entity coordination2-3 foundation/processingBusiness verbs
ExposersProtocol mapping1 processing/orchestrationHTTP verbs

Additional Resources

// was this page useful?