AxisBus.Analyzers
2.2.0
dotnet add package AxisBus.Analyzers --version 2.2.0
NuGet\Install-Package AxisBus.Analyzers -Version 2.2.0
<PackageReference Include="AxisBus.Analyzers" Version="2.2.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="AxisBus.Analyzers" Version="2.2.0" />
<PackageReference Include="AxisBus.Analyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add AxisBus.Analyzers --version 2.2.0
#r "nuget: AxisBus.Analyzers, 2.2.0"
#:package AxisBus.Analyzers@2.2.0
#addin nuget:?package=AxisBus.Analyzers&version=2.2.0
#tool nuget:?package=AxisBus.Analyzers&version=2.2.0
Axis Framework
Write your domain. Axis owns the architecture. β an opinionated, ports-and-adapters .NET framework where the core stays pure, every failure is a typed value, and a monolith splits into microservices by moving a folder. Typed building blocks, Railway-Oriented orchestration, swappable infrastructure adapters, observability and the HTTP edge β all composing without ceremony.
// A whole feature. This is all you write β the framework does the rest.
public Task<AxisResult<CreateOrderResponse>> HandleAsync(CreateOrderCommand cmd)
=> customerFactory.GetByIdAsync(cmd.CustomerId)
.ThenAsync(customer => orderFactory.CreateAsync(new()
{
CustomerId = customer.CustomerId,
ProductId = cmd.ProductId,
Quantity = cmd.Quantity
}))
.ThenAsync(order => bus.PublishAsync(new OrderCreatedEvent(order.OrderId))) // enqueue the "OrderCreated" fact in the outboxβ¦
.ThenAsync(order => unitOfWork.SaveChangesAsync()) // β¦then one commit persists the order AND the event, atomically
.TapAsync(order => logger.LogInformation("Order {OrderId} created", order.OrderId))
.MapAsync(order => new CreateOrderResponse { OrderId = order.OrderId });
// Validation, DI wiring, errorβHTTP mapping, tracing and metrics are the framework's
// job β not yours, and not in this file. The event is staged on the bus (an outbox
// adapter) and the single SaveChangesAsync commits it together with the order in one
// transaction: publish first, commit once β never a commit-then-publish dual write.
Use this page as a map: read the trunk below (~5 min) to understand why Axis exists, then jump to the family or package you need β without reading hundreds of lines.
The trunk (read first)
Why Axis? Three promises
1 Β· You write the domain. Axis owns the architecture.
A feature is a vertical slice through a hexagonal boundary β a CQRS use case (Command/Query β Response β Handler β Validator) exposed by a Facade. You write the handler; the framework provides the pipeline, the dependency wiring, the transaction boundary, the validation gate, the error-to-HTTP mapping and the telemetry. The architectural questions are answered once, by the framework, and enforced for everyone.
2 Β· Ports and adapters β a pure core that never depends on a vendor.
Every piece of infrastructure (persistence, messaging, cache, object storage, e-mail) is a small port that returns AxisResult and never throws. The adapter owns the try/catch and the SDK; the application sees only the interface. Migrating Postgres β SQL Server, or RabbitMQ β Kafka, is a DI change at the composition root.
3 Β· Monolithic-first β split into microservices by moving a folder. Start with one deployable hosting API, workers and jobs. Inside it, code is vertical-sliced by bounded context, and because a namespace mirrors its folder path exactly, promoting a bounded context to its own microservice is a structural move, not a rewrite β the BC's folders become projects along the layers, every namespace stays identical, and the database can be isolated on the same terms (already separated by schema, already isolated by nature). Cross-BC traffic already flows through the three sanctioned channels, so nothing breaks when the boundary becomes a network hop.
The three sanctioned cross-BC channels
A bounded context never reaches into another BC's ports or internals. Everything crosses the boundary through exactly one of:
| Need | Channel |
|---|---|
| Synchronous read/write | the other BC's public Facade |
| Fire-and-forget side effect | an event on the Bus |
| Multi-BC process with compensation | a Saga whose stage handlers live in the owning BC |
Because these are the same channels whether the BCs share a process or sit on opposite ends of a network, the monolith β microservices promotion changes deployment, not design.
What's in the box
This is one repository that ships everything needed to build the Axis way:
π¦ Packages (src/) |
The NuGet libraries, organised into five families. Targets .NET 10. |
| π‘οΈ Analyzers & generators | Roslyn analyzers and a source generator enforce the invariants at build time β a violated rule fails the build, not the code review. |
π Rules (rules/) |
Around 400 canonical rules (conventions + framework) β the single source of truth every analyzer and doc derives from. |
ποΈ Scaffolds (src/scaffolds/) |
A complete ECommerce reference application β domain, facades, pipelines, ports, use cases and tests. |
π Docs (docs/) |
Navigable documentation in English and Portuguese, plus the architecture decision records (ADRs). |
π€ Claude Code agents (.agents/claude/) |
Three subagents plus a slash command that automate the Axis workflow β review, planning and migrating an existing repo toward the pattern. |
One source of truth
The rule base in rules/ is canonical: the analyzers make the mechanical rules non-negotiable at build time, while the docs carry the judgment ones. A human reading a doc and a build failing on a violated rule are enforcing the very same invariant.
Installation
dotnet add package AxisResult
Every package is independent β add only what a slice needs. β Start with AxisResult, the railway everything else returns.
The map β the five families
Inspired by the GoF taxonomy, the packages are organised into five families by responsibility, mirrored one-to-one by the numbered top-level folders. Knowing the family of a package tells you roughly what kind of problem it solves and where it sits in your stack.
0 Β· Foundations β typed building blocks
Zero infrastructure, pure types and abstractions. Start here.
AxisResultβ Railway-Oriented Programming, the typedResultmonad with async supportAxisTypesβ strongly-typed value objects (AxisEntityId) +[ValueObject]source generatorAxisMediator.Contractsβ pure CQRS contracts shared by the mediator and its consumers
1 Β· Observability β what your operators stare at
AxisLoggerβ structured logging, correlation, enrichersAxisTelemetryβ OpenTelemetry traces / metrics / logs, with an Azure Monitor / Application Insights adapter
2 Β· Application & Flow β the verbs of your domain
AxisMediatorβ request/response mediator, pipelines, behavioursAxisSagaβ long-running process orchestration with compensationsAxisValidatorβ declarative validation that returnsAxisResultAxisBusβ event bus abstraction: asynchronous fan-out of events to handlers
3 Β· Infrastructure & Integration β adapters, always behind a port
The application never depends on a vendor.
AxisRepositoryβ persistence ports, unit of work, Postgres/MySQL adapters (raw Npgsql/MySqlConnector, no ORM)AxisCacheβ cache abstraction with an in-memory adapterAxisStorageβ blob/file storage abstraction (Cloudflare R2 / S3-compatible)AxisEmailβ transactional email sender (MimeKit / SMTP)
4 Β· Edge β the door your clients knock on
AxisResult.HttpResponseβ mapsAxisErrorβIActionResult/ProblemDetailsat the HTTP boundary
Packages at a glance
| Package | Family | One-liner | Docs |
|---|---|---|---|
AxisResult |
Foundations | Railway-Oriented Result monad with async/ValueTask and typed errors |
docs |
AxisTypes |
Foundations | Strongly-typed value objects + [ValueObject] source generator |
docs |
AxisMediator.Contracts |
Foundations | Pure CQRS contracts shared by the mediator and its consumers | docs |
AxisLogger |
Observability | Structured logging with enrichment | docs |
AxisTelemetry |
Observability | OpenTelemetry integration, Azure Monitor adapter | docs |
AxisMediator |
Application & Flow | In-process request/response mediator with pipelines | docs |
AxisSaga |
Application & Flow | Saga orchestration with compensations and state | docs |
AxisValidator |
Application & Flow | Declarative validation returning AxisResult |
docs |
AxisBus |
Application & Flow | Event bus abstraction β asynchronous fan-out of events | docs |
AxisRepository |
Infrastructure | Persistence ports, unit of work, Postgres/MySQL adapters | docs |
AxisCache |
Infrastructure | Cache abstraction with an in-memory adapter | docs |
AxisStorage |
Infrastructure | Blob/file storage abstraction (Cloudflare R2) | docs |
AxisEmail |
Infrastructure | Transactional email (MimeKit / SMTP) | docs |
AxisResult.HttpResponse |
Edge | Maps AxisError β IActionResult/ProblemDetails at the HTTP edge |
docs |
Foundational utilities β dependency-injection helpers, the testing kit, and migrations β round out the set.
The rule base
The rules/ tree is split into conventions/ (architecture, domain, persistence, edge, testing, style, process) and framework/ (per-package invariants, mirroring the five families). The analyzers enforce the mechanical rules at build time; the docs carry the judgment ones.
Claude Code agents
On top of the rule base, the framework ships three Claude Code subagents plus one orchestrating slash command that automate the Axis workflow itself β reviewing a diff, planning where a feature belongs, and migrating an existing repository toward the pattern BC by BC. Source of truth, install steps (project + global scope) and editing rules: .agents/claude/README.md.
| Agent | Role |
|---|---|
axis-reviewer |
Pre-commit/pre-push review gate β runs the deterministic gates (build, analyzers, tests, linters) first, then the topology/semantic judgment no analyzer covers. Read-only. |
axis-architect |
Planning gate that runs BEFORE code β decides BC boundary, subdomain classification, aggregate level and cross-BC channel. Read-only. |
axis-bc-migrator |
Migrates one bounded context of an existing (non-Axis) repository toward the pattern; fixes what's safe, defers what needs a decision. Invoked per BC by /axis-audit, which fans out one worker per bounded context and consolidates the report. |
Suggested learning order
The packages are independent, but reading them in this order builds the model bottom-up, each step using only what came before.
AxisResultβ the railway and typed errors. Everything else returns one.AxisTypesβ typed identities and value objects. The types your domain talks in.AxisCacheβ small infrastructure, easy first read.AxisBusβ events and integration messages.AxisStorageβ blobs and files.AxisEmailβ transactional email.AxisLoggerβ structured logging.AxisRepositoryβ persistence and unit of work.AxisValidatorβ input rules intoAxisResult.AxisTelemetryβ OpenTelemetry traces and metrics.AxisMediatorβ the verb pipeline; ties the application together.AxisSagaβ long processes built on top of the mediator.AxisResult.HttpResponseβ the HTTP edge, mapping results to HTTP responses.
Design principles
- You own the domain; the framework owns the architecture. The recurring structural decisions are made once and enforced for everyone.
- Errors are values, not exceptions. Every operation that can fail says so in its return type.
- The type system is the documentation. Reading the signature is reading the contract.
- Depend on ports, never on vendors. Infrastructure sits behind an
AxisResultport; swapping a provider is a DI change. - Boundaries, not pyramids. Exceptions stop at the infrastructure boundary; everything inside speaks in
AxisResult. - Monolithic-first, microservice-ready. One deployable today; promote a bounded context to its own service by moving folder, not by rewriting Core.
- One package, one responsibility. Pick what you need. Nothing here pulls in a kitchen sink.
License
Apache 2.0
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.