[API] `usingMachineRunner` to borrow a machine (as opposed to owning)
- Langage dominant
- TypeScript
- Étoiles
- 8
- Forks
- 2
- Métriques de merge des PR
- Aucune PR mergée en 30 j
Description
# Problem
A `MachineRunner` instance contains a living Actyx subscription before it is destroyed. Currently, the programmer has to make sure that either `for-await` loop is used or `destroy` to make sure an orphan subscription is not running. There are a lot of cases where an instance is used temporarily without a 'for-await' loop; it is created, extracted, and then destroyed. Such cases have a risk---a missed `destroy` call.
Missing a destroy call is caused by an early return or a throw. But its root cause is the lifetime ownership of `MachineRunner`. For this issue, I am going to borrow some terms:
- Lifetime of a MachineRunner: The period when a MachineRunner lives and its destruction
- Ownership of a MachineRunner; The responsibility of a function or an object over a MachineRunner's lifetime --- the need to explicitly call 'destroy' after an instance is not used anymore
For example, an early return creates an orphaned machine.
```typescript
const extractSomeData = async (): Promise => {
const machine = createMachineRunner(actyx, tags, Initial, payload); // a machine is created
const state = (await machine?.peek())?.value;
// An early return. Here, the program forgets to call `machine.destroy()`, thus creating a living orphan machine
if (!state) {
return null;
}
const payload = state.payload;
machine.destroy();
return payload;
}
```
A manual review is required to see the mistake in the piece of code above. The proper fix is:
```typescript
if (!state) {
machine.destroy();
return null;
}
```
This is only one example of a missable `destroy` call. Another class of missable is less visible to a code review, which is a thrown Exception.
# Solution
The proposed API aims to take over the ownership of a `MachineRunner` and instead allow the programmer to borrow an instance.
The API set will expose two things:
- a function `usingMachineRunner` which accepts a similar set of arguments to `createMachineRunner`.
- a type `MachineRunnerUseFn` which enables the user to write a 'dependency-injection' style.
With this API, the problematic example above can be rewritten as:
```typescript
const extractSomeData = () => usingMachineRunner(actyx, tags, Initial, Payload, async (machine) => {
const state = (await machine?.peek())?.value;
if (!state) return null;
const payload = state.payload;
return payload;
});
```
`usingMachineRunner` does what `createMachineRunner` does and then execute the function passed by the user. With this pattern, the library now can destroy the machine after the function execution is done or if it throws, therefore eliminating the need for an explicit call to the `destroy` method.
`for-await` loop can also be used in the same way.
```typescript
usingMachineRunner(actyx, tags, Initial, Payload, async (machine) => {
for await (const state of machine) {
...
}
});
```
Dependency injection can be achieved by the provided type. For example, a class wants to allow parametrization to only `tags` and the function body.
```typescript
import { Initial } from "protocol/someRole";
class SomeClass {
actyx: Actyx,
// Dependency injection happens here
useMachine(tags: Tags, useFn: MachineRunnerUseFn){
return usingMachineRunner(this.actyx, tags, Initial, useFn);
}
}
// subsequently the class can be used this way
const someClass = new SomeClass(...);
const result = await someClass.useMachine(createSomeTags(), (machine) => {
for await (const state of machine) {
...
}
});
```
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Évaluation
Cette issue n'a pas encore été évaluée.