docs consolidation and others

This commit is contained in:
master
2026-01-06 19:02:21 +02:00
parent d7bdca6d97
commit 4789027317
849 changed files with 16551 additions and 66770 deletions

View File

@@ -0,0 +1,370 @@
# 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
```bash
mkdir MyService
cd MyService
dotnet new console -n MyService
cd MyService
```
Add the required packages:
```bash
dotnet add package StellaOps.Microservice
dotnet add package StellaOps.Router.Transport.InMemory
```
## Step 2: Define Your Data Models
Create `Models/OrderModels.cs`:
```csharp
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`:
```csharp
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`:
```csharp
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`:
```csharp
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:
```bash
cd ..
dotnet new web -n MyGateway
cd MyGateway
dotnet add package StellaOps.Router.Gateway
dotnet add package StellaOps.Router.Transport.InMemory
```
Update `Program.cs`:
```csharp
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:**
```bash
cd MyGateway
dotnet run
```
**Terminal 2 - Microservice:**
```bash
cd MyService
dotnet run
```
## Step 7: Test the API
Create an order:
```bash
curl -X POST http://localhost:5000/orders \
-H "Content-Type: application/json" \
-d '{"customerId": "CUST-001", "amount": 99.99}'
```
Response:
```json
{
"orderId": "ORD-abc123def456",
"createdAt": "2024-01-15T10:30:00Z",
"status": "created"
}
```
Get an order:
```bash
curl http://localhost:5000/orders/ORD-abc123def456
```
## Step 8: Add Validation (Optional)
Add JSON Schema validation to your endpoints:
```csharp
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:
```csharp
[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](../examples/) directory for complete working examples.

View File

@@ -0,0 +1,122 @@
# Router Rate Limiting
Router rate limiting is a **gateway-owned** control plane feature implemented in `StellaOps.Router.Gateway`. It enforces limits centrally so microservices do not implement ad-hoc HTTP throttling.
## Behavior
When a request is denied the Router returns:
- `429 Too Many Requests`
- `Retry-After: <seconds>`
- `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (Unix seconds)
- JSON body:
```json
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again in 12 seconds.",
"retryAfter": 12,
"limit": 100,
"current": 101,
"window": 60,
"scope": "environment"
}
```
## Model
Two scopes exist:
- **Instance (`for_instance`)**: in-memory sliding window; protects a single Router process.
- **Environment (`for_environment`)**: Valkey-backed fixed window; protects the whole environment across Router instances.
Environment checks are gated by an **activation threshold** (`process_back_pressure_when_more_than_per_5min`) to avoid unnecessary Valkey calls at low traffic.
## Configuration
Configuration is under the `rate_limiting` root.
### Minimal (instance only)
```yaml
rate_limiting:
process_back_pressure_when_more_than_per_5min: 5000
for_instance:
rules:
- per_seconds: 60
max_requests: 600
```
### Environment (Valkey)
```yaml
rate_limiting:
process_back_pressure_when_more_than_per_5min: 0 # always check environment
for_environment:
valkey_connection: "valkey.stellaops.local:6379"
valkey_bucket: "stella-router-rate-limit"
circuit_breaker:
failure_threshold: 5
timeout_seconds: 30
half_open_timeout: 10
rules:
- per_seconds: 60
max_requests: 600
```
### Rule stacking (AND logic)
Multiple rules on the same target are evaluated with **AND** semantics:
```yaml
rate_limiting:
for_environment:
rules:
- per_seconds: 1
max_requests: 10
- per_seconds: 3600
max_requests: 3000
```
If any rule is exceeded the request is denied. The Router returns the **most restrictive** `Retry-After` among violated rules.
### Microservice overrides
Overrides are **replacement**, not merge:
```yaml
rate_limiting:
for_environment:
rules:
- per_seconds: 60
max_requests: 600
microservices:
scanner:
rules:
- per_seconds: 10
max_requests: 50
```
### Route overrides
Route-level configuration is under:
`rate_limiting.for_environment.microservices.<microservice>.routes.<route_name>`
See `docs/modules/router/guides/rate-limiting-routes.md` for match types and specificity rules.
## Notes
- If `rules` is present, it takes precedence over legacy single-window keys (`per_seconds`, `max_requests`, `allow_*`).
- For allowed requests, headers represent the **smallest window** rule for deterministic, low-cardinality output (not a full multi-rule snapshot).
- If Valkey is unavailable, environment limiting is **fail-open** (instance limits still apply).
## Testing
- Unit tests: `dotnet test StellaOps.Router.slnx -c Release`
- Valkey integration tests (Docker required): `STELLAOPS_INTEGRATION_TESTS=true dotnet test StellaOps.Router.slnx -c Release --filter FullyQualifiedName~ValkeyRateLimitStoreIntegrationTests`
- k6 load tests: `tests/load/router-rate-limiting-load-test.js` (see `tests/load/README.md`)

View File

@@ -0,0 +1,90 @@
# Per-Route Rate Limiting (Router)
This document describes **per-route** rate limiting configuration for the Router gateway (`StellaOps.Router.Gateway`).
## Overview
Per-route rate limiting lets you apply different limits to specific HTTP paths **within the same microservice**.
Configuration is nested as:
`rate_limiting.for_environment.microservices.<microservice>.routes.<route_name>`
## Configuration
### Example (rules + routes)
```yaml
rate_limiting:
for_environment:
valkey_connection: "valkey.stellaops.local:6379"
valkey_bucket: "stella-router-rate-limit"
# Default environment rules (used when no microservice override exists)
rules:
- per_seconds: 60
max_requests: 600
microservices:
scanner:
# Default rules for the microservice (used when no route override exists)
rules:
- per_seconds: 60
max_requests: 600
routes:
scan_submit:
pattern: "/api/scans"
match_type: exact
rules:
- per_seconds: 10
max_requests: 50
scan_status:
pattern: "/api/scans/*"
match_type: prefix
rules:
- per_seconds: 1
max_requests: 100
scan_by_id:
pattern: "^/api/scans/[a-f0-9-]+$"
match_type: regex
rules:
- per_seconds: 1
max_requests: 50
```
### Match types
`match_type` supports:
- `exact`: exact path match (case-insensitive), ignoring a trailing `/`.
- `prefix`: literal prefix match; patterns commonly end with `*` (e.g. `/api/scans/*`).
- `regex`: regular expression (compiled at startup; invalid regex fails fast).
### Specificity rules
When multiple routes match a path, the most specific match wins:
1. `exact`
2. `prefix` (longest prefix wins)
3. `regex` (longest pattern wins)
## Inheritance (resolution)
Rate limiting rules resolve with **replacement** semantics:
- `routes.<route_name>.rules` replaces the microservice rules.
- `microservices.<name>.rules` replaces the environment rules.
- If a level provides no rules, the next-less-specific level applies.
## Notes
- Per-route rate limiting applies at the **environment** scope (Valkey-backed).
- The Router returns `429 Too Many Requests` and a `Retry-After` header when a limit is exceeded.
## See also
- `docs/modules/router/guides/rate-limiting-config.md` (full configuration guide)
- `docs/modules/router/rate-limiting.md` (module dossier)