Implement InMemory Transport Layer for StellaOps Router

- Added InMemoryTransportOptions class for configuration settings including timeouts and latency.
- Developed InMemoryTransportServer class to handle connections, frame processing, and event management.
- Created ServiceCollectionExtensions for easy registration of InMemory transport services.
- Established project structure and dependencies for InMemory transport library.
- Implemented comprehensive unit tests for endpoint discovery, connection management, request/response flow, and streaming capabilities.
- Ensured proper handling of cancellation, heartbeat, and hello frames within the transport layer.
This commit is contained in:
StellaOps Bot
2025-12-05 01:00:10 +02:00
parent 8768c27f30
commit 175b750e29
111 changed files with 25407 additions and 19242 deletions

View File

@@ -1,4 +1,6 @@
namespace StellaOps.Router.Common;
using StellaOps.Router.Common.Models;
namespace StellaOps.Router.Common.Abstractions;
/// <summary>
/// Provides global routing state derived from all live connections.
@@ -21,21 +23,9 @@ public interface IGlobalRoutingState
/// <param name="method">The HTTP method.</param>
/// <param name="path">The request path.</param>
/// <returns>The available connection states.</returns>
IEnumerable<ConnectionState> GetConnectionsForEndpoint(
IReadOnlyList<ConnectionState> GetConnectionsFor(
string serviceName,
string version,
string method,
string path);
/// <summary>
/// Registers a connection and its endpoints.
/// </summary>
/// <param name="connection">The connection state to register.</param>
void RegisterConnection(ConnectionState connection);
/// <summary>
/// Removes a connection from the routing state.
/// </summary>
/// <param name="connectionId">The connection ID to remove.</param>
void UnregisterConnection(string connectionId);
}

View File

@@ -0,0 +1,17 @@
namespace StellaOps.Router.Common.Abstractions;
/// <summary>
/// Provides region information for routing decisions.
/// </summary>
public interface IRegionProvider
{
/// <summary>
/// Gets the current gateway region.
/// </summary>
string Region { get; }
/// <summary>
/// Gets the neighbor regions in order of preference.
/// </summary>
IReadOnlyList<string> NeighborRegions { get; }
}

View File

@@ -0,0 +1,19 @@
using StellaOps.Router.Common.Models;
namespace StellaOps.Router.Common.Abstractions;
/// <summary>
/// Provides extensibility for routing decisions.
/// </summary>
public interface IRoutingPlugin
{
/// <summary>
/// Chooses an instance for the routing context.
/// </summary>
/// <param name="context">The routing context.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The routing decision, or null if this plugin cannot decide.</returns>
Task<RoutingDecision?> ChooseInstanceAsync(
RoutingContext context,
CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,51 @@
using StellaOps.Router.Common.Models;
namespace StellaOps.Router.Common.Abstractions;
/// <summary>
/// Represents a transport client for sending requests to microservices.
/// </summary>
public interface ITransportClient
{
/// <summary>
/// Sends a request and waits for a response.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="requestFrame">The request frame.</param>
/// <param name="timeout">The timeout for the request.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The response frame.</returns>
Task<Frame> SendRequestAsync(
ConnectionState connection,
Frame requestFrame,
TimeSpan timeout,
CancellationToken cancellationToken);
/// <summary>
/// Sends a cancellation request.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="correlationId">The correlation ID of the request to cancel.</param>
/// <param name="reason">Optional reason for cancellation.</param>
Task SendCancelAsync(
ConnectionState connection,
Guid correlationId,
string? reason = null);
/// <summary>
/// Sends a streaming request and processes the streaming response.
/// </summary>
/// <param name="connection">The connection to use.</param>
/// <param name="requestHeader">The request header frame.</param>
/// <param name="requestBody">The request body stream.</param>
/// <param name="readResponseBody">Callback to read the response body stream.</param>
/// <param name="limits">Payload limits to enforce.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task SendStreamingAsync(
ConnectionState connection,
Frame requestHeader,
Stream requestBody,
Func<Stream, Task> readResponseBody,
PayloadLimits limits,
CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,19 @@
namespace StellaOps.Router.Common.Abstractions;
/// <summary>
/// Represents a transport server that accepts connections from microservices.
/// </summary>
public interface ITransportServer
{
/// <summary>
/// Starts listening for incoming connections.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
Task StartAsync(CancellationToken cancellationToken);
/// <summary>
/// Stops accepting new connections.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
Task StopAsync(CancellationToken cancellationToken);
}

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Router.Common;
namespace StellaOps.Router.Common.Enums;
/// <summary>
/// Defines the frame types used in the router protocol.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Router.Common;
namespace StellaOps.Router.Common.Enums;
/// <summary>
/// Defines the health status of a microservice instance.

View File

@@ -1,7 +1,8 @@
namespace StellaOps.Router.Common;
namespace StellaOps.Router.Common.Enums;
/// <summary>
/// Defines the transport types supported for microservice-to-router communication.
/// Note: HTTP is explicitly excluded per specification.
/// </summary>
public enum TransportType
{
@@ -21,9 +22,9 @@ public enum TransportType
Tcp,
/// <summary>
/// TLS/mTLS transport with certificate-based authentication.
/// Certificate-based TCP (TLS/mTLS) transport with certificate-based authentication.
/// </summary>
Tls,
Certificate,
/// <summary>
/// RabbitMQ transport for queue-based communication.

View File

@@ -1,24 +0,0 @@
namespace StellaOps.Router.Common;
/// <summary>
/// Provides extensibility for routing decisions.
/// </summary>
public interface IRoutingPlugin
{
/// <summary>
/// Gets the priority of this plugin. Lower values run first.
/// </summary>
int Priority { get; }
/// <summary>
/// Filters or reorders candidate connections for routing.
/// </summary>
/// <param name="candidates">The candidate connections.</param>
/// <param name="endpoint">The target endpoint.</param>
/// <param name="gatewayRegion">The gateway's region.</param>
/// <returns>The filtered/reordered connections.</returns>
IEnumerable<ConnectionState> Filter(
IEnumerable<ConnectionState> candidates,
EndpointDescriptor endpoint,
string gatewayRegion);
}

View File

@@ -1,24 +0,0 @@
namespace StellaOps.Router.Common;
/// <summary>
/// Represents a transport client that connects to routers.
/// </summary>
public interface ITransportClient : IAsyncDisposable
{
/// <summary>
/// Gets the transport type for this client.
/// </summary>
TransportType TransportType { get; }
/// <summary>
/// Connects to a router endpoint.
/// </summary>
/// <param name="host">The router host.</param>
/// <param name="port">The router port.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The established connection.</returns>
Task<ITransportConnection> ConnectAsync(
string host,
int port,
CancellationToken cancellationToken = default);
}

View File

@@ -1,37 +0,0 @@
namespace StellaOps.Router.Common;
/// <summary>
/// Represents a bidirectional transport connection.
/// </summary>
public interface ITransportConnection : IAsyncDisposable
{
/// <summary>
/// Gets the unique identifier for this connection.
/// </summary>
string ConnectionId { get; }
/// <summary>
/// Gets a value indicating whether the connection is open.
/// </summary>
bool IsConnected { get; }
/// <summary>
/// Sends a frame over the connection.
/// </summary>
/// <param name="frame">The frame to send.</param>
/// <param name="cancellationToken">Cancellation token.</param>
ValueTask SendAsync(Frame frame, CancellationToken cancellationToken = default);
/// <summary>
/// Receives the next frame from the connection.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The received frame, or null if connection closed.</returns>
ValueTask<Frame?> ReceiveAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Closes the connection gracefully.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
Task CloseAsync(CancellationToken cancellationToken = default);
}

View File

@@ -1,45 +0,0 @@
namespace StellaOps.Router.Common;
/// <summary>
/// Represents a transport server that accepts connections from microservices.
/// </summary>
public interface ITransportServer : IAsyncDisposable
{
/// <summary>
/// Gets the transport type for this server.
/// </summary>
TransportType TransportType { get; }
/// <summary>
/// Starts listening for incoming connections.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
Task StartAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Stops accepting new connections.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
Task StopAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Occurs when a new connection is established.
/// </summary>
event EventHandler<TransportConnectionEventArgs>? ConnectionEstablished;
/// <summary>
/// Occurs when a connection is closed.
/// </summary>
event EventHandler<TransportConnectionEventArgs>? ConnectionClosed;
}
/// <summary>
/// Event arguments for transport connection events.
/// </summary>
public sealed class TransportConnectionEventArgs : EventArgs
{
/// <summary>
/// Gets the connection that triggered the event.
/// </summary>
public required ITransportConnection Connection { get; init; }
}

View File

@@ -0,0 +1,12 @@
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Payload for the Cancel frame.
/// </summary>
public sealed record CancelPayload
{
/// <summary>
/// Gets the reason for cancellation.
/// </summary>
public string? Reason { get; init; }
}

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Router.Common;
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Represents a claim requirement for endpoint authorization.

View File

@@ -1,4 +1,6 @@
namespace StellaOps.Router.Common;
using StellaOps.Router.Common.Enums;
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Represents the state of a connection between a microservice and the router.

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Router.Common;
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Describes an endpoint's identity and metadata.

View File

@@ -1,4 +1,6 @@
namespace StellaOps.Router.Common;
using StellaOps.Router.Common.Enums;
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Represents a protocol frame in the router transport layer.

View File

@@ -0,0 +1,34 @@
using StellaOps.Router.Common.Enums;
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Payload for the Heartbeat frame sent periodically by microservices.
/// </summary>
public sealed record HeartbeatPayload
{
/// <summary>
/// Gets the instance ID.
/// </summary>
public required string InstanceId { get; init; }
/// <summary>
/// Gets the health status.
/// </summary>
public required InstanceHealthStatus Status { get; init; }
/// <summary>
/// Gets the current in-flight request count.
/// </summary>
public int InFlightRequestCount { get; init; }
/// <summary>
/// Gets the error rate (0.0 to 1.0).
/// </summary>
public double ErrorRate { get; init; }
/// <summary>
/// Gets the timestamp when this heartbeat was created.
/// </summary>
public DateTime TimestampUtc { get; init; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,17 @@
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Payload for the Hello frame sent by microservices on connection.
/// </summary>
public sealed record HelloPayload
{
/// <summary>
/// Gets the instance descriptor.
/// </summary>
public required InstanceDescriptor Instance { get; init; }
/// <summary>
/// Gets the endpoints registered by this instance.
/// </summary>
public required IReadOnlyList<EndpointDescriptor> Endpoints { get; init; }
}

View File

@@ -1,4 +1,4 @@
namespace StellaOps.Router.Common;
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Describes a microservice instance's identity.

View File

@@ -0,0 +1,30 @@
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Configuration for payload and memory limits.
/// </summary>
public sealed record PayloadLimits
{
/// <summary>
/// Default payload limits.
/// </summary>
public static readonly PayloadLimits Default = new();
/// <summary>
/// Gets the maximum request bytes per call.
/// Default: 10 MB.
/// </summary>
public long MaxRequestBytesPerCall { get; init; } = 10 * 1024 * 1024;
/// <summary>
/// Gets the maximum request bytes per connection.
/// Default: 100 MB.
/// </summary>
public long MaxRequestBytesPerConnection { get; init; } = 100 * 1024 * 1024;
/// <summary>
/// Gets the maximum aggregate in-flight bytes across all requests.
/// Default: 1 GB.
/// </summary>
public long MaxAggregateInflightBytes { get; init; } = 1024 * 1024 * 1024;
}

View File

@@ -0,0 +1,48 @@
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Neutral routing context that does not depend on ASP.NET.
/// Gateway will adapt from HttpContext to this neutral model.
/// </summary>
public sealed record RoutingContext
{
/// <summary>
/// Gets the HTTP method (GET, POST, PUT, PATCH, DELETE).
/// </summary>
public required string Method { get; init; }
/// <summary>
/// Gets the request path.
/// </summary>
public required string Path { get; init; }
/// <summary>
/// Gets the request headers.
/// </summary>
public IReadOnlyDictionary<string, string> Headers { get; init; } = new Dictionary<string, string>();
/// <summary>
/// Gets the resolved endpoint descriptor.
/// </summary>
public EndpointDescriptor? Endpoint { get; init; }
/// <summary>
/// Gets the available connections for routing.
/// </summary>
public IReadOnlyList<ConnectionState> AvailableConnections { get; init; } = [];
/// <summary>
/// Gets the gateway's region for routing decisions.
/// </summary>
public required string GatewayRegion { get; init; }
/// <summary>
/// Gets the requested version, if specified.
/// </summary>
public string? RequestedVersion { get; init; }
/// <summary>
/// Gets the cancellation token for the request.
/// </summary>
public CancellationToken CancellationToken { get; init; }
}

View File

@@ -0,0 +1,29 @@
using StellaOps.Router.Common.Enums;
namespace StellaOps.Router.Common.Models;
/// <summary>
/// Represents the outcome of a routing decision.
/// </summary>
public sealed record RoutingDecision
{
/// <summary>
/// Gets the selected endpoint.
/// </summary>
public required EndpointDescriptor Endpoint { get; init; }
/// <summary>
/// Gets the selected connection.
/// </summary>
public required ConnectionState Connection { get; init; }
/// <summary>
/// Gets the transport type to use.
/// </summary>
public required TransportType TransportType { get; init; }
/// <summary>
/// Gets the effective timeout for the request.
/// </summary>
public required TimeSpan EffectiveTimeout { get; init; }
}