Want to keep track of and work with all fish of a specific type? Meet the RegistryFish pattern. (Updated version to Pond V2)
Something that has come up quite a few times is the need to track all fish of a certain kind. You may, for example, have a
MaterialRequestFish representing a specific material request triggered by a worker or a machine. There are going to be many instances of this fish, one for each material request. Now, what if you wanted to show a list of all these available material requests somewhere?
One thing you might want to do is to implement one huge fish that tracks all instances internally, something like an
AllOpenMaterialRequestsFish. This fish would then contain all material requests and logic for dealing with all these material requests at once.
But this will make things unnecessarily complicated. Instead of just implementing logic for one instance, you will always have to deal with all the irrelevant instances. For example: even if you wanted to use just one material request to schedule a forklift or AGV, your logic would need to deal with all the irrelevant material requests as well.
There is a better way...
A fish should always be as small as possible, it should model only the one object or workflow it corresponds to. This pattern will lead you to a data model that is scalable, reusable, composable, and maintainable. Keeping in line with this, we split the problem into two parts:
- Implement state and logic of a single entity
- Write a fish responsible for a single instance (e.g.:
- Track and access many instances of an entity
- Write a registry fish that tracks the names or the id of all instances (e.g.:
This is the Registry Fish Pattern. It allows you to cleanly separate the concerns of the logic of an individual entity and keeping track of many instances thereof. Let's jump in with an example.
Let's take a fish representing a material request as an example. Here is how you could write this fish:
If we now wanted to somehow deal with all material requests, we could write a registry fish as follows:
Here is how you could now, for example, use the
MaterialRequestFish.registry to show a list of all existing material request names:
What if you want to observe the state of the actual
MaterialRequestFish? You can do so using the RxPond. Here is how:
Hopefully, this snippet gives you an idea of the Registry Fish Pattern!
One thing that you may have noticed is that the registry is pretty generic and could be used all over different projects. Let's see how we can pack that into an npm package.
It would be convenient to have a module to observe all entities fish in the registry.
By the way, this pattern is not only useful for a registry and its entities. We can use it to resolve references in one fish and wake up other fish according to a given field. E.g.: Forklift -> current job / material request -> production order.
We can create a very simple helper that returns the states of the referenced fish.
To start from the user perspective, it would be nice to have a function like this:
The approach we showed above is probably a bit buggy when the registry fish is empty or gets empty; it will not emit at all. It would be better if the Observable would emit an empty array. So, no fish = no entries in the array.
To fix this, we check the length of the array of known fish names in the registry, and when it is empty, a stream with an empty array is emitted. We can do this using RxJS's
Additionally, we could improve the performance a lot if we rebuild the pipeline only if the state of the registryFish changed. RxJS's
distinctUntilChanged(deepEqual) will do this for us out of the box.
Note that we also included
pond as a parameter to observe the registry and its referenced fish. Finally, the
mapToProperty function will give you the freedom to may any state to an array of properties.
Here is an example:
Suppose you are not familiar with RxJS or focus on other things. I add a wrapper around the
observeRegistry$ function in the node package. It is named
observeRegistry without the
$, and it has an additional parameter for any stateChanged callback.
Here, additional an example:
All the above functions, including the non-RxJS version, are available in the
@actyx-contrib/registry package. Check out the repository or add it to your project with
npm install @actyx-contrib/registry.