Files
git.stella-ops.org/docs/router/GETTING_STARTED.md
StellaOps Bot e6c47c8f50 save progress
2025-12-28 23:49:56 +02:00

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.