Skip to main content

Cross-Cutting Concepts Overview

This section describes the cross-cutting concepts that are implemented across all services in the BookWorm application. These concepts ensure consistency, maintainability, and operational excellence throughout the system.

Architecture Components

The BookWorm application implements cross-cutting concerns through two main components:

BookWorm.Chassis

A comprehensive building block library that provides core functionality for all microservices, including:

  • CQRS Pattern Implementation - Command and Query separation with Mediator
  • Event-Driven Architecture - Integration events with MassTransit and RabbitMQ
  • Database Access - Repository pattern with Entity Framework Core and PostgreSQL
  • Domain-Driven Design - Specifications, guards, and domain abstractions
  • AI Integration - Agent Framework, Ollama, and RAG capabilities
  • Exception Handling - Centralized error management and custom exceptions
  • API Endpoints - Minimal APIs with automatic registration and versioning
  • Data Serialization - JSON converters and type transformation
  • Guards & Defensive Programming - Input validation and security guards
  • Repository & Specifications - Data access abstraction with query composition

BookWorm.ServiceDefaults

Aspire-based service defaults that standardize common configurations:

  • Authentication & Authorization - Keycloak integration with JWT tokens
  • Service Discovery - Aspire service discovery for microservice communication
  • Health Checks - Comprehensive health monitoring endpoints
  • OpenAPI Documentation - Automated API specification generation
  • Telemetry - OpenTelemetry integration for observability
  • Rate Limiting & CORS - API protection and cross-origin resource sharing
  • HTTP Client Configuration - Resilient HTTP communication patterns

Testing Strategy (TUnit Framework)

BookWorm uses TUnit (v1.11.45), a modern .NET testing framework integrated with the Microsoft Testing Platform, providing superior performance and developer experience compared to traditional frameworks (xUnit/NUnit/MSTest).

Why TUnit?

  • Source Generator-Based: Compile-time test discovery and execution for better performance
  • Microsoft Testing Platform Integration: Native integration with Visual Studio Test Explorer and CLI
  • Modern Async/Await: First-class async support without blocking
  • Enhanced Assertions: Shouldly library for fluent, readable assertions
  • Better IDE Integration: Improved test discovery and debugging

Test Project Structure

tests/
├── BookWorm.{Service}.UnitTests/ # TUnit-based unit tests
├── BookWorm.{Service}.ContractTests/ # Verify.TUnit snapshot tests
├── BookWorm.{Service}.IntegrationTests/ # Service integration tests
└── BookWorm.ArchTests/ # TngTech.ArchUnitNET.TUnit architecture tests

Testing Stack

ToolPurposeVersion
TUnitTest framework with Microsoft Testing Platform1.11.45
MoqMocking library for dependencies4.20.72
BogusFake data generation for test scenarios35.6.5
ShouldlyFluent assertion library4.3.0
Verify.TUnitSnapshot/contract testing31.9.4
TngTech.ArchUnitNET.TUnitArchitecture rule enforcement0.13.1

Microsoft Testing Platform Extensions

Integrated extensions for comprehensive test execution and diagnostics:

  • Microsoft.Testing.Extensions.CodeCoverage: Code coverage reporting with metrics
  • Microsoft.Testing.Extensions.TrxReport: TRX format reports for CI/CD pipelines
  • Microsoft.Testing.Extensions.CrashDump: Automatic crash dump collection for failures
  • Microsoft.Testing.Extensions.HangDump: Hang dump collection for hanging tests

Test Types

Unit Tests (*.UnitTests):

[Test]
public async Task CreateBook_WithValidData_ShouldSucceed()
{
// Arrange
var book = _faker.CreateBook();
_repositoryMock.Setup(x => x.AddAsync(book)).Returns(Task.CompletedTask);

// Act
await _sut.CreateBookAsync(book);

// Assert
_repositoryMock.Verify(x => x.AddAsync(book), Times.Once);
book.CreatedAt.ShouldNotBeNull();
}

Contract Tests (*.ContractTests):

[Test]
public async Task BookCreatedEvent_ShouldMatchSnapshot()
{
// Arrange
var @event = new BookCreatedEvent(BookId: Guid.NewGuid(), Title: "Test Book");

// Act & Assert
await Verify(@event).UseDirectory("Snapshots");
}

Architecture Tests (*.ArchTests):

[Test]
public void Services_ShouldNotDependOnOtherServices()
{
var rule = ArchRuleDefinition.Types()
.That().ResideInNamespace("BookWorm.Catalog")
.Should().NotDependOnAny("BookWorm.Ordering", "BookWorm.Basket");

rule.Check(Architecture);
}

CQRS Pattern with Mediator

BookWorm implements Command Query Responsibility Segregation (CQRS) using Mediator.SourceGenerator (v3.0.1), a source generator-based library that provides compile-time command/query handling.

Why Mediator over Mediator?

  • Source Generator: No runtime reflection, all wiring generated at compile-time
  • Performance: Zero runtime overhead compared to Mediator's reflection-based approach
  • Type Safety: Compile-time errors for missing handlers
  • Smaller Footprint: Minimal dependencies and assembly size

Implementation Pattern

Command Example:

public record CreateBookCommand(string Title, string Author) : ICommand<BookId>;

public class CreateBookCommandHandler : ICommandHandler<CreateBookCommand, BookId>
{
private readonly IBookRepository _repository;

public CreateBookCommandHandler(IBookRepository repository)
=> _repository = repository;

public async ValueTask<BookId> Handle(CreateBookCommand command, CancellationToken ct)
{
var book = new Book(command.Title, command.Author);
await _repository.AddAsync(book, ct);
return book.Id;
}
}

Query Example:

public record GetBookByIdQuery(BookId Id) : IQuery<BookDto>;

public class GetBookByIdQueryHandler : IQueryHandler<GetBookByIdQuery, BookDto>
{
private readonly IBookRepository _repository;

public GetBookByIdQueryHandler(IBookRepository repository)
=> _repository = repository;

public async ValueTask<BookDto> Handle(GetBookByIdQuery query, CancellationToken ct)
{
var book = await _repository.GetByIdAsync(query.Id, ct);
return book.ToDto();
}
}

Registration

services.AddMediator(); // Source generator auto-discovers handlers

Hybrid Caching Strategy

BookWorm uses Microsoft.Extensions.Caching.Hybrid for a two-level (L1 + L2) caching strategy, combining in-memory and distributed caching for optimal performance.

Why Hybrid Caching?

  • L1 (In-Memory): Fast local cache with microsecond access times
  • L2 (Redis): Distributed cache shared across service instances
  • Automatic Coordination: Hybrid cache manages L1/L2 synchronization automatically
  • Stampede Protection: Built-in protection against cache stampede scenarios
  • Serialization: Efficient binary serialization for distributed cache

Implementation

Configuration (via BookWorm.Chassis):

builder.Services.AddHybridCache(options =>
{
options.MaximumPayloadBytes = 1024 * 1024; // 1MB max cache entry
options.MaximumKeyLength = 1024;
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(5),
LocalCacheExpiration = TimeSpan.FromMinutes(1)
};
});

Usage Example:

public class BookService
{
private readonly HybridCache _cache;
private readonly IBookRepository _repository;

public async Task<Book> GetBookAsync(BookId id, CancellationToken ct)
{
return await _cache.GetOrCreateAsync(
$"book:{id}",
async cancel => await _repository.GetByIdAsync(id, cancel),
cancellationToken: ct
);
}
}

Caching Strategy

ScenarioL1 (Memory)L2 (Redis)TTL
Hot Data (Books)5 minutes
Session Data (Basket)30 minutes
Static Data (Config)1 hour
Search Results2 minutes

Cache Invalidation

Event-Driven Invalidation:

public class BookUpdatedEventHandler : IConsumer<BookUpdatedEvent>
{
private readonly HybridCache _cache;

public async Task Consume(ConsumeContext<BookUpdatedEvent> context)
{
await _cache.RemoveAsync($"book:{context.Message.BookId}");
}
}

AI Integration (Azure OpenAI)

BookWorm integrates Azure OpenAI for AI-powered features using Microsoft Semantic Kernel and Microsoft Agents AI Framework.

AI Stack

  • Provider: Azure OpenAI Service (enterprise-grade)
  • Models:
    • GPT-4o-mini: Chat completions, content generation
    • text-embedding-3-large: Semantic embeddings (3072 dimensions)
  • Framework: Microsoft Semantic Kernel for orchestration
  • Agent Framework: Microsoft Agents AI Framework with A2A Protocol
  • Tool Protocol: Model Context Protocol (MCP) via BookWorm.McpTools

Integration Pattern

Services consuming AI capabilities (Chat, Rating, Catalog) reference Azure OpenAI via Aspire service discovery and use Semantic Kernel for orchestration.

MCP Integration:

  • BookWorm.McpTools exposes service operations as standardized AI agent tools
  • Chat and Rating services consume MCP tools for agent workflows
  • A2A Protocol enables agent-to-agent communication between services