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 written 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 a queue, a function, 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.
Get started
The same handler, in the language you build in. .NET is the reference implementation; Go, TypeScript, and Python are early ports of the same spec.
dotnet add package Benzene.AspNet.Core --prerelease
[Message("greet")]
[HttpEndpoint("POST", "/greet")]
public class GreetHandler : IMessageHandler<GreetRequest, GreetResponse>
{
public Task<IBenzeneResult<GreetResponse>> HandleAsync(GreetRequest message) =>
Task.FromResult(BenzeneResult.Ok(new GreetResponse { Greeting = $"Hello, {message.Name}!" }));
}
// Wire it onto any host - here ASP.NET Core, in Program.cs:
builder.Services.UsingBenzene(x => x.AddMessageHandlers(typeof(GreetHandler).Assembly));
app.UseBenzene(b => b.UseHttp(http => http.UseMessageHandlers()));
go get github.com/daniellepelley/benzene-go
func greetHandler(ctx context.Context, req GreetRequest) benzene.Result[GreetResponse] {
if req.Name == "" {
return benzene.BadRequest[GreetResponse]("name is required")
}
return benzene.Ok(GreetResponse{Greeting: "Hello, " + req.Name + "!"})
}
// Register it against a topic, reachable over HTTP (and the wire envelope):
benzene.Register(registry, benzene.NewTopic("greet"),
benzene.Handler[GreetRequest, GreetResponse](greetHandler))
Early port — the API is still settling. See the repo for the current state.
npm install @benzene/express @benzene/core-message-handlers @benzene/http @benzene/results
@httpEndpoint("POST", "/greet")
@message("greet", { requestType: GreetRequest, responseType: GreetResponse })
export class GreetHandler implements IMessageHandler<GreetRequest, GreetResponse> {
handleAsync(request: GreetRequest): Promise<IBenzeneResultOf<GreetResponse>> {
return Promise.resolve(BenzeneResult.ok({ greeting: `Hello, ${request.name}!` }));
}
}
// Wire it onto any host - here Express, in index.ts:
app.use(benzene((pipeline) => useMessageHandlers(pipeline, GreetHandler)));
Early port — the API is still settling. See the repo for the current state.
pip install benzene-core benzene-http
from benzene.core import BenzeneMessageApplication, Registry, message
from benzene.results import Result
@message("say:hello")
async def hello(request: dict) -> Result:
return Result.ok({"greeting": f"Hello {request['name']}"})
# Wire it into an application - reachable over HTTP via benzene-http:
app = BenzeneMessageApplication(Registry().add(hello))
Early port — the API is still settling. See the repo for the current state.
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.
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.
Spec UI
A Swagger-UI-style browser for a sample Benzene message spec — topics, payloads, and validation rules.
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 →