Observability & Operations

Deployment Architectures

Monolith, microservices, Kubernetes, edge, serverless

PURISTA services are infrastructure-agnostic. The same business logic runs on a laptop, in a Docker container, on Kubernetes, or as a serverless function. The only thing that changes is the event bridge adapter and bootstrap configuration.

Deployment patterns

PatternArchitectureBest forComplexity
MonolithAll services in one processFastest delivery, smallest ops overheadLow
MicroservicesOne service per containerIndependent release cycles, team autonomyMedium
Serverless / FaaSFunction-per-triggerBursty workloads, platform-managed scalingMedium
EdgeLightweight single-processIoT, on-device, constrained environmentsLow

Deployment decision tree

flowchart TD
    A["Start here"] --> B{"Team size?"}
    B -->|Small, one team| C["Monolithic"]
    B -->|Multiple teams| D{"Release independence needed?"}
    D -->|No| C
    D -->|Yes| E["Microservice"]
    A --> F{"Workload pattern?"}
    F -->|Bursty, sporadic| G["Serverless / FaaS"]
    F -->|Continuous, low latency| H{"Environment?"}
    H -->|Cloud / Data center| E
    H -->|Edge / Device| I["Edge"]

Monolith — start here

All services share one process and one in-memory event bridge:

import { DefaultEventBridge } from '@purista/core'
import { userV1Service } from './services/user.js'
import { emailV1Service } from './services/email.js'

const eventBridge = new DefaultEventBridge()
await eventBridge.start()

const userService = await userV1Service.getInstance(eventBridge)
const emailService = await emailV1Service.getInstance(eventBridge)

await userService.start()
await emailService.start()

When to use: Small team, fast delivery, single deploy target.

Microservices — split when ready

Each service runs in its own container:

import { AmqpBridge } from '@purista/amqpbridge'
import { userV1Service } from './service.js'

const eventBridge = new AmqpBridge({ url: process.env.AMQP_URL })
const userService = await userV1Service.getInstance(eventBridge)
await userService.start()

When to use: Multiple teams, independent releases, different scaling needs.

Serverless — function-per-command

Individual commands deploy as functions. Serverless functions are short-lived, so use DefaultEventBridge (in-memory) with cloud-native stores for state and secrets:

import { DefaultEventBridge } from '@purista/core'
import { AwsSecretStore } from '@purista/aws-secret-store'
import { RedisStateStore } from '@purista/redis-state-store'
import { userV1Service } from './service.js'

export const handler = async (event) => {
  const eventBridge = new DefaultEventBridge()
  await eventBridge.start()

  const userService = await userV1Service.getInstance(eventBridge, {
    secretStore: new AwsSecretStore({ region: process.env.AWS_REGION }),
    stateStore: new RedisStateStore({ config: { url: process.env.REDIS_URL } }),
  })
  await userService.start()

  const response = await eventBridge.invoke(event)
  await userService.destroy()
  await eventBridge.destroy()
  return response
}

When to use: Bursty workloads, sporadic traffic, pay-per-invocation.

Edge — lightweight and local

Run services at the edge with minimal infrastructure:

import { MqttBridge } from '@purista/mqttbridge'
import { sensorV1Service } from './service.js'

const eventBridge = new MqttBridge({
  url: process.env.MQTT_URL,
  clientId: 'edge-sensor-001',
})

const sensorService = await sensorV1Service.getInstance(eventBridge)
await sensorService.start()

When to use: IoT, on-device processing, constrained environments.

Runtime configuration

EnvironmentEvent BridgeQueue BridgeStore
Local devDefaultEventBridgeDefaultQueueBridgeDefaultStateStore
CI / testingDefaultEventBridgeDefaultQueueBridgeDefaultStateStore
StagingAmqpBridge or NatsBridgeRedisQueueBridgeRedisStateStore
ProductionAmqpBridge or NatsBridgeRedisQueueBridge or NatsQueueBridgeRedisStateStore or cloud-native
ServerlessDefaultEventBridgeRedisQueueBridgeRedisStateStore
EdgeMqttBridgeMQTT-nativeDapr state store

Production checklist

  • Event bridge chosen and configured for durability requirements
  • Queue bridge configured for pull-based workloads
  • Graceful shutdown implemented (gracefulShutdown(logger, [eventBridge, ...services]))
  • Health checks exposed
  • OpenTelemetry exporter configured
  • Secrets in secret stores, not environment variables or code
  • Integration tests pass against real broker/store setup
  • Retry policies defined and documented
  • Idempotency implemented for command side effects

Common pitfalls

  • Designing for microservices too early. Start with a monolith. Extract when boundaries are clear.
  • Hardcoding bridge configuration. Use environment variables and config stores.
  • Ignoring cold starts. Serverless functions have startup latency.
  • Assuming shared memory. Even in a monolith, services should not share state.

Checklist

  • Deployment model matches team size and workload pattern
  • Bridge configuration is externalized
  • Graceful shutdown works in all deployment targets
  • Health checks are exposed and monitored
  • Integration tests pass against the target infrastructure
  • Scaling strategy is documented
  • Rollback plan exists for production deployments

Related

Read Next
Getting Started

from Learning Paths & Tutorials