What is BSB?

BSB is a framework for building microservices that communicate through events. Instead of direct HTTP calls between services, every service publishes and subscribes to events through a central event bus.

The key insight: your business logic should not care about infrastructure. Whether your events go through an in-memory bus, RabbitMQ, or Kafka - your service code stays the same.

Core Principles

Event-Driven

Services communicate exclusively through typed events. No direct coupling between services - just event contracts.

Plugin Architecture

Everything is a plugin: configuration, logging, events, metrics, and your services. Swap implementations without changing code.

Container-First

Designed for Docker and Kubernetes. Mount plugin directories at runtime. Configure through environment variables.

Type-Safe

Define event schemas with Zod. Get compile-time type checking and runtime validation across all services.

How It Works

When BSB starts, it discovers and loads plugins, then orchestrates the service lifecycle:

BSB Runtime
Config
📋 Logging
📊 Metrics
Events Plugin routes events between all service plugins
Service A Your Code
Service B Your Code
Service C Your Code

Plugin Types

BSB has five types of plugins. Service plugins are where you write your business logic. The other four are core infrastructure plugins that power the framework:

  • service-* - Your code goes here. Business logic, API handlers, workers - this is what you build.
  • config-* - Loads configuration (YAML, Consul, etcd, etc.)
  • events-* - Routes events between services (in-memory, RabbitMQ, Kafka, etc.)
  • logging-* - Handles log output (console, Graylog, Datadog, etc.)
  • metrics-* - Collects metrics (Prometheus, OpenTelemetry, etc.)

Event Communication

Services communicate through three event patterns:

Pattern Description Use Case
emitEvent Fire-and-forget. First listener receives. Background jobs, notifications
emitEventAndReturn Request-response. Wait for a result. Validation, data lookup, RPC-style calls
emitBroadcast All listeners receive the event. Cache invalidation, real-time updates

Service Lifecycle

Every service plugin follows a predictable lifecycle:

  1. Constructor - Plugin is instantiated with configuration
  2. init() - Set up event listeners and dependencies
  3. run() - Start processing (called after all services init)
  4. dispose() - Clean up on shutdown

Services can declare dependencies on other services using initBeforePlugins, initAfterPlugins, runBeforePlugins, and runAfterPlugins.

Type-Safe Events with Schemas

BSB uses schemas to define event contracts. This gives you autocomplete, type checking, and runtime validation:

// In init() - register listener. Trace comes FROM the caller.
async init(trace: Trace) {
  await this.events.onReturnableEvent('user.validate',
    async (callerTrace, data) => {
      // callerTrace lets you see who called this event
      return { valid: data.email.includes('@') };
    }
  );
}

// In run() - emit event. Pass YOUR trace so callers can track you.
async run(trace: Trace) {
  const result = await this.events.emitEventAndReturn(
    'user.validate',
    trace,  // pass startup trace for observability
    { email: 'user@example.com' },
    5  // timeout seconds
  );
}
// In Init() - register listener. Trace comes FROM the caller.
func (p *Plugin) Init(ctx context.Context) error {
    p.events.OnReturnableEvent("user.validate",
        func(ctx context.Context, data ValidateUserInput) (ValidateUserOutput, error) {
            // ctx contains the caller's trace
            return ValidateUserOutput{Valid: true}, nil
        },
    )
    return nil
}

// In Run() - emit event. Pass YOUR trace so callers can track you.
func (p *Plugin) Run(ctx context.Context) error {
    result, err := events.EmitAndReturn[ValidateUserOutput](
        ctx,  // pass startup context for observability
        "user.validate",
        ValidateUserInput{Email: "user@example.com"},
        5*time.Second,
    )
    return err
}
# In init() - register listener. Trace comes FROM the caller.
async def init(self, trace: Trace):
    @self.events.on_returnable_event("user.validate")
    async def validate(caller_trace: Trace, data: ValidateUserInput):
        # caller_trace lets you see who called this event
        return ValidateUserOutput(valid="@" in data.email)

# In run() - emit event. Pass YOUR trace so callers can track you.
async def run(self, trace: Trace):
    result = await self.events.emit_and_return(
        "user.validate",
        trace,  # pass startup trace for observability
        ValidateUserInput(email="user@example.com"),
        timeout=5
    )

Ready to Build?

Now that you understand the concepts, create your first service plugin.

Start with Node.js