Getting Started with Momentum
Welcome to Momentum - a comprehensive .NET 9 template system that generates complete, production-ready microservices solutions. Whether you're building APIs, event-driven backends, or stateful processing systems, Momentum provides the architecture, patterns, and infrastructure you need to get productive immediately.
Quick Start (2 Minutes)
Get a complete microservices solution running in under 2 minutes:
1. Install the Template
# Install from NuGet (recommended)
dotnet new install Momentum.Template
2. Generate Your Solution
# Create a complete microservices solution
dotnet new mmt -n OrderService --allow-scripts yes
cd OrderService
3. Start Everything with Aspire
Make sure you have the aspire workload installed:
dotnet workload install aspire
# Launch the complete application stack
dotnet run --project src/OrderService.AppHost
# Access the Aspire Dashboard: https://localhost:18110
# API endpoints: https://localhost:8111
# Documentation: http://localhost:8119
That's it! You now have a running microservices solution with:
- ✅ REST and gRPC APIs with sample endpoints
- ✅ Background event processing with Wolverine
- ✅ PostgreSQL database with migrations
- ✅ Apache Kafka messaging
- ✅ Comprehensive observability
- ✅ Live documentation
- ✅ Sample business domain (Cashiers/Invoices)
Technology Stack
Momentum is built on modern, production-proven technologies:
- 🎯 .NET 9: Latest framework with performance optimizations
- 🏗️ .NET Aspire: Local development orchestration and observability
- 🎭 Orleans: Stateful actor-based processing for complex workflows
- ⚡ Wolverine: CQRS/MediatR pattern with message handling
- 🚀 gRPC + REST: Dual API protocols for performance and compatibility
- 📡 Apache Kafka: Event streaming and reliable messaging
- 🗄️ PostgreSQL: Robust relational database with JSON support
- 🔄 Liquibase: Version-controlled database migrations
- 📊 OpenTelemetry: Distributed tracing and observability
- 🧪 Testcontainers: Real infrastructure for integration testing
Template System Overview
Momentum Template (dotnet new mmt
) generates customized microservices solutions that mirror real-world business operations. The template leverages Wolverine for CQRS patterns and supports multiple architectures from simple APIs to complex event-driven systems with Orleans actors.
Core Architecture Principles
- 🎯 Real-World Mirroring: Code structure corresponds to business operations and processes
- 🚫 No Smart Objects: Entities are data records, not self-modifying objects
- 🏢 Front/Back Office: Synchronous APIs vs Asynchronous processing
- 📡 Event-Driven: Integration events via Kafka with Wolverine message handling
- 🧪 Testing First: Comprehensive testing with real infrastructure
Prerequisites
Before getting started, ensure you have:
- .NET 9 SDK - Download here
- IDE: Visual Studio, VS Code with C# Dev Kit, or JetBrains Rider
- Docker Desktop - Required for databases, Kafka, and local development
Template Configuration Options
The template supports extensive customization through parameters. Here are the most common configurations:
Simple API Service
# Generate API-only service
dotnet new mmt -n PaymentService --api --back-office false --orleans false --docs false
Orleans Processing Engine
# Generate stateful processing service
dotnet new mmt -n WorkflowEngine --orleans --api false --port 9000
Full Stack Solution
# Generate complete solution with custom settings
dotnet new mmt -n EcommercePlatform --org "Acme Corp" --port 7000
Minimal Setup
# Clean slate without sample code
dotnet new mmt -n CleanService --no-sample
Available Template Parameters
The template offers comprehensive configuration options:
Core Components:
--api
: REST/gRPC API project (default: true)--back-office
: Background processing project (default: true)--orleans
: Orleans stateful processing project (default: false)--aspire
: .NET Aspire orchestration project (default: true)--docs
: VitePress documentation project (default: true)
Infrastructure:
--kafka
: Apache Kafka messaging (default: true)--db
: Database setup (default, npgsql, liquibase, none)--port
: Base port number (default: 8100)
Customization:
--org
: Organization name for copyright headers, github, etc--no-sample
: Exclude sample code (default: false, use--no-sample
to skip)--project-only
: Generate only projects without solution files--libs
: Include Momentum libraries as project references--lib-name
: Custom prefix to replace "Momentum" in library names
[!NOTE] For complete parameter documentation and all available combinations, see the template.json
file and the Template Options Guide for detailed use cases and examples.
Understanding the Generated Solution
The template generates a production-ready solution with clear separation of concerns:
Project Structure
OrderService/
├── src/
│ ├── OrderService.Api/ # REST & gRPC endpoints
│ ├── OrderService.BackOffice/ # Event processing
│ ├── OrderService.BackOffice.Orleans/ # Stateful processing (if enabled)
│ ├── OrderService.AppHost/ # Aspire orchestration
│ ├── OrderService/ # Core domain logic
│ └── OrderService.Contracts/ # Integration events
├── infra/
│ └── OrderService.Database/ # Liquibase migrations
├── tests/
│ └── OrderService.Tests/ # Comprehensive testing
├── docs/ # VitePress documentation
└── compose.yml # Docker Compose for services
Business Domain Organization
Generated code follows Domain-Driven Design principles with Wolverine CQRS patterns:
src/OrderService/
├── Cashiers/ # Sample business domain
│ ├── Commands/ # Business actions (Wolverine handlers)
│ ├── Queries/ # Information retrieval (Wolverine handlers)
│ ├── Data/ # Database operations
│ └── Contracts/ # Domain events
└── Invoices/ # Another sample domain
├── Commands/ # Commands with validation
├── Queries/ # Queries for data retrieval
├── Data/ # Database access layer
└── Contracts/ # Integration events
Port Configuration
Services use a base port system (default: 8100):
Service | HTTP | HTTPS | gRPC | Purpose |
---|---|---|---|---|
Aspire Dashboard | 18100 | 18110 | - | Development dashboard |
API | 8101 | 8111 | 8102 | REST & gRPC endpoints |
BackOffice | 8103 | 8113 | - | Background processing |
Orleans | 8104 | 8114 | - | Stateful processing |
Documentation | 8119 | - | - | VitePress docs |
PostgreSQL | 54320 | - | - | Database |
Kafka | 59092 | - | - | Message broker |
Customize the base port with --port 9000
to use 9100, 9101, etc.
Individual Libraries Approach
For existing projects or custom architectures, you can use Momentum Libraries individually. All these capabilities can also be configured through the template system with selective feature inclusion:
📚 Note: The template system (dotnet new mmt
) allows you to configure which libraries and features to include, so you can generate solutions with only the specific Momentum capabilities you need.
Build a simple API service using individual Momentum Libraries when you need to add capabilities to existing projects:
1. Create New Project
# Create a new ASP.NET Core API project
dotnet new webapi -n OrderService
cd OrderService
# Add the essential Momentum packages
dotnet add package Momentum.ServiceDefaults --version 0.0.1
dotnet add package Momentum.Extensions --version 0.0.1
2. Configure Service Defaults
Replace the content of Program.cs
:
using Momentum.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add Momentum service defaults (observability, health checks, validation)
builder.AddServiceDefaults();
// Add your application services
builder.Services.AddScoped<IOrderService, OrderService>();
var app = builder.Build();
// Configure the HTTP request pipeline
app.MapDefaultEndpoints(); // Health checks, metrics
// Add your API endpoints
app.MapPost("/orders", async (CreateOrderCommand command, IOrderService orderService) =>
{
var result = await orderService.CreateOrderAsync(command);
return result.IsSuccess
? Results.Created($"/orders/{result.Value.Id}", result.Value)
: Results.BadRequest(result.Errors);
});
app.MapGet("/orders/{id:guid}", async (Guid id, IOrderService orderService) =>
{
var result = await orderService.GetOrderAsync(id);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound();
});
await app.RunAsync();
3. Define Commands and Models
Create Models/Order.cs
:
namespace OrderService.Models;
public record Order(
Guid Id,
string CustomerName,
string ProductName,
decimal Amount,
DateTime CreatedAt
);
public record CreateOrderCommand(
string CustomerName,
string ProductName,
decimal Amount
);
4. Implement Service with Result Types
Create Services/IOrderService.cs
:
using Momentum.Extensions;
using OrderService.Models;
namespace OrderService.Services;
public interface IOrderService
{
Task<Result<Order>> CreateOrderAsync(CreateOrderCommand command);
Task<Result<Order>> GetOrderAsync(Guid id);
}
Create Services/OrderService.cs
:
using Momentum.Extensions;
using OrderService.Models;
using FluentValidation.Results;
namespace OrderService.Services;
public class OrderService : IOrderService
{
private static readonly Dictionary<Guid, Order> _orders = new();
public Task<Result<Order>> CreateOrderAsync(CreateOrderCommand command)
{
// Validate input (in real apps, use FluentValidation)
if (string.IsNullOrWhiteSpace(command.CustomerName))
{
var errors = new List<ValidationFailure>
{
new("CustomerName", "Customer name is required")
};
return Task.FromResult(Result<Order>.Failure(errors));
}
if (command.Amount <= 0)
{
var errors = new List<ValidationFailure>
{
new("Amount", "Amount must be greater than zero")
};
return Task.FromResult(Result<Order>.Failure(errors));
}
// Create the order
var order = new Order(
Id: Guid.CreateVersion7(),
CustomerName: command.CustomerName,
ProductName: command.ProductName,
Amount: command.Amount,
CreatedAt: DateTime.UtcNow
);
_orders[order.Id] = order;
return Task.FromResult(Result<Order>.Success(order));
}
public Task<Result<Order>> GetOrderAsync(Guid id)
{
if (_orders.TryGetValue(id, out var order))
{
return Task.FromResult(Result<Order>.Success(order));
}
var errors = new List<ValidationFailure>
{
new("Id", "Order not found")
};
return Task.FromResult(Result<Order>.Failure(errors));
}
}
5. Test Your Service
# Run the application
dotnet run
# The service starts with:
# - API endpoints: https://localhost:7001
# - Health check: https://localhost:7001/health
# - Metrics: https://localhost:7001/metrics
# Test creating an order
curl -X POST https://localhost:7001/orders \
-H "Content-Type: application/json" \
-d '{
"customerName": "John Doe",
"productName": "Laptop",
"amount": 1299.99
}'
# Test retrieving an order (use the ID from the response above)
curl https://localhost:7001/orders/{order-id}
# Check health status
curl https://localhost:7001/health
Library Integration Results
Congratulations! You've added powerful capabilities to your application:
- ✅ Structured error handling with Result types
- ✅ Built-in health checks and metrics
- ✅ OpenTelemetry observability
- ✅ Structured logging with Serilog
- ✅ Production-ready service defaults
Understanding What Happened
In just a few minutes, you added powerful capabilities to your application:
Service Defaults (builder.AddServiceDefaults()
)
- Health Checks:
/health
(detailed) and/alive
(simple) endpoints - OpenTelemetry: Metrics, tracing, and logging correlation
- Serilog: Structured logging with exception details
- Resilience: HTTP client retry and circuit breaker patterns
- Service Discovery: Automatic endpoint resolution in distributed systems
Result Types (ResultOfT
)
- Error Handling: No more try-catch blocks for business logic
- Type Safety: Compile-time guarantees about success/failure states
- Validation: Built-in support for FluentValidation results
- API Friendly: Easy conversion to HTTP status codes
Observability Out-of-the-Box
- Structured Logs: JSON format with correlation IDs
- Metrics: Application and infrastructure metrics
- Tracing: Request tracking across service boundaries
- Health Monitoring: Automated health check endpoints
Individual Libraries Overview
When using libraries individually (rather than the template), each library serves a specific purpose and can be used independently:
Momentum.Extensions - Core Foundation
Essential utilities and patterns for any .NET application.
dotnet add package Momentum.Extensions
Key Features:
- ResultOfT Types: Elegant error handling without exceptions
- Validation Integration: FluentValidation helpers and extensions
- Data Access: Enhanced Dapper extensions and LINQ2DB support
- Messaging Abstractions: Base interfaces for CQRS and event-driven design with Wolverine integration
Use When: You want robust error handling and core utilities in any .NET project.
Momentum.ServiceDefaults - Production Readiness
Complete service configuration for Aspire-based applications.
dotnet add package Momentum.ServiceDefaults
Key Features:
- Aspire Integration: Full .NET Aspire service defaults implementation
- Observability Stack: OpenTelemetry + Serilog for monitoring
- Health Checks: Built-in application health monitoring
- Resilience: HTTP client resilience patterns
- Service Discovery: Automatic service resolution
Use When: Building microservices, APIs, or any distributed application that needs production-ready configuration.
Momentum.ServiceDefaults.Api - API Enhancements
Additional features specifically for REST and gRPC APIs.
dotnet add package Momentum.ServiceDefaults.Api
Key Features:
- OpenAPI: Enhanced Swagger documentation with XML docs
- gRPC Support: Service registration and health checks
- Route Conventions: Kebab-case URL transformations
- Response Types: Automatic response type generation
Use When: Building REST APIs or gRPC services that need enhanced documentation and conventions.
Momentum.Extensions.SourceGenerators - Code Generation
Compile-time code generation for common patterns.
dotnet add package Momentum.Extensions.SourceGenerators
Key Features:
- DbCommand Generation: Type-safe database command handlers
- Zero Runtime Overhead: All generation happens at compile time
- IDE Integration: Generated code appears in IntelliSense
- Customizable: Configure generation through attributes and MSBuild properties
Use When: You want to eliminate boilerplate code and ensure type safety for database operations.
Momentum.Extensions.Messaging.Kafka - Event-Driven Architecture
Kafka integration with CloudEvents standard support.
dotnet add package Momentum.Extensions.Messaging.Kafka
Key Features:
- CloudEvents: Standards-compliant event serialization
- Kafka Integration: Producer and consumer patterns
- Partition Key Support: Automatic partitioning strategies
- Observability: Built-in metrics and tracing
Use When: Building event-driven microservices that need reliable messaging.
Advanced Library Integration Example
When using individual libraries, here's how to build a more complete application that demonstrates library integration:
1. Setup Project
# Create new project
dotnet new webapi -n EcommerceService
cd EcommerceService
# Add comprehensive Momentum packages
dotnet add package Momentum.ServiceDefaults
dotnet add package Momentum.Extensions
dotnet add package Momentum.Extensions.SourceGenerators
dotnet add package Npgsql # For PostgreSQL
dotnet add package FluentValidation.DependencyInjectionExtensions
2. Database Models and Commands
Create Domain/Orders/Order.cs
:
namespace EcommerceService.Domain.Orders;
// Database entity
public class OrderEntity
{
public Guid Id { get; set; }
public string CustomerName { get; set; } = string.Empty;
public string ProductName { get; set; } = string.Empty;
public decimal Amount { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
// Public model
public record Order(
Guid Id,
string CustomerName,
string ProductName,
decimal Amount,
DateTime CreatedAt
);
// Extension for conversion
public static class OrderExtensions
{
public static Order ToModel(this OrderEntity entity) => new(
entity.Id,
entity.CustomerName,
entity.ProductName,
entity.Amount,
entity.CreatedAt
);
}
3. Commands with Validation
Create Domain/Orders/Commands/CreateOrder.cs
:
using FluentValidation;
using Momentum.Extensions;
namespace EcommerceService.Domain.Orders.Commands;
public record CreateOrderCommand(
string CustomerName,
string ProductName,
decimal Amount
) : ICommand<Result<Order>>;
public class CreateOrderValidator : AbstractValidator<CreateOrderCommand>
{
public CreateOrderValidator()
{
RuleFor(x => x.CustomerName)
.NotEmpty()
.WithMessage("Customer name is required")
.MinimumLength(2)
.WithMessage("Customer name must be at least 2 characters")
.MaximumLength(100)
.WithMessage("Customer name cannot exceed 100 characters");
RuleFor(x => x.ProductName)
.NotEmpty()
.WithMessage("Product name is required")
.MaximumLength(200)
.WithMessage("Product name cannot exceed 200 characters");
RuleFor(x => x.Amount)
.GreaterThan(0)
.WithMessage("Amount must be greater than zero")
.LessThanOrEqualTo(1000000)
.WithMessage("Amount cannot exceed $1,000,000");
}
}
4. Database Commands with Source Generation
Create Domain/Orders/Data/OrderDbCommands.cs
:
using Dapper;
using Momentum.Extensions.Abstractions.Dapper;
using System.Data;
namespace EcommerceService.Domain.Orders.Data;
public static class CreateOrderDbCommandHandler
{
[DbCommand]
public record DbCommand(OrderEntity Order) : ICommand<OrderEntity>;
public static async Task<OrderEntity> Handle(
DbCommand command,
IDbConnection db,
CancellationToken cancellationToken)
{
const string sql = """
INSERT INTO orders (id, customer_name, product_name, amount, created_at, updated_at)
VALUES (@Id, @CustomerName, @ProductName, @Amount, @CreatedAt, @UpdatedAt)
RETURNING *;
""";
var order = command.Order;
return await db.QuerySingleAsync<OrderEntity>(sql, new
{
order.Id,
order.CustomerName,
order.ProductName,
order.Amount,
order.CreatedAt,
order.UpdatedAt
});
}
}
public static class GetOrderDbCommandHandler
{
[DbCommand]
public record DbCommand(Guid OrderId) : ICommand<OrderEntity?>;
public static async Task<OrderEntity?> Handle(
DbCommand command,
IDbConnection db,
CancellationToken cancellationToken)
{
const string sql = "SELECT * FROM orders WHERE id = @OrderId";
return await db.QuerySingleOrDefaultAsync<OrderEntity>(sql, new { command.OrderId });
}
}
5. Business Logic Handler
Create Domain/Orders/Commands/CreateOrderHandler.cs
:
using EcommerceService.Domain.Orders.Data;
using FluentValidation;
using Momentum.Extensions;
namespace EcommerceService.Domain.Orders.Commands;
public static class CreateOrderCommandHandler
{
public static async Task<Result<Order>> Handle(
CreateOrderCommand command,
IValidator<CreateOrderCommand> validator,
IMessageBus messageBus,
CancellationToken cancellationToken)
{
// Validate input
var validationResult = await validator.ValidateAsync(command, cancellationToken);
if (!validationResult.IsValid)
{
return Result<Order>.Failure(validationResult.Errors);
}
// Create database entity
var orderEntity = new OrderEntity
{
Id = Guid.CreateVersion7(),
CustomerName = command.CustomerName,
ProductName = command.ProductName,
Amount = command.Amount,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
// Save to database using generated command
var dbCommand = new CreateOrderDbCommandHandler.DbCommand(orderEntity);
var savedOrder = await messageBus.InvokeAsync(dbCommand, cancellationToken);
return Result<Order>.Success(savedOrder.ToModel());
}
}
6. API Configuration
Update Program.cs
:
using EcommerceService.Domain.Orders.Commands;
using FluentValidation;
using Momentum.Extensions;
using Npgsql;
using System.Data;
var builder = WebApplication.CreateBuilder(args);
// Add Momentum service defaults
builder.AddServiceDefaults();
// Add database connection
builder.Services.AddScoped<IDbConnection>(provider =>
{
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? "Host=localhost;Database=ecommerce;Username=postgres;Password=password";
return new NpgsqlConnection(connectionString);
});
// Add FluentValidation
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderValidator>();
// Add custom services
builder.Services.AddScoped<IOrderService, OrderService>();
var app = builder.Build();
// Configure pipeline
app.MapDefaultEndpoints();
// Add API endpoints
app.MapPost("/orders", async (
CreateOrderCommand command,
IValidator<CreateOrderCommand> validator,
IMessageBus messageBus,
CancellationToken cancellationToken) =>
{
var result = await CreateOrderCommandHandler.Handle(command, validator, messageBus, cancellationToken);
return result.IsSuccess
? Results.Created($"/orders/{result.Value.Id}", result.Value)
: Results.BadRequest(result.Errors.Select(e => new { e.PropertyName, e.ErrorMessage }));
});
app.MapGet("/orders/{id:guid}", async (
Guid id,
IMessageBus messageBus,
CancellationToken cancellationToken) =>
{
var dbCommand = new GetOrderDbCommandHandler.DbCommand(id);
var orderEntity = await messageBus.InvokeAsync(dbCommand, cancellationToken);
return orderEntity is not null
? Results.Ok(orderEntity.ToModel())
: Results.NotFound();
});
await app.RunAsync();
7. Database Setup
Create a simple migration script setup.sql
:
-- Create orders table
CREATE TABLE IF NOT EXISTS orders (
id UUID PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL,
product_name VARCHAR(200) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
);
-- Create index for performance
CREATE INDEX IF NOT EXISTS idx_orders_created_at ON orders(created_at);
8. Configuration
Create appsettings.Development.json
:
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=ecommerce;Username=postgres;Password=password"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9. Run and Test
# Start PostgreSQL (using Docker)
docker run --name postgres-ecommerce -e POSTGRES_PASSWORD=password -e POSTGRES_DB=ecommerce -p 5432:5432 -d postgres:15
# Run the setup script
psql -h localhost -U postgres -d ecommerce -f setup.sql
# Start the application
dotnet run
# Test creating orders
curl -X POST https://localhost:7001/orders \
-H "Content-Type: application/json" \
-d '{
"customerName": "Alice Johnson",
"productName": "Gaming Laptop",
"amount": 1899.99
}'
# Test validation errors
curl -X POST https://localhost:7001/orders \
-H "Content-Type: application/json" \
-d '{
"customerName": "",
"productName": "Invalid Product",
"amount": -100
}'
Integration Patterns
Combining Multiple Libraries
The power of Momentum Libraries comes from how they work together:
var builder = WebApplication.CreateBuilder(args);
// Foundation: Service defaults for observability and health
builder.AddServiceDefaults();
// API enhancements: OpenAPI, gRPC, route conventions
builder.AddApiDefaults();
// Extensions: Result types, validation, data access
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
// Source generators: Automatic DbCommand handling
// (Automatically activated when package is referenced)
// Messaging: Event-driven capabilities
builder.AddKafkaMessaging(builder.Configuration);
var app = builder.Build();
// All the endpoints and middleware are configured
app.MapDefaultEndpoints();
app.MapApiEndpoints();
await app.RunAsync();
Error Handling Patterns
Consistent error handling across your application:
// Service layer
public async Task<Result<Customer>> GetCustomerAsync(Guid id)
{
var customer = await customerRepository.GetByIdAsync(id);
return customer switch
{
null => Result<Customer>.NotFound("Customer", id.ToString()),
_ => Result<Customer>.Success(customer)
};
}
// API layer
app.MapGet("/customers/{id:guid}", async (Guid id, ICustomerService service) =>
{
var result = await service.GetCustomerAsync(id);
return result.Match(
onSuccess: customer => Results.Ok(customer),
onFailure: errors => Results.BadRequest(errors)
);
});
Event-Driven Integration
Publish and consume events across services:
// Domain event
[EventTopic("ecommerce.orders.order-created")]
public record OrderCreated(
[PartitionKey] Guid CustomerId,
Order Order
);
// Command handler that publishes events
public static async Task<(Result<Order>, OrderCreated?)> Handle(
CreateOrderCommand command,
IMessageBus messageBus)
{
// Business logic...
var order = await CreateOrderInDatabase(command);
// Create integration event
var orderCreated = new OrderCreated(order.CustomerId, order);
return (Result<Order>.Success(order), orderCreated);
}
// Event handler in another service
public class OrderCreatedHandler
{
public async Task Handle(OrderCreated orderCreated, CancellationToken cancellationToken)
{
// Process the order creation in inventory service
await inventoryService.ReserveItemsAsync(orderCreated.Order.Items, cancellationToken);
}
}
Best Practices
1. Start Small, Scale Up
// Begin with essentials
builder.AddServiceDefaults();
builder.Services.AddScoped<IMyService, MyService>();
// Add capabilities as needed
builder.AddApiDefaults(); // When you need enhanced APIs
builder.AddKafkaMessaging(); // When you need events
// Source generators activate automatically
2. Consistent Error Handling
// Always use Result<T> for business operations
public async Task<Result<Customer>> CreateCustomerAsync(CreateCustomerCommand command)
{
// Validation
var validationResult = await validator.ValidateAsync(command);
if (!validationResult.IsValid)
return Result<Customer>.Failure(validationResult.Errors);
// Business logic
try
{
var customer = await customerRepository.CreateAsync(command.ToEntity());
return Result<Customer>.Success(customer.ToModel());
}
catch (Exception ex) when (ex is not ValidationException)
{
logger.LogError(ex, "Failed to create customer");
return Result<Customer>.Failure("An error occurred while creating the customer");
}
}
3. Leverage Source Generation
// Mark database commands for generation
[DbCommand]
public record GetCustomersQuery(int Page, int PageSize) : IQuery<IEnumerable<Customer>>;
// The source generator creates the handler automatically
// Use dependency injection to access the generated handler
4. Configuration Patterns
// Use configuration sections for complex settings
builder.Services.Configure<OrderServiceOptions>(
builder.Configuration.GetSection("OrderService"));
// Validate configuration at startup
builder.Services.AddOptions<OrderServiceOptions>()
.Bind(builder.Configuration.GetSection("OrderService"))
.ValidateDataAnnotations()
.ValidateOnStart();
Next Steps
Choose your path based on how you're using Momentum:
If You Used the Template
- Explore the Generated Solution - Understand what was created
- Add Your Business Domain - Replace sample code with your logic
- Deploy to Production - Deploy your microservices (coming soon)
- Template Options Reference - Complete parameter guide
If You're Using Individual Libraries
- Service Configuration Guide - Deep dive into observability, health checks, and resilience
- Error Handling Patterns - Master the Result pattern and validation
- Database Operations - Learn DbCommand source generation and best practices
- Event-Driven Messaging - Build robust event-driven architectures with Kafka
- Testing Strategies - Comprehensive testing patterns for Momentum applications
Advanced Topics for Both Approaches
- CQRS Implementation - Command/Query separation patterns
- Architecture Decisions - Design patterns and architectural guidance
- Best Practices - Production-ready patterns and guidelines
- Troubleshooting - Common issues and solutions
Community and Support
- API Reference: Browse the complete API documentation
- Sample Applications: See real-world examples
- GitHub Discussions: Ask questions and share experiences
- Contributing: Help improve the libraries for everyone
Common Patterns Quick Reference
Result Type Usage
// Success
return Result<Customer>.Success(customer);
// Single error
return Result<Customer>.Failure("Customer not found");
// Multiple errors (validation)
return Result<Customer>.Failure(validationResult.Errors);
// Chaining operations
var result = await GetCustomerAsync(id);
if (result.IsFailure) return result;
return await UpdateCustomerAsync(result.Value);
Service Registration
// Essential services
builder.AddServiceDefaults();
// API services
builder.AddApiDefaults();
// Database
builder.Services.AddScoped<IDbConnection>(_ =>
new NpgsqlConnection(connectionString));
// Validation
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
// Custom services
builder.Services.AddScoped<IOrderService, OrderService>();
API Endpoint Patterns
// Command endpoint with validation
app.MapPost("/orders", async (CreateOrderCommand command, IMessageBus bus) =>
{
var result = await bus.InvokeAsync(command);
return result.IsSuccess
? Results.Created($"/orders/{result.Value.Id}", result.Value)
: Results.BadRequest(result.Errors);
});
// Query endpoint
app.MapGet("/orders/{id:guid}", async (Guid id, IMessageBus bus) =>
{
var query = new GetOrderQuery(id);
var result = await bus.InvokeAsync(query);
return result.IsSuccess ? Results.Ok(result.Value) : Results.NotFound();
});
Ready to build amazing applications?
Template Users (Recommended)
- Template Walkthrough - Understand your generated solution
- Adding Business Domains - Build your application logic
- Template Options Guide - Master all configuration options
Library Users
- Service Configuration - Configure observability and infrastructure
- Database Integration - Add database operations with source generation
- Event Messaging - Build event-driven microservices
Reference & Examples
- API Reference - Complete library documentation
- Sample Applications - Real-world examples
- GitHub Discussions - Community support
Momentum: Real-world microservices. Modern architecture. Production-ready from day one.