S

super-dev — full-stack

@super-dev.app · Full-stack · ★ open to work
12.8k subscribers·47 videos·Active since 2017

Full-stack .NET / Angular dev with a DevOps streak on Azure Cloud. Also fluent in Flutter + Firebase. I build products, ship them, and keep them healthy.

Back to articles
.NET
.NET

.NET microservices with gRPC

Article 4 of 5 — Modern .NET
Minimal APIs, CQRS, gRPC, source generators: clean, testable .NET 8 without the ceremony.

Between microservices, JSON over HTTP/1.1 pays dearly for its comfort: verbose serialization, no strong contract, one connection per call. gRPC answers that precise context — compact binary over HTTP/2, a shared contract and generated code on both sides. In .NET, the integration is first-class.

The .proto contract, single source of truth

It all starts with a .proto file: it describes the messages and the service, independent of any language. This is the contract — neither client nor server writes it by hand. You declare a service Pricing exposing an rpc GetQuote (QuoteRequest) call that returns a QuoteReply , with every field numbered ( string sku = 1; , int32 quantity = 2; ) — those numbers are the key to backward compatibility: you never reuse one.

On the .NET side, you reference this file in the .csproj via <Protobuf Include="pricing.proto" /> . The Grpc.Tools package then generates, at compile time, the server base class and the typed client — no DTOs to copy by hand:

C#
1// Generated by Grpc.Tools from pricing.proto — do not edit
2public partial class QuoteRequest
3{
4 public string Sku { get; set; }
5 public int Quantity { get; set; }
6}

Typed server and client

The server derives from the generated Pricing.PricingBase class and overrides the method. No routing to wire, no manual deserialization: you receive a strongly typed message.

C#
1public sealed class PricingService(IPriceBook book) : Pricing.PricingBase
2{
3 public override async Task<QuoteReply> GetQuote(
4 QuoteRequest request, ServerCallContext context)
5 {
6 var unitPrice = await book.LookupAsync(request.Sku, context.CancellationToken);
7
8 return new QuoteReply { UnitPriceCents = unitPrice * request.Quantity };
9 }
10}

On the calling side, you don't write an HttpClient either. You inject the generated client via AddGrpcClient , and call it like a local method:

C#
1builder.Services.AddGrpcClient<Pricing.PricingClient>(options =>
2 options.Address = new Uri("https://pricing:443"));

Streaming, the deciding argument

Where REST hits a ceiling, gRPC excels: HTTP/2 enables streaming in both directions. A server-side stream pushes results as they come; a bidirectional stream opens a full-duplex channel ideal for telemetry or chat. You write into an IServerStreamWriter<T> and the client iterates with await foreach — no polling, no hand-rolled WebSocket.

gRPC versus REST: choosing with eyes open

gRPC isn't universal. Its strengths — compact binary, strong contract, streaming, low latency — make it the internal tool of choice (service-to-service). Its limits are real: a browser doesn't speak gRPC natively (you need gRPC-Web and a proxy), the binary isn't human-readable, and debugging requires dedicated tooling. For a public API consumed by third parties, REST/JSON often remains the right call. The Microsoft guide compares the two in the gRPC for .NET docs .

The healthy reflex: REST on the public façade, gRPC on the inside . The .proto contract then becomes the formal boundary between your services — versioned, shared, and checked by the compiler.
super-dev — portfolio.app
// More in .NET
{ }
Strangle a .NET 8 monolith without breaking everything
9 min • 3.2k reads
ƒ()
Minimal APIs + EF Core: a clean .NET 8 API
8 min • 3.1k reads
CQRS and vertical slices without the ceremony
7 min • 2.3k reads