Skip to content

Getting started

By the end of this you will have a bus running on channels, a subscription receiving events, and an understanding of the one call that matters at start-up.

No broker is involved. The in-memory backend is a real backend rather than a test double, so everything here is the same code path a NATS-backed bus takes.

1. Build a bus, with its handler

import (
    "gitlab.com/phpboyscout/go/messaging"
    "gitlab.com/phpboyscout/go/messaging/memory"
)

bus, err := messaging.New(memory.New(), messaging.Settings{
    Subscriptions: []messaging.SubscriptionSpec{
        {
            Name:    "greeter",
            Pattern: "greeting.>",
            Shape:   messaging.Fanout,
            Handler: func(ctx context.Context, d messaging.Delivery) error {
                log.Info("received", "subject", d.Subject, "id", d.Event.ID)

                return nil
            },
        },
    },
})
if err != nil {
    return err
}

The handler is part of the subscription's declaration, not a separate step: a subscription without one is a construction error, so there is no window where a subscription exists with nothing to run against it.

d.Subject is the concrete subject, not the pattern that selected it. A subscription on greeting.> needs to know whether it got greeting.en or greeting.fr.

Two things have not happened yet: nothing is connected, and nothing is receiving. Construction registers your subscriptions as intent, which is why it does not need a broker to be reachable.

2. Start, and gate once

if err := bus.Start(ctx); err != nil {
    return err
}
defer bus.Stop(context.Background())

select {
case <-bus.Ready():
    // publish and consume freely from here
case <-time.After(30 * time.Second):
    return errors.New("the bus did not become ready")
}

This is the call that matters. Ready() closes only once every subscription you registered at construction exists on the connection, so a publisher never has to know whether any subscriber is ready — only whether the bus is.

The budget is yours to choose, because how long your service should wait for messaging before declaring itself broken is a property of your service and not of this module.

3. Publish

err := bus.Publish(ctx, "greeting.en", cloudevents.Event{
    SpecVersion: "1.0",
    ID:          "01JQ...",
    Source:      "urn:example:tutorial",
    Type:        "com.example.greeting.v1",
    Data:        []byte(`{"text":"hello"}`),
})

Publishing before Ready() closes returns an error, and the message is not held and delivered later. That matters more than it looks: "it was rejected" and "it was quietly held and delivered afterwards" are opposite contracts that look identical from the publisher's side.

What you have

A bus on channels. To put it on NATS, swap memory.New() for the NATS backend and change nothing else — that is the whole point of the exercise.

Next: wire a bus into a controller, which is how it looks in a real service.