跳到主要内容
版本:dev

gRPC Support

Fory can generate Dart gRPC service companions for schemas that define services. The generated code uses normal package:grpc clients, service bases, method descriptors, call options, deadlines, cancellations, and status codes, while request and response objects are serialized with Fory instead of protobuf.

Use this mode when both RPC peers are generated from the same Fory IDL, protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use normal protobuf gRPC generation for APIs that must be consumed by generic protobuf clients, reflection tools, or components that expect protobuf message bytes.

Add Dependencies

The fory package does not add gRPC dependencies. Add grpc (and the build_runner dev dependency that generates the Fory serializer code) in the application that compiles or runs generated service companions:

dependencies:
fory: ^1.1.0
grpc: ^4.0.0

dev_dependencies:
build_runner: ^2.4.0

The same dependencies cover both client and server applications.

Define a Service

Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers rpc_service definitions. A Fory IDL service looks like this:

package demo.greeter;

message HelloRequest {
string name = 1;
}

message HelloReply {
string reply = 1;
}

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

Generate Dart model and gRPC companion code with --grpc:

foryc service.fdl --dart_out=./lib/generated --grpc

Then run build_runner once to emit the Fory serializer part file for the generated models (this step is required before the code can run):

dart run build_runner build --delete-conflicting-outputs

For this schema, the Dart generator emits (the model file and module are named from the package leaf, greeter):

FilePurpose
demo/greeter/greeter.dartFory model types and the schema module
demo/greeter/greeter.fory.dartSerializers and registration (built by build_runner)
demo/greeter/greeter_grpc.dartgRPC client, service base, and method descriptors
GreeterForyModule in greeter.dartFory registration module for generated types
GreeterServiceBase in greeter_grpc.dartBase class for server implementations
GreeterClient in greeter_grpc.dartClient stub for gRPC calls

The generated client and service base obtain a ready Fory automatically and register the schema's types on first use, so no manual registration step is required. To share a custom Fory (for example one configured with extra modules), call GreeterForyModule.install(yourFory) once before the first RPC; this is optional.

Implement a Server

Extend the generated GreeterServiceBase and host it with grpc-dart's Server:

import 'dart:io';

import 'package:grpc/grpc.dart';
import 'demo/greeter/greeter.dart';
import 'demo/greeter/greeter_grpc.dart';

class GreeterService extends GreeterServiceBase {

Future<HelloReply> sayHello(ServiceCall call, HelloRequest request) async {
final reply = HelloReply()..reply = 'Hello, ${request.name}';
return reply;
}
}

Future<void> main() async {
final server = Server.create(services: [GreeterService()]);
await server.serve(address: InternetAddress.loopbackIPv4, port: 50051);
}

Create a Client

Use the generated client with a ClientChannel:

import 'package:grpc/grpc.dart';
import 'demo/greeter/greeter.dart';
import 'demo/greeter/greeter_grpc.dart';

Future<void> main() async {
final channel = ClientChannel(
'localhost',
port: 50051,
options: const ChannelOptions(
credentials: ChannelCredentials.insecure(),
),
);
final client = GreeterClient(channel);

final reply = await client.sayHello(HelloRequest()..name = 'Fory');
print(reply.reply);

await channel.shutdown();
}

Streaming RPCs

Fory service definitions can use the same gRPC streaming shapes:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}

Generated Dart methods follow grpc-dart conventions. Single responses return a ResponseFuture<R> (client-streaming adapts the call with .single); streaming responses return a ResponseStream<R>. On the server, single requests arrive as the message type and streaming requests as a Stream; the method returns a Future for single responses and a Stream for streaming responses:

IDL shapeClient methodServer method (override)
rpc A (Req) returns (Res)ResponseFuture<Res> a(Req request, {CallOptions?})Future<Res> a(ServiceCall call, Req request)
rpc A (Req) returns (stream Res)ResponseStream<Res> a(Req request, {CallOptions?})Stream<Res> a(ServiceCall call, Req request)
rpc A (stream Req) returns (Res)ResponseFuture<Res> a(Stream<Req> request, {...})Future<Res> a(ServiceCall call, Stream<Req> request)
rpc A (stream Req) returns (stream Res)ResponseStream<Res> a(Stream<Req> request, {...})Stream<Res> a(ServiceCall call, Stream<Req> request)

Server implementations use the generated streaming method shapes directly:

class GreeterService extends GreeterServiceBase {

Stream<HelloReply> lotsOfReplies(
ServiceCall call,
HelloRequest request,
) async* {
for (final greeting in ['Hello, ${request.name}', 'Welcome, ${request.name}']) {
yield HelloReply()..reply = greeting;
}
}


Future<HelloReply> lotsOfGreetings(
ServiceCall call,
Stream<HelloRequest> request,
) async {
final names = <String>[];
await for (final message in request) {
names.add(message.name);
}
return HelloReply()..reply = names.join(', ');
}


Stream<HelloReply> chat(
ServiceCall call,
Stream<HelloRequest> request,
) async* {
await for (final message in request) {
yield HelloReply()..reply = 'Hello, ${message.name}';
}
}
}

Generated clients return the standard grpc-dart call objects:

// Server streaming.
await for (final reply in client.lotsOfReplies(HelloRequest()..name = 'Fory')) {
print(reply.reply);
}

// Client streaming.
final summary = await client.lotsOfGreetings(
Stream.fromIterable([
HelloRequest()..name = 'Ada',
HelloRequest()..name = 'Grace',
]),
);
print(summary.reply);

// Bidirectional streaming.
await for (final reply in client.chat(
Stream.fromIterable([HelloRequest()..name = 'Fory']),
)) {
print(reply.reply);
}

The generated descriptors preserve the exact IDL service and method names for the gRPC path, while the Dart methods use camelCase names.

Generated Module Names

Dart model files and schema modules are named after the package's last segment, not the gRPC service name. (When a schema has no package, the source file stem is used instead.)

Schema input (package)Model fileSchema module
service.fdl (demo.greeter)greeter.dartGreeterForyModule
api.fdl (demo.order_events)order_events.dartOrderEventsForyModule
greeter.fdl (demo.greeter)greeter.dartGreeterForyModule

A gRPC service named Greeter still generates the companion <stem>_grpc.dart with GreeterClient and GreeterServiceBase; it does not change the schema module name. If several schema files use the same package leaf, place them in distinct output directories or choose package/file names that produce distinct Dart model files.

Operations

The generated service code only replaces request and response serialization. All normal gRPC operational features still belong to your gRPC stack:

  • Deadlines and cancellations
  • TLS and authentication
  • Name resolution and load balancing
  • Client and server interceptors
  • Status codes and metadata
  • Channel lifecycle management

Troubleshooting

Missing package:grpc Types

Add grpc to your application dependencies. Generated Fory service files import grpc-dart APIs, but fory intentionally does not depend on gRPC.

Generated Code References a Missing .fory.dart Part

Run dart run build_runner build --delete-conflicting-outputs after generating or regenerating the Dart sources. The serializer part file is produced by build_runner, not by foryc.

Protobuf Clients Cannot Decode the Service

Fory gRPC companions do not use protobuf wire encoding for messages. Use a Fory-generated client for Fory-generated services, or expose a separate protobuf service endpoint for generic protobuf clients.