Backend Stack
Vulcan's backend is built with .NET 10 LTS, using ASP.NET Core Minimal APIs and Entity Framework Core.
Core Technologies
| Technology | Version | Purpose |
|---|---|---|
| .NET | 10.0 LTS | Modern, high-performance framework for building APIs and services |
| ASP.NET Core | 10.0 | Web framework for building RESTful APIs with robust middleware |
| Entity Framework Core | 10.0 | Modern ORM for .NET with LINQ support, JSON complex types, and migrations |
Validation & Documentation
| Technology | Version | Purpose |
|---|---|---|
| FluentValidation | 11.x | Strongly-typed validation rules for .NET applications |
| Swagger/OpenAPI | 7.x | API documentation and interactive testing interface |
Database
| Technology | Version | Purpose |
|---|---|---|
| PostgreSQL | 16.x | Robust, open-source relational database with advanced features |
| Npgsql | 10.x | .NET data provider for PostgreSQL |
CQRS & Logging
| Technology | Version | Purpose |
|---|---|---|
| Mediator | 3.0.1 | Source generator-based CQRS dispatcher |
| Serilog | 3.x | Structured logging framework |
Testing
| Technology | Version | Purpose |
|---|---|---|
| xUnit | 2.x | Unit testing framework for .NET applications |
| FluentAssertions | 7.x | Fluent assertion library for tests |
Architecture Patterns
Vertical Slice Architecture
Each feature is self-contained with all CRUD operations colocated:
Features/
└── Leads/
├── GetAllLeads/
│ ├── GetAllLeadsQuery.cs
│ ├── GetAllLeadsHandler.cs
│ └── GetAllLeadsResponse.cs
├── GetLead/
├── CreateLead/
├── UpdateLead/
└── DeleteLead/CQRS Pattern
Commands (writes) and Queries (reads) are separated:
csharp
// Query - read operation
public record GetLeadQuery(Guid Id) : IRequest<LeadResponse?>;
// Command - write operation
public record CreateLeadCommand(
string Name,
string Email
) : IRequest<CreateLeadResponse>;Mediator Pattern
Using Mediator 3.0.1 for dispatching commands and queries:
csharp
// Handler
public class GetLeadHandler : IRequestHandler<GetLeadQuery, LeadResponse?>
{
private readonly AppDbContext _context;
public GetLeadHandler(AppDbContext context)
{
_context = context;
}
public async ValueTask<LeadResponse?> Handle(
GetLeadQuery request,
CancellationToken cancellationToken)
{
var lead = await _context.Leads
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
return lead?.ToResponse();
}
}Minimal API Endpoints
csharp
public static class LeadEndpoints
{
public static void MapLeadEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/leads")
.WithTags("Leads")
.RequireAuthorization();
group.MapGet("/", async (IMediator mediator) =>
await mediator.Send(new GetAllLeadsQuery()));
group.MapGet("/{id:guid}", async (Guid id, IMediator mediator) =>
await mediator.Send(new GetLeadQuery(id)));
group.MapPost("/", async (CreateLeadCommand cmd, IMediator mediator) =>
await mediator.Send(cmd));
}
}Database Conventions
| Type | Convention | Example |
|---|---|---|
| Tables | snake_case plural | leads, work_packages |
| Columns | snake_case | first_name, created_at |
| Primary Keys | id | id |
| Foreign Keys | [entity]_id | customer_id |
| Indexes | ix_[table]_[columns] | ix_leads_email |
Build Commands
bash
dotnet run # Start service
dotnet build # Build
dotnet test # Run tests
dotnet ef migrations add <Name> # Create migration
dotnet ef database update # Apply migrationsKey Conventions
Important
- Use
ValueTask<T>notTask<T>for Mediator handlers - All endpoints require authorization unless
[AllowAnonymous] - One database per capability - never share databases
- Validators are colocated with Commands in feature folders