The Bond logo: a stylized glue gun

About

Bond-over-gRPC provides code generation from Bond IDL service definitions to send Bond objects via gRPC.

Features

Defining Services

The Bond IDL has been extended to support the definition of services and generic services. These definitions are used by the Bond compiler to generate classes that provide:

To generate these classes, pass the --grpc flag to gbc (the Bond compiler tool).

Note that gRPC doesn’t provide a messaging pattern that matches the semantics of methods with a return type of nothing; to compensate, gbc provides generated wrappers to simulate the appropriate semantics.

Also note that Bond-over-gRPC only provides interfaces for gRPC’s streaming in C#; this functionality will be added to C++ in the coming months.

Implementations

Bond-over-gRPC is available for C# and C++.

Bond-over-gRPC for C#

Given a service definition like the following:

service Example
{
    ExampleResponse ExampleMethod(ExampleRequest);
}

gbc will generate C# classes for gRPC with the --grpc flag:

gbc c# --grpc example.bond

The key generated C# classes for gRPC are:

To build the service functionality, simply write a concrete service implementation by subclassing the server base and supplying the business logic:

public class ExampleServiceImpl : Example.ExampleBase
{
    public override async Task<IMessage<ExampleResponse>> ExampleMethod(IMessage<ExampleRequest> param, ServerCallContext context)
    {
        ExampleRequest request = param.Payload.Deserialize();
        var response = new ExampleResponse();

        // Service business logic goes here

        return Message.From(response);
    }
}

This service implementation is hooked up to a gRPC server as follows:

var server = new Grpc.Core.Server
{
    Services = { Example.BindService(new ExampleServiceImpl()) },
    Ports = { new Grpc.Core.ServerPort(ExampleHost, ExamplePort, Grpc.Core.ServerCredentials.Insecure) }
};
server.Start();

At this point the server is ready to receive requests and route them to the service implementation.

On the client side, the proxy stub establishes a connection to the server like this:

var channel = new Grpc.Core.Channel(ExampleHost, ExamplePort, Grpc.Core.ChannelCredentials.Insecure);
var client = new Example.ExampleClient(channel);

The proxy stub can then be used to make calls to the server as follows:

var request = new ExampleRequest();
// Fill in request fields here

IMessage<ExampleResponse> responseMessage = await client.Method(request);

ExampleResponse response = responseMessage.Payload.Deserialize().Payload;
// Examine response here

Note that the signatures generated by gbc are slightly different from the ones in the gRPC documentation: on the service side, the request is wrapped in IMessage<T> and on the client side, the response is wrapped in IMessage<T>; this allows for better control over the time of deserialization and also helps prevent slicing when using polymorphic Bond types. Note also that Bond-over-gRPC does not provide synchronous APIs in C# by design.

For more information about gRPC in C#, take a look at the gRPC C# tutorial.

There is a Bond-over-gRPC standalone example project.

See also the following example:

Bond-over-gRPC for C++

Given a service definition like the following:

service Example
{
    ExampleResponse ExampleMethod(ExampleRequest);
}

gbc will generate C++ classes for gRPC with the --grpc flag:

gbc c++ --grpc example.bond

The key generated C++ classes for gRPC are:

To build the service functionality, simply write a concrete service implementation by subclassing the server base and supplying the business logic:

class ExampleServiceImpl final : public Example::Service
{
public:
    using Example::Service::Service;

private:
    void ExampleMethod(
        bond::ext::grpc::unary_call<ExampleRequest, ExampleResponse> call) override
    {
        ExampleRequest request = call.request().Deserialize();
        ExampleResponse response;

        // Service business logic goes here

        call.Finish(response);
    }
}

This service implementation is hooked up to a gRPC server as follows:

bond::ext::grpc::thread_pool threadPool;
const std::string server_address{ Host + ":" + Port };

std::unique_ptr<ExampleServiceImpl> service{ new ExampleServiceImpl{ threadPool } };

grpc::ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());

auto server = bond::ext::grpc::server::Start(builder, std::move(service));

At this point the server is ready to receive requests and route them to the service implementation.

On the client side, the proxy stub establishes a connection to the server like this:

auto ioManager = std::make_shared<bond::ext::grpc::io_manager>();
bond::ext::grpc::thread_pool threadPool;
const std::string server_address{ Host + ":" + Port };

Example::Client client{
    grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials()),
    ioManager,
    threadPool };

The proxy stub can then be used to make calls to the server as follows:

ExampleRequest request;
// Fill in request fields here

// Blocking version using std::future
try
{
    ExampleResponse response = client.AsyncExampleMethod(request)
        .get().response().Deserialize();
    // Examine response here
}
catch (const bond::ext::grpc::UnaryCallException& e)
{
    // Examine e.status()
}

// Async version with a callback
client.AsyncExampleMethod(
    request,
    [](bond::ext::grpc::unary_call_result<ExampleResponse> result)
    {
        if (result.status().ok())
        {
            ExampleResponse response = result.response().Deserialize();
            // Examine response here
        }
        else
        {
            // Examine result.status()
        }
    });

Note these APIs are significantly different from the APIs presented in the gRPC documentation; Bond-over-gRPC is attempting to provide a more straightforward API for asynchronous communication than gRPC currently presents in C++. Bond-over-gRPC does not provide synchronous APIs in C++ by design.

The proxy stub has a number of overloads for each method. The simplest is demonstrated above, and there are ones that take bonded<T> and grpc::ClientContext arguments.

Using bonded<T> to wrap the request and response objects allows for better control over the time of deserialization and also helps prevent slicing when using polymorphic Bond types. As demonstrated above, convenience APIs are provided in some places to hide the use bonded<T> where possible. For use of bonded request objects and ClientContext arguments, see the pingpong example.

For more information about gRPC in C++, take a look at the gRPC C++ tutorial; however, keep in mind that the Bond-over-gRPC APIs diverge significantly from those documented there.

See also the following example: