Why Potions Framework
Take this simple question : how do you code a page that displays a list of products that are fetched through an api ?
This happens all the time and yet, I’m guessing that most developers will answer with their favorite UI framework while missing most of the real complexity of the question : caching, offline mode, layout shifts, error handling…
We, at Potions, think that those “advanced features” are now close to mandatory to please users (and Google Search engines robots), very frequent in a website… And they are not addressed in usual frameworks.
We created Potions framework to handle those concerns while remaining agnostic of the UI framework… And simple.
Here is our answer to the question, by the way.
You currently deal with the complex stuff at the end.
A simple static web page can be declared in pure beautiful HTML.
As soon as your page interface starts reacting to user’s interaction, you can consider that the user is changing the “UI” and maybe the “state” of your page… Your troubles start.
React and other UI framework like Vue, Angular or Svelte will help you structure both your code and your page. They all rely on the great idea that UI is a function of a state and that a user interaction shouldn’t mess with the UI directly but only with the state. Your page becomes “reactive” to any state change.
Once your page starts to grow you’ll need to handle a scarily growing state, probably with state management framework like Redux.
After a while, if you want to handle async stuff, off-line mode, etc… you’ll discover the need for a middleware management tool to deal with the various fetches, web workers, service workers, IndexedDb and others… All of which can be considered as “services”.
It is only natural to shift from a component division of your code to a state management and then to a service coordination as your website grows.
We, at Potions think that it should be the other way around
We built a “service oriented framework” that put “business logic” right at the forefront of the developer considerations and the UI back to being a “side-effect” in a pure declarative world.
We are not going to fool you. It is way harder to think your app this way than the other way around, but it way more robust, efficient and scalable.
Warning : once you’ll shift your mindset it will be very hard to go back.
How it works
Our “service oriented framework” is a simple PubSub where both publishers and subscribers are actors.
Messages that transit in the PubSub are objects with at least a type property.
An actor is a unit of computation with a state that only reacts to messages one at a time.
An actor can therefore be defined by
- an actorName
- a dictionnary of behaviors (one per message type that the actor listens to)
- an initialState
The framework provides two main methods :
- create(actorName, behaviors, initialState)
- send(message)
For (huge) convenience, we created a third method
- get(actorName) that returns an empty state if the actor isn’t created yet and the state of the actor once it is created.
The state of an actor is an RxJS BehaviorSubject to which you can subscribe any method… like a render method.
Here is the famous “counter” example
javascriptimport { create, send, get } from "./system"; import { reducer$ } from "./utils"; import { COUNTER, INCREMENT, DECREMENT, DOUBLE } from "./constants"; get(COUNTER).subscribe(console.log); create( COUNTER, { [INCREMENT]: reducer$((_: any, state: number) => state + 1), [DECREMENT]: reducer$((_: any, state: number) => state - 1), [DOUBLE]: reducer$((_: any, state: number) => state * 2) }, 0 ); send({ type: INCREMENT }); send({ type: INCREMENT }); send({ type: DOUBLE }); send({ type: DECREMENT });
Notice that we subscribed console.log to the COUNTER actor’s state prior to defining the actor, yet it works. This is extremely useful to separate concerns between UI and state : the only thing they share is a name.
You can also notice the reducer$ function. It is one of the useful methods that the library provides to abstract the underlying Rxjs library complexity.
Here are the most common behaviors :
- the reducer that takes a message, a state (and eventually the system for side-effects) and returns the next state then completes
- the transformer that takes a message, a state (and eventually the system for side-effects) and returns a message to send back the completes
- the source that takes a message, a state (and eventually the system for side-effects) and returns an observable that keeps sending messages and never completes (event-listeners for instance)
The behaviour needs to “complete” to listen to another message.
For those who want to understand how an actor works and have already a certain knowledg of RxJS. Here is the core of the re(actor)
Here are some of the key benefits of this approach
- be UI framework agnostic
- even be interpreter agnostic… The same code should be interpretable in Python, node.js, in a browser…
- readability, testability, scalability : actors are declared
- concurrency
- separation of concerns