Building Collaborative Web Applications with the modelix Stack

In this post, we take a look at a sample application built on top of the modelix stack during my 20% time at itemis. The application allows for real-time collaboration between all clients. It doesn't matter if a client is MPS or the web interface.

Below you find a short demo video about what the application can do and in this post, we dig into some of the details of how it works.

Overview

The language engineering tool of choice is MPS. With MPS the language is designed and the MPS-specific editors are created. On top of that, a language-specific model client is generated from MPS. The generated code is a representation of the MPS meta-model but does not require MPS at runtime. The client is entirely independent of MPS and only requires the model-api and the model client from modelix to work. The client also exposes the meta-model as easy-to-use classes. Somebody working with the client doesn’t need to be aware of the fact that language engineering is used nor do they require specific MPS knowhow. For them, it simply looks like classes they can manipulate.

This language-specific model client is used to build a web application. In this example, the application was created using ktor, a Koltin framework for building server applications.

MPS

From an MPS perspective, nothing really changes. The language is modeled the same way as usual. Storing models within the modelix model server works seamlessly with the modelix MPS persistence. The modelix persistence supports importing, synchronizing, and exporting models out of the box.

The only additional step in MPS is to export a language-specific modelix model client. Generating the client is done via code generation and requires no change to the language definition. The language-specific model client will include the constraints defined in the structure aspect of MPS but will not contain scopes or other aspects of the MPS language. The client allows for similar operations as the smodel language from MPS but without a dependency on MPS at runtime.

Web Application

The web application follows a progressive enhancement approach. A majority of the web application is HTML rendered on the server-side. Formatting and layout are added via CSS and a small portion of JavaScript is used to enhance the user experience and adds real-time collaboration. Without JavaScript, the website gracefully falls back and is still useable.

But why not use  and connect it directly to the model server? There are a couple of reasons for that:

First, it’s always going to be slower than sending HTML into the browser. Browsers are heavily optimized for parsing and rending HTML. Sending JavaScript, parsing it, and executing it to turn an arbitrary data structure into HTML on the client is slower. Yes, there are ways to improve responsiveness by doing a server-side rendering for the initial render but this leads us to the second reason.

We don't trust the client/browser. All data we get from the client requires scrutiny. We cannot ensure that the client isn't misbehaving. If we were to connect the browser directly to the model server, the model server would need to check if the edits by the client conform to the meta-model and its constraints. As of now, the modelix model server isn’t concerned with the constraints provided by the meta-model. The server is used to store the model and to distribute changes to the clients. For an untrusted client to connect to the model server we would need to enforce the model constraints on the model server. The server might even need to know the version of the meta-model that is assumed by the client if we want to support meta-model versions and migrations on the server.

Third, the client doesn’t know about the model. In this implementation, all the browser/client is working with is editors. The server is then responsible for turning the editor actions from the client into changes to the model. This keeps the client simple. In fact, most of the JavaScript running on the client is pretty generic and concerned with improving the user experience of an editor and not language-specific.

Server

The server side is entirely written in Kotlin using the generated model client and ktor. Editors are defined with the kotlinx.html DSL which allows for type-safe templating. Kotlins flexibility enables extending kotlinx.html with simple and reusable templates to define the editors like this:

On top of that, the sample uses TailwindCSS which is a CSS framework adopting a utility CSS approach. It pairs nicely with server-side templating. Providing similar flexibility as inline styles but without many of their limitations.

Client / Browser

The client is mostly boring. It uses Turbo and Stimulus to progressively enhance the user experience. Turbo is used to update the editors within the browser when the model changes. The server will render the editors and push the changes to the client. The client code doesn’t even know about nodes, it replaces parts of the DOM based on ids.

Stimulus is used for the automatic submission of changes. As soon as the user leaves an input element in the editor it will submit these changes to the server. If this fails for some reason or JavaScript is disabled the whole application will fall back to HTML forms and the user can manually submit their changes. Stimulus is also used to integrate with existing JavaScript editors on the client, for instance, a Markdown editor with preview:

What’s missing?

This being a small example built within a couple of days there are lots of parts missing.

Authentication

While our demo at server.modelix.org does support authentication it isn’t integrated into this example. All edits are technically done by the same user and the web application doesn’t have authentication at all right now. Adding this would be straightforward.

Presence

Being a collaborative editor you want to know where somebody else is editing, ideally within MPS and within the web application. For instance by showing a cursor position like Google Docs is doing or by highlighting the input element of the editor where somebody else is editing.

Human to Human Collaboration

With presence, one would know that someone is in the same area but it doesn’t communicate what the other one's intentions are. Ideally, we would be able to pull up a chat window that allows messaging everyone editing the same thing. Or even to start a voice/video call. While we can technically deconflict concurrent edits it is always best to avoid these conflicts by enabling the people involved via communication.

Model Checking

The example doesn’t use any type-system or checking rules defined in MPS and that is for a good reason: right now there is no easy way to reuse them. While having a specific implementation for editors is ok or even desired to be able to provide a web-specific user experience, rewriting complex model checking rules isn’t. Support for reusing MPS type checking and model checking might land in modelix soon though.

If you liked the content consider subscribing to the email newsletter below. The newsletter delivers all posts directly into your inbox. For feedback on the topic feel free to reach out to me. You can find me on Twitter @dumdidum or write a mail to kolja@hey.com.