An opinionated, open source .NET framework for building elegant, robust applications inspired by fractals. Simple, repeated structures (entities, aggregates, domain events, commands and queries with their dispatchers) keep systems modular, scalable and easy to maintain.
| Package | Contents |
|---|---|
Efc.Fractalize.Domain |
Building blocks with no dependencies: EntityBase, AggregateRootBase, IDomainEvent, EventBase, and the ICommand / IQuery contracts with their handlers and dispatchers |
Efc.Fractalize.Ddd |
Domain-driven design core with no dependencies: EntityBase with identity equality, AggregateRootBase with domain events, DomainEventBase, ValueObjectBase, DateRange, and the IReadRepository / IRepository / IUnitOfWorkBase contracts. It overlaps with Efc.Fractalize.Domain until the maintainers decide which types survive |
Efc.Fractalize.Application |
CommandDispatcher and its fluent configurator, CommandDispatcherConfigurator (AddCommandDispatcher) |
Efc.Fractalize.Infrastructure |
QueryDispatcher and QueryDispatcherConfigurator (AddQueryDispatcher), CqrsConfigurator (AddCqrsDispatchers: commands and queries in one call) and OptionsConfigurator (AddValidatedOptions: options validated at startup). Depends on Efc.Fractalize.Application |
The libraries target net10.0 and are published to NuGet.
dotnet add package Efc.Fractalize.InfrastructureDefine a query and its handler, register them and dispatch the query:
using Efc.Fractalize.Domain.Queries;
using Efc.Fractalize.Infrastructure.Extensions;
public sealed record GetGreeting(string Name) : IQuery<string>;
public sealed class GetGreetingHandler : IQueryHandler<GetGreeting, string>
{
public Task<string> HandleAsync(GetGreeting query, CancellationToken cancellationToken = default) =>
Task.FromResult($"Hello, {query.Name}!");
}
// At startup: register the command and query dispatchers and the handlers of this assembly.
services.AddCqrsDispatchers(typeof(GetGreetingHandler).Assembly);
// Anywhere: resolve the dispatcher for the query and send it.
var greeting = await provider
.GetRequiredService<IQueryDispatcher<GetGreeting, string>>()
.DispatchAsync(new GetGreeting("Fractalize"));Commands work the same way with ICommand, ICommandHandler<TCommand> and ICommandDispatcher<TCommand>. Each
configurator can also be used on its own: AddCommandDispatcher(...) (Efc.Fractalize.Application.Commands) and
AddQueryDispatcher(...) (Efc.Fractalize.Infrastructure.Queries) register the dispatcher and scan the handlers
of the given assemblies (every application dependency when none is given), and AddCommandDispatcher<TCommand>() /
AddQueryDispatcher<TQuery, TResult>() register the dispatcher of a single message. Every registration uses
TryAdd*, so a host can replace any implementation and calling a configurator twice is harmless.
Options are bound and validated with data annotations when the application starts, as the engineering standards require:
using Efc.Fractalize.Infrastructure.Extensions;
// From configuration (appsettings + environment variables)...
services.AddValidatedOptions<ShipmentRepositoryOptions>(configuration, ShipmentRepositoryOptions.SectionName);
// ...or from code (tests, other hosts, values read from a database).
services.AddValidatedOptions<ShipmentRepositoryOptions>(o => o.ConnectionString = connectionString);The 2.x registration helpers still work with the same behaviour, but they are marked [Obsolete] and will be
removed in the next major version. Replace them as follows:
| 2.x helper (obsolete) | Replacement |
|---|---|
AddCqrs() |
AddCqrsDispatchers(assemblies) (CqrsConfigurator, Efc.Fractalize.Infrastructure) |
AddCommandHandlers(), AddCommandDispatchers() |
AddCommandDispatcher(assemblies) (CommandDispatcherConfigurator) |
TryAddCommandDispatcher<TCommand>(), ConfigureCommandHandler<TCommand>() |
AddCommandDispatcher<TCommand>() |
AddQueryHandlers(), AddQueryDispatchers() |
AddQueryDispatcher(assemblies) (QueryDispatcherConfigurator) |
TryAddQueryDispatcher<TQuery, TResult>(), ConfigureQueryHandler<TQuery, TResult>() |
AddQueryDispatcher<TQuery, TResult>() |
AddConfigurationSection<T>(configuration, sectionName) |
AddValidatedOptions<T>(configuration, sectionName) (OptionsConfigurator) |
Differences to keep in mind:
- The new configurators use
TryAdd*: a handler or dispatcher registered earlier is kept, not duplicated. AddCommandDispatcher(...)andAddQueryDispatcher(...)also register the dispatcher for every message type, so the per-messageAddCommandDispatcher<TCommand>()/AddQueryDispatcher<TQuery, TResult>()calls are optional.AddValidatedOptionsalways registers the options and validates them at startup: a missing or invalid section fails when the host starts, whileAddConfigurationSectionskipped missing sections and never validated.
Prerequisites, commands and the checks every pull request must pass are in
CONTRIBUTING.md. In short:
dotnet build src/Efc.Fractalize.Framework.sln -c Release -warnaserror
dotnet test src/Efc.Fractalize.Framework.sln -c Release- Engineering standards: file-scoped namespaces and no unused code
(build errors), minimum 80 % line coverage (CI gate), low coupling and high cohesion, Options pattern and
fluent
*Configuratorclasses. - English only in code, comments, docs, commits and pull requests.
- Conventional Commits for every commit and pull request title; releases follow
Semantic Versioning and
CHANGELOG.mdis generated by release-please. - Protected
main: pull requests only, required checks, code-owner approval, resolved conversations, signed commits, merge commit or squash (no rebase merges), no force pushes or deletion. - Protected release tags:
v*tags can't be moved or deleted. - Least-privilege workflows: read-only token by default, explicit per-job permissions, every action pinned to a full commit SHA, checkouts without persisted credentials.
- Supply chain: CodeQL, dependency review, Dependabot and OpenSSF Scorecard.
- Secrets never in Git: secret scanning with push protection,
.envfiles ignored.
These standards come from the public-repo-template.
Read CONTRIBUTING.md before opening a pull request, follow the
Code of Conduct, and report vulnerabilities privately as described in
SECURITY.md. For questions, see SUPPORT.md.
MIT Β© 2025 Dev Cracks