Welcome to the first post of the series on Decidim! In this post, we’ll talk about the Decidim architecture at a higher level so we can dig deeper in the next posts.
In the post where we introduced this series, we explained Decidim is not a Rails app, but a set of Rails engines. Let’s see what led to this decision, the alternatives and what this means for the developer.
Decidim started off as a fork of Madrid’s Consul. Consul is a full Rails app, which means you need to fork the repo for every installation, and probably add some commits modifying the source code to customize it to your needs. At least that’s how it worked back in August 2016, when we created the Decidim repo.
Updating a git fork from the original repo can be cumbersome, but it becomes impossible if your fork is heavily customized. This was our case with our version for Barcelona. It was indeed so hard that we decided to rewrite the whole platform from scratch. Before that, we contributed on Decidim with several PRs, and we wrote a document (in Spanish) offering collaboration and ideas on how to modularize Consul so that installations didn’t need to be forked. The talks on this document didn’t come to a successful end, so we started working on Decidim on August 2016.
At this point, we faced our first major decision. We wanted Decidim to be reusable for other organizations, but we didn’t want to use the same approach as Consul did at the time. We considered using Elixir’s umbrella apps, but we are not experts on Elixir development, so we stuck to Ruby on Rails. We started exploring Rails engines and found the Spree repo. Spree is a “modular e-commerce solution” for Ruby on Rails built on Rails engines, which sounds exactly like what we needed.
We decided to copy Spree’s structure: a set of Rails engines, each of them a different gem, and a meta gem that wraps them all. Here’s the initial structure we had:
- Core: held the user authentication system, the public layout, and some essential pieces to tie all the engines together. It also held the participatory process space.
- Admin: held the permissions to enter the admin and its layout. Allowed to manage the organization settings and other global elements.
- A different engine for each component
- Comments: an engine that could be used by any other engine to add a comments system.
As time passed, we added other participatory spaces to the platform and forced us to separate the participatory processes from core to its own gem. Each of these gems is considered a Decidim module. Modules can be a space (participatory spaces or assemblies, for example), a component (like proposals or meetings) or a cross-engine feature (comments, for example).
As we stated before, Decidim is not a Rails app, but a collection of Rails engines that expand the features of a Rails app. With this code organization, the developer is in full control of the Rails app, but the Decidim code is hidden in gems. Decidim has a generator that performs these steps:
- Generates a Rails app
- Adds the Decidim meta gem as a dependency
- Copies the migrations from Decidim to build the correct database schema
- Adds an initializer
- Adds a special controller, so developers expand some features
That’s it. Your most basic Decidim app does not differ much from a simple Rails app, in lines of code. True, you’ll probably want to expand it a little bit more, but the most simple and functional app only needs this.
Working with Rails engines hasn’t been easy, but we couldn’t think of any other abstraction to keep using Rails and not hitting the same distribution problems Consul had at the time. However, they give some compelling advantages too! Let’s review those pros and cons.
- A Rails engine can be distributed via a gem. We can easily version them, so Decidim apps only need to update their dependencies and copy the new DB migrations.
- Each Rails engine is isolated. This forces the developers to come up with the right abstractions so that different engines can work together, but as a side effect now you have a way for the community to develop new modules for Decidim.
- Rails engines are mini-applications, they can have their custom assets, controllers, etc. We can define a basic style so that each component can reuse them, and they can add specific styles for their needs.
- Developing a feature that affects many engines is costly. You need to change many pieces so that everything keeps working as expected.
- Releasing a gem per engine takes much time, as we need to review every one of them is ready. Most of this process can be automated, though.
My take? Working with Rails engines helps you separate concerns. If you find a bug in the proposals section, it’s clear you need to check the proposals engine. The problem, though, appears when you need strategies to finds out which spaces or components are installed for the current installation. We’ll review these strategies in a later post.
Decidim’s tech stack
At the moment of writing this, Decidim uses this stack:
- Ruby on Rails as the framework, with its engines
- PostgreSQL for the database, with the PostGIS extension to geolocate content
- GraphQL for APIs
- React for the comments system, written in Typescript and using Apollo as the GraphQL client
- Zurb’s Foundation as the CSS framework, using SCSS
That's all for today. Follow Codegram on Twitter to keep updated!