Benzene

Write your business logic once and reach it over HTTP, SQS, SNS, Kafka, Event Hub, or gRPC — all at once, on the cloud you already run. Putting a queue in front of an HTTP endpoint, or adding a second event source to a worker, takes a line of wiring, not a rewrite.

Get started in 5 minutes View on GitHub
Build Status codecov NuGet License: MIT

Why Benzene?

Mix transports without the glue

Serverless ties your logic to its trigger. An SNS function can't also take SQS, and putting a queue in front of an HTTP service is bespoke plumbing. A Benzene handler is plain C# against a topic, so the same logic is reachable over HTTP, SQS, SNS, Kafka, and more at the same time. You add or change a transport in the wiring, never in the handler.

See what every service does

Handlers, topics, payloads, and validation rules are introspectable. Benzene generates OpenAPI and AsyncAPI specs and a live service map straight from your code, so your contract and cross-service topology track what actually runs instead of a hand-drawn diagram that drifts.

Test-first, out of the box

Every transport ships a test host and helpers, so you exercise a handler exactly as SQS, Lambda, or HTTP would invoke it: in memory, in a normal unit test, with no cloud and no emulator. Mock a dependency, send a message, and assert the result.

Cross-cutting concerns, once

Correlation IDs, logging, tracing, validation, retries, and health checks are composable middleware shared across every transport. Write them once instead of scattering them across handlers or re-implementing them per event source.

The core idea

Benzene separates what your service does from how it's invoked. A message handler contains your logic. A transport turns an incoming request into a message and routes it to the matching handler through the middleware pipeline — the same pipeline, whichever transport is on the other end, and however many of them at once.

Your Message Handlers ASP.NET Core AWS Lambda Azure Functions Kafka Cloudflare gRPC / Worker

Quickstart

A message handler, mapped to a topic

[Message("hello:world")]
[HttpEndpoint("GET", "/hello/{name}")]
public class HelloWorldMessageHandler : IMessageHandler<HelloWorldRequest, HelloWorldResponse>
{
    public Task<IBenzeneResult<HelloWorldResponse>> HandleAsync(HelloWorldRequest message)
    {
        return Task.FromResult(BenzeneResult.Ok(new HelloWorldResponse { Message = $"Hello {message.Name}" }));
    }
}

Hosted as one AWS Lambda, reached over four transports

public class StartUp : BenzeneStartUp
{
    public override void ConfigureServices(IServiceCollection services, IConfiguration config) =>
        services.UsingBenzene(x => x.AddMessageHandlers(typeof(HelloWorldMessageHandler).Assembly));

    public override void Configure(IBenzeneApplicationBuilder app, IConfiguration config) =>
        app.UseAwsLambda(aws =>
        {
            // One function, the same handler reached four ways:
            aws.UseApiGateway(http => http.UseMessageHandlers());       // HTTP
            aws.UseSqs(sqs => sqs.UseMessageHandlers());                // SQS queue
            aws.UseSns(sns => sns.UseMessageHandlers());                // SNS topic
            aws.UseEventBridge(events => events.UseMessageHandlers());  // EventBridge
        });
}

public class Function : AwsLambdaHost<StartUp>;

dotnet add package Benzene.Aws.Lambda.ApiGateway --prerelease

Add Benzene.Aws.Lambda.Sqs, .Sns, and .EventBridge the same way for the other transports — or a different host entirely. See the full AWS walkthrough →

And it runs wherever you already are

Cloud portability is the bonus, not the pitch — most teams pick one platform and stay. The point is that Benzene meets whichever one your platform team already chose: the same handlers run unchanged on any of these, so "which host" stays a deployment detail rather than an architecture decision.

AWS Lambda, API Gateway, SQS, SNS, Kafka, EventBridge
Azure Functions (isolated worker), Event Hub, Service Bus
Google Cloud Cloud Functions, Cloud Run
Cloudflare Containers
Kubernetes Any container host, with liveness/readiness health checks
Virtual machines / self-hosted ASP.NET Core or a long-running worker - consumes Kafka, HTTP, or any custom transport

Try it live

No sign-up, no install — these are the same self-contained dashboard pages your own Benzene services would serve, running here against sample data.

Mesh UI

A service-mesh dashboard over sample health checks, contract drift, and cross-service traffic.

Open the demo →

Spec UI

A Swagger-UI-style browser for a sample Benzene message spec — topics, payloads, and validation rules.

Open the demo →

Built for production, not just prototypes

The quickstart is five minutes; the reason to adopt Benzene is what happens after. Three deeper looks, for whoever is asking the question:

Why Benzene

The case for adopting it — lower cost of change, less lock-in, quality by construction, built to last. Read on →

Architecture

Ports and adapters applied honestly: handlers, transports, one pipeline, and a service that describes itself. See how it fits →

Operations

Observability, health, failure handling, and deployment — what it takes to run it, honestly scoped. Run it in production →

Read the docs →