BytLabs Backend Packages
A batteries-included foundation for building consistent .NET microservices.
A curated set of .NET libraries that encode one opinionated, production-ready way to build a service — Domain-Driven Design, CQRS, MongoDB persistence, multitenancy, GraphQL, and observability — so every microservice across your organization looks, behaves, and is operated the same way.
Overview
Building microservices means solving the same cross-cutting problems over and over: domain modelling, command/query handling, persistence, tenant isolation, API shape, validation, error handling, logging, tracing, and health. BytLabs Backend Packages solve them once, consistently, and hand you a clean surface to build on.
Adopt them and you get a service that is uniform in architecture, consistent in error handling and logging, standardized in testing, and aligned on shared domain patterns and security practices — which makes onboarding faster and long-term maintenance dramatically cheaper.
Why a shared foundation
| Without a shared foundation | With BytLabs |
|---|---|
| Every team wires DDD / CQRS / persistence differently | One consistent architecture across all services |
| Repetitive boilerplate for logging, tracing, health, tenancy | Configured in a few fluent calls |
| Inconsistent error handling and API contracts | Standardized GraphQL errors and mutation conventions |
| Hand-rolled data access and tenant isolation | Generic repository with automatic database-per-tenant |
| Slow, divergent service bootstrap | Start from a working template in minutes |
Capabilities
| Capability | What it provides | Package |
|---|---|---|
| Domain-Driven Design | Aggregates, entities, value objects, domain events, soft-delete, audit, business rules | BytLabs.Domain |
| CQRS | Commands and queries on MediatR with automatic FluentValidation and logging pipelines | BytLabs.Application |
| Data access | Generic repository, unit-of-work, transactions, and a dynamic-data query engine for MongoDB | BytLabs.DataAccess, BytLabs.DataAccess.MongoDB |
| Multitenancy | Request-scoped tenant resolution with transparent database-per-tenant isolation | BytLabs.Multitenancy |
| GraphQL API | HotChocolate with BytLabs defaults: mutation conventions, typed errors, authorization, projections / filtering / sorting | BytLabs.Api, BytLabs.Api.Graphql |
| Observability | Serilog and OpenTelemetry logs, metrics, and traces, plus liveness / readiness / startup health checks | BytLabs.Observability |
| Dynamic data | Schema-less JSON fields on aggregates, queryable and filterable through the API | BytLabs.Domain + data/API packages |
| State machines (optional) | Rule-guarded state transitions for aggregates with a formal lifecycle | BytLabs.States.Domain |
Architecture
Services built on these packages follow Clean Architecture and DDD with four layers, each mapped to a package set.
---
config:
look: handDrawn
theme: neutral
---
flowchart TB
API["API — BytLabs.Api, BytLabs.Api.Graphql<br/>GraphQL endpoints, host, typed errors"]
APP["Application — BytLabs.Application<br/>commands, queries, handlers, validation"]
DOM["Domain — BytLabs.Domain (+ BytLabs.States.Domain)<br/>aggregates, value objects, events, rules"]
INF["Infrastructure — BytLabs.DataAccess, BytLabs.DataAccess.MongoDB<br/>MongoDB persistence and DI wiring"]
CC["Cross-cutting — BytLabs.Multitenancy, BytLabs.Observability"]
API --> APP
APP --> DOM
INF --> APP
INF --> DOM
CC -.-> API
CC -.-> INF
How a request flows
---
config:
look: handDrawn
theme: neutral
---
sequenceDiagram
actor Client
participant GraphQL as GraphQL Mutation
participant Pipeline as MediatR Pipeline
participant Handler as Command Handler
participant Aggregate
participant Repo as Repository (tenant DB)
participant Events as Domain Event Handlers
Client->>GraphQL: mutation(input = command)
GraphQL->>Pipeline: send command
Pipeline->>Pipeline: validation + logging
Pipeline->>Handler: handle
Handler->>Aggregate: load / create, enforce invariants
Handler->>Repo: InsertAsync / UpdateAsync (tenant "{database}-{tenantId}")
Repo-->>Events: dispatch domain events (on success)
Handler-->>GraphQL: DTO
GraphQL-->>Client: payload, or typed errors (BusinessError / ValidationError)
Package catalog
Each package has a focused, example-driven guide in the Library Reference.
Core
| Package | Description |
|---|---|
BytLabs.Domain |
DDD building blocks: Entity, AggregateRootBase, ValueObject, domain events, audit, soft-delete, dynamic data, business rules |
BytLabs.Application |
CQRS (ICommand / IQuery and handlers), IRepository / IUnitOfWork, DomainEventHandler, validation and logging pipeline, AddCQS |
Data access
| Package | Description |
|---|---|
BytLabs.DataAccess |
Provider-agnostic unit-of-work, command transactions, and domain-event dispatch |
BytLabs.DataAccess.MongoDB |
MongoDB repository, per-tenant database resolution, BSON setup, dynamic-data queries, health checks |
API and hosting
| Package | Description |
|---|---|
BytLabs.Api |
ApiServiceBuilder fluent host (user context, tenancy, logging, metrics, tracing, health) and configuration binding |
BytLabs.Api.Graphql |
HotChocolate setup with BytLabs defaults, typed errors, type registration helpers, dynamic-data inputs |
Cross-cutting and optional
| Package | Description |
|---|---|
BytLabs.Multitenancy |
Tenant resolution and database-per-tenant isolation |
BytLabs.Observability |
Serilog and OpenTelemetry logging, metrics, tracing, and health checks |
BytLabs.States.Domain |
State-machine aggregates with rule-guarded transitions (RulesEngine) |
BytLabs.Infrastructure |
Shared infrastructure exception (reserved for future helpers) |
Dependency flow
Arrows point from a package to the packages it depends on.
---
config:
look: handDrawn
theme: neutral
---
flowchart LR
App[BytLabs.Application] --> Domain[BytLabs.Domain]
DataAccess[BytLabs.DataAccess] --> App
Mongo[BytLabs.DataAccess.MongoDB] --> DataAccess
Mongo --> MT[BytLabs.Multitenancy]
Gql[BytLabs.Api.Graphql] --> App
Gql --> Domain
Api[BytLabs.Api] --> App
Api --> Obs[BytLabs.Observability]
Api --> MT
Design principles and conventions
- Domain first. Business logic lives in aggregates derived from
AggregateRootBase<TId>. State changes go through intent-revealing methods that enforce invariants and raise domain events; entities expose private setters and are created via factory methods. - CQRS with a pipeline. Writes are commands; reads are queries or GraphQL projections. Registering
handlers with
AddCQSautomatically adds FluentValidation and logging behaviors, and (when enabled) transactions and domain-event dispatch. - Database-per-tenant. Tenancy is physical and transparent: a tenant id is resolved from the request and the matching MongoDB database is selected per request. Domain code carries no tenant field and no per-query tenant filter.
- Dynamic data. Aggregates may expose a schema-less
JsonElementpayload (IHaveDynamicData) that is stored natively and is filterable and sortable through the GraphQL API. - Consistent error model.
DomainExceptionbecomes aBusinessError; validation failures become aValidationErrorwith field details. Both appear in the mutation payload's typederrorsunion. - Observability by default. Structured logging (Serilog), metrics and tracing (OpenTelemetry), and liveness / readiness / startup probes are wired by the host builder.
Getting started
Option A — Start from the template (recommended)
The fastest path is the BytLabs.MicroserviceTemplate, a working service that doubles as a
recipe catalog: a minimal Order aggregate plus an advanced Product aggregate that demonstrates
every pattern (dynamic data, soft-delete, sub-entities, advanced GraphQL, authorization), each with a
focused how-to page. Copy it, rename it, and start modelling your domain.
Option B — Add the packages to an existing service
Requirements: .NET 8 SDK, MongoDB 4.2 or newer (the MongoDB driver is 3.x), and an OpenTelemetry collector if you intend to export telemetry.
Packages are versioned together. With central package management (Directory.Packages.props):
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<BytLabsPackageVersion>4.2.0</BytLabsPackageVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BytLabs.Domain" Version="$(BytLabsPackageVersion)" />
<PackageVersion Include="BytLabs.Application" Version="$(BytLabsPackageVersion)" />
<PackageVersion Include="BytLabs.DataAccess" Version="$(BytLabsPackageVersion)" />
<PackageVersion Include="BytLabs.DataAccess.MongoDB" Version="$(BytLabsPackageVersion)" />
<PackageVersion Include="BytLabs.Api" Version="$(BytLabsPackageVersion)" />
<PackageVersion Include="BytLabs.Api.Graphql" Version="$(BytLabsPackageVersion)" />
</ItemGroup>
Quick tour
1. Host (Program.cs) — one fluent chain wires the standard concerns:
var builder = WebApplication.CreateBuilder(args);
var app = ApiServiceBuilder.CreateBuilder(builder)
.WithHttpContextAccessor(uc => uc.AddResolver<HttpUserContextResolver>())
.WithMultiTenantContext(mt => mt.AddResolver<FromHeaderTenantIdResolver>())
.WithLogging().WithMetrics().WithTracing().WithHealthChecks()
.WithServiceConfiguration(services =>
{
services.AddInfrastructure(builder.Configuration); // your DI (below)
services.AddGraphQLService()
.AddMongoDbQuerySettings()
.AddCommandTypes().AddDtoTypes()
.AddMutationType<Mutation>().AddQueryType<Query>();
})
.BuildWebApp(app => { app.UseAuthentication(); app.UseAuthorization(); app.MapGraphQL(); });
app.Run();
2. Infrastructure wiring — CQS, AutoMapper, and per-aggregate repositories:
services.AddCQS(new[] { typeof(CreateProductCommand).Assembly });
services.AddAutoMapper(typeof(ProductMappingProfile));
services.AddMongoDatabase(config.GetConfiguration<MongoDatabaseConfiguration>())
.AddMongoRepository<Product, Guid>();
3. A feature — aggregate, command, and handler:
public sealed class Product : AggregateRootBase<Guid>, ISoftDeletable
{
public string Name { get; private set; }
public bool IsDeleted { get; private set; }
private Product(Guid id, string name) : base(id) => Name = name;
public static Product Create(Guid id, string name)
{
var p = new Product(id, name);
p.AddDomainEvent(new ProductCreated(id, name));
return p;
}
}
public record CreateProductCommand(Guid Id, string Name) : ICommand<ProductDto>;
public class CreateProductCommandHandler(IRepository<Product, Guid> repo, IMapper mapper)
: ICommandHandler<CreateProductCommand, ProductDto>
{
public async Task<ProductDto> Handle(CreateProductCommand request, CancellationToken ct)
=> mapper.Map<ProductDto>(await repo.InsertAsync(Product.Create(request.Id, request.Name), ct));
}
That is a complete, multi-tenant, observable, validated GraphQL mutation. See the Library Reference for the full API of each building block.
Configuration
{
"ObservabilityConfiguration": {
"ServiceName": "my-service",
"CollectorUrl": "http://localhost:4317",
"Timeout": 1000
},
"MongoDatabaseConfiguration": {
"DatabaseName": "myService",
"ConnectionString": "mongodb://localhost:27017?retryWrites=false",
"UseTransactions": false
}
}
Multitenancy. Each tenant's data lives in its own database, named
"{DatabaseName}-{tenantId}", resolved per request (for example from aTenantheader). Your domain code needs no tenant field or filter.Transactions. MongoDB transactions require a replica set — keep
UseTransactions: falsefor a standalone server.
Testing
Services built on the packages test cleanly at two levels:
- Unit tests exercise aggregates and domain logic directly (xUnit + FluentAssertions), with no infrastructure.
- Acceptance tests boot the API in-process with
WebApplicationFactoryand drive it through the generated GraphQL client against a real MongoDB, authenticating via a test scheme and selecting a tenant with a header.
The recipe catalog in the BytLabs.MicroserviceTemplate demonstrates both end to end.
Versioning and compatibility
- All BytLabs packages share a single version (
BytLabsPackageVersion) and should be upgraded together. - Targets .NET 8. Requires MongoDB 4.2 or newer (MongoDB.Driver 3.x) and HotChocolate 14.
Documentation
- Library Reference — per-package guides with usage examples.
- Getting Started — install, configure, and ship your first feature.
- Published documentation at docs.bytlabs.co.
- The recipe catalog in the BytLabs.MicroserviceTemplate for end-to-end patterns.
Documentation is built with DocFX. To preview locally:
docfx docfx.json --serve
Support
Contributing
Contributions are welcome. Fork the repository, create a feature branch, and open a pull request. Please
keep changes consistent with the existing architecture and update the relevant page under
docs/libraries/.
License
Licensed under the MIT License — see LICENSE.