Home / books / backup / building-with-larc-original-20251226 / chapter-19-advanced-patterns

Advanced Patterns

"Any sufficiently advanced technology is indistinguishable from magic. Any sufficiently advanced LARC pattern is indistinguishable from over-engineering." — Clarke's Third Law, Revised

You've mastered the basics of LARC. Your components communicate gracefully. Your state management is pristine. Your error handling would make a DevOps engineer weep tears of joy. But now you're ready for the advanced stuff—the patterns that separate the "just building apps" developers from the "architect a scalable micro-frontend ecosystem" developers.

Fair warning: some of these patterns are powerful. Some are clever. Some might be too clever. Use your judgment, and remember that the best code is the code your teammates can understand at 9 AM on a Monday.

Message Forwarding and Bridging

Sometimes you need messages from one bus to appear on another. Maybe you're integrating a third-party widget. Maybe you're building a multi-window application. Maybe you just like making things complicated (no judgment).

Basic Message Forwarding

Forward messages from one bus to another:

class MessageBridge extends Component {
  constructor(sourceBus, targetBus, messageTypes) {
    super();
    this.sourceBus = sourceBus;
    this.targetBus = targetBus;
    this.messageTypes = messageTypes || ['*']; // Forward all by default
  }

  init() {
    this.messageTypes.forEach(type => {
      this.sourceBus.on(type, (data) => {
        this.targetBus.emit(type, data);
      });
    });
  }

  render() {
    return null; // Bridges don't render
  }
}

// Usage
const mainBus = createBus();
const widgetBus = createBus();

// Forward user actions from widget to main app
const bridge = new MessageBridge(
  widgetBus,
  mainBus,
  ['user-click', 'user-input']
);

Bidirectional Bridging

When you need messages flowing both ways:

class BidirectionalBridge extends Component {
  constructor(busA, busB, config = {}) {
    super();
    this.busA = busA;
    this.busB = busB;
    this.config = {
      aToB: config.aToB || ['*'], // Types to forward A -> B
      bToA: config.bToA || ['*'], // Types to forward B -> A
      transform: config.transform || ((data) => data), // Transform data
      filter: config.filter || (() => true) // Filter messages
    };
  }

  init() {
    // Forward A -> B
    this.config.aToB.forEach(type => {
      this.busA.on(type, (data) => {
        if (this.config.filter(type, data, 'aToB')) {
          const transformed = this.config.transform(data, 'aToB');
          this.busB.emit(type, transformed);
        }
      });
    });

    // Forward B -> A
    this.config.bToA.forEach(type => {
      this.busB.on(type, (data) => {
        if (this.config.filter(type, data, 'bToA')) {
          const transformed = this.config.transform(data, 'bToA');
          this.busA.emit(type, transformed);
        }
      });
    });
  }

  render() {
    return null;
  }
}

// Usage with transformation
const bridge = new BidirectionalBridge(mainBus, widgetBus, {
  aToB: ['theme-changed', 'user-logged-in'],
  bToA: ['widget-action'],
  transform: (data, direction) => {
    // Add metadata for tracking
    return {
      ...data,
      bridged: true,
      direction,
      timestamp: Date.now()
    };
  },
  filter: (type, data, direction) => {
    // Don't forward internal messages
    return !type.startsWith('internal-');
  }
});

Message Translation

When buses speak different dialects:

class MessageTranslator extends Component {
  constructor(sourceBus, targetBus, translations) {
    super();
    this.sourceBus = sourceBus;
    this.targetBus = targetBus;
    this.translations = translations;
  }

  init() {
    Object.entries(this.translations).forEach(([sourceType, config]) => {
      this.sourceBus.on(sourceType, (data) => {
        const targetType = config.type || sourceType;
        const targetData = config.transform
          ? config.transform(data)
          : data;

        this.targetBus.emit(targetType, targetData);
      });
    });
  }

  render() {
    return null;
  }
}

// Usage: Translate between LARC app and legacy jQuery plugin
const translator = new MessageTranslator(larcBus, jqueryBus, {
  'user-logged-in': {
    type: 'userLogin', // Different naming convention
    transform: (data) => ({
      userId: data.id, // Different property names
      userName: data.username,
      timestamp: new Date().toISOString()
    })
  },
  'cart-updated': {
    type: 'cartChange',
    transform: (data) => ({
      items: data.cartItems.map(item => ({
        id: item.productId,
        qty: item.quantity,
        price: item.unitPrice
      }))
    })
  }
});

Multi-Bus Architectures

One bus is good. Multiple buses? That's when things get interesting (and complicated).

Domain-Segregated Buses

Separate concerns by domain:

class MultiDomainApp {
  constructor() {
    // Separate buses for different domains
    this.buses = {
      auth: createBus({ namespace: 'auth' }),
      cart: createBus({ namespace: 'cart' }),
      ui: createBus({ namespace: 'ui' }),
      analytics: createBus({ namespace: 'analytics' })
    };

    // Create cross-domain bridges
    this.setupBridges();
  }

  setupBridges() {
    // Auth events trigger analytics
    new MessageBridge(
      this.buses.auth,
      this.buses.analytics,
      ['user-logged-in', 'user-logged-out']
    );

    // Cart events trigger UI updates
    new MessageBridge(
      this.buses.cart,
      this.buses.ui,
      ['cart-updated']
    );

    // Auth changes affect cart
    this.buses.auth.on('user-logged-out', () => {
      this.buses.cart.emit('clear-cart');
    });
  }

  getComponentProps(domain) {
    return {
      bus: this.buses[domain],
      globalBus: this.buses.ui // Some components need global access
    };
  }
}

// Usage
const app = new MultiDomainApp();

class LoginForm extends Component {
  constructor(props) {
    super(props);
    this.authBus = props.bus; // Domain-specific bus
  }

  async handleLogin(username, password) {
    // Emit on auth bus
    this.authBus.emit('login-attempt', { username });

    const result = await this.authenticate(username, password);

    if (result.success) {
      this.authBus.emit('user-logged-in', {
        userId: result.userId,
        username: username
      });
    }
  }
}

const loginForm = new LoginForm(app.getComponentProps('auth'));

Hierarchical Bus Structure

Create parent-child bus relationships:

class HierarchicalBus {
  constructor(parent = null) {
    this.parent = parent;
    this.children = new Set();
    this.handlers = new Map();

    if (parent) {
      parent.children.add(this);
    }
  }

  emit(type, data, options = {}) {
    const { bubble = false, propagate = false } = options;

    // Handle locally
    this._emitLocal(type, data);

    // Bubble up to parent
    if (bubble && this.parent) {
      this.parent.emit(type, data, { bubble: true });
    }

    // Propagate down to children
    if (propagate) {
      this.children.forEach(child => {
        child.emit(type, data, { propagate: true });
      });
    }
  }

  _emitLocal(type, data) {
    const handlers = this.handlers.get(type) || [];
    handlers.forEach(handler => handler(data));

    const wildcardHandlers = this.handlers.get('*') || [];
    wildcardHandlers.forEach(handler => handler(type, data));
  }

  on(type, handler) {
    if (!this.handlers.has(type)) {
      this.handlers.set(type, []);
    }
    this.handlers.get(type).push(handler);
  }

  destroy() {
    if (this.parent) {
      this.parent.children.delete(this);
    }
    this.children.clear();
    this.handlers.clear();
  }
}

// Usage: App with nested modules
const appBus = new HierarchicalBus();
const moduleBus = new HierarchicalBus(appBus);
const subModuleBus = new HierarchicalBus(moduleBus);

// Local event
subModuleBus.emit('button-clicked', { id: 123 });

// Bubble up to parent
subModuleBus.emit('critical-error', { error: 'Oh no!' }, { bubble: true });

// Propagate down to all children
appBus.emit('theme-changed', { theme: 'dark' }, { propagate: true });

Backend Integration Strategies

LARC runs in the browser, but your data lives on a server. Let's build bridges between these two worlds.

API Gateway Component

Centralize all API calls in one component:

class APIGateway extends Component {
  init() {
    this.state = {
      baseURL: '/api',
      token: localStorage.getItem('authToken'),
      requestQueue: [],
      online: navigator.onLine
    };

    // Listen for API requests
    this.on('api-request', this.handleRequest);
    this.on('auth-token-updated', (data) => {
      this.state.token = data.token;
    });

    // Handle online/offline
    window.addEventListener('online', () => {
      this.state.online = true;
      this.flushQueue();
    });
    window.addEventListener('offline', () => {
      this.state.online = false;
    });
  }

  async handleRequest({ method, endpoint, data, requestId }) {
    if (!this.state.online) {
      this.state.requestQueue.push({ method, endpoint, data, requestId });
      this.emit('api-offline', { requestId });
      return;
    }

    try {
      const response = await fetch(`${this.state.baseURL}${endpoint}`, {
        method,
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this.state.token}`
        },
        body: data ? JSON.stringify(data) : undefined
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const result = await response.json();

      this.emit('api-success', {
        requestId,
        endpoint,
        result
      });

    } catch (error) {
      this.emit('api-error', {
        requestId,
        endpoint,
        error: error.message
      });
    }
  }

  async flushQueue() {
    const queue = [...this.state.requestQueue];
    this.state.requestQueue = [];

    for (const request of queue) {
      await this.handleRequest(request);
    }
  }

  render() {
    return null;
  }
}

// Usage in other components
class UserProfile extends Component {
  loadUserData(userId) {
    const requestId = crypto.randomUUID();

    this.emit('api-request', {
      method: 'GET',
      endpoint: `/users/${userId}`,
      requestId
    });

    this.once(`api-success`, (data) => {
      if (data.requestId === requestId) {
        this.state.user = data.result;
      }
    });

    this.once(`api-error`, (data) => {
      if (data.requestId === requestId) {
        this.state.error = data.error;
      }
    });
  }
}

WebSocket Integration

Real-time bidirectional communication:

class WebSocketBridge extends Component {
  init() {
    this.state = {
      connected: false,
      reconnectAttempts: 0,
      maxReconnectAttempts: 5
    };

    this.ws = null;
    this.connect();

    // Listen for outgoing messages
    this.on('ws-send', this.sendMessage);
    this.on('ws-disconnect', () => this.disconnect());
  }

  connect() {
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsURL = `${protocol}//${window.location.host}/ws`;

    this.ws = new WebSocket(wsURL);

    this.ws.onopen = () => {
      this.state.connected = true;
      this.state.reconnectAttempts = 0;
      this.emit('ws-connected');
    };

    this.ws.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        // Emit as LARC message
        this.emit(message.type, message.data);
      } catch (error) {
        console.error('Invalid WebSocket message:', event.data);
      }
    };

    this.ws.onclose = () => {
      this.state.connected = false;
      this.emit('ws-disconnected');
      this.attemptReconnect();
    };

    this.ws.onerror = (error) => {
      this.emit('ws-error', { error });
    };
  }

  sendMessage({ type, data }) {
    if (this.ws && this.state.connected) {
      this.ws.send(JSON.stringify({ type, data }));
    } else {
      console.warn('WebSocket not connected, message queued');
      // Could implement a queue here
    }
  }

  attemptReconnect() {
    if (this.state.reconnectAttempts >= this.state.maxReconnectAttempts) {
      this.emit('ws-reconnect-failed');
      return;
    }

    this.state.reconnectAttempts++;
    const delay = Math.min(1000 * Math.pow(2, this.state.reconnectAttempts), 30000);

    setTimeout(() => {
      this.connect();
    }, delay);
  }

  disconnect() {
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
  }

  render() {
    return null;
  }
}

// Usage
class ChatComponent extends Component {
  init() {
    this.state = { messages: [] };

    // Receive messages from WebSocket
    this.on('chat-message', (data) => {
      this.state.messages.push(data);
    });
  }

  sendMessage(text) {
    // Send via WebSocket
    this.emit('ws-send', {
      type: 'chat-message',
      data: {
        text,
        userId: this.getCurrentUserId(),
        timestamp: Date.now()
      }
    });
  }
}

GraphQL Integration

For those who prefer structured queries:

class GraphQLClient extends Component {
  init() {
    this.state = {
      endpoint: '/graphql',
      cache: new Map()
    };

    this.on('graphql-query', this.executeQuery);
    this.on('graphql-mutation', this.executeMutation);
  }

  async executeQuery({ query, variables, requestId, cache = true }) {
    // Check cache
    const cacheKey = JSON.stringify({ query, variables });
    if (cache && this.state.cache.has(cacheKey)) {
      this.emit('graphql-result', {
        requestId,
        data: this.state.cache.get(cacheKey),
        cached: true
      });
      return;
    }

    try {
      const response = await fetch(this.state.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query, variables })
      });

      const result = await response.json();

      if (result.errors) {
        throw new Error(result.errors[0].message);
      }

      // Cache result
      if (cache) {
        this.state.cache.set(cacheKey, result.data);
      }

      this.emit('graphql-result', {
        requestId,
        data: result.data
      });

    } catch (error) {
      this.emit('graphql-error', {
        requestId,
        error: error.message
      });
    }
  }

  async executeMutation({ mutation, variables, requestId }) {
    try {
      const response = await fetch(this.state.endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query: mutation, variables })
      });

      const result = await response.json();

      if (result.errors) {
        throw new Error(result.errors[0].message);
      }

      // Invalidate cache on mutation
      this.state.cache.clear();

      this.emit('graphql-result', {
        requestId,
        data: result.data
      });

    } catch (error) {
      this.emit('graphql-error', {
        requestId,
        error: error.message
      });
    }
  }

  render() {
    return null;
  }
}

// Usage
class UserList extends Component {
  loadUsers() {
    const requestId = crypto.randomUUID();

    this.emit('graphql-query', {
      query: `
        query GetUsers($limit: Int) {
          users(limit: $limit) {
            id
            username
            email
          }
        }
      `,
      variables: { limit: 10 },
      requestId
    });

    this.once('graphql-result', (data) => {
      if (data.requestId === requestId) {
        this.state.users = data.data.users;
      }
    });
  }
}

Micro-Frontends with LARC

Split your monolith into independently deployable micro-frontends. It's like microservices, but with more JavaScript!

Module Federation Pattern

Load remote LARC modules dynamically:

class MicroFrontendLoader extends Component {
  init() {
    this.state = {
      modules: new Map(),
      loading: new Set()
    };

    this.on('load-module', this.loadModule);
    this.on('unload-module', this.unloadModule);
  }

  async loadModule({ name, url, props }) {
    if (this.state.modules.has(name)) {
      console.warn(`Module ${name} already loaded`);
      return;
    }

    if (this.state.loading.has(name)) {
      console.warn(`Module ${name} is already loading`);
      return;
    }

    this.state.loading.add(name);
    this.emit('module-loading', { name });

    try {
      // Dynamic import
      const module = await import(/* webpackIgnore: true */ url);

      // Initialize module with props
      const instance = new module.default(props);

      this.state.modules.set(name, instance);
      this.state.loading.delete(name);

      this.emit('module-loaded', { name });

    } catch (error) {
      this.state.loading.delete(name);
      this.emit('module-load-error', {
        name,
        error: error.message
      });
    }
  }

  unloadModule({ name }) {
    const module = this.state.modules.get(name);
    if (module && module.destroy) {
      module.destroy();
    }
    this.state.modules.delete(name);
    this.emit('module-unloaded', { name });
  }

  render() {
    return html`
      <div class="micro-frontend-container">
        ${Array.from(this.state.modules.entries()).map(([name, module]) => html`
          <div key=${name} class="module-wrapper" data-module=${name}>
            ${module.render ? module.render() : ''}
          </div>
        `)}
      </div>
    `;
  }
}

// Usage: Load shopping cart from different server
loader.receive('load-module', {
  name: 'shopping-cart',
  url: 'https://cdn.example.com/modules/cart.js',
  props: {
    bus: sharedBus,
    apiEndpoint: '/api/cart'
  }
});

Shell Application Pattern

Create a shell that hosts multiple micro-frontends:

class MicroFrontendShell extends Component {
  init() {
    this.state = {
      activeModule: null,
      modules: {
        'dashboard': {
          url: '/modules/dashboard.js',
          title: 'Dashboard'
        },
        'products': {
          url: '/modules/products.js',
          title: 'Products'
        },
        'checkout': {
          url: '/modules/checkout.js',
          title: 'Checkout'
        }
      }
    };

    this.loader = new MicroFrontendLoader({ bus: this.bus });
    this.on('navigate-to-module', this.navigateToModule);
  }

  async navigateToModule({ module }) {
    // Unload previous module
    if (this.state.activeModule) {
      this.emit('unload-module', { name: this.state.activeModule });
    }

    // Load new module
    const config = this.state.modules[module];
    if (config) {
      await this.emit('load-module', {
        name: module,
        url: config.url,
        props: {
          bus: this.bus,
          navigate: (to) => this.navigateToModule({ module: to })
        }
      });

      this.state.activeModule = module;
    }
  }

  render() {
    return html`
      <div class="shell">
        <nav class="shell-nav">
          ${Object.entries(this.state.modules).map(([key, config]) => html`
            <button
              key=${key}
              class=${this.state.activeModule === key ? 'active' : ''}
              onclick=${() => this.navigateToModule({ module: key })}
            >
              ${config.title}
            </button>
          `)}
        </nav>
        <main class="shell-content">
          ${this.loader.render()}
        </main>
      </div>
    `;
  }
}

Plugin Systems

Let users extend your application with their own components.

Plugin Registry

class PluginRegistry extends Component {
  init() {
    this.state = {
      plugins: new Map(),
      hooks: new Map()
    };

    this.on('register-plugin', this.registerPlugin);
    this.on('unregister-plugin', this.unregisterPlugin);
    this.on('execute-hook', this.executeHook);
  }

  registerPlugin({ id, plugin }) {
    if (this.state.plugins.has(id)) {
      throw new Error(`Plugin ${id} already registered`);
    }

    // Validate plugin interface
    if (!plugin.init || typeof plugin.init !== 'function') {
      throw new Error('Plugin must have an init() method');
    }

    this.state.plugins.set(id, plugin);

    // Register plugin hooks
    if (plugin.hooks) {
      Object.entries(plugin.hooks).forEach(([hookName, handler]) => {
        if (!this.state.hooks.has(hookName)) {
          this.state.hooks.set(hookName, []);
        }
        this.state.hooks.get(hookName).push({ id, handler });
      });
    }

    // Initialize plugin
    plugin.init({
      bus: this.bus,
      emit: (type, data) => this.emit(type, data)
    });

    this.emit('plugin-registered', { id });
  }

  unregisterPlugin({ id }) {
    const plugin = this.state.plugins.get(id);
    if (!plugin) return;

    // Remove hooks
    this.state.hooks.forEach((handlers, hookName) => {
      this.state.hooks.set(
        hookName,
        handlers.filter(h => h.id !== id)
      );
    });

    // Cleanup plugin
    if (plugin.destroy) {
      plugin.destroy();
    }

    this.state.plugins.delete(id);
    this.emit('plugin-unregistered', { id });
  }

  async executeHook({ hook, data }) {
    const handlers = this.state.hooks.get(hook) || [];

    let result = data;

    for (const { id, handler } of handlers) {
      try {
        result = await handler(result);
      } catch (error) {
        console.error(`Plugin ${id} hook ${hook} failed:`, error);
      }
    }

    return result;
  }

  render() {
    return null;
  }
}

// Example plugin
const analyticsPlugin = {
  init({ bus, emit }) {
    this.bus = bus;
    this.emit = emit;

    // Listen to all messages
    bus.on('*', (type, data) => {
      this.trackEvent(type, data);
    });
  },

  hooks: {
    'before-submit': async (formData) => {
      // Validate or transform data
      console.log('Analytics: Form submission', formData);
      return formData;
    },

    'after-navigation': async (route) => {
      // Track page view
      console.log('Analytics: Page view', route);
      return route;
    }
  },

  trackEvent(type, data) {
    // Send to analytics service
    if (window.gtag) {
      window.gtag('event', type, data);
    }
  },

  destroy() {
    console.log('Analytics plugin destroyed');
  }
};

// Register plugin
registry.receive('register-plugin', {
  id: 'analytics',
  plugin: analyticsPlugin
});

// Use hooks
const formData = { name: 'Alice', email: 'alice@example.com' };
registry.receive('execute-hook', {
  hook: 'before-submit',
  data: formData
}).then(result => {
  console.log('After hook:', result);
});

Middleware Patterns

Intercept and transform messages as they flow through your application.

Message Middleware

class MessageMiddleware extends Component {
  init() {
    this.state = {
      middlewares: []
    };

    this.on('register-middleware', this.registerMiddleware);

    // Intercept all messages
    this.interceptBus();
  }

  registerMiddleware({ middleware, priority = 0 }) {
    this.state.middlewares.push({ middleware, priority });

    // Sort by priority (higher first)
    this.state.middlewares.sort((a, b) => b.priority - a.priority);
  }

  interceptBus() {
    const originalEmit = this.bus.emit.bind(this.bus);

    this.bus.emit = async (type, data) => {
      let context = {
        type,
        data,
        timestamp: Date.now(),
        stopped: false
      };

      // Run through middleware chain
      for (const { middleware } of this.state.middlewares) {
        context = await middleware(context);

        if (context.stopped) {
          return; // Stop propagation
        }
      }

      // Emit transformed message
      originalEmit(context.type, context.data);
    };
  }

  render() {
    return null;
  }
}

// Example middleware: Logging
const loggingMiddleware = async (context) => {
  console.log(`[Middleware] ${context.type}`, context.data);
  return context;
};

// Example middleware: Rate limiting
const rateLimitMiddleware = (() => {
  const limits = new Map();

  return async (context) => {
    const key = context.type;
    const now = Date.now();
    const limit = limits.get(key) || { count: 0, resetAt: now + 1000 };

    if (now > limit.resetAt) {
      limit.count = 0;
      limit.resetAt = now + 1000;
    }

    limit.count++;

    if (limit.count > 10) {
      console.warn(`Rate limit exceeded for ${key}`);
      context.stopped = true;
    }

    limits.set(key, limit);
    return context;
  };
})();

// Example middleware: Transform
const transformMiddleware = async (context) => {
  // Add metadata to all messages
  context.data = {
    ...context.data,
    _meta: {
      timestamp: context.timestamp,
      version: '1.0'
    }
  };
  return context;
};

// Register middleware
middleware.receive('register-middleware', {
  middleware: loggingMiddleware,
  priority: 100
});

middleware.receive('register-middleware', {
  middleware: rateLimitMiddleware,
  priority: 90
});

middleware.receive('register-middleware', {
  middleware: transformMiddleware,
  priority: 80
});

Async Middleware with Error Handling

class AsyncMiddleware extends Component {
  init() {
    this.state = {
      middlewares: []
    };
  }

  async runMiddleware(context) {
    try {
      for (const middleware of this.state.middlewares) {
        context = await middleware(context);

        if (context.stopped) {
          break;
        }
      }

      return context;

    } catch (error) {
      console.error('Middleware error:', error);

      // Emit error event
      this.emit('middleware-error', {
        error: error.message,
        context
      });

      // Stop propagation on error
      context.stopped = true;
      return context;
    }
  }
}

// Example: Authentication middleware
const authMiddleware = async (context) => {
  const protectedMessages = ['api-request', 'user-action'];

  if (protectedMessages.includes(context.type)) {
    const token = localStorage.getItem('authToken');

    if (!token) {
      console.warn('Authentication required');
      context.stopped = true;

      // Redirect to login
      setTimeout(() => {
        bus.emit('navigate', { route: '/login' });
      }, 0);
    }
  }

  return context;
};

Conclusion

These advanced patterns are powerful tools in your LARC toolkit. Use them judiciously. Not every application needs a multi-bus architecture or a plugin system. But when you do need them, you'll be glad you have them.

Remember: the goal is to build maintainable, scalable applications—not to use every pattern just because you can. Choose patterns that solve real problems in your codebase, and your future self (and your teammates) will thank you.

In the next chapter, we'll take your LARC application from development to production, covering deployment strategies, performance optimization, and how to sleep soundly knowing your app is running smoothly in the wild.