If you have been reading about gRPC for a while, but haven’t gotten around to check it out, this is your short cut guide to using gRPC in Aurelia 2!
I think gRPC is looking promising on paper. It’s still early days and both browsers, as well as Azure services I use, doesn’t support all features of it yet. However, knowledge is an easy burden, so I thought I’d at least check it out and see how to use it from the web in a Aurelia 2 SPA 😁
🥞Aurelia2 Web Client – .NET Back-end
The Project consists of a web client using au2. As for server I decided to try gRPC support of .NET, so the back-end is a .NET 5 project.
There is a gRPC Service template available in Visual Studio. I use VS2019 Community Preview Edition, this allowed me to select .NET 5 as a framework.
As for the Web Client project, that’s just a regular Aurelia 2 project created from a CLI.
The data used is slightly more complex than all starter scenarios I’ve seen, where the reply is just a string. I wanted to try it with more complex objects and see if it was still as easy. Spoiler alert: it wasn’t 😩
🤖gRPC protobuf
If you are new to gRPC: the word protobuf is short for protocol buffer language and is a way to describe services and types. To work with these files, there is a compiler available with multiple plugins. The compiler and the plugins are used to generate data access classes from your .proto
files.
The .proto
file used in this demo can be seen below.
syntax = "proto3";
option csharp_namespace = "GrpcNETService.Generated";
service DroidService {
rpc GetAll (DroidsRequest) returns (DroidsReply);
}
message DroidsRequest { }
message DroidsReply {
repeated Droid droids = 1;
}
message Droid {
repeated string armament = 1;
string class = 2;
repeated string equipment = 3;
int32 height = 4;
string manufacturer = 5;
string model = 6;
repeated string plating_color = 7;
int32 price = 8;
repeated string sensor_color = 9;
}
This .proto
file defines a service, an rpc call named GetAll. It also defines the the request and response types used to call that service.
Last but not least, the definitions of our Droid
type, that we will try to work with in the front-end project.
🧾Back-end .NET 5 gRPC c# Project Setup
First off create a new project from the gRPC project template in Visual Studio. I use Visual Studio 2019 Community Edition.
✅Installing prerequisites
There’s one dependency we need to install to get gRPC for Web working, just run the following from the Package Manager Console:
Install-Package Grpc.AspNetCore.Web
👩🔧Compiling .proto files with Visual Studio
Luckily the gRPC project in .NET is setup to compile .proto
files for you! I tried doing this from the command line at first. Reason was I wanted to run one command to compile all .proto
files for both projects at the same time. Turned out the c# experience was not as smooth as I hoped for with a command line solution.
However, the Visual Studio gRPC project template re-compiles the .proto
files on every build. That solution is good enough and avoids the generated code becoming out of sync for the .NET project!
I solved sharing the .proto
files between the back-end and front-end projects by putting a “protos” folder in the root of the directory, holding all .proto
files. But for VS2019 to find the files, we need to modify the csproj
-file and add a new ItemGroup
👇.
<ItemGroup>
<Protobuf Include="../protos/*.proto" GrpcServices="Server" OutputDir="%(RelativePath)Services" CompileOutputs="false" />
</ItemGroup>
This will help Visual Studio to find the protos
directory in the root and it will output the compiled files into the Services
folder inside the project. The option GrpcServices="Server"
tells the compiler we are only needed in compiling protobuf files needed to host a gRPC server.
👨🏭Making it work
There’s a few steps needed to startup.cs
to make the c# project work.
- Nuget – reference gRPC and gRPC Web
- Setup and use CORS
- Add the gRPC endpoint for our service
public class Startup
{
const string ERROR_REPLY = "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909";
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddCors(o => o.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
}));
List<Droid> droids = FakeDroidStore.GetAllDroids();
services.AddSingleton(droids);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseGrpcWeb();
app.UseCors();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<DroidService>().EnableGrpcWeb().RequireCors("AllowAll");
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync(ERROR_REPLY);
});
});
}
}
I also added a collection of items from a fake store to the DI container. This is just a test – don’t do that in production code! 😁
🧾Front-end Aurelia 2 Project Setup
The Aurelia 2 project was setup by running (Node is needed to be able to use npx
):
npx au2 new
Then I choose the following settings:
✅Installing prerequisites
To work with protobuf files and compile the web-shims you need to install some files. Get the following and make sure they are accessible in your path (on Windows):
👩🔧Compiling .proto files from the command line
Compiling the proto files for the web project was made with the protoc compiler from the command line.
For ease of use in or project the grpc-web output is in TypeScript.
⚠ this is still a beta feature and might have issues.
Position in the root/protos directory and run the following. (It should be on the same line!)
protoc droids.proto --js_out=import_style=commonjs:../web-client/src/proto/gen --grpc-web_out=import_style=typescript,mode=grpcwebtext:../web-client/src/proto/gen
This will compile the protobuf and put the generated files inside the web project in the folder web-client/src/proto/gen
.
👨🏭Making it work
Calling the gRPC Service is quite forward. First instantiate a ServiceClient
, then a service Request
. Then call the service using the request.
const client = new DroidServiceClient("https://localhost:5001");
const request = new DroidsRequest();
Note, in the proto file, the name of the service is GetAll. This is converted to getAll following JS standards. The return type however looks like follows:
message DroidsReply {
repeated Droid droids = 1;
}
The repeated keyword indicates that the field is a collection of values. This will lead to the the type being suffixed with List
and accessible as droidsList.
There’s two ways to work with the reply type. One is getting the droidsList
type from the response. Then when binding to that in the view, we use getters created on the type, for ex (and here we see another repeated property getting the List
suffix):
<div>Plating colors available is ${droid.getPlatingColorList()}</div>
Another way is to use the toObject
method on the response and get the “raw” droidsList
from that.
this.droids = response.toObject().droidsList;
This enables us to bind to the view without referring to methods, which perhaps feels more “Aurelia native”. Do note below that the reserved word class
is changed to pb_class
👍
<div>Class: ${droid.pb_class}</div>
🧱Using JavaScript Files in our TypeScript Project
Since one of the files from the generated protobuf code is a .js
file, we need to tell the ts compiler to allow .js
files as well. Modify the tsconfig and add the compiler option:
"allowJs": true
If using dumber to build and bundle the project, then modify gulpfile.js
and add a step to include js
files in the build, see line 137 👇
function build() {
// Merge all js/css/html file streams to feed dumber.
// dumber knows nothing about .ts/.less/.scss/.md files,
// gulp-* plugins transpiled them into js/css/html before
// sending to dumber.
return (
merge2(
gulp.src("src/**/*.json"),
buildJs("src/**/*.ts"),
buildJs("src/**/*.js"),
buildHtml("src/**/*.html"),
buildCss("src/**/*.css")
)
🎢Running the Project
Build and start the back-end project from Visual Studio 2019. Install npm dependencies and run the front-end project with npm start
. The web-client should open in a new browser. After pressing the “Get All Droids” button, if everything works, two lists of droid info will be displayed.
🔮Conclusion
The size of the gRPC payload sent to the client is about half the size of the json payload (approx 2kb vs 4kb). So that’s pretty cool! If you are having having huge payloads in your projects, it means there’s lot’s to gain form gRPC.
The developer experience I’d say feels slightly off. Maybe it’s just a case of “who moved my 🧀”? Automating the protobuf compilation in the front-end project as well, making it a build step, would probably help a bit. Using the getters on the reply object also feels slightly off when binding to views in the front-end, but since it’s not necessary to use them I guess that’s ok.
Using the protobuf defined types directly in the back-end project also feels slightly “off”. Would be great to get some feedback from developers actually using gRPC in production if they are using the protobuf defined types, or other abstractions for them?!
Then there is still the issue of native support in browsers and hosting services. In my opinion, the coolest part of gRPC is streaming, and this needs to be sorted to fully enjoy gRPC. However, in some near future, I suspect a lot of web apps will transition to gRPC from using json based API’s.
As for service to service communication, I’d be willing to try out gRPC. Especially if the setup enables streaming, that’d be great for some applications 😃
🔗Resources
Find the code on my github
Read more about gRPC at grpc.io
Aurelia 2 documentation
thank you traducir