Skip to content
Back to writing

Technical

Event-Driven Architecture: Lessons from Enterprise IoT Transformation

3 min read

When I joined Packsize, the software organization was struggling with a legacy monolith that couldn't scale. By leading the transformation to distributed, event-driven systems, we achieved a 15x increase in throughput while serving 3000+ IoT devices and 2000+ customers.

The Challenge: Legacy Systems at Scale

Our original system suffered from classic enterprise problems:

  • Tight coupling: Changes in one area broke unexpected parts
  • Deployment bottlenecks: All code had to deploy together
  • Team dependencies: Multiple teams stepping on each other
  • Limited scalability: The entire system scaled as one unit

More critically, we served industrial IoT devices requiring high reliability and regional deployment capabilities.

Domain-Driven Design Foundation

Before writing code, we mapped our business domains:

  • Device Management: IoT device registration, monitoring, control
  • Order Processing: Customer orders and fulfillment workflows
  • Packaging Optimization: Box sizing and material calculation
  • Customer Management: User accounts, permissions, billing
  • Analytics: Data aggregation and business intelligence

Each domain became a bounded context with its own data model, business rules, and team ownership.

Event-Driven Architecture Patterns

Event Sourcing

Instead of storing current state, we stored the sequence of events that led to that state:

interface DeviceEvent {
  deviceId: string;
  eventType: 'REGISTERED' | 'ACTIVATED' | 'DEACTIVATED' | 'ERROR';
  timestamp: Date;
  payload: any;
}

This gave us:

  • Complete audit trail: Every change is recorded
  • Time travel: Replay events to any point in time
  • Debugging: Understand exactly what happened
  • Analytics: Rich data for business intelligence

CQRS (Command Query Responsibility Segregation)

We separated write operations (commands) from read operations (queries):

  • Commands: Change state, published as events
  • Queries: Read from optimized projections
  • Projections: Materialized views built from events

Enterprise Event Bus

I built our enterprise event bus using Azure Service Bus, designed for:

  • Reliability: Guaranteed message delivery with retry logic
  • Scalability: Handle thousands of events per second
  • Observability: Full tracing and monitoring of event flows
  • Schema evolution: Backward-compatible event versioning

Implementation Strategy

Phase 1: Strangler Fig Pattern

We didn't rewrite everything at once. Instead, we gradually replaced functionality:

  1. Identify boundaries: Find natural seams in the monolith
  2. Extract services: Pull out well-defined capabilities
  3. Route traffic: Gradually shift load to new services
  4. Retire old code: Remove unused monolith pieces

Phase 2: Event-First Design

New features were built event-first:

  1. Define events: What happened in the business?
  2. Design aggregates: What entities produce these events?
  3. Build projections: What queries do we need?
  4. Implement handlers: How do we respond to events?

Phase 3: Cross-Cutting Concerns

We standardized infrastructure concerns:

  • Service discovery: Consul for service registration
  • Configuration: Centralized config management
  • Monitoring: Distributed tracing with Application Insights
  • Security: OAuth 2.0 with Azure AD integration

Technical Implementation

Event Schema Design

interface BaseEvent {
  id: string;
  aggregateId: string;
  aggregateType: string;
  eventType: string;
  version: number;
  timestamp: Date;
  correlationId: string;
  causationId: string;
}

interface DeviceRegisteredEvent extends BaseEvent {
  eventType: 'DeviceRegistered';
  data: {
    deviceId: string;
    customerId: string;
    deviceType: string;
    location: string;
  };
}

Event Store Implementation

We used Azure Cosmos DB as our event store:

  • Partitioning: By aggregate ID for optimal performance
  • Consistency: Strong consistency within partition
  • Scalability: Automatic scaling based on throughput
  • Global distribution: Multi-region deployment

Message Processing

class EventHandler {
  async handle(event: BaseEvent): Promise<void> {
    try {
      await this.processEvent(event);
      await this.updateProjections(event);
      await this.publishDownstreamEvents(event);
    } catch (error) {
      await this.handleError(event, error);
    }
  }
}

Operational Excellence

Monitoring and Observability

  • Event flow visualization: Real-time event stream monitoring
  • Performance metrics: Latency, throughput, error rates
  • Business metrics: Devices processed, orders fulfilled
  • Alerting: Proactive notification of issues

Deployment Strategy

  • Blue-green deployments: Zero-downtime releases
  • Feature flags: Gradual rollout of new functionality
  • Rollback capability: Quick recovery from issues
  • Regional deployment: Independent service deployment by region

Results: Transformational Performance

The transformation delivered exceptional results:

  • 15x increase in boxes processed and staged
  • Deployment lead time: From weeks to minutes
  • Change failure rate: Reduced to under 5%
  • Mean time to recovery: Under 15 minutes
  • Regional deployment: Independent service deployment by region
  • Team velocity: 3x increase in feature delivery
  • System reliability: 99.9% uptime across all services

Lessons Learned

What Worked Well

  1. Domain-driven design: Clear boundaries reduced complexity
  2. Gradual migration: Strangler fig pattern minimized risk
  3. Event-first thinking: Natural decoupling and scalability
  4. Strong observability: Fast problem identification and resolution

What We'd Do Differently

  1. Start with events: Define event schemas before building services
  2. Invest in tooling: Better event visualization and debugging tools
  3. Team training: More upfront investment in event-driven patterns
  4. Testing strategy: Better integration testing for event flows

Getting Started with Event-Driven Architecture

If you're considering event-driven architecture:

  1. Start small: Pick one bounded context to experiment
  2. Define events: Focus on business events, not technical ones
  3. Build observability: You can't manage what you can't see
  4. Plan for failure: Design retry logic and error handling
  5. Invest in team skills: Event-driven thinking is different

Event-driven architecture isn't just a technical pattern—it's a way of thinking about business processes that leads to more resilient, scalable, and maintainable systems. The investment in transformation pays dividends in team velocity, system reliability, and business agility.

  • architecture
  • microservices
  • event-driven
  • azure
  • iot