Skip to content

Port and Adapter Specification

This is the API specification for Port interfaces, Adapter implementation base classes, the Specification pattern, DI registration, and source generator attributes provided by the Functorium framework. For design principles and implementation guides, see Port Architecture and Definitions and Adapter Implementation.

TypeNamespaceDescription
IObservablePortFunctorium.Abstractions.ObservabilitiesBase interface for all Ports/Adapters (provides Observability category)
IRepository<TAggregate, TId>Functorium.Domains.RepositoriesAggregate Root level Repository CRUD contract
IQueryPortFunctorium.Applications.QueriesNon-generic QueryPort marker interface
IQueryPort<TEntity, TDto>Functorium.Applications.QueriesSpecification-based query + pagination contract
PageRequestFunctorium.Applications.QueriesOffset-based pagination request
PagedResult<T>Functorium.Applications.QueriesOffset-based pagination result
CursorPageRequestFunctorium.Applications.QueriesKeyset(Cursor)-based pagination request
CursorPagedResult<T>Functorium.Applications.QueriesKeyset(Cursor)-based pagination result
SortExpressionFunctorium.Applications.QueriesMulti-field sort expression
SortFieldFunctorium.Applications.QueriesSort field + direction pair
SortDirectionFunctorium.Applications.QueriesSort direction SmartEnum (Ascending, Descending)
Specification<T>Functorium.Domains.SpecificationsSpecification pattern abstract base class
ExpressionSpecification<T>Functorium.Domains.SpecificationsExpression Tree-based Specification abstract class
IExpressionSpec<T>Functorium.Domains.SpecificationsInterface indicating Expression Tree provision capability
PropertyMap<TEntity, TModel>Functorium.Domains.Specifications.ExpressionsEntity-Model Expression auto-conversion property mapping
SpecificationExpressionResolverFunctorium.Domains.Specifications.ExpressionsUtility for extracting/composing Expression Trees from Specifications
EfCoreRepositoryBase<TAggregate, TId, TModel>Functorium.Adapters.RepositoriesEF Core Repository common base class
InMemoryRepositoryBase<TAggregate, TId>Functorium.Adapters.RepositoriesInMemory Repository common base class
DapperQueryBase<TEntity, TDto>Functorium.Adapters.RepositoriesDapper-based QueryAdapter common base class
InMemoryQueryBase<TEntity, TDto>Functorium.Adapters.RepositoriesInMemory QueryAdapter common base class
DapperSpecTranslator<TEntity>Functorium.Adapters.RepositoriesSpecification to SQL WHERE clause translation registry
IHasStringIdFunctorium.Adapters.RepositoriesCommon string Id interface for EF Core models
ObservablePortRegistrationFunctorium.Abstractions.RegistrationsObservable Port DI registration extension methods
OptionsConfiguratorFunctorium.Adapters.Abstractions.OptionsFluentValidation-based options validation registration
GenerateObservablePortAttributeFunctorium.Adapters.SourceGeneratorsObservable wrapper auto-generation attribute
ObservablePortIgnoreAttributeFunctorium.Adapters.SourceGeneratorsObservable wrapper generation exclusion attribute

The base interface implemented by all Ports and Adapters. Identifies the request category in the Observability layer through the RequestCategory property.

namespace Functorium.Abstractions.Observabilities;
public interface IObservablePort
{
string RequestCategory { get; }
}
PropertyTypeDescription
RequestCategorystringCategory used in observability logs/metrics (e.g., "Repository", "ExternalApi", "Messaging")
IObservablePort
├── IRepository<TAggregate, TId> — Aggregate Root CRUD (Domain Layer)
├── IQueryPort — Non-generic marker (Application Layer)
│ └── IQueryPort<TEntity, TDto> — Specification-based query (Application Layer)
└── (User-defined Port) — External API, Messaging, etc.

Inheriting IObservablePort enables the [GenerateObservablePort] source generator to auto-generate Tracing, Logging, and Metrics.


Repository Contract (IRepository<TAggregate, TId>)

Section titled “Repository Contract (IRepository<TAggregate, TId>)”

A persistence contract at the Aggregate Root level. Generic constraints enforce Aggregate Root-level persistence at compile time.

namespace Functorium.Domains.Repositories;
public interface IRepository<TAggregate, TId> : IObservablePort
where TAggregate : AggregateRoot<TId>
where TId : struct, IEntityId<TId>
{
FinT<IO, TAggregate> Create(TAggregate aggregate);
FinT<IO, TAggregate> GetById(TId id);
FinT<IO, TAggregate> Update(TAggregate aggregate);
FinT<IO, int> Delete(TId id);
FinT<IO, Seq<TAggregate>> CreateRange(IReadOnlyList<TAggregate> aggregates);
FinT<IO, Seq<TAggregate>> GetByIds(IReadOnlyList<TId> ids);
FinT<IO, Seq<TAggregate>> UpdateRange(IReadOnlyList<TAggregate> aggregates);
FinT<IO, int> DeleteRange(IReadOnlyList<TId> ids);
}
Type ParameterConstraintDescription
TAggregateAggregateRoot<TId>Only Aggregate Roots can be Repository targets (prevents direct Entity persistence)
TIdstruct, IEntityId<TId>Ulid-based EntityId implementation type
MethodReturn TypeDescription
Create(aggregate)FinT<IO, TAggregate>Creates a single Aggregate
GetById(id)FinT<IO, TAggregate>Retrieves an Aggregate by ID (NotFound error if absent)
Update(aggregate)FinT<IO, TAggregate>Updates a single Aggregate
Delete(id)FinT<IO, int>Deletes an Aggregate by ID (returns deleted count)
CreateRange(aggregates)FinT<IO, Seq<TAggregate>>Batch creation
GetByIds(ids)FinT<IO, Seq<TAggregate>>Batch retrieval (PartialNotFound error if some are missing)
UpdateRange(aggregates)FinT<IO, Seq<TAggregate>>Batch update
DeleteRange(ids)FinT<IO, int>Batch deletion (returns deleted count)

All methods return FinT<IO, T>. Success is expressed as Fin.Succ(value), and failure is expressed as domain/adapter errors.


QueryPort Contract (IQueryPort<TEntity, TDto>)

Section titled “QueryPort Contract (IQueryPort<TEntity, TDto>)”

A read-only port for Specification-based queries and direct DTO returns. Corresponds to the Read model in CQRS.

namespace Functorium.Applications.Queries;
public interface IQueryPort : IObservablePort { }

A non-generic marker interface used for runtime type checking, DI scanning, and generic constraints.

public interface IQueryPort<TEntity, TDto> : IQueryPort
{
FinT<IO, PagedResult<TDto>> Search(
Specification<TEntity> spec,
PageRequest page,
SortExpression sort);
FinT<IO, CursorPagedResult<TDto>> SearchByCursor(
Specification<TEntity> spec,
CursorPageRequest cursor,
SortExpression sort);
IAsyncEnumerable<TDto> Stream(
Specification<TEntity> spec,
SortExpression sort,
CancellationToken cancellationToken = default);
}
MethodReturn TypeDescription
Search(spec, page, sort)FinT<IO, PagedResult<TDto>>Offset-based pagination search
SearchByCursor(spec, cursor, sort)FinT<IO, CursorPagedResult<TDto>>Keyset(Cursor)-based pagination search. O(1) performance for deep pages
Stream(spec, sort, ct)IAsyncEnumerable<TDto>Streaming query for large data. Yields without loading everything into memory
Type ParameterDescription
TEntityDomain entity type (Specification target)
TDtoReturn DTO type (directly returned to presentation layer)

An offset-based pagination request. This is an Application-level query concern, not a domain invariant.

namespace Functorium.Applications.Queries;
public sealed record PageRequest
{
public const int DefaultPageSize = 20;
public const int MaxPageSize = 10_000;
public int Page { get; }
public int PageSize { get; }
public int Skip => (Page - 1) * PageSize;
public PageRequest(int page = 1, int pageSize = DefaultPageSize);
}
Property/ConstantTypeDescription
DefaultPageSizeintDefault page size (20)
MaxPageSizeintMaximum page size (10,000)
PageintCurrent page number (corrected to 1 if less than 1)
PageSizeintPage size (corrected to DefaultPageSize if less than 1, capped at MaxPageSize)
SkipintNumber of items to skip (computed property: (Page - 1) * PageSize)

An offset-based pagination result container.

public sealed record PagedResult<T>(
IReadOnlyList<T> Items,
int TotalCount,
int Page,
int PageSize)
{
public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize);
public bool HasPreviousPage => Page > 1;
public bool HasNextPage => Page < TotalPages;
}
PropertyTypeDescription
ItemsIReadOnlyList<T>List of items for the current page
TotalCountintTotal number of items
PageintCurrent page number
PageSizeintPage size
TotalPagesintTotal number of pages (computed property)
HasPreviousPageboolWhether a previous page exists
HasNextPageboolWhether a next page exists

A keyset(cursor)-based pagination request. Provides O(1) performance for deep pages compared to offset-based pagination.

public sealed record CursorPageRequest
{
public const int DefaultPageSize = 20;
public const int MaxPageSize = 10_000;
public string? After { get; }
public string? Before { get; }
public int PageSize { get; }
public CursorPageRequest(
string? after = null,
string? before = null,
int pageSize = DefaultPageSize);
}
Property/ConstantTypeDescription
DefaultPageSizeintDefault page size (20)
MaxPageSizeintMaximum page size (10,000)
Afterstring?Retrieve items after this cursor (forward pagination)
Beforestring?Retrieve items before this cursor (backward pagination)
PageSizeintPage size (corrected to DefaultPageSize if less than 1, capped at MaxPageSize)

A keyset(cursor)-based pagination result container.

public sealed record CursorPagedResult<T>(
IReadOnlyList<T> Items,
string? NextCursor,
string? PrevCursor,
bool HasMore);
PropertyTypeDescription
ItemsIReadOnlyList<T>List of items for the current page
NextCursorstring?Next page cursor (null if no more items)
PrevCursorstring?Previous page cursor
HasMoreboolWhether more pages exist

A multi-field sort expression. Combines sort conditions via Fluent API.

public sealed class SortExpression
{
public Seq<SortField> Fields { get; }
public bool IsEmpty { get; }
public static SortExpression Empty { get; }
public static SortExpression By(string fieldName);
public static SortExpression By(string fieldName, SortDirection direction);
public SortExpression ThenBy(string fieldName);
public SortExpression ThenBy(string fieldName, SortDirection direction);
}
MemberTypeDescription
FieldsSeq<SortField>List of sort fields (applied in order)
IsEmptyboolWhether sort conditions are empty
EmptySortExpressionNo sorting (static property)
By(fieldName)SortExpressionCreates single field ascending sort (static factory)
By(fieldName, direction)SortExpressionCreates single field + direction sort (static factory)
ThenBy(fieldName)SortExpressionChains additional sort field (ascending)
ThenBy(fieldName, direction)SortExpressionChains additional sort field + direction

A pair of sort field and direction.

public sealed record SortField(string FieldName, SortDirection Direction);
PropertyTypeDescription
FieldNamestringField name to sort by
DirectionSortDirectionSort direction

A SmartEnum representing sort direction.

public sealed class SortDirection : SmartEnum<SortDirection, string>
{
public static readonly SortDirection Ascending; // Value: "asc"
public static readonly SortDirection Descending; // Value: "desc"
public static SortDirection Parse(string? value);
}
MemberValueDescription
Ascending"asc"Ascending order
Descending"desc"Descending order
Parse(value)-Parses "asc"/"desc" case-insensitively. Returns Ascending for null/empty string

An abstract base class that encapsulates domain conditions and supports And/Or/Not composition.

namespace Functorium.Domains.Specifications;
public abstract class Specification<T>
{
public static Specification<T> All { get; }
public virtual bool IsAll => false;
public abstract bool IsSatisfiedBy(T entity);
public Specification<T> And(Specification<T> other);
public Specification<T> Or(Specification<T> other);
public Specification<T> Not();
public static Specification<T> operator &(Specification<T> left, Specification<T> right);
public static Specification<T> operator |(Specification<T> left, Specification<T> right);
public static Specification<T> operator !(Specification<T> spec);
}
MemberTypeDescription
AllSpecification<T>Specification satisfied by all entities (Null Object). All & X = X
IsAllboolWhether this Specification is the identity element (All)
IsSatisfiedBy(entity)boolChecks whether the entity satisfies the condition
And(other)Specification<T>AND composition
Or(other)Specification<T>OR composition
Not()Specification<T>NOT negation

Operator overloading:

OperatorEquivalent MethodDescription
&And()AND composition. Includes All identity optimization (All & X = X)
|Or()OR composition
!Not()NOT negation

An Expression Tree-based Specification abstract class. Implementing ToExpression() automatically provides IsSatisfiedBy().

public abstract class ExpressionSpecification<T> : Specification<T>, IExpressionSpec<T>
{
public abstract Expression<Func<T, bool>> ToExpression();
public sealed override bool IsSatisfiedBy(T entity); // Expression compile + caching
}
MemberDescription
ToExpression()Returns the condition as Expression<Func<T, bool>> (must be implemented by subclass)
IsSatisfiedBy(entity)Compiles and evaluates the Expression. Compiled delegate is cached (sealed)

An interface indicating that a Specification can provide an Expression Tree. Used by LINQ providers such as EF Core for automatic SQL translation.

namespace Functorium.Domains.Specifications;
public interface IExpressionSpec<T>
{
Expression<Func<T, bool>> ToExpression();
}

Property mapping for auto-converting Entity Expression to Model Expression. Used in EfCoreRepositoryBase’s BuildQuery() method for Specification to SQL conversion.

namespace Functorium.Domains.Specifications.Expressions;
public sealed class PropertyMap<TEntity, TModel>
{
public PropertyMap<TEntity, TModel> Map<TValue, TModelValue>(
Expression<Func<TEntity, TValue>> entityProp,
Expression<Func<TModel, TModelValue>> modelProp);
public string? TranslateFieldName(string entityFieldName);
public Expression<Func<TModel, bool>> Translate(Expression<Func<TEntity, bool>> expression);
}
MethodReturn TypeDescription
Map(entityProp, modelProp)PropertyMap<TEntity, TModel>Registers Entity-Model property mapping. Fluent API
TranslateFieldName(name)string?Translates Entity field name to Model field name (null if no mapping)
Translate(expression)Expression<Func<TModel, bool>>Converts Entity Expression to Model Expression

Supported Entity property expressions:

FormExample
Direct member accessp => p.Name
Type conversionp => (decimal)p.Price
ToString() callp => p.Id.ToString()

A utility that extracts Expression Trees from Specifications and recursively composes And/Or/Not combinations.

namespace Functorium.Domains.Specifications.Expressions;
public static class SpecificationExpressionResolver
{
public static Expression<Func<T, bool>>? TryResolve<T>(Specification<T> spec);
}
MethodReturn TypeDescription
TryResolve(spec)Expression<Func<T, bool>>?Extracts Expression from Specification. Direct extraction for IExpressionSpec implementations, recursive composition for And/Or/Not combinations. Returns null if unsupported

EfCoreRepositoryBase<TAggregate, TId, TModel>

Section titled “EfCoreRepositoryBase<TAggregate, TId, TModel>”

Common base class for EF Core Repositories. Includes declared in the constructor are automatically applied to all read queries through ReadQuery(), structurally preventing N+1 problems.

namespace Functorium.Adapters.Repositories;
public abstract class EfCoreRepositoryBase<TAggregate, TId, TModel>
: IRepository<TAggregate, TId>
where TAggregate : AggregateRoot<TId>
where TId : struct, IEntityId<TId>
where TModel : class, IHasStringId
{
protected EfCoreRepositoryBase(
IDomainEventCollector eventCollector,
Func<IQueryable<TModel>, IQueryable<TModel>>? applyIncludes = null,
PropertyMap<TAggregate, TModel>? propertyMap = null);
}
Type ParameterConstraintDescription
TAggregateAggregateRoot<TId>Aggregate Root type
TIdstruct, IEntityId<TId>Ulid-based EntityId type
TModelclass, IHasStringIdEF Core entity model (string Id required)
ParameterTypeDescription
eventCollectorIDomainEventCollectorDomain event collector
applyIncludesFunc<IQueryable<TModel>, IQueryable<TModel>>?Navigation Property Include declaration (N+1 prevention). No Includes if null
propertyMapPropertyMap<TAggregate, TModel>?Mapping for Specification to Model Expression conversion. Required when using BuildQuery/ExistsBySpec
MemberTypeDescription
DbContextDbContextEF Core DbContext (abstract property)
DbSetDbSet<TModel>Entity model’s DbSet (abstract property)
ToDomain(model)TAggregateModel to Domain mapping (abstract method)
ToModel(aggregate)TModelDomain to Model mapping (abstract method)
MemberTypeDescription
EventCollectorIDomainEventCollectorDomain event collector
PropertyMapPropertyMap<TAggregate, TModel>?Specification to Model property mapping
IdBatchSizeint (virtual, default 500)Batch size for preventing SQL IN clause parameter limit
ReadQuery()IQueryable<TModel>Read-only query with Includes auto-applied (AsNoTracking)
ReadQueryIgnoringFilters()IQueryable<TModel>Read query with Includes + global filter bypass (for Soft Delete queries)
BuildQuery(spec)Fin<IQueryable<TModel>>Specification to Model Expression query builder (PropertyMap required)
ExistsBySpec(spec)FinT<IO, bool>Specification-based existence check (PropertyMap required)
ByIdPredicate(id)Expression<Func<TModel, bool>>Single ID matching Expression (virtual, default IHasStringId-based implementation)
ByIdsPredicate(ids)Expression<Func<TModel, bool>>Multiple ID matching Expression (virtual, default IHasStringId-based implementation)
MethodDescription
NotFoundError(id)Creates AdapterErrorType.NotFound error. Actual subclass name is included in the error code
PartialNotFoundError(requestedIds, foundAggregates)Creates AdapterErrorType.PartialNotFound error. Includes list of missing IDs
NotConfiguredError(message)Creates AdapterErrorType.NotConfigured error
NotSupportedError(currentValue, message)Creates AdapterErrorType.NotSupported error

String Id interface that EF Core models must implement. Provides default implementation for EfCoreRepositoryBase’s ByIdPredicate/ByIdsPredicate.

namespace Functorium.Adapters.Repositories;
public interface IHasStringId
{
string Id { get; set; }
}

Common base class for InMemory Repositories. Provides default implementation of full IRepository CRUD based on ConcurrentDictionary.

namespace Functorium.Adapters.Repositories;
public abstract class InMemoryRepositoryBase<TAggregate, TId>
: IRepository<TAggregate, TId>
where TAggregate : AggregateRoot<TId>
where TId : struct, IEntityId<TId>
{
protected InMemoryRepositoryBase(IDomainEventCollector eventCollector);
}
MemberTypeDescription
StoreConcurrentDictionary<TId, TAggregate>In-memory store (abstract property). Subclass provides a static instance
MemberTypeDescription
EventCollectorIDomainEventCollectorDomain event collector
RequestCategorystring (virtual, default "Repository")Observability category

Common infrastructure for Dapper-based QueryAdapters. Subclasses are responsible only for SQL declaration and WHERE building.

namespace Functorium.Adapters.Repositories;
public abstract class DapperQueryBase<TEntity, TDto>
{
protected DapperQueryBase(IDbConnection connection);
protected DapperQueryBase(
IDbConnection connection,
DapperSpecTranslator<TEntity> translator,
string tableAlias = "");
}
MemberTypeDescription
SelectSqlstringSELECT query (up to FROM + JOIN, excluding WHERE)
CountSqlstringCOUNT query (up to FROM + JOIN, excluding WHERE)
DefaultOrderBystringDefault ORDER BY clause (e.g., "p.created_at DESC")
AllowedSortColumnsDictionary<string, string>Allowed sort column mapping (DTO field name to DB column name)
MethodDescription
BuildWhereClause(spec)Specification to SQL WHERE clause conversion. Default implementation provided when DapperSpecTranslator is injected. Otherwise, subclass override is required
PaginationClauseDB dialect-specific Offset pagination clause (virtual, default "LIMIT @PageSize OFFSET @Skip")
CursorPaginationClauseDB dialect-specific Keyset pagination clause (virtual, default "LIMIT @PageSize")
GetCursorValue(item, fieldName)Extract cursor value from DTO (virtual, Reflection-based default implementation + caching)
Params(values)DynamicParameters creation helper (static method)
MethodReturn TypeDescription
Search(spec, page, sort)FinT<IO, PagedResult<TDto>>Offset-based pagination search (COUNT + SELECT multi-query)
SearchByCursor(spec, cursor, sort)FinT<IO, CursorPagedResult<TDto>>Keyset-based search (PageSize + 1 strategy for HasMore determination)
Stream(spec, sort, ct)IAsyncEnumerable<TDto>Streaming query via QueryUnbufferedAsync (DbConnection required)

Common infrastructure for InMemory-based QueryAdapters. InMemory counterpart base class for DapperQueryBase.

namespace Functorium.Adapters.Repositories;
public abstract class InMemoryQueryBase<TEntity, TDto>
{
// (No constructor parameters)
}
MemberTypeDescription
DefaultSortFieldstringDefault sort field name
GetProjectedItems(spec)IEnumerable<TDto>Filtering + DTO projection (including JOIN logic)
SortSelector(fieldName)Func<TDto, object>Sort key selector (field name to selector function)
MethodReturn TypeDescription
Search(spec, page, sort)FinT<IO, PagedResult<TDto>>Offset-based pagination search
SearchByCursor(spec, cursor, sort)FinT<IO, CursorPagedResult<TDto>>Keyset-based search
Stream(spec, sort, ct)IAsyncEnumerable<TDto>In-memory streaming query

A registry that translates Specifications to SQL WHERE clauses. Once configured per entity type, multiple Dapper adapters can share it with different table aliases.

namespace Functorium.Adapters.Repositories;
public sealed class DapperSpecTranslator<TEntity>
{
public DapperSpecTranslator<TEntity> WhenAll(
Func<string, (string Where, DynamicParameters Params)> handler);
public DapperSpecTranslator<TEntity> When<TSpec>(
Func<TSpec, string, (string Where, DynamicParameters Params)> handler)
where TSpec : Specification<TEntity>;
public (string Where, DynamicParameters Params) Translate(
Specification<TEntity> spec, string tableAlias = "");
public static DynamicParameters Params(params (string Name, object Value)[] values);
public static string Prefix(string tableAlias);
}
MethodReturn TypeDescription
WhenAll(handler)DapperSpecTranslator<TEntity>Register IsAll (identity) Specification handler (Fluent API)
When<TSpec>(handler)DapperSpecTranslator<TEntity>Register SQL translation handler for a specific Specification type (Fluent API)
Translate(spec, alias)(string Where, DynamicParameters Params)Translates Specification to SQL WHERE clause
Params(values)DynamicParametersDynamicParameters creation helper (static)
Prefix(tableAlias)stringReturns table alias prefix (e.g., "p" to "p.", "" to "")

DI Registration (ObservablePortRegistration)

Section titled “DI Registration (ObservablePortRegistration)”

A collection of IServiceCollection extension methods for registering IObservablePort implementations in the DI container. Uses ActivatorUtilities.CreateInstance to auto-inject ActivitySource, ILogger, and IMeterFactory into implementation type constructors.

namespace Functorium.Abstractions.Registrations;
public static class ObservablePortRegistration
{
// Single interface registration
public static IServiceCollection RegisterScopedObservablePort<TService, TImpl>(...);
public static IServiceCollection RegisterTransientObservablePort<TService, TImpl>(...);
public static IServiceCollection RegisterSingletonObservablePort<TService, TImpl>(...);
// Multiple interfaces to single implementation registration (For suffix)
public static IServiceCollection RegisterScopedObservablePortFor<T1, T2, TImpl>(...);
public static IServiceCollection RegisterScopedObservablePortFor<T1, T2, T3, TImpl>(...);
public static IServiceCollection RegisterScopedObservablePortFor<TImpl>(
..., params Type[] serviceTypes);
public static IServiceCollection RegisterTransientObservablePortFor<T1, T2, TImpl>(...);
public static IServiceCollection RegisterTransientObservablePortFor<T1, T2, T3, TImpl>(...);
public static IServiceCollection RegisterTransientObservablePortFor<TImpl>(
..., params Type[] serviceTypes);
public static IServiceCollection RegisterSingletonObservablePortFor<T1, T2, TImpl>(...);
public static IServiceCollection RegisterSingletonObservablePortFor<T1, T2, T3, TImpl>(...);
public static IServiceCollection RegisterSingletonObservablePortFor<TImpl>(
..., params Type[] serviceTypes);
}
PatternDescription
Register{Lifetime}ObservablePort<TService, TImpl>Register a single interface with one implementation
Register{Lifetime}ObservablePortFor<T1, T2, TImpl>Register 2 interfaces with one implementation
Register{Lifetime}ObservablePortFor<T1, T2, T3, TImpl>Register 3 interfaces with one implementation
Register{Lifetime}ObservablePortFor<TImpl>(params Type[])Register N interfaces with one implementation (4 or more)
LifetimeInstance sharing scope
ScopedOne instance per HTTP request
TransientNew instance per request
SingletonOne instance for the entire application

All service interface type parameters have the class, IObservablePort constraint. The params Type[] overload validates IObservablePort implementation and interface implementation of the implementation class at runtime.

Methods with the For suffix first register the implementation, then register each service interface to reference the same instance via GetRequiredService<TImplementation>(). This enables resolving a single implementation through multiple interfaces.

// Usage example: Register IProductRepository and IProductQuery with the same Observable implementation
services.RegisterScopedObservablePortFor<IProductRepository, IProductQuery, ProductObservable>();

Options Configuration (OptionsConfigurator)

Section titled “Options Configuration (OptionsConfigurator)”

A utility for registering FluentValidation-based options validation in DI.

namespace Functorium.Adapters.Abstractions.Options;
public static class OptionsConfigurator
{
public static OptionsBuilder<TOptions> RegisterConfigureOptions<TOptions, TValidator>(
this IServiceCollection services,
string configurationSectionName)
where TOptions : class
where TValidator : class, IValidator<TOptions>;
}
OrderBehaviorDescription
1IValidator<TOptions> registrationRegisters TValidator as Scoped in DI
2BindConfigurationBinds the configurationSectionName section of appsettings.json to TOptions
3FluentValidation connectionConnects FluentValidation validation through IValidateOptions<TOptions> implementation
4ValidateOnStartRun options validation at program startup
5IStartupOptionsLogger auto-registrationIf TOptions implements IStartupOptionsLogger, auto-registers for options value logging at startup
// Usage example
services.RegisterConfigureOptions<DatabaseOptions, DatabaseOptionsValidator>("Database");

Applying this attribute to an Adapter class causes the source generator to automatically generate an Observable wrapper class. The generated Observable provides OpenTelemetry-based Tracing, Logging, and Metrics.

namespace Functorium.Adapters.SourceGenerators;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class GenerateObservablePortAttribute : Attribute;
PropertyValueDescription
AttributeTargetsClassCan only be applied to classes
AllowMultiplefalseApplied only once per class
InheritedfalseNot inherited by derived classes

Prerequisites:

  • The project must reference the Functorium.SourceGenerators package
  • Interface methods in the Adapter class require the virtual keyword (Pipeline overrides)
// Usage example
[GenerateObservablePort]
public class ProductRepositoryInMemory
: InMemoryRepositoryBase<Product, ProductId>, IProductRepository
{
// virtual methods...
}
// -> ProductRepositoryInMemoryObservable class is auto-generated

An attribute that excludes a specific method from Observable wrapper generation. Used for helper methods or internal methods where observability is unnecessary.

namespace Functorium.Adapters.SourceGenerators;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class ObservablePortIgnoreAttribute : Attribute;
PropertyValueDescription
AttributeTargetsMethodCan only be applied to methods
AllowMultiplefalseApplied only once per method
InheritedfalseNot inherited by derived classes
// Usage example
[GenerateObservablePort]
public class ProductRepository : InMemoryRepositoryBase<Product, ProductId>, IProductRepository
{
[ObservablePortIgnore]
public virtual FinT<IO, int> GetCount() => ...; // Excluded from Observable wrapper
}

DocumentDescription
Port Architecture and DefinitionsPort design principles, interface definition pattern guide by type
Adapter ImplementationRepository, External API, Query Adapter implementation guide by type
Adapter Pipeline and DI RegistrationObservable Pipeline creation and DI registration guide
Adapter TestingAdapter unit/integration testing guide
Entity and Aggregate SpecificationAggregateRoot<TId>, IEntityId<TId> API specification
Error System SpecificationAdapterErrorType (NotFound, PartialNotFound, etc.) API specification
Observability Specification3-Pillar field/tag specification, Meter definition rules
Source Generator SpecificationObservablePortGenerator source generator detailed specification