Skip to content

Commit ab55e8c

Browse files
committed
docs: add runtime module loading architecture design
Add comprehensive design documentation for runtime module loading system. ## Overview This document describes the architecture and implementation plan for dynamic module loading and management in ColouredFlow. ## Key Components 1. **ModuleReference**: Type definition for various module reference methods - Reference by name (from registry) - Reference by Flow ID (from database) - Reference by Flow name (from database) 2. **ModuleRegistry**: In-memory registry for loaded modules - Register/unregister modules - Version management - Hot reload support 3. **ModuleLoader**: Load modules from various sources - Database (Flow table) - Cache management - Auto-detection of ports 4. **ModuleResolver**: Resolve module references at runtime - Resolve single references - Resolve all references in a CPN - Validation ## Use Cases - Pre-register common modules at startup - Load modules on-demand from database - Version management (module:v1, module:v2) - Hot reload for module updates ## Benefits - Separation of concerns - Reusability across flows - Easy versioning - Memory efficiency - Lazy loading 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1b810bc commit ab55e8c

1 file changed

Lines changed: 342 additions & 0 deletions

File tree

docs/runtime_module_loading.md

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
# Runtime Module Loading
2+
3+
This document describes the runtime module loading and management system in ColouredFlow.
4+
5+
## Architecture Overview
6+
7+
The runtime module loading system consists of four main components:
8+
9+
```
10+
┌─────────────────────────────────────────────────────────┐
11+
│ ColouredPetriNet │
12+
│ ┌─────────────────────────────────────────────────┐ │
13+
│ │ Substitution Transition │ │
14+
│ │ - subst: {:module_ref, ...} │ │
15+
│ └──────────────────┬──────────────────────────────┘ │
16+
└─────────────────────┼──────────────────────────────────┘
17+
18+
19+
┌────────────────────────┐
20+
│ ModuleResolver │
21+
│ (Resolve references) │
22+
└────────────┬───────────┘
23+
24+
25+
┌────────────────────────┐
26+
│ ModuleLoader │
27+
│ (Load from sources) │
28+
└────────────┬───────────┘
29+
30+
┌────────────┴───────────┐
31+
│ │
32+
▼ ▼
33+
┌────────────────┐ ┌────────────────┐
34+
│ ModuleRegistry │ │ ModuleSource │
35+
│ (In-memory) │ │ (DB/File/API) │
36+
└────────────────┘ └────────────────┘
37+
```
38+
39+
## Key Concepts
40+
41+
### 1. Module Reference
42+
43+
A module reference allows substitution transitions to reference modules dynamically:
44+
45+
```elixir
46+
# Reference by name (from registry)
47+
%Transition{
48+
name: "auth",
49+
subst: {:module_ref, name: "authentication"}
50+
}
51+
52+
# Reference by Flow ID (load from database)
53+
%Transition{
54+
name: "auth",
55+
subst: {:module_ref, flow_id: 123, port_specs: [...]}
56+
}
57+
58+
# Reference by Flow name (load from database)
59+
%Transition{
60+
name: "auth",
61+
subst: {:module_ref, flow_name: "auth_flow_v2", port_specs: [...]}
62+
}
63+
64+
# Direct module name (static, as before)
65+
%Transition{
66+
name: "auth",
67+
subst: "authentication" # Must be in cpnet.modules
68+
}
69+
```
70+
71+
### 2. Module Registry
72+
73+
An in-memory registry that stores loaded modules:
74+
75+
```elixir
76+
# Start the registry
77+
{:ok, _pid} = ColouredFlow.Runtime.ModuleRegistry.start_link()
78+
79+
# Register a module
80+
ModuleRegistry.register("authentication", auth_module)
81+
82+
# Get a module
83+
{:ok, module} = ModuleRegistry.get("authentication")
84+
85+
# List all modules
86+
modules = ModuleRegistry.list()
87+
88+
# Unregister
89+
:ok = ModuleRegistry.unregister("authentication")
90+
```
91+
92+
### 3. Module Loader
93+
94+
Loads modules from various sources:
95+
96+
```elixir
97+
# Load from database by Flow ID
98+
{:ok, module} = ModuleLoader.load_from_flow(
99+
flow_id: 123,
100+
port_specs: [{"input", :input}, {"output", :output}]
101+
)
102+
103+
# Load from database by Flow name
104+
{:ok, module} = ModuleLoader.load_from_flow(
105+
flow_name: "authentication_v2",
106+
port_specs: [...]
107+
)
108+
109+
# Auto-detect and load
110+
{:ok, module} = ModuleLoader.load_from_flow_auto(
111+
flow_id: 123,
112+
module_name: "authentication"
113+
)
114+
```
115+
116+
### 4. Module Resolver
117+
118+
Resolves module references at runtime:
119+
120+
```elixir
121+
# Resolve a module reference
122+
{:ok, module} = ModuleResolver.resolve(
123+
{:module_ref, flow_id: 123, port_specs: [...]},
124+
context
125+
)
126+
127+
# Resolve all module references in a CPN
128+
{:ok, cpnet_with_modules} = ModuleResolver.resolve_all(cpnet)
129+
```
130+
131+
## Usage Scenarios
132+
133+
### Scenario 1: Load Flow as Module on Demand
134+
135+
```elixir
136+
# Define a transition that references a Flow by ID
137+
cpnet = %ColouredPetriNet{
138+
places: [...],
139+
transitions: [
140+
%Transition{
141+
name: "authenticate",
142+
subst: {:module_ref,
143+
flow_id: 123,
144+
port_specs: [
145+
{"credentials", :input},
146+
{"success", :output},
147+
{"failure", :output}
148+
]
149+
},
150+
socket_assignments: [...]
151+
}
152+
]
153+
}
154+
155+
# At runtime, before execution
156+
{:ok, resolved_cpnet} = ModuleResolver.resolve_all(cpnet)
157+
158+
# Now resolved_cpnet has the module loaded
159+
# resolved_cpnet.modules contains the authentication module
160+
```
161+
162+
### Scenario 2: Module Registry for Shared Modules
163+
164+
```elixir
165+
# Application startup: Pre-register common modules
166+
defmodule MyApp.Application do
167+
def start(_type, _args) do
168+
# Load and register authentication module
169+
{:ok, auth_module} = ModuleLoader.load_from_flow(
170+
flow_id: 123,
171+
port_specs: [...]
172+
)
173+
ModuleRegistry.register("authentication", auth_module)
174+
175+
# Load and register other modules
176+
ModuleRegistry.register("notification", notif_module)
177+
ModuleRegistry.register("email", email_module)
178+
179+
# ... start supervisors
180+
end
181+
end
182+
183+
# In your flows: Reference by name
184+
cpnet = %ColouredPetriNet{
185+
transitions: [
186+
%Transition{
187+
name: "auth",
188+
subst: {:module_ref, name: "authentication"},
189+
socket_assignments: [...]
190+
}
191+
]
192+
}
193+
```
194+
195+
### Scenario 3: Versioned Modules
196+
197+
```elixir
198+
# Register multiple versions
199+
ModuleRegistry.register("authentication:v1", auth_module_v1)
200+
ModuleRegistry.register("authentication:v2", auth_module_v2)
201+
ModuleRegistry.register("authentication", auth_module_v2) # Latest
202+
203+
# Reference specific version
204+
%Transition{
205+
subst: {:module_ref, name: "authentication:v1"}
206+
}
207+
208+
# Or use latest
209+
%Transition{
210+
subst: {:module_ref, name: "authentication"}
211+
}
212+
```
213+
214+
### Scenario 4: Hot Reload
215+
216+
```elixir
217+
# Update a module at runtime
218+
new_auth_module = build_new_auth_module()
219+
ModuleRegistry.update("authentication", new_auth_module)
220+
221+
# New enactments will use the updated module
222+
# Existing enactments continue with their original module
223+
```
224+
225+
## Implementation Details
226+
227+
### Module Reference Types
228+
229+
```elixir
230+
@type module_ref() ::
231+
# Reference by name in registry
232+
{:module_ref, name: String.t()} |
233+
234+
# Reference by Flow ID with port specs
235+
{:module_ref, flow_id: integer(), port_specs: [port_spec()]} |
236+
237+
# Reference by Flow ID with auto-detection
238+
{:module_ref, flow_id: integer(), module_name: String.t()} |
239+
240+
# Reference by Flow name
241+
{:module_ref, flow_name: String.t(), port_specs: [port_spec()]} |
242+
243+
# Reference by Flow name with auto-detection
244+
{:module_ref, flow_name: String.t(), module_name: String.t()}
245+
```
246+
247+
### Caching Strategy
248+
249+
1. **Registry Cache**: Modules in registry are kept in memory
250+
2. **Loader Cache**: Recently loaded modules are cached (with TTL)
251+
3. **Enactment Cache**: Each enactment keeps its resolved modules
252+
253+
### Error Handling
254+
255+
```elixir
256+
# Module not found
257+
{:error, :module_not_found}
258+
259+
# Flow not found
260+
{:error, :flow_not_found}
261+
262+
# Invalid port specs
263+
{:error, {:invalid_port_specs, reason}}
264+
265+
# Load failure
266+
{:error, {:load_failed, reason}}
267+
```
268+
269+
## Configuration
270+
271+
```elixir
272+
# In config/config.exs
273+
config :coloured_flow, ColouredFlow.Runtime.ModuleRegistry,
274+
# Enable/disable registry
275+
enabled: true,
276+
277+
# Cache TTL for loaded modules (milliseconds)
278+
cache_ttl: 60_000,
279+
280+
# Max cached modules
281+
max_cache_size: 100
282+
283+
config :coloured_flow, ColouredFlow.Runtime.ModuleLoader,
284+
# Default repo for loading flows
285+
repo: MyApp.Repo,
286+
287+
# Auto-register loaded modules
288+
auto_register: true
289+
```
290+
291+
## Migration Guide
292+
293+
### Before (Static Modules)
294+
295+
```elixir
296+
# Define module in the same CPN
297+
cpnet = %ColouredPetriNet{
298+
modules: [auth_module, email_module],
299+
transitions: [
300+
%Transition{
301+
name: "auth",
302+
subst: "authentication" # Static reference
303+
}
304+
]
305+
}
306+
```
307+
308+
### After (Dynamic Loading)
309+
310+
```elixir
311+
# Just reference, no need to embed
312+
cpnet = %ColouredPetriNet{
313+
modules: [], # Empty!
314+
transitions: [
315+
%Transition{
316+
name: "auth",
317+
subst: {:module_ref, flow_id: 123, port_specs: [...]}
318+
}
319+
]
320+
}
321+
322+
# Resolve at runtime
323+
{:ok, resolved_cpnet} = ModuleResolver.resolve_all(cpnet)
324+
# Now resolved_cpnet.modules contains the loaded module
325+
```
326+
327+
## Benefits
328+
329+
1. **Separation of Concerns**: Flows and modules can be managed independently
330+
2. **Reusability**: One Flow can be used as a module in many other flows
331+
3. **Versioning**: Easy to manage multiple versions of modules
332+
4. **Hot Updates**: Update modules without restarting
333+
5. **Lazy Loading**: Load modules only when needed
334+
6. **Memory Efficiency**: Share module definitions across enactments
335+
336+
## Future Enhancements
337+
338+
1. **Remote Module Loading**: Load modules from remote services
339+
2. **Module Marketplace**: Share modules across organizations
340+
3. **Dependency Management**: Resolve transitive module dependencies
341+
4. **Module Signing**: Verify module integrity
342+
5. **Performance Monitoring**: Track module load times and cache hits

0 commit comments

Comments
 (0)