Introducing the Fusio worker system
With the latest release of Fusio (an open source API management system) we have introduced a new worker system which allows to build backend logic in multiple different programming languages. With this post I want to talk about the technical details and the benefits of this system.
Explaining the problem
At Fusio it is possible to create a route (i.e. /my/todos) which is then available and can be called by a user. If the route gets invoked Fusio will execute the assigned action to this route. Fusio contains already many actions to solve specific tasks, but in most cases the user wants to provide some custom logic.
Since every user has a different skill set and knows different programming languages we would also like to support different programming languages so that a user can simply choose the fitting language. Our task was now to build an action which allows us to execute multiple different languages.
Possible solutions
Embedding
We have thought about this topic for a long time. At the start we have tried to support different languages by trying to embed different languages into our system via extensions. Since Fusio is build in PHP we could embed languages like Javascript or Lua which are avaialble as extensions. While this is possible, it makes the setup of Fusio more complicated since you mostly need to compile those extensions and you are limited to a very small set of languages.
WASM
With the latest developments of WASM we also thought about implementing only an action which could execute WASM code and thus it would be possible to transform every language code into WASM which then could be executed by Fusio through the PHP WASM extension. While this idea is still nice it has currently a bad UX since the user needs to compile the code to WASM and the extension currently has also no WASI support. So we will probably revisit this idea if the ecosystem around WASM has improved but for now it is not the right tool for the job.
RPC
Our last solution was to build an RPC system where Fusio redirects all requests to a specific worker which then executes the code and returns the response.
The worker is then implemented in the target language which we want to support. In the end we have chosen this solution since it is really flexible and allows us to support many different languages. Also it has the benefits that the code of the user is not executed at the server where Fusio is running but instead in a different instance.
The RPC worker system
While the idea sounds great there where a few challenges which we needed to solve. At first we needed to find a fitting RPC system, we have looked at JsonRPC, Thrift and GRPC. In the end we have decided to start with Thrift since it is easy to get started and supports most programming languages.
To execute action code the worker needs some information. A typically action code of Fusio looks like:
At this code we can see that we get a connection “my_db” which we have configured at the Fusio “Connection” panel and which we want to use at the action. This means that the worker needs to know the credentials of the database so that the worker is able to connect to this database.
RPC: setConnection
To solve this we have added an event listener so that Fusio calls a “setConnection” RPC method on every configured worker if a user changes or adds a new connection to Fusio. Then the worker needs to persist these settings so that it can be used later on.
RPC: setAction
Then we needed to tell the worker about our action code. That means if we create or update an action for a specific language Fusio will invoke the specific worker and call the “setAction” method. The worker then needs to persist the action code so that it can be executed later on. If your language requires a compile step this is also the place to execute it i.e. our Java worker generates a “.class” file based on the “.java” file at this step.
RPC: executeAction
The last step is to execute the actual action code. If a user invokes a route at Fusio, Fusio invokes then the “executeAction” RPC method on the fitting worker. With this it passes all request and context information to the worker. Then the worker executes the code and returns a response. If you are interested in more technical details you can take a look at our worker documentation.
Connection types
As mentioned above it is possible to configure a connection at Fusio which we can then use at the worker. How the connection is implemented depends on the worker. I.e. if we have configured a “mysql” connection the Javascript-Worker uses the “mysql2” npm package, the Java-Worker uses the “mysql / mysql-connector-java” maven package and the Python-Worker uses the “PyMySQL” pip package. So in the end every worker uses a popular library to handle a specific connection type so that every user of the specific language can easily work with this connection.
Packages
Currently it is not possible to dynamically add an external package from a package manager. I.e. in your Javascript-Worker you may want to use a different npm package, in this case you currently need to add the package to the Dockerfile and rebuild the image of the worker. So we need to gather some experiences whether it is enough to add needed packages to the base image or whether there is actual a need to dynamically add packages via RPC.
Scaling
At Fusio every action is complete indepednent from other actions. This means that you can see an action as a micro-service which executes only a specific task. The worker system fits also very well into the FAAS world. This means we could create a worker as function at a cloud provider which would allow us to automatically scale the worker on demand.
Conclusion
This was a short introduction of the new Fusio worker system. If you want to get more information about Fusio you can checkout our website or Github. We think that this worker system is really great and it will enable many different users to work with Fusio. Also you can find a list of all available worker implementations at our website.