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:
@@ -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);
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Router.Common;
|
||||
namespace StellaOps.Router.Common.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the frame types used in the router protocol.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Router.Common;
|
||||
namespace StellaOps.Router.Common.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the health status of a microservice instance.
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Router.Common;
|
||||
namespace StellaOps.Router.Common.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a claim requirement for endpoint authorization.
|
||||
@@ -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.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Router.Common;
|
||||
namespace StellaOps.Router.Common.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Describes an endpoint's identity and metadata.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace StellaOps.Router.Common;
|
||||
namespace StellaOps.Router.Common.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a microservice instance's identity.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
Reference in New Issue
Block a user