Actyx / Actyx/machines

[API] `usingMachineRunner` to borrow a machine (as opposed to owning)

Ouverte
#62 1 commentaire 0 réactions 1 personne assignée Réclamée par @Kelerchian Voir sur GitHub
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.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.