Communication Patterns

The lifeblood of any Happen system is communication between nodes. Happen distills communication down to fundamental patterns that can be composed to create any interaction model.

System-wide Broadcasting

System-wide broadcasting creates an environment where information flows freely throughout the entire ecosystem. Any node can transmit events that propagate across the system, reaching all nodes without targeting specific recipients.

// Weather monitoring service broadcasts a system-wide alert
weatherMonitor.broadcast({
  type: "severe-weather",
  payload: {
    condition: "hurricane",
    region: "Gulf Coast",
    expectedImpact: "severe",
    timeframe: "36 hours",
  },
});

// Any node in the system can listen for these alerts
emergencyServices.on(type => type === "severe-weather", event => {
  deployEmergencyTeams(event.payload.region);
});

Broadcasting is ideal for:

  • System-wide notifications

  • Important state changes that many nodes might care about

  • Environmental changes affecting the entire system

  • Crisis or exceptional condition alerts

Direct Communication

Direct communication establishes dedicated point-to-point channels between nodes, enabling private exchanges, guaranteed delivery, and response-oriented interactions.

Request-Response Pattern

// Order service directly communicates with payment service
async function processOrder(event, context) {
  // Send to payment service and await response using .return()
  const result = await orderNode.send(paymentNode, {
    type: "process-payment",
    payload: {
      orderId: event.payload.orderId,
      amount: calculateTotal(event.payload.items),
      currency: "USD",
      customerId: event.payload.customerId,
    }
  }).return();
  
  // Handle the response
  if (result.status === "approved") {
    return createShipment;
  } else {
    return handlePaymentFailure;
  }
}

// Payment service handles the request
paymentNode.on("process-payment", event => {
  // Process the payment
  const result = chargeCustomer(event.payload);
  
  // Return result directly
  return {
    status: result.success ? "approved" : "declined",
    transactionId: result.transactionId,
    message: result.message
  };
});

The .return() method explicitly indicates that you're expecting a response, making the code more readable and intention-clear. The receiving node simply returns a value from its handler as usual.

Response Handling with Callbacks

For more sophisticated response handling, you can provide a callback to .return():

// Send request and handle response with a callback
orderNode.send(inventoryNode, {
  type: "check-inventory",
  payload: { itemId: "123", quantity: 5 }
}).return(response => {
  if (response.available) {
    processAvailableItem(response);
  } else {
    handleOutOfStock(response);
  }
});

Fire-and-Forget Communication

When no response is needed, simply don't call .return():

// Notification without needing a response
function notifyCustomer(event, context) {
  // Send notification without calling .return()
  orderNode.send(emailNode, {
    type: "send-email",
    payload: {
      to: event.payload.customerEmail,
      subject: "Order Confirmation",
      orderId: event.payload.orderId
    }
  });
  
  // Continue processing immediately
  return finalizeOrder;
}

// Email node doesn't need to return anything
emailNode.on("send-email", event => {
  sendCustomerEmail(event.payload);
  // No explicit return needed for fire-and-forget
});

This approach has several advantages:

  • The sending node clearly indicates its expectations

  • The API is more extensible for future enhancements

  • It provides a cleaner way to handle responses

  • It avoids relying on node context details

Direct communication is best for:

  • Request-response interactions

  • Private or sensitive information exchange

  • Operations requiring acknowledgment

  • Complex workflows requiring coordination

Implicit Contracts

In Happen, contracts between nodes emerge naturally from event patterns:

  • A node's interface is defined by the events it handles and emits

  • The causal relationships between events form a natural contract

  • New nodes can understand existing patterns through observation

  • Contracts can evolve naturally as systems change

This emergent approach eliminates the need for formal interface definitions or schema registries.

Choosing the Right Pattern

While Happen allows you to freely mix communication patterns, here are some guidelines:

  • Use broadcasting when information needs to reach multiple recipients

  • Use direct communication for targeted interactions

  • Use .return() when you need a response

  • Skip .return() for fire-and-forget operations

The right combination of patterns will depend on your specific domain and requirements. By leveraging these fundamental communication patterns and composing them in different ways, you can create a wide range of interaction models that fit your specific needs.

Last updated