Skip to content

Go Library as Contract

Instead of an OpenAPI or AsyncAPI document as the cross-service contract, a shared Go package defines types, codecs, and channel/route specs. Both services import it. The Go compiler enforces the contract: any field rename, type change, or constraint modification breaks compilation on both sides immediately — no stale YAML, no schema drift, no code-generation step.

Pattern

contract/
  contract.go   ← shared types, codecs, route/channel specs

producer/
  main.go       ← imports contract/, calls Publish / nethttp.Call

consumer/
  main.go       ← imports contract/, calls SubscribeHandler / nethttp.Register

HTTP example

// contract/contract.go
var CreateUser = rest.NewRoute[CreateUserReq, User](
    "POST", "/users", createUserReqCodec, userCodec,
    rest.RouteMeta{OperationID: "createUser"},
)

// server/main.go
handle, _ := contract.CreateUser.Register(builder)
nethttp.Register(mux, handle, myHandler, opts)

// client/main.go — same Route, no duplication
handle := contract.CreateUser.ClientHandle()
user, err := nethttp.Call(ctx, http.DefaultClient, serverURL, handle, req, nil, opts)

MQTT example

// contract/contract.go
var ReadingsChannel = events.NewChannel[SensorReading](
    "sensors/{sensorID}/readings", sensorReadingCodec,
    events.Subscribe{...}, events.Publish{...},
    events.TopicParam{Name: "sensorID"}.WithCodec(uuidCodec),
)

// producer/main.go
handle, _ := contract.ReadingsChannel.Register(producerBuilder)
adaptermqtt.Publish(ctx, client, handle, 1, false, reading, vars, opts)

// consumer/main.go
handle, _ := contract.ReadingsChannel.Register(consumerBuilder)
client.Subscribe(topic, 1, adaptermqtt.SubscribeHandler(ctx, handle, fn, opts))

When to use this pattern

Use the shared Go contract when: - All communicating services are written in Go - Compile-time contract enforcement matters more than cross-language interoperability - You want to avoid the drift between code and a separate spec file

For external-facing APIs (consumed by non-Go clients), generate an OpenAPI/AsyncAPI spec from the same codec definitions — both are supported simultaneously.

Examples