Resource management is crucial for scaling digital twin representations in service architectures. Proper resource cleanup ensures system stability during scaling up and down. Rigorous resource management prevents resource exhaustion and orphaned resources consuming capacity. The Substrates API’s approach to resource management ensures resources are acquired and released during scaling operations. This is important in environments with rapidly fluctuating circuit numbers. The framework’s approach allows developers to focus on modeling while the underlying resource lifecycle is handled automatically, enabling reliable scaling without resource degradation.
The following code snippet is typical of the code presented in this series. It demonstrates how to use the Substrates API in a simple “Hello World” manner. It follows a typical pattern: creating a Circuit and a Conduit, subscribing to the Conduit’s Source, composing a Pipe, emitting a value, and waiting for the Queue to be drained.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var cortex = cortex (); var circuit = cortex.circuit (); var conduit = circuit.conduit ( String.class, Inlet::pipe ); var subscription = conduit.source ().consume ( capture -> out.printf ( "%s -> %s%n", capture.subject ().name (), capture.emission () ) ); var pipe = conduit.compose ( cortex.name ( "William" ) ); pipe.emit ( "Hello, World!" ); circuit.queue ().await (); |
The code above lacked resource cleanup, as illustrated below. Notice that the resource ordering is reversed compared to the code sequencing above, which is common for nested resource constructs. For instance, a Subscription operates within the lifespan of a Conduit, while a Conduit operates within the lifespan of a Circuit.
1 2 3 4 5 |
subscription.close (); conduit.close (); circuit.close (); |
The Substrates API introduces three abstractions to help with resource management and structuring of code: Resources, Scopes, and Closures. A Resource encapsulates anything that requires explicit Closure when no longer in use. A Scope establishes a bounded context for managing these resources, automatically handling cleanup when its context terminates. The Closure bridges these concepts, providing controlled access to Resources within a Scope while ensuring proper resource management. By combining these abstractions, developers can safely interact with Substrates Resources without the concern of resource leaks. Here is the code using such constructs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
final var cortex = cortex (); try ( final var scope = cortex.scope () ) { scope .closure ( cortex.circuit () ) .consume ( circuit -> scope.closure ( circuit.conduit ( String.class, Inlet::pipe ) ) .consume ( conduit -> scope.closure ( conduit.source ().consume ( capture -> out.printf ( "%s -> %s%n", capture.subject ().name (), capture.emission () ) ) ) .consume ( subscription -> { final var pipe = conduit.compose ( cortex.name ( "William" ) ); pipe.emit ( "Hello, World!" ); circuit.queue ().await (); } ) ) ); } |
Another nice aspect of the Substrates API design in the context of resource management diagnostics is that scopes can be named and nested, and because like Name and Subject the Scope interface extends the Extent interface which extends Iterable it is possible to iterate outwards from a current scope to enclosing scopes.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
try ( var outer = cortex.scope ( cortex.name ( "outer" ) ) ) { for ( var scope : outer.scope ( cortex.name ( "inner" ) ) ) { out.println ( scope.subject ().part () ); } } |
The following is printed when the above code is executed, in which an outer and nested inner scope are created.
1 2 3 4 |
Subject[name=inner,type=SCOPE,id=61246ad5-9088-4c81-8775-b83ab509eda8] Subject[name=outer,type=SCOPE,id=5c2e4bc7-74c9-4a08-9a9b-512dfafeacf7] |
In the next article in the series, we look at how our code can be executed safely as a Script and by a Circuit.