View Source Bonfire Architecture

hacking

Hacking

Bonfire is an unusual piece of software, developed in an unusual way. It originally started with requests by Moodle users to be able to share and collaborate on educational resources with their peers and has been forked and evolved a lot since then.

Hacking on it is actually pretty fun. The codebase has a unique feeling to work with and we've relentlessly refactored to manage the ever-growing complexity that a distributed social networking toolkit implies. That said, it is not easy to understand without context, which is what this document is here to provide.

design-decisions

Design Decisions

Feature goals:

  • Flexibility for developers and users.
  • Extreme configurability and extensibility.
  • Integrated federation with the existing fediverse.

Operational goals:

  • Easy to set up and run.
  • Light on resources for small deployments.
  • Scalable for large deployments.

stack

Stack

Our main implementation language is Elixir, which is designed for building reliable systems. We have almost our own dialect.

We use the Phoenix web framework with LiveView and Surface for UI components and views.

Surface is a different syntax for LiveView that is designed to be more convenient and understandable to frontend developers, with extra compile time checks. Surface views and components are compiled into LiveView code (so once you hit runtime, Surface in effect doesn't exist any more).

Some extensions use the Absinthe GraphQL library to expose an API.

the-bonfire-environment

The Bonfire Environment

We like to think of bonfire as a comfortable way of developing software - there are a lot of conveniences built in once you know how they all work. The gotcha is that while you don't know them, it can be a bit overwhelming. Don't worry, we've got your back.

Note: these are still at the early draft stage, we expect to gradually improve documentation over time.

code-structure

Code Structure

The code is broadly composed of these namespaces, many of which are packaged as "extensions" which are in separate git repos, and are included in the app by way of mix dependencies.

  • Bonfire.* - Core application logic (very little code).
  • Bonfire.*.* - Bonfire extensions (eg Bonfire.Social.Posts) containing mostly context modules, APIs, and routes
  • Bonfire.Data.* - Extensions containing database schemas and migrations
  • Bonfire.UI.* - UI component extensions
  • Bonfire.*.*.LiveHandler - Backend logic to handle events in the frontend
  • Bonfire.Editor.* (pluggable text editors, eg. CKEditor for WYSIWYG markdown input)
  • Bonfire.GraphQL.* - Optional GraphQL API
  • Bonfire.Federate.* - Optional Federation hooks
  • ActivityPub - ActivityPub S2S models, logic and various helper modules
  • ActivityPubWeb - ActivityPub S2S REST endpoints, activity ingestion and push federation facilities
  • ValueFlows.* - economic extensions implementing the ValueFlows vocabulary

Contexts are were we put any core logic. A context often is circumscribed to providing logic for a particular object type (e. g. Bonfire.Social.Posts implements Bonfire.Data.Social.Post).

All Bonfire objects use an ULID as their primary key. We use the Pointers library (with extra logic in Bonfire.Common.Pointers) to reference any object by its primary key without knowing what type it is beforehand. This is very useful as it allows for example following or liking many different types of objects (as opposed to say only a user or a post) and this approach allows us to store the context of the like/follow by only storing its primary key (see Bonfire.Data.Social.Follow) for an example.

Context modules usually have one/2, many/2, and many_paginated/1 functions for fetching objects, which in turn call a query/2 function. These take a keyword list as filters (and an optional opts argument) allowing objects to be fetched by arbitrary criteria.

Examples:

Users.one(username: "bob") # Fetching by username
Posts.many_paginated(by: "01E9TQP93S8XFSV2ZATX1FQ528") # List a page of posts by its author
EconomicResources.many(deleted: true) # List any deleted resources

Context modules also have functions for creating, updating and deleting objects, as well as hooks for federating or indexing in the search engine.

Here is an incomplete sample of some of current extensions and modules:

Additional extensions, libraries, and modules

general-structure

General structure

  • Bonfire app
    • A flavour running Bonfire.Application as supervisor
      • Configs assembled from extensions at flavour/$FLAVOUR/config
      • Phoenix at Bonfire.Web.Endpoint
      • GraphQL schema assembled from extensions at Bonfire.GraphQL.Schema
      • Database migrations assembled from extensions at flavour/$FLAVOUR/repo/migrations
      • Data seeds assembled from extensions at flavour/$FLAVOUR/repo/seeds
      • Extensions and libraries as listed in flavour/$FLAVOUR/config/deps.*

naming

Naming

It is said that naming is one of the four hard problems of computer science (along with cache management and off-by-one errors). We don't claim our scheme is the best, but we do strive for consistency.

naming-guidelines

Naming guidelines

federation-libraries

Federation libraries

activitypub

ActivityPub

This namespace handles the ActivityPub logic and stores AP activities. It is largely adapted Pleroma code with some modifications, for example merging of the activity and object tables and new actor object abstraction.

It also contains some functionality that isn't part of the AP spec but is required for federation:

Also refer to MRF documentation to learn how to rewrite or discard messages.

activitypubweb

ActivityPubWeb

This namespace contains the ActivityPub Server-to-Server REST API, the activity ingestion pipeline (ActivityPubWeb.Transmogrifier) and the push federation facilities (ActivityPubWeb.Federator, ActivityPubWeb.Publisher and others). The outgoing federation module is designed in a modular way allowing federating through different protocols in the future.

activitypub-integration-with-bonfire-s-application-logic

ActivityPub integration with Bonfire's application logic

The callback functions defined in ActivityPub.Adapter are implemented in Bonfire.Federate.ActivityPub.Adapter.

When implementing federation for a new object type it needs to be implemented for both directions: for outgoing federation using the hooks in Bonfire.Federate.ActivityPub.Publisher and for incoming federation using the hooks in Bonfire.Federate.ActivityPub.Receiver.