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