Data Flow

Happen offers a unique approach to system design by providing two complementary layers that form a cohesive fabric for the movement, management, and manipulation of data: an event system and a state system. Rather than forcing you to choose between these approaches, Happen enables you to use both simultaneously, allowing you to leverage the strengths of each where appropriate.

Two Complementary Layers

At the heart of Happen are two fundamentally different but complementary architectural layers:

Event System: The Communication Layer

The event system forms the communication layer where nodes interact through message passing. Each event represents something that happened, and nodes react to these events by processing them and potentially emitting new events.

This layer excels at modeling:

  • Distributed systems where components operate independently

  • Real-time reactions to external stimuli

  • Complex communication patterns with rich history tracking

  • Systems where the sequence and timing of interactions matter

State System: The Data Layer

The state system forms the data layer where each node manages its internal state. State evolves through well-defined transformations, and nodes can access and modify their state in a consistent manner.

This layer excels at modeling:

  • Entity lifecycles with clear states and transformations

  • Consistent, atomic updates across multiple entities

  • Business processes with well-defined rules

  • Systems where the current state determines what can happen next

Distinct APIs for Distinct Concerns

Happen provides separate APIs for each layer, making it clear which layer you're working with:

const orderNode = createNode("order-service");

// Event System: Process events through handlers
orderNode.on(type => type === "order-submitted", (event) => {
  validateOrder(event.payload);
  orderNode.broadcast({
    type: "order-validated",
    payload: {
      orderId: event.payload.orderId,
      items: event.payload.items
    }
  });
});

// State System: Transform state through the .state namespace
orderNode.state.set(state => {
  return {
    ...state,
    orders: {
      ...state.orders,
      "order-123": {
        ...state.orders["order-123"],
        status: "processing",
        processedAt: Date.now()
      }
    }
  };
});

This clear separation of concerns allows you to use the right tool for each task without artificial constraints.

Layer Interaction

While the event and state layers are conceptually distinct, they interact seamlessly within nodes:

// Event processing triggering state changes
paymentNode.on(type => type === "payment-received", (event) => {
  const { orderId, amount } = event.payload;
  
  // Process the payment
  const result = processPayment(event.payload);
  
  // Update state in response to the event
  paymentNode.state.set(state => {
    const orders = state.orders || {};
    const order = orders[orderId] || {};
    
    return {
      ...state,
      orders: {
        ...orders,
        [orderId]: {
          ...order,
          paymentStatus: "paid",
          paymentId: result.transactionId,
          paidAt: Date.now()
        }
      }
    };
  });
  
  // Emit another event based on the state change
  paymentNode.broadcast({
    type: "payment-confirmed",
    payload: {
      orderId,
      transactionId: result.transactionId
    }
  });
});

This ability to seamlessly move between layers allows you to create systems where communication and state management work together naturally.

State Operations

The state system provides powerful capabilities for managing state at different granularities.

Individual Entity Operations

For focused state updates on specific entities:

// Individual entity transformation
orderNode.state.set(state => {
  const orders = state.orders || {};
  const order = orders["order-123"] || {};
  
  return {
    ...state,
    orders: {
      ...orders,
      "order-123": {
        ...order,
        status: "processing"
      }
    }
  };
});

Multi-Entity Operations

For operations that need to maintain consistency across multiple entities:

// Multi-entity transformation
orderNode.state.set((state, views) => {
  // Get state from this node
  const orders = state.orders || {};
  const order = orders["order-123"];
  
  if (!order) return state;
  
  // Get state from inventory node through views
  const inventory = views.inventory.get();
  
  // Update both order and inventory
  const updatedInventory = updateInventory(inventory, order.items);
  
  // Return transformed state
  return {
    ...state,
    orders: {
      ...orders,
      "order-123": {
        ...order,
        status: "processing"
      }
    }
  };
});

Functional Composition

The state system embraces functional composition as a core principle, allowing you to build complex transformations from simpler ones:

// Define transformation functions
const validateOrder = (state) => ({
  ...state,
  validated: true,
  validatedAt: Date.now()
});

const processPayment = (state) => ({
  ...state,
  paymentProcessed: true,
  paymentProcessedAt: Date.now()
});

const prepareShipment = (state) => ({
  ...state,
  shipmentPrepared: true,
  shipmentPreparedAt: Date.now()
});

// Apply composed transformation
orderNode.state.set(state => {
  // Get the order
  const orders = state.orders || {};
  const order = orders["order-123"] || {};
  
  // Base transformation
  const baseState = {
    ...order,
    status: "processing"
  };
  
  // Apply composed transformations
  const processedOrder = [validateOrder, processPayment, prepareShipment]
    .reduce((currentState, transform) => transform(currentState), baseState);
  
  // Return the updated state
  return {
    ...state,
    orders: {
      ...orders,
      "order-123": processedOrder
    }
  };
});

This functional approach allows for powerful, flexible composition without requiring special helpers or syntax.

When to Use Each Layer

While Happen allows you to freely use both layers, here are some guidelines for choosing the right approach:

Use the Event System when:

  • You need to communicate between nodes

  • Events arrive from external sources

  • The history and sequence of interactions matter

  • You need to broadcast information to multiple recipients

  • You're modeling reactive behavior

Use the State System when:

  • You need atomic updates to a node's state

  • State consistency is critical

  • You're working with well-defined entity lifecycles

  • You need to validate state changes before applying them

  • You're modeling procedural behavior

The Best of Both Worlds

By providing both event and state layers, Happen allows you to build systems that are:

  • Reactive to external events and stimuli

  • Consistent in their state management

  • Distributed across multiple components

  • Predictable in their behavior

  • Flexible in their evolution

  • Comprehensible to developers

This dual-layer architecture gives Happen a unique power: the ability to create complex systems where communication and state management work together seamlessly, using the right approach for each concern.

Last updated