Composable Graph Architecture — Design Summary
We discussed how to model and manage deeply nested, reusable computation graphs in a low-code environment. Key design decisions and strategies:
1. Graph Structure and Routing
- Use namespaced routes like
/app/graph/:graphId
for persisted graphs and/app/graph
for creating new graphs with immediate redirect. - Subgraphs are reusable compositions; they should be instantiated per call site to ensure isolation of state and wiring.
2. Instance Management
- Avoid cloning full subgraph structures unless necessary.
- Prefer template + override modeling:
- Templates define shared structure (sockets, logic).
- Instances override only the minimal set of props/values.
- Maintain per-instance identity while referring to shared structure.
3. Socket Resolution
- Store only socket overrides in the instance.
- At runtime or render time, use a
resolveSocket()
function to merge instance data with template defaults. - This avoids memory overhead from instantiating all sockets eagerly.
4. Composition Usages
- Track which nodes instantiate a given subgraph within the same store/slice as templates.
- Maintain an index:
usages: Record<templateId, Set<nodeIds>>
to support reverse lookups, updates, and synchronization.
5. Evaluation and Efficiency
-
Graph evaluation should work via contextual resolution, not full materialization.
-
Lazy instantiation of socket state is not premature optimization — it's a necessary boundary for performance and structural correctness in compositional systems.