Skip to content

Messaging & Event-Driven Best Practices

Production-ready patterns for event-driven systems.


Message Design

Idempotency

Implement idempotent message handlers:

import hashlib
from google.cloud import firestore

db = firestore.Client()

def process_message_idempotent(message):
    # Generate idempotency key
    message_id = message.message_id
    idempotency_key = hashlib.sha256(
        f"{message_id}:{message.data}".encode()
    ).hexdigest()

    # Check if already processed
    doc_ref = db.collection('processed_messages').document(idempotency_key)
    if doc_ref.get().exists:
        print(f"Message {message_id} already processed")
        return

    # Process message
    try:
        result = process_business_logic(message)

        # Mark as processed
        doc_ref.set({
            'message_id': message_id,
            'processed_at': firestore.SERVER_TIMESTAMP,
            'result': result
        })
    except Exception as e:
        print(f"Error processing message: {e}")
        raise

Message Structure

{
  "metadata": {
    "message_id": "uuid-12345",
    "correlation_id": "trace-abc",
    "timestamp": "2026-03-09T12:00:00Z",
    "version": "1.0",
    "source": "order-service"
  },
  "data": {
    "order_id": "12345",
    "customer_id": "67890",
    "amount": 100.00,
    "items": [...]
  }
}

Pub/Sub Best Practices

Message Attributes

from google.cloud import pubsub_v1

publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path('project-id', 'orders')

# Use attributes for filtering
data = json.dumps(order_data).encode('utf-8')
future = publisher.publish(
    topic_path,
    data,
    order_type='premium',
    priority='high',
    region='us-east',
    correlation_id=correlation_id
)

Error Handling

from google.cloud import pubsub_v1
import time

subscriber = pubsub_v1.SubscriberClient()
subscription_path = subscriber.subscription_path('project-id', 'my-sub')

def callback(message):
    try:
        # Process message
        process_message(message.data)
        message.ack()
    except RetryableError as e:
        # Nack for retry
        print(f"Retryable error: {e}")
        message.nack()
    except FatalError as e:
        # Ack to prevent reprocessing
        print(f"Fatal error: {e}")
        log_to_dead_letter(message)
        message.ack()

Monitoring

# Monitor subscription backlog
from google.cloud import monitoring_v3

client = monitoring_v3.MetricServiceClient()
project_name = f"projects/{project_id}"

# Query unacked messages
query = monitoring_v3.ListTimeSeriesRequest(
    name=project_name,
    filter='metric.type="pubsub.googleapis.com/subscription/num_unacked_messages_by_region"',
    interval=monitoring_v3.TimeInterval({
        "end_time": {"seconds": int(time.time())},
        "start_time": {"seconds": int(time.time() - 3600)},
    }),
)

results = client.list_time_series(request=query)
for result in results:
    print(f"Subscription: {result.resource.labels['subscription_id']}")
    print(f"Unacked messages: {result.points[0].value.int64_value}")

Event-Driven Patterns

Event Sourcing

from google.cloud import firestore

db = firestore.Client()

class OrderAggregate:
    def __init__(self, order_id):
        self.order_id = order_id
        self.events = []
        self.state = {}

    def apply_event(self, event):
        # Apply event to state
        if event['type'] == 'OrderCreated':
            self.state = {
                'order_id': event['data']['order_id'],
                'status': 'created',
                'items': event['data']['items']
            }
        elif event['type'] == 'OrderPaid':
            self.state['status'] = 'paid'
        elif event['type'] == 'OrderShipped':
            self.state['status'] = 'shipped'

    def load_from_events(self):
        # Load events from Firestore
        events_ref = db.collection('events').where('order_id', '==', self.order_id)
        for doc in events_ref.stream():
            event = doc.to_dict()
            self.apply_event(event)
            self.events.append(event)

    def save_event(self, event):
        # Save event to Firestore
        db.collection('events').add(event)
        self.apply_event(event)
        self.events.append(event)

CQRS (Command Query Responsibility Segregation)

# Write side (Commands)
class OrderCommandHandler:
    def create_order(self, command):
        # Validate command
        order = Order(command['order_id'])

        # Save to write database (Cloud SQL)
        save_to_sql(order)

        # Publish event
        publish_event('OrderCreated', order.to_dict())

# Read side (Queries)
class OrderQueryHandler:
    def get_order(self, order_id):
        # Read from read database (BigQuery)
        return query_bigquery(f"SELECT * FROM orders WHERE order_id = '{order_id}'")

    def get_orders_by_customer(self, customer_id):
        # Optimized for queries
        return query_bigquery(f"SELECT * FROM orders WHERE customer_id = '{customer_id}'")

# Event handler to sync read model
def sync_read_model(event):
    if event['type'] == 'OrderCreated':
        # Update BigQuery
        insert_to_bigquery('orders', event['data'])

Saga Pattern

from google.cloud import pubsub_v1, firestore

class OrderSaga:
    def __init__(self, order_id):
        self.order_id = order_id
        self.db = firestore.Client()
        self.publisher = pubsub_v1.PublisherClient()

    def start(self, order_data):
        # Step 1: Reserve inventory
        self.publish_command('inventory', 'ReserveItems', order_data)
        self.update_saga_state('inventory_reserved', 'pending')

    def handle_inventory_reserved(self, event):
        # Step 2: Process payment
        self.publish_command('payment', 'ProcessPayment', event['data'])
        self.update_saga_state('payment_processed', 'pending')

    def handle_payment_processed(self, event):
        # Step 3: Ship order
        self.publish_command('shipping', 'ShipOrder', event['data'])
        self.update_saga_state('order_shipped', 'pending')

    def handle_failure(self, step, error):
        # Compensating transactions
        if step == 'payment_processed':
            self.publish_command('inventory', 'ReleaseItems', {'order_id': self.order_id})
        elif step == 'order_shipped':
            self.publish_command('payment', 'RefundPayment', {'order_id': self.order_id})
            self.publish_command('inventory', 'ReleaseItems', {'order_id': self.order_id})

    def publish_command(self, service, command, data):
        topic_path = self.publisher.topic_path('project-id', f'{service}-commands')
        message = {
            'command': command,
            'saga_id': self.order_id,
            'data': data
        }
        self.publisher.publish(topic_path, json.dumps(message).encode('utf-8'))

    def update_saga_state(self, step, status):
        doc_ref = self.db.collection('sagas').document(self.order_id)
        doc_ref.set({
            step: status,
            'updated_at': firestore.SERVER_TIMESTAMP
        }, merge=True)

Performance Optimization

Batching

from google.cloud import pubsub_v1
from concurrent import futures

publisher = pubsub_v1.PublisherClient(
    batch_settings=pubsub_v1.types.BatchSettings(
        max_messages=100,
        max_bytes=1024 * 1024,  # 1 MB
        max_latency=0.1,  # 100 ms
    )
)

# Publish in batch
publish_futures = []
for message in messages:
    future = publisher.publish(topic_path, message.encode('utf-8'))
    publish_futures.append(future)

# Wait for all
futures.wait(publish_futures, return_when=futures.ALL_COMPLETED)

Parallel Processing

from concurrent.futures import ThreadPoolExecutor

def process_message(message):
    # Process individual message
    pass

def callback(message):
    with ThreadPoolExecutor(max_workers=10) as executor:
        executor.submit(process_message, message)
    message.ack()

Checklist

Design

  • [ ] Messages are idempotent
  • [ ] Correlation IDs included
  • [ ] Message schema versioned
  • [ ] Error handling implemented
  • [ ] Dead letter topics configured
  • [ ] Monitoring set up

Reliability

  • [ ] Retry logic with exponential backoff
  • [ ] Circuit breakers implemented
  • [ ] Timeouts configured
  • [ ] Health checks enabled
  • [ ] Graceful shutdown

Performance

  • [ ] Batching enabled
  • [ ] Appropriate concurrency
  • [ ] Message filtering used
  • [ ] Compression enabled
  • [ ] Connection pooling

Last Updated: March 2026
Status: ✅ Complete