Happy to announce that gRPC support has now been published to MyGet, looking forward for any early preview adopters to test it out and report back any issues before the next release.
gRPC support is implemented in these 2 new packages below which requires netcoreapp3.0 on the server and either netstandard2.1 or netcoreapp3.0 on the client:
- ServiceStack.Extensions - Server Support in
GrpcFeature
(requires netcoreapp3.0) - ServiceStack.GrpcClient - .NET
GrpcServiceClient
(requires netstandard2.1 or netcoreapp3.0)
Happy to report that gRPC integration went as well as I could’ve hoped where you can now call your existing ServiceStack Services via gRPC which in addition for reusing your existing Services, ServiceStack’s gRPC’s support ends up providing a nicer development experience then the default gRPC development experience in .NET Core 3.0 which requires maintaining an external .proto
specification, and tooling to generate code-generated classes used in both server implementations and generated client proxies.
Instead we make use of marc gravell’s protobuf-net.Grpc library to enable ServiceStack’s normal code-first development experience of using typed Request/Response DTOs to implement and consume gRPC Services using the C# GrpcServiceClient
which implements IServiceClientAsync
so client applications will be able to easily switch between different .NET ServiceClients which all implement IServiceClientAsync
(despite their implementations being very different), e.g:
//IServiceClientAsync client = new JsonServiceClient(BaseUrl);
IServiceClientAsync client = new GrpcServiceClient(BaseUrl);
var response = await client.GetAsync(new Hello { Name = "gRPC!" });
GrpcServiceClient
is a full-featured ServiceClient which supports the same WebServiceException
structured Error Handling including the same built-in Auth support for UserName/Password, SessionId
and JWT/API Key BearerToken
including seamless auto-retry support for RefreshTokens.
Limitations
As gRPC mandates a static service contract (i.e. can only return the same Response DTO Type) your Request DTOs are required to implement IReturn<TResponse>
or IReturnVoid
which is also the best practices for ServiceStack Services which enables the nicest development experience in typed generic service clients. In order to receive structured Error Responses your IReturn<T>
Response DTOs requires ResponseStatus
property as normal.
The other limitations is required to support ProtoBuf serialization which requires a numerical index for each field, to avoid your DTOs needing a dependency to protobuf-net Attributes you can define this using [DataContract]
and [DataMember(Order=N)]
attributes, all together this looks like:
[Route("/hello/{Name}")]
[DataContract]
public class Hello : IReturn<HelloResponse>
{
[DataMember(Order = 1)]
public string Name { get; set; }
}
[DataContract]
public class HelloResponse
{
[DataMember(Order = 1)]
public string Result { get; set; }
[DataMember(Order = 2)]
public ResponseStatus ResponseStatus { get; set; }
}
Basically these are the same attributes required for your Services to support SOAP.
Note: only Services that implement
IReturn*
and annotated with ProtoBuf attributes are registered as gRPC Services. If you get an error that your Service you’re trying to call doesn’t exist that’s likely the issue.
To enable gRPC support you need to register the GrpcFeature
from the ServiceStack.Extensions netcoreapp3.0
package in your AppHost:
public override void Configure(Container container)
{
Plugins.Add(new GrpcFeature(App));
}
gRPC uses .NET Core 3’s endpoint routing which you need to enable in your Startup
class with app.UseRouting()
and services.AddServiceStackGrpc()
which enables ServiceStack’s code-first gRPC support:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) =>
Configuration = configuration;
public void ConfigureServices(IServiceCollection services)
{
services.AddServiceStackGrpc();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseRouting(); //enable endpoint routing
app.UseServiceStack(new AppHost {
AppSettings = new NetCoreAppSettings(Configuration)
});
}
}
Also note that gRPC works over HTTP/2 and TLS which requires extra TLS configuration when deployed to production, in development it uses the default development certificate.
Get Started
The easiest way to get started is to start from a new grpc template which is effectively a copy of the web project template pre-configured with gRPC support:
$ web new grpc GrpcApp
Tests
The IntegrationTest project shows an example of calling your App’s gRPC Service:
[Test] // Requires Host project running on https://localhost:5001 (default)
public async Task Can_call_Hello_Service_WebHost()
{
var client = new GrpcServiceClient("https://localhost:5001");
var response = await client.GetAsync(new Hello { Name = "World" });
Assert.That(response.Result, Is.EqualTo("Hello, World!"));
}
As well as an example of calling a stand-alone self-host integration test which shows how to call an unencrypted gRPC Service:
public IServiceClientAsync CreateClient() => new GrpcServiceClient(BaseUri);
[Test]
public async Task Can_call_Hello_Service()
{
GrpcClientFactory.AllowUnencryptedHttp2 = true;
var client = CreateClient();
var response = await client.GetAsync(new Hello { Name = "World" });
Assert.That(response.Result, Is.EqualTo("Hello, World!"));
}
More Examples
For more GrpcServiceClient
examples check out the integration tests at:
Feedback
Happy to answer any questions about the implementation and looking forward to hearing any early adopter feedback or reported issues you run into.
Implementation Notes
RpcGateway
gRPC executes ServiceStack Services via the new AppHost.RpcGateway
that enables calling all ServiceStack Services via a single API:
Task<TResponse> ExecuteAsync<TResponse>(object requestDto, IRequest req)
That unlike MQ’s which uses ServiceController.ExecuteMessage
to execute internal/trusted Services, the RpcGateway
executes the full HTTP Request Pipeline where it goes through the same global Request/Response filters that normal HTTP ServiceStack Services execute, e.g. the same Auth validation and any other Request/Response filters.
The difference between HTTP Services is that instead of ServiceStack handling serializing the response, it returns the Response DTO back to ASP .NET Core gRPC which handles sending the response back to the HTTP/2 connected client. The RpcGateway
also handles filters short-circuiting the Request Pipeline which gets converted into an Error Response Status that’s injected into the Response DTO’s ResponseStatus
.
ProtoBuf Inheritance
Google’s protocol buffers doesn’t have any support for inheritance and protobuf-net has to retrofit its inheritance support by embedding sub classes as different fields in the base class type. This is awkward since all known sub classes needs to be defined on the base type using a consistent and non-conflicting numerical field id. Since AutoQuery requires inheritance it was important to be able to provide a seamless OOB solution which we’ve enabled by generating a Murmur2 hash (best overall for speed/randomness) of the types name that’s modded to within the 2^29-1
range of valid field ids in protocol buffers so the possibility of a hash collision is very very low but is still possible.
You may instead want to use your own user-defined field id for inherited classes using the [Id]
attribute, for AutoQuery Services it should at least start from 10
to avoid id conflicts with base class properties, e.g:
[Route("/query/rockstars")]
[DataContract, Id(10)]
public class QueryRockstars : QueryDb<Rockstar>
{
[DataMember(Order = 1)]
public int? Age { get; set; }
}
The [Id]
needs to be unique for all sub classes and must not conflict with base class field ids, so since AutoQuery Services share the same base-class, any user-defined [Id(N)]
on AutoQuery Services needs to be unique across all your AutoQuery Services.