equalsEvent Pattern Matching

In Happen, we've taken a different approach to event pattern matching. Instead of providing complex pattern syntax with its own parsing rules and limitations, we leverage JavaScript's first-class functions to give you complete freedom in defining how events should be matched.

Function-Based Matchers

At its core, a pattern in Happen is simply a function that decides whether an event should be handled:

// A pattern is a function that returns true or false for an event type
node.on(eventType => eventType === 'order.submitted', (event, context) => {
  // Process order submission
  // ...
  // Return next function or result
  return processPayment;
});

This function receives the event's type string and returns a boolean: true if the event should be handled, false if it should be ignored.

This simple approach provides extraordinary flexibility:

// Match exact event type
node.on(type => type === 'order.submitted', handleOrderSubmission);

// Match events by domain prefix
node.on(type => type.startsWith('order.'), (event, context) => {
  // Log all order events
  logOrderEvent(event);
  // Continue to domain-specific handler
  return getDomainSpecificHandler(event.type);
});

// Match multiple specific events
node.on(type => ['payment.succeeded', 'payment.failed'].includes(type), 
function(event, context) {
  // Process payment result
  processPaymentResult(event);
  // Branch based on success or failure
  return event.type === 'payment.succeeded' ? 
    handlePaymentSuccess : handlePaymentFailure;
}
);

// Match with regular expressions
node.on(type => /^user\.(created|updated|deleted)$/.test(type), 
function(event, context) {
  // Handle user lifecycle event
  updateUserCache(event);
  // Determine next step based on event type
  const actions = {
    'user.created': notifyUserCreated,
    'user.updated': notifyUserUpdated,
    'user.deleted': notifyUserDeleted
  };
  // Return appropriate next function
  return actions[event.type];
}
);

// Complex conditional matching
node.on(type => {
  const [domain, action] = type.split('.');
  return domain === 'inventory' && action.includes('level');
}, function(event, context) {
  // Process inventory level event
  processInventoryLevel(event);
  // Check if restock needed
  if (isRestockNeeded(event.payload)) {
    return createRestockOrder;
  }
  // Otherwise complete
  return { processed: true };
});

Creating Your Own Pattern System

With function-based matchers, you can build any pattern matching system that suits your needs. Here's an example of how you might create your own pattern utilities:

Now you can use these utilities to create expressive, reusable matchers:

String Pattern Support

For convenience, Happen also supports string patterns which are converted to matcher functions internally:

String patterns support several features:

  • Exact matching: 'order.submitted'

  • Wildcards: 'order.*'

  • Alternative patterns: '{order,payment}.created'

  • Multiple segments: 'user.profile.*'

However, function matchers provide greater flexibility and expressiveness when you need more complex matching logic.

Building Domain-Specific Pattern Systems

For larger applications, you might want to build a more structured pattern system. Here's an example of a domain-oriented approach:

Benefits of Function-Based Pattern Matching

This approach to pattern matching offers several advantages:

  1. Unlimited Flexibility: Any matching logic can be implemented

  2. Zero Parse-Time Overhead: Patterns are just functions, no parsing needed

  3. Type Safety: TypeScript can fully type your pattern functions

  4. Testability: Pattern functions can be unit tested independently

  5. Composition: Combine matchers to create complex patterns

  6. Familiar JavaScript: No special syntax to learn, just standard JS

Best Practices

  • Keep matcher functions pure: They should depend only on the input event type

  • Create reusable pattern factories: Build a library of matcher creators for your application

  • Compose simple matchers: Build complex patterns by combining simple ones

  • Test your matchers: Unit test complex matching logic independently

  • Consider performance: For high-frequency events, optimize your matcher functions

Last updated