# Event-Driven Architecture Migration Guide

This guide helps you migrate existing Bangstack applications to use the new event-driven architecture features introduced in v0.4.0.

## Table of Contents
1. [Overview](#overview)
2. [Prerequisites](#prerequisites)
3. [Migration Steps](#migration-steps)
4. [Event System Components](#event-system-components)
5. [Adding Events to Existing Workflows](#adding-events-to-existing-workflows)
6. [Real-Time Frontend Updates](#real-time-frontend-updates)
7. [Testing Event-Driven Features](#testing-event-driven-features)
8. [Troubleshooting](#troubleshooting)

## Overview

Bangstack v0.4.0 introduces a powerful event-driven architecture that enables:
- ✅ Redis pub/sub event bus for decoupled component communication
- ✅ Automatic event type generation with TypeScript interfaces
- ✅ WebSocket server for real-time updates
- ✅ React hooks for subscribing to events in the frontend
- ✅ Automatic event emission from CRUD operations

## Prerequisites

Before migrating, ensure you have:
- Bangstack v0.4.0 or later installed
- Redis available (local or cloud provider like Upstash)
- An existing Bangstack workflow (or create a new one)

## Migration Steps

### Step 1: Add Redis Infrastructure

First, add Redis to your workflow's infrastructure configuration:

```yaml
technology:
  cache:
    provider: redis
    use: local  # or 'upstash' for cloud

# If using local Redis
stages:
  local:
    infrastructure:
      services:
        redis:
          provider: local
          connection: redis://localhost:6379
```

For Upstash (recommended for production):

```yaml
technology:
  cache:
    provider: redis
    use: upstash

stages:
  production:
    infrastructure:
      services:
        redis:
          provider: upstash
          connection: "{{ env.UPSTASH_REDIS_URL }}"
```

### Step 2: Define Your Events

Add an `events` section to your workflow YAML:

```yaml
events:
  user_registered:
    description: Triggered when a new user signs up
    payload:
      user_id: string
      email: string
      created_at: string

  task_created:
    description: Task was created
    payload:
      task_id: string
      title: string
      user_id: string

  task_updated:
    description: Task was updated
    payload:
      task_id: string
      updates: object
```

### Step 3: Regenerate Your Application

Run the Bangstack generator to create the event infrastructure:

```bash
bang generate workflow.yaml --output ./your-app
```

This will generate:
- Event type definitions in `backend/src/types/events.ts`
- Event bus in `backend/src/events/event-bus.ts`
- Event emitter utility in `backend/src/events/event-emitter.ts`
- Event handlers for each event in `backend/src/events/handlers/`
- WebSocket server in `backend/src/websocket/server.ts` (if frontend is configured)
- React hooks in `frontend/src/hooks/useEvents.ts` (if React frontend)

### Step 4: Start Redis

If using local Redis:

```bash
# Using Docker (recommended)
docker run -d -p 6379:6379 redis:latest

# Or install Redis locally
brew install redis  # macOS
redis-server
```

### Step 5: Update Your Backend Server

The generated backend server automatically initializes the event infrastructure. Verify in `backend/src/index.ts`:

```typescript
import { getEventBus } from './events/event-bus';
import { initializeAllSubscribers } from './events/subscriber-initializer';
import { initializeRealtimeServer } from './websocket/server';
import { initializeEventBridge } from './websocket/event-bridge';

// Event infrastructure initialization
const eventBus = getEventBus({
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT || '6379')
});

// Initialize event subscribers
initializeAllSubscribers(eventBus);

// Initialize WebSocket for real-time updates
const wss = initializeRealtimeServer(server);
initializeEventBridge(eventBus, wss);
```

### Step 6: Implement Event Handlers

Generated event handlers are stubs. Implement your business logic:

```typescript
// backend/src/events/handlers/user_registered-handler.ts
import { UserRegisteredEvent } from '../../types/events';

export async function handleUserRegisteredHandler(event: UserRegisteredEvent) {
  console.log('User registered:', event.payload);

  // TODO: Implement your business logic
  // Examples:
  // - Send welcome email
  // - Create default user preferences
  // - Trigger analytics tracking
  // - Update user statistics

  // Example: Send welcome email
  await emailService.send({
    to: event.payload.email,
    template: 'welcome',
    data: { userId: event.payload.user_id }
  });
}
```

## Event System Components

### Event Types (`backend/src/types/events.ts`)

TypeScript interfaces for type-safe event handling:

```typescript
export interface UserRegisteredEvent {
  user_id: string;
  email: string;
  created_at: string;
}

export interface TaskCreatedEvent {
  task_id: string;
  title: string;
  user_id: string;
}

// Type guard for runtime type checking
export function isUserRegisteredEvent(event: any): event is UserRegisteredEvent {
  return event &&
    typeof event.user_id === 'string' &&
    typeof event.email === 'string' &&
    typeof event.created_at === 'string';
}
```

### Event Bus (`backend/src/events/event-bus.ts`)

Redis-backed pub/sub event bus:

```typescript
import Redis from 'ioredis';

export class EventBus {
  private publisher: Redis;
  private subscriber: Redis;

  async publish<T>(eventType: string, payload: T): Promise<void> {
    await this.publisher.publish(eventType, JSON.stringify(payload));
  }

  subscribe<T>(eventType: string, handler: (payload: T) => Promise<void>): void {
    this.subscriber.subscribe(eventType);
    this.subscriber.on('message', async (channel, message) => {
      if (channel === eventType) {
        const payload = JSON.parse(message) as T;
        await handler(payload);
      }
    });
  }
}
```

### Event Emitter (`backend/src/events/event-emitter.ts`)

Utility for emitting events with request context:

```typescript
import { getEventBus } from './event-bus';

export async function emitEventWithContext<T>(
  req: Request,
  eventType: string,
  payload: T
): Promise<void> {
  const eventBus = getEventBus();

  // Enrich payload with context
  const enrichedPayload = {
    ...payload,
    timestamp: new Date().toISOString(),
    user_id: req.user?.id,
    request_id: req.id
  };

  await eventBus.publish(eventType, enrichedPayload);
}
```

## Adding Events to Existing Workflows

### Automatic CRUD Event Emission

When you define events in your workflow, CRUD endpoints automatically emit events:

```yaml
events:
  task_created:
    description: Task was created
    payload:
      task_id: string
      title: string

  task_updated:
    description: Task was updated
    payload:
      task_id: string

models:
  Task:
    fields:
      id:
        type: string
        primary_key: true
      title:
        type: string
```

Generated route automatically emits events:

```typescript
// backend/src/routes/task.ts
import { emitEventWithContext } from '../events/event-emitter';

router.post('/', async (req, res) => {
  const task = await prisma.task.create({
    data: req.body
  });

  // Automatically generated event emission
  await emitEventWithContext(req, 'task_created', {
    task_id: task.id,
    title: task.title
  });

  res.json(task);
});

router.put('/:id', async (req, res) => {
  const task = await prisma.task.update({
    where: { id: req.params.id },
    data: req.body
  });

  // Automatically generated event emission
  await emitEventWithContext(req, 'task_updated', {
    task_id: task.id
  });

  res.json(task);
});
```

### Manual Event Emission

Emit events manually from any backend code:

```typescript
import { emitEventWithContext } from './events/event-emitter';

// In your route handler
await emitEventWithContext(req, 'user_registered', {
  user_id: user.id,
  email: user.email,
  created_at: user.createdAt.toISOString()
});
```

## Real-Time Frontend Updates

### WebSocket Server

The generated WebSocket server broadcasts events to connected clients:

```typescript
// backend/src/websocket/server.ts
import { WebSocketServer } from 'ws';

export function initializeRealtimeServer(server: Server): WebSocketServer {
  const wss = new WebSocketServer({ server });

  wss.on('connection', (ws) => {
    console.log('Client connected');

    ws.on('message', (message) => {
      // Handle client messages (e.g., authentication)
    });
  });

  return wss;
}
```

### Event Bridge

Connects the event bus to WebSocket clients:

```typescript
// backend/src/websocket/event-bridge.ts
export function initializeEventBridge(eventBus: EventBus, wss: WebSocketServer) {
  // Subscribe to all events and broadcast to WebSocket clients
  eventBus.subscribeAll((eventType, payload) => {
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({ type: eventType, payload }));
      }
    });
  });
}
```

### React WebSocket Hooks

Use generated React hooks to subscribe to events:

```typescript
// frontend/src/hooks/useEvents.ts
import { useEffect, useState } from 'react';
import { WebSocketClient } from '../lib/websocket-client';

export function useEvent<T>(eventType: string, handler: (payload: T) => void) {
  useEffect(() => {
    const client = WebSocketClient.getInstance();
    client.connect();

    const unsubscribe = client.subscribe(eventType, handler);

    return () => {
      unsubscribe();
    };
  }, [eventType, handler]);
}

export function useEvents() {
  const [events, setEvents] = useState<any[]>([]);

  useEffect(() => {
    const client = WebSocketClient.getInstance();
    client.connect();

    const unsubscribe = client.subscribeAll((event) => {
      setEvents(prev => [...prev, event]);
    });

    return () => {
      unsubscribe();
    };
  }, []);

  return events;
}
```

### Using Events in React Components

```typescript
// Example: Real-time task list
import { useEvent } from '../hooks/useEvents';
import { TaskCreatedEvent } from '../types/events';

export function TaskList() {
  const [tasks, setTasks] = useState([]);

  // Subscribe to task_created events
  useEvent<TaskCreatedEvent>('task_created', (payload) => {
    setTasks(prev => [...prev, {
      id: payload.task_id,
      title: payload.title
    }]);
  });

  // Subscribe to task_updated events
  useEvent('task_updated', (payload) => {
    setTasks(prev => prev.map(task =>
      task.id === payload.task_id
        ? { ...task, ...payload.updates }
        : task
    ));
  });

  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>{task.title}</li>
      ))}
    </ul>
  );
}
```

## Testing Event-Driven Features

### Testing Event Emission

```typescript
// backend/src/routes/__tests__/task.test.ts
import { EventBus } from '../../events/event-bus';

describe('Task Routes', () => {
  let eventBus: EventBus;
  let publishSpy: jest.SpyInstance;

  beforeEach(() => {
    eventBus = new EventBus(/* ... */);
    publishSpy = jest.spyOn(eventBus, 'publish');
  });

  it('should emit task_created event when creating task', async () => {
    const response = await request(app)
      .post('/api/tasks')
      .send({ title: 'Test Task' });

    expect(publishSpy).toHaveBeenCalledWith('task_created', {
      task_id: response.body.id,
      title: 'Test Task'
    });
  });
});
```

### Testing Event Handlers

```typescript
// backend/src/events/handlers/__tests__/user_registered-handler.test.ts
import { handleUserRegisteredHandler } from '../user_registered-handler';

describe('User Registered Handler', () => {
  it('should send welcome email', async () => {
    const event = {
      user_id: '123',
      email: 'test@example.com',
      created_at: new Date().toISOString()
    };

    await handleUserRegisteredHandler(event);

    expect(emailService.send).toHaveBeenCalledWith({
      to: 'test@example.com',
      template: 'welcome',
      data: { userId: '123' }
    });
  });
});
```

### Testing WebSocket Integration

```typescript
// frontend/src/hooks/__tests__/useEvents.test.tsx
import { renderHook } from '@testing-library/react-hooks';
import { useEvent } from '../useEvents';
import { WebSocketClient } from '../../lib/websocket-client';

describe('useEvent', () => {
  it('should subscribe to event and call handler', () => {
    const handler = jest.fn();
    const { unmount } = renderHook(() => useEvent('task_created', handler));

    // Simulate event
    WebSocketClient.getInstance().simulateEvent('task_created', {
      task_id: '123',
      title: 'Test'
    });

    expect(handler).toHaveBeenCalledWith({
      task_id: '123',
      title: 'Test'
    });

    unmount();
  });
});
```

## Troubleshooting

### Redis Connection Issues

**Problem:** Cannot connect to Redis

**Solution:**
```bash
# Check if Redis is running
redis-cli ping  # Should return "PONG"

# Check connection string
echo $REDIS_HOST
echo $REDIS_PORT

# Verify .env file
cat backend/.env | grep REDIS
```

### Events Not Being Received

**Problem:** Event handlers not executing

**Solution:**
1. Check event bus initialization in `backend/src/index.ts`
2. Verify Redis pub/sub connections are active
3. Check event type spelling matches exactly
4. Review logs for subscription errors:

```typescript
// Add logging to event-bus.ts
subscribe<T>(eventType: string, handler: (payload: T) => Promise<void>): void {
  console.log(`Subscribing to event: ${eventType}`);
  this.subscriber.subscribe(eventType);
  // ...
}
```

### WebSocket Connection Failing

**Problem:** Frontend can't connect to WebSocket

**Solution:**
1. Verify WebSocket server is initialized in backend
2. Check CORS configuration allows WebSocket connections
3. Verify WebSocket URL is correct:

```typescript
// frontend/src/lib/websocket-client.ts
const wsUrl = process.env.REACT_APP_WS_URL || 'ws://localhost:3000';
console.log('Connecting to WebSocket:', wsUrl);
```

### Type Errors in Event Payloads

**Problem:** TypeScript errors with event payload types

**Solution:**
1. Regenerate event types: `bang generate workflow.yaml --output ./`
2. Verify event definition in workflow YAML matches usage
3. Use type guards for runtime validation:

```typescript
import { isTaskCreatedEvent } from '../types/events';

eventBus.subscribe('task_created', async (payload) => {
  if (!isTaskCreatedEvent(payload)) {
    console.error('Invalid event payload:', payload);
    return;
  }

  // TypeScript now knows payload is TaskCreatedEvent
  console.log(payload.task_id);
});
```

## Next Steps

1. **Read the Event-Driven Schema Guide**: See [BANGSTACK-EVENT-DRIVEN-SCHEMA.md](./BANGSTACK-EVENT-DRIVEN-SCHEMA.md) for the full event system design
2. **Explore Examples**: Check `examples/example-event-driven.workflow.yaml` for a complete example
3. **Learn Best Practices**: Review event naming conventions and payload design patterns
4. **Scale Your Events**: Learn about event versioning and backward compatibility

## Additional Resources

- [Event-Driven Schema Reference](./BANGSTACK-EVENT-DRIVEN-SCHEMA.md)
- [Extended Workflow Guide](./EXTENDED-SCHEMA-GUIDE.md)
- [API Reference](./API-REFERENCE.md)
- [Example Workflows](../examples/)

---

**Need help?** Open an issue on [GitHub](https://github.com/mogullabs/bangstack/issues) or check the [discussions](https://github.com/mogullabs/bangstack/discussions).
