Getting Started with the Stella Ops Router
Audience: Developers building a microservice that joins the Stella Ops Router fabric. Scope: A hands-on walkthrough that builds a small order service and a gateway end to end, then points you at production hardening (transports, auth, rate limiting, observability).
For the architecture and rationale, see the Router module overview. For admission control, see Rate Limiting.
Prerequisites
- .NET 10 SDK
- Docker (optional, for containerized deployment)
Step 1: Create the Microservice Project
mkdir MyService
cd MyService
dotnet new console -n MyService
cd MyService
Add the required packages:
dotnet add package StellaOps.Microservice
dotnet add package StellaOps.Router.Transport.InMemory
Step 2: Define Your Data Models
Create Models/OrderModels.cs:
namespace MyService.Models;
public sealed class CreateOrderRequest
{
public required string CustomerId { get; init; }
public required decimal Amount { get; init; }
public string? Description { get; init; }
}
public sealed class CreateOrderResponse
{
public required string OrderId { get; init; }
public DateTimeOffset CreatedAt { get; init; }
public string Status { get; init; } = "created";
}
public sealed class Order
{
public required string OrderId { get; init; }
public required string CustomerId { get; init; }
public required decimal Amount { get; init; }
public string? Description { get; init; }
public DateTimeOffset CreatedAt { get; init; }
}
Step 3: Create Endpoints
Create Endpoints/CreateOrderEndpoint.cs:
using MyService.Models;
using StellaOps.Microservice;
namespace MyService.Endpoints;
/// <summary>
/// Creates a new order.
/// </summary>
[StellaEndpoint("POST", "/orders", TimeoutSeconds = 30)]
public sealed class CreateOrderEndpoint : IStellaEndpoint<CreateOrderRequest, CreateOrderResponse>
{
private readonly ILogger<CreateOrderEndpoint> _logger;
public CreateOrderEndpoint(ILogger<CreateOrderEndpoint> logger)
{
_logger = logger;
}
public Task<CreateOrderResponse> HandleAsync(
CreateOrderRequest request,
CancellationToken cancellationToken)
{
var orderId = $"ORD-{Guid.NewGuid():N}"[..16];
_logger.LogInformation(
"Created order {OrderId} for customer {CustomerId}, amount: {Amount}",
orderId, request.CustomerId, request.Amount);
return Task.FromResult(new CreateOrderResponse
{
OrderId = orderId,
CreatedAt = DateTimeOffset.UtcNow
});
}
}
Create Endpoints/GetOrderEndpoint.cs:
using MyService.Models;
using StellaOps.Microservice;
namespace MyService.Endpoints;
/// <summary>
/// Retrieves an order by ID.
/// </summary>
[StellaEndpoint("GET", "/orders/{id}", TimeoutSeconds = 10)]
public sealed class GetOrderEndpoint : IRawStellaEndpoint
{
private readonly ILogger<GetOrderEndpoint> _logger;
public GetOrderEndpoint(ILogger<GetOrderEndpoint> logger)
{
_logger = logger;
}
public Task<RawResponse> HandleAsync(
RawRequestContext context,
CancellationToken cancellationToken)
{
var orderId = context.PathParameters["id"];
_logger.LogInformation("Fetching order {OrderId}", orderId);
// In a real app, you'd look up the order from a database
var order = new Order
{
OrderId = orderId,
CustomerId = "CUST-001",
Amount = 99.99m,
Description = "Sample order",
CreatedAt = DateTimeOffset.UtcNow.AddHours(-1)
};
var json = System.Text.Json.JsonSerializer.Serialize(order);
var body = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json));
var headers = new HeaderCollection();
headers.Set("Content-Type", "application/json");
return Task.FromResult(new RawResponse
{
StatusCode = 200,
Headers = headers,
Body = body
});
}
}
Step 4: Configure the Service
Update Program.cs:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MyService.Endpoints;
using StellaOps.Microservice;
using StellaOps.Router.Common.Enums;
using StellaOps.Router.Transport.InMemory;
var builder = Host.CreateApplicationBuilder(args);
// Configure logging
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
// Configure the microservice
builder.Services.AddStellaMicroservice(options =>
{
options.ServiceName = "order-service";
options.Version = "1.0.0";
options.Region = "local";
options.InstanceId = $"order-{Environment.MachineName}";
// Connect to gateway (use InMemory for development)
options.Routers =
[
new RouterEndpointConfig
{
Host = "localhost",
Port = 5100,
TransportType = TransportType.InMemory
}
];
});
// Register transport
builder.Services.AddInMemoryTransport();
// Register endpoint handlers
builder.Services.AddScoped<CreateOrderEndpoint>();
builder.Services.AddScoped<GetOrderEndpoint>();
var host = builder.Build();
Console.WriteLine("Order Service starting...");
Console.WriteLine("Endpoints:");
Console.WriteLine(" POST /orders");
Console.WriteLine(" GET /orders/{id}");
await host.RunAsync();
Step 5: Create the Gateway
Create a separate project for the gateway:
cd ..
dotnet new web -n MyGateway
cd MyGateway
dotnet add package StellaOps.Router.Gateway
dotnet add package StellaOps.Router.Transport.InMemory
Update Program.cs:
using StellaOps.Router.Gateway;
using StellaOps.Router.Gateway.Authorization;
using StellaOps.Router.Gateway.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Add gateway services
builder.Services.AddRouterGateway(builder.Configuration);
// Add process-local transport only for an explicit local harness
builder.Services.AddRouterGatewayLocalInMemoryHarness(builder.Environment);
// No-op authorization for demo (use real auth in production)
builder.Services.AddNoOpAuthorityIntegration();
builder.Services.AddAuthentication();
// OpenAPI
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
// Middleware pipeline
app.UseAuthentication();
app.UseClaimsAuthorization();
// OpenAPI endpoint (aggregates from all microservices)
app.MapRouterOpenApi();
// Health check
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));
// Router gateway handles all other routes
app.UseRouterGateway();
Console.WriteLine("Gateway starting on http://localhost:5000");
app.Run("http://localhost:5000");
Step 6: Run the Services
Open two terminals:
Terminal 1 - Gateway:
cd MyGateway
dotnet run
Terminal 2 - Microservice:
cd MyService
dotnet run
Step 7: Test the API
Create an order:
curl -X POST http://localhost:5000/orders \
-H "Content-Type: application/json" \
-d '{"customerId": "CUST-001", "amount": 99.99}'
Response:
{
"orderId": "ORD-abc123def456",
"createdAt": "2024-01-15T10:30:00Z",
"status": "created"
}
Get an order:
curl http://localhost:5000/orders/ORD-abc123def456
Step 8: Add Validation (Optional)
Add JSON Schema validation to your endpoints:
using StellaOps.Microservice;
using StellaOps.Microservice.Validation;
[StellaEndpoint("POST", "/orders", TimeoutSeconds = 30)]
[ValidateSchema] // Enables JSON Schema validation
public sealed class CreateOrderEndpoint : IStellaEndpoint<CreateOrderRequest, CreateOrderResponse>
{
// ...
}
The source generator will create a JSON Schema based on your request type’s properties and validate incoming requests automatically.
Step 9: Add Streaming Support (Optional)
For large file uploads or real-time data:
[StellaEndpoint("POST", "/orders/{id}/documents", SupportsStreaming = true)]
public sealed class UploadDocumentEndpoint : IRawStellaEndpoint
{
public async Task<RawResponse> HandleAsync(
RawRequestContext context,
CancellationToken cancellationToken)
{
var orderId = context.PathParameters["id"];
var contentType = context.Headers["Content-Type"] ?? "application/octet-stream";
// Stream the body directly without buffering
await using var bodyStream = context.Body;
var totalBytes = 0L;
var buffer = new byte[8192];
int bytesRead;
while ((bytesRead = await bodyStream.ReadAsync(buffer, cancellationToken)) > 0)
{
totalBytes += bytesRead;
// Process chunk...
}
var headers = new HeaderCollection();
headers.Set("Content-Type", "application/json");
var response = $$"""{"orderId":"{{orderId}}","bytesReceived":{{totalBytes}}}""";
return new RawResponse
{
StatusCode = 200,
Headers = headers,
Body = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(response))
};
}
}
Next Steps
- Production Deployment: Switch from InMemory to TCP or TLS transport.
- Authentication: Integrate with the Authority module for OAuth/OIDC.
- Rate Limiting: Configure admission control at the gateway — see Rate Limiting.
- Observability: Add OpenTelemetry tracing.
- Testing: Write integration tests using the
Router.Testinglibrary.
See the samples directory for complete, runnable example projects (gateway plus billing and inventory microservices).
