Client SDK Generation

Because every Benzene handler declares its contract — a topic and typed request/response — Benzene can generate a strongly-typed C# client SDK for a service. Callers get a typed client instead of hand-assembling messages, and the client stays in sync with the service's handlers.

The generator lives in the Benzene.CodeGen.Client package and produces a {Service}ServiceClient class (implementing an I{Service}ServiceClient interface) with one …Async method per handler.

How it works

Generation runs off a service description called an EventServiceDocument — the same model behind the OpenAPI/AsyncAPI spec. You can build that document two ways:

Generating from a handler assembly

Given handlers in an assembly:

[Message("hello:world")]
public class HelloWorldMessageHandler : IMessageHandler<HelloWorldMessage, HelloWorldResponse>
{
    public Task<IBenzeneResult<HelloWorldResponse>> HandleAsync(HelloWorldMessage message)
        => BenzeneResult.Ok(new HelloWorldResponse { Message = $"Hello {message.Name}" }).AsTask();
}

Build the service document and run the SDK builder:

using Benzene.CodeGen.Client;
using Benzene.Core.MessageHandlers;
using Benzene.Schema.OpenApi.EventService;

// 1. Discover the handlers and turn them into a service document
var definitions = new ReflectionMessageHandlersFinder(typeof(HelloWorldMessageHandler).Assembly)
    .FindDefinitions();
var document = definitions.ToEventServiceDocument();

// 2. Generate the client SDK
var sdkBuilder = new MessageClientSdkBuilder(
    serviceName: "HelloWorld",
    baseNamespace: "Benzene.Examples.Clients");

var codeFiles = sdkBuilder.BuildCodeFiles(document);

// 3. Write the generated files out
foreach (var file in codeFiles)   // each ICodeFile has a Name and Lines
{
    File.WriteAllLines(file.Name, file.Lines);
}

This produces HelloWorldServiceClient.cs containing a HelloWorldServiceClient with a HelloWorldAsync(HelloWorldMessage message) method (plus a HealthCheckAsync() and header-aware overloads).

Using the generated client

The generated client takes an IBenzeneMessageSender (from Benzene.Clients) in its constructor and returns results as IBenzeneResult<T> — the same result model your handlers use:

public class HelloWorldServiceClient : IHelloWorldServiceClient
{
    public HelloWorldServiceClient(IBenzeneMessageSender sender) { /* generated */ }

    public Task<IBenzeneResult<HelloWorldResponse>> HelloWorldAsync(HelloWorldMessage message) { /* generated */ }
    public Task<IBenzeneResult<HelloWorldResponse>> HelloWorldAsync(HelloWorldMessage message, IDictionary<string, string> headers) { /* generated */ }
}

The generator also emits a sibling HelloWorldServiceClientRouting.RequiredTopics array, for ValidateOutboundRouting()'s startup check — see Validating routes at startup.

Configure the underlying transport by routing each of the client's topics via AddOutboundRouting(...) — for example .UseSqs(...)/.UseSns(...) from Benzene.Clients.Aws to call the service via AWS, or an HTTP transport for calling it over HTTP. The generated client is transport-agnostic; the outbound route registered for each topic decides how the message is actually sent.

var client = new HelloWorldServiceClient(sender);
var result = await client.HelloWorldAsync(new HelloWorldMessage { Name = "World" });
if (BenzeneResult.IsSuccess(result))
{
    Console.WriteLine(result.Payload.Message);
}

Generating message handler stubs

The same package includes MessageHandlerBuilder, which generates handler stubs from a service document — useful for scaffolding a new service from an existing contract:

var handlerFiles = new MessageHandlerBuilder("MyService.Handlers").BuildCodeFiles(document);

Generating from a deployed service

To generate a client from a service you don't have the source for, fetch its EventServiceDocument from the running service's spec endpoint (the service must have UseSpec() in its pipeline), then feed that document into MessageClientSdkBuilder exactly as above. The Benzene.CodeGen.Cli tool wraps this flow for command-line use.

Further Reading