Skip to content

Forma.Core

Forma.Core is the foundation package that provides all core abstractions and interfaces for the Forma library. It has zero external dependencies and defines the contracts that all other packages implement.

NuGet

Installation

bash
dotnet add package Forma.Core

Overview

Forma.Core provides the fundamental building blocks used across all Forma packages. It defines the contracts (interfaces) that your application code depends on, keeping your domain logic independent of any concrete implementation.

In addition to core abstractions for request handling, Forma.Core includes functional programming primitives (Result<T> / Error and Option<T>) for safe, composable error handling and optional value management.

Key Abstractions

IRequest

Marker interface for a request that produces no return value (command).

csharp
using Forma.Core.Abstractions;

// A command that does not return a value
public record CreateUserCommand(string Name, string Email) : IRequest;

IRequest<TResponse>

Marker interface for a request that produces a typed return value (query or command-with-response).

csharp
// A query that returns a UserDto
public record GetUserQuery(int UserId) : IRequest<UserDto>;
public record UserDto(int Id, string Name, string Email);

// A command that returns the created entity's ID
public record CreateOrderCommand(string ProductName, int Quantity) : IRequest<int>;

IHandler<TRequest>

Handler for a request with no return value.

csharp
using Forma.Abstractions;

public class CreateUserCommandHandler : IHandler<CreateUserCommand>
{
    public Task HandleAsync(CreateUserCommand request, CancellationToken cancellationToken = default)
    {
        // Implement command logic here
        Console.WriteLine($"Creating user: {request.Name}");
        return Task.CompletedTask;
    }
}

IHandler<TRequest, TResponse>

Handler for a request that returns a value.

csharp
public class GetUserQueryHandler : IHandler<GetUserQuery, UserDto>
{
    public Task<UserDto> HandleAsync(GetUserQuery request, CancellationToken cancellationToken = default)
    {
        // Fetch and return the user
        var user = new UserDto(request.UserId, "John Doe", "john@example.com");
        return Task.FromResult(user);
    }
}

IPipelineBehavior<TRequest, TResponse>

Wraps handler execution to inject cross-cutting concerns (logging, validation, caching, etc.).

csharp
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
        => _logger = logger;

    public async Task<TResponse> HandleAsync(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Handling {RequestType}", typeof(TRequest).Name);
        var response = await next(cancellationToken);
        _logger.LogInformation("Handled {RequestType}", typeof(TRequest).Name);
        return response;
    }
}

IRequestPreProcessor<TRequest>

Runs before the handler for a specific request type.

csharp
public class ValidationPreProcessor : IRequestPreProcessor<CreateUserCommand>
{
    public Task ProcessAsync(CreateUserCommand request, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.Name))
            throw new ArgumentException("Name is required.");
        if (string.IsNullOrWhiteSpace(request.Email))
            throw new ArgumentException("Email is required.");
        return Task.CompletedTask;
    }
}

IRequestPostProcessor<TRequest, TResponse>

Runs after the handler for a specific request type.

csharp
public class AuditPostProcessor : IRequestPostProcessor<CreateUserCommand, Unit>
{
    public Task ProcessAsync(
        CreateUserCommand request,
        Unit response,
        CancellationToken cancellationToken)
    {
        Console.WriteLine($"Audit: user '{request.Name}' was created.");
        return Task.CompletedTask;
    }
}

IRequestMediator

The mediator contract used to dispatch requests.

csharp
// Injected into your services/controllers
public class UserController
{
    private readonly IRequestMediator _mediator;

    public UserController(IRequestMediator mediator)
        => _mediator = mediator;

    public async Task<UserDto> GetUser(int id)
        => await _mediator.SendAsync(new GetUserQuery(id));
}

Functional Programming Primitives

Forma.Core includes functional programming types in the Forma.Core.FP namespace:

Result<T>

Represents an operation that can either succeed with a value or fail with an error. Enables railway-oriented programming with explicit error handling.

csharp
using Forma.Core.FP;

public Result<int> Divide(int a, int b)
{
    if (b == 0)
        return Result<int>.Failure(Error.BusinessRule("DivideByZero", "Division by zero"));
    return Result<int>.Success(a / b);
}

// Chain operations
var result = Divide(10, 2)
    .Then(x => Result<int>.Success(x * 2))
    .Match(
        onSuccess: x => $"Result: {x}",
        onFailure: e => $"Error: {e.Message}"
    );

Option<T>

Represents a value that may or may not exist — a safer alternative to nullable types.

csharp
using Forma.Core.FP;

public Option<User> FindUser(int id)
{
    var user = _repository.GetById(id);
    return Option<User>.From(user); // Some if not null, None otherwise
}

// Chain operations
var greeting = FindUser(123)
    .Then(u => Option<string>.Some(u.Name))
    .Match(
        some: name => $"Hello, {name}!",
        none: () => "User not found"
    );

For complete documentation, see Forma.Core.FP.

Design Philosophy

  • Interface-first — your application code depends only on abstractions from Forma.Core, never on implementation details from Forma.Mediator, Forma.Decorator, etc.
  • Zero dependenciesForma.Core references no external NuGet packages, keeping your domain model lightweight.
  • Composable — combine any number of Forma packages without conflict; they all share the same core contracts.
PackageWhat it adds
Forma.Core (FP primitives)Functional programming primitives docs: Result<T> and Option<T>
Forma.MediatorIRequestMediator implementation + DI registration
Forma.DecoratorDecorate<TService, TDecorator>() extension for DI
Forma.ChainsIChainInvoker<T> + chain handler pipeline
Forma.PubSub.InMemoryIn-memory publish-subscribe messaging

Released under the MIT License.