save progress
This commit is contained in:
370
docs/router/GETTING_STARTED.md
Normal file
370
docs/router/GETTING_STARTED.md
Normal 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.
|
||||
Reference in New Issue
Block a user