Skip to content

Transports gRPC

quyvu edited this page Oct 13, 2026 · 2 revisions

gRPC Transport

Typed requests between services over HTTP/2, with no broker to run: the client calls the hosts of the services that own the data.

Installation

dotnet add package FxMap.Grpc

Every service that maps data needs the core FxMap package, and the owning service also needs a data provider. All FxMap.* packages must use the same version.

Client: the service that needs data

List the hosts of the services that own the entities. You do not say which host owns which entity: FxMap asks the hosts the first time it needs a distributed key.

builder.Services.AddFxMap(cfg =>
{
    cfg.AddEntitiesFromAssemblyContaining<SomeEntityAssemblyMarker>();
    cfg.AddProfilesFromAssemblyContaining<SomeProfileAssemblyMarker>();
    cfg.AddGrpcClients(c => c.AddGrpcHosts("http://localhost:5001", "http://localhost:5013"));
});

Hosts from configuration

Since 4.1.3, AddGrpcHosts also takes a list of hosts (an IEnumerable<string> or an array), so the hosts can come straight from appsettings.json:

// appsettings.json
// { "FxMap": { "GrpcHosts": [ "https://users:5001", "https://products:5001" ] } }
 
var hosts = builder.Configuration.GetSection("FxMap:GrpcHosts").Get<string[]>() ?? [];
 
builder.Services.AddFxMap(cfg =>
{
    cfg.AddEntitiesFromAssemblyContaining<SomeEntityAssemblyMarker>();
    cfg.AddGrpcClients(c => c.AddGrpcHosts(hosts));
});

The three forms are equal: AddGrpcHosts("a", "b"), AddGrpcHosts(["a", "b"]) and AddGrpcHosts(list). A host that is listed twice is used once, and an empty list registers nothing.

Server: the service that owns the data

Register the entities and a data provider as usual, add gRPC, and map the FxMap endpoint:

builder.Services.AddFxMap(cfg =>
    {
        cfg.AddEntitiesFromAssemblyContaining<UserConfig>();
    })
    .AddEntityFrameworkCore(c => c.AddDbContexts(typeof(UserDbContext)));
 
builder.Services.AddGrpc();
 
var app = builder.Build();
app.MapFxMapperGrpc();
app.Run();

The server needs an HTTP/2 endpoint. Without TLS, use a dedicated HTTP/2-only endpoint next to your HTTP/1.1 one: see the Kestrel guide.

How a request travels

  1. The first time a distributed key is needed, the client asks the listed hosts which keys they own (GetDistributedKeys), and remembers the answer.
  2. Each request holds the ids and the expressions for one distributed key and one level. The client sends it to the host that owns the key (GetItems), and a hundred objects still make one request.
  3. The headers of the IContext become gRPC metadata, so the owner sees them. A channel is created once for each host and reused.
  4. The owner answers with the values, and with the expression errors it isolated (4.1.3+), which travel in the gRPC contract like the values.

Complete example

// Shared contract
public sealed class UserDistributedKey : IDistributedKey;
 
// Owner (users service): the entity and how it is exposed
public class UserConfig : EntityConfigureOf<User>
{
    protected override void Configure()
    {
        Id(x => x.Id);
        DefaultProperty(x => x.Name);
        UseDistributedKey<UserDistributedKey>();
    }
}
 
// Client (orders service): the DTO and where its values come from
public class OrderResponse
{
    public string Id { get; set; }
    public string UserId { get; set; }
    public string? UserName { get; set; }
    public string? UserEmail { get; set; }
}
 
public class OrderResponseProfile : ProfileOf<OrderResponse>
{
    protected override void Configure() =>
        UseDistributedKey<UserDistributedKey>()
            .Of(x => x.UserId)
            .For(x => x.UserName)
            .For(x => x.UserEmail, "Email");
}
 
// In an endpoint of the orders service
await distributedMapper.MapDataAsync(orders);

Good to know

No broker, no supervisor

A call goes straight to the owner, so there is no broker to run. The brokered transports use the supervisor pattern; gRPC does not, because ASP.NET Core and Kestrel handle the HTTP/2 connections. Each call has a time limit of 3 seconds, and it cannot be changed in 4.1.3.

A host that is down

If a host cannot be reached when FxMap asks which keys it owns, FxMap leaves it aside and asks it again the next time it needs a key it has not found. With ThrowIfException() the error is thrown instead (this works wherever you call it, also after AddGrpcClients, which was a defect before 4.1.2).

Load balancing

FxMap picks the host that owns a key; it does not spread the calls of one key over several instances. To balance the load, give the host one address that does it (a service mesh, Kubernetes Service or a load balancer that understands HTTP/2).


Source: https://fxmapper.net/docs/transports/grpc/ — the website is always the most up to date version.

Clone this wiki locally