Hey Scott, I’m looking to formalize the API’s for calling both Internal and External Services with the new IServiceGateway interfaces which I’m going to start recommending everyone use to call other Services in future as they can be easily substituted and integrated with Discovery Services like Consul, so I’m also hoping you could add support for it in your Consul plugin.
It wont take much effort to support as the interfaces are already implemented in all Service Clients as as well as the new InProcessServiceGateway so your plugin would only need to alternate between the two - I show an example of this at the bottom of this post.
The InProcessServiceGateway
is the default implementation of base.Gateway
in ServiceStack’s base Service
class which can now be used instead of ResolveService<T>()
or ExecuteRequest()
to call another Service, where you can execute a Service with just a Request DTO, e.g:
public class MyServices : Service
{
public object Any(Request request)
{
return Gateway.Send(request.ConvertTo<ExternalRequest>());
}
}
As I want to make calling Internal and External Services indistinguishable any Exceptions are automatically converted into WebServiceException
- the same as would be thrown from a Service Client.
The IServiceGateway
interfaces represent the minimal surface area required to support all of ServiceStack’s different calling conventions. GET, POST, etc requests can still be made by adding IGet
, IPost
, interface markers on the Request DTO’s and the ServiceGatewayExtensions.cs provide nicer convenience API’s when Request DTO’s implement IReturn<T>
and IReturnVoid
.
There’s both Sync:
public interface IServiceGateway
{
/// Normal Request/Reply Services
TResponse Send<TResponse>(object requestDto);
/// Auto Batched Request/Reply Requests
List<TResponse> SendAll<TResponse>(IEnumerable<object> requestDtos);
/// OneWay Service
void Publish(object requestDto);
/// Auto Batched OneWay Requests
void PublishAll(IEnumerable<object> requestDtos);
}
And Async versions:
public interface IServiceGatewayAsync
{
Task<TResponse> SendAsync<TResponse>(object requestDto,
CancellationToken token = default(CancellationToken));
Task<List<TResponse>> SendAllAsync<TResponse>(IEnumerable<object> requestDtos,
CancellationToken token = default(CancellationToken));
Task PublishAsync(object requestDto,
CancellationToken token = default(CancellationToken));
Task PublishAllAsync(IEnumerable<object> requestDtos,
CancellationToken token = default(CancellationToken));
}
With tests for testing each calling convention in both ServiceGatewayTests.cs and ServiceGatewayTestsAsync.cs which run the same under different scenarios:
- AllInternalServiceGatewayTests - Use InProcessGateway to execute all Services (default)
- AllExternalServiceGatewayTests - Use Service Client to execute all Services
This is the easiest to support where you can just register a ServiceClient against the IServiceGateway
interface and it will use that for all base.Gateway
requests:
public override void Configure(Container container)
{
container.Register<IServiceGateway>(c => new JsonServiceClient(baseUrl));
}
-
MixedServiceGatewayTests - Uses InProc for Internal Services and Service Client for external ones. When you need to support internal and external calls you’ll need to implement the
IServiceGatewayFactory
instead as you’ll need to capture the currentIRequest
in order to make in-process calls:
public interface IServiceGatewayFactory
{
IServiceGateway GetServiceGateway(IRequest request);
}
To make it easy for you to create a IServiceGatewayFactory
for Consul I’ve created a ServiceGatewayFactoryBase base class which allows you to implement it with the least effort required, basically you just need to implement IServiceGateway GetGateway(Type requestType)
as seen in MixedServiceGatewayNativeAsyncTests tests.
This example Factory uses a convention to determine which Services should be called internally or externally based on whether the Request DTO Type Name contains “External”:
class ServiceGatewayFactory : ServiceGatewayFactoryBase
{
public override IServiceGateway GetGateway(Type requestType)
{
var gateway = requestType.Name.Contains("External")
? new JsonServiceClient(baseUrl)
: (IServiceGateway)localGateway;
return gateway;
}
}
Whereas your Consul implementation would need to look up the Request DTO Type based on the registry.
To get ServiceStack to use your implementation your plugin would just need to register an IServiceGatewayFactory
in ServiceStack’s IOC. As the factory captures the current IRequest
it’s not thread-safe to use as a singleton and needs to be registered with ReuseScope.None
so a new instance is returned each time, e.g:
class AppHost : AppSelfHostBase
{
public AppHost()
: base("Mixed AppHost Tests" typeof(MyServices).Assembly) { }
public override void Configure(Container container)
{
container.Register<IServiceGatewayFactory>(x => new ServiceGatewayFactory())
.ReusedWithin(ReuseScope.None);
}
}
The latest v4.0.55 on MyGet has these latest bits, I hope you can extend your Consul plugin to also register a Consul-aware IServiceGatewayFactory
as seen above so users using this new API will be able to seamlessly adopt Consul discovery services when they want to decouple their application. Please let me know if you have any questions I can help with.