Message Brokers
Gebweb integrates with external message brokers (RabbitMQ AMQP,
ActiveMQ STOMP, AWS SQS, Kafka) via the language's messaging
module. Application code registers one or more named broker
handles on the app and tags handler methods with
@OnMessage("handle-name"). A worker process drives the
consume / subscribe loop and dispatches each incoming message to
the registered handlers.
The broker handle types come from stdlib/messaging (see the
language reference under "Standard library > Messaging" for
backend-specific configuration). Gebweb only routes between
handle names and handler methods; it does not own the broker
connection.
Wiring
import gebweb;
import messaging;
let orders = messaging.connect({
"driver": "rabbitmq",
"url": "amqp://guest:guest@localhost:5672/",
"queue": "orders"
});
let app = gebweb.app([HomeController()]);
gebweb.useMessageQueue(app, "orders", orders);
gebweb.registerMessageHandlers(app, [OrderProcessor()]);
useMessageQueue(app, name, handle) registers a queue handle (a
MessageQueue returned by messaging.connect).
useMessageTopic(app, name, handle) does the same for a topic
handle from messaging.topic. The name is the string passed to
@OnMessage; multiple handles can be registered under different
names and each handler routes to the matching one.
Topics (pub/sub)
Use a topic when one published message should be delivered to
several independent subscribers (e.g. a user.created event
consumed by both the email service and the analytics service).
Use a queue when exactly one consumer should receive each message
(work distribution).
import messaging;
let userEvents = messaging.topic({
"driver": "sns",
"region": "us-east-1",
"topic": "user-events",
});
gebweb.useMessageTopic(app, "user-events", userEvents);
Handlers register against the topic name the same way:
class UserEventListener {
@OnMessage("user-events")
func onEvent(any msg): void { /* ... */ }
}
Writing handlers
A handler is a method on any class. Decorate it with
@OnMessage("handle-name") and it joins the dispatch list for
that handle. The method receives the message dict produced by
the broker:
class OrderProcessor {
@OnMessage("orders")
func handle(any msg): void {
let m = msg as dict<string, any>;
let order = json.parse(m["body"] as string);
/* process; throw to surface the failure to the worker
* log. Queue backends will redeliver per their
* visibility-timeout / requeue rules. */
}
}
Multiple methods can subscribe to the same handle - stack
decorators or split across classes. They run in registration
order; if any handler throws, the loop collects the errors and
re-throws one aggregated RuntimeError after the full dispatch.
Running the worker
gebweb.runMessageWorker(app) blocks the calling process,
walking each registered handle and running consume() (queues)
or subscribe() (topics) in sequence. Most deployments have a
single broker handle per process; pass opts.handle to pin the
worker to one handle:
gebweb.runMessageWorker(app, {"handle": "orders"});
For a process that owns more than one (but not all) registered
handles, pass opts.handles as a list:
gebweb.runMessageWorker(app, {"handles": ["orders", "audit"]});
The gebweb worker --handle <name> CLI flag (repeatable)
populates GEBWEB_WORKER_HANDLES, which the facade reads
automatically; main.gb doesn't need to plumb anything through.
Different worker processes can specialise on different broker
handles by composing --handle flags.
To consume multiple handles concurrently in one process, spawn
each in its own task with async.run or async.scope:
import async.scope as ascope;
ascope.scope(func(any group): void {
group.spawn(func(): void {
gebweb.runMessageWorker(app, {"handle": "orders"});
});
group.spawn(func(): void {
gebweb.runMessageWorker(app, {"handle": "events"});
});
});
The structured-concurrency scope cancels in-flight loops cleanly if one of them throws.
CLI entry point
A typical main.gb branches on an env var so one binary serves
HTTP, a job worker, or a messaging worker:
if (sys.getenv("GEBWEB_RUN") == "messaging") {
gebweb.runMessageWorker(app);
} else if (sys.getenv("GEBWEB_RUN") == "worker") {
gebweb.runWorker(app);
} else {
gebweb.serve(app, "0.0.0.0:3000");
}
Differences vs @Job and @On
| Decorator | Source of messages | Dispatch |
|---|---|---|
@Job("name") |
Local SQL job table (gebweb_jobs) populated by gebweb.enqueue. |
Single worker pulls rows, calls one handler. |
@On("event-name") |
In-process gebweb.publish. |
Synchronous; every subscriber runs in the publisher's thread. |
@OnMessage("handle-name") |
External broker (RabbitMQ / STOMP / SQS / Kafka). | Worker drives the broker's consume / subscribe loop and routes each message to all registered handlers. |
Use @Job for asynchronous work owned by this app. Use
@OnMessage when work originates from (or is shared with)
other services on a broker.