9.1 KiB
9.1 KiB
Getting Started with StellaOps Router
This guide walks you through building your first microservice with the StellaOps Router framework.
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;
using StellaOps.Router.Transport.InMemory;
var builder = WebApplication.CreateBuilder(args);
// Add gateway services
builder.Services.AddRouterGateway(builder.Configuration);
// Add InMemory transport for development
builder.Services.AddInMemoryTransport();
// 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 rate limits in router.yaml
- Observability: Add OpenTelemetry tracing
- Testing: Write integration tests using the Router.Testing library
See the examples directory for complete working examples.