ModelQL

When working with large models you will quickly run into performance issues when you try to replicate the whole model into the client.

While the data structure for model replication in Modelix supports partial loading of models, you still need a way to describe which data you need on the client. Loading data on demand while traversing the model also results in a poor performance, because of the potentially large number of fine-grained request.

A first attempt to solve this problem was to disallow lazy loading and require the client to load all required data at the beginning, before working with the model. A special query language was used to filter the data and an attempt to access a node that is not included by that query resulted in an exception, forcing the developer to adjust the query. While this results in a more predictable performance, it is also hard to maintain and still not optimal for the performance. You have to download all the data at the beginning that you might eventually need, potentially exceeding the available memory of the system.

The ModelQL query language provides a more dynamic way of loading parts of the model on demand, but still allows reducing the number of request to a minimum. The downside is that it’s not just a different implementation hidden behind the model-api, but requires to use a different API.

Reactive Streams

The query language is inspired by Reactive Streams and the execution engine uses Kotlin Flows, which is a Coroutines compatible implementation of Reactive Streams.

Often it’s useful to know if a stream is expected to return only one element or multiple elements. Project Reactor, another implementation of Reactive Streams, introduced the notion of Mono and Flux to distinguish them. You will also find them in ModelQL.