View Source Bonfire Architecture
Hacking
Bonfire is an unusual piece of software, developed in an unusual way. It originally started as a project to create a generic federation library/framework, while building an app for educators to share and collaborate on learning 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
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
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
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.
Code Structure
The code is broadly composed namespaces such as these, many of which are packaged as "extensions" which live in separate git repositories, which are included in the app by way of mix dependencies:
Bonfire.*
- Core application logic (very little code).Bonfire.*.*
- Bonfire extensions (egBonfire.Posts
) containing mostly context modules, APIs, and routesBonfire.Data.*
- Extensions containing database schemas and migrationsBonfire.UI.*
- UI component extensionsBonfire.*.*.LiveHandler
- Backend logic to handle events in the frontendBonfire.Editor.*
(pluggable text editors, eg. CKEditor for WYSIWYG markdown input)Bonfire.GraphQL.*
- Optional GraphQL APIBonfire.Federate.*
- Optional Federation hooksActivityPub
- ActivityPub S2S models, logic and various helper modulesActivityPub.Web
- ActivityPub S2S REST endpoints, activity ingestion and push federation facilitiesValueFlows.*
- 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.Posts
implements Bonfire.Data.Social.Post
).
All Bonfire objects use an ULID as their primary key. We use the Needle
library (with extra logic in Bonfire.Common.Needles
) 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:
Bonfire.Me.Accounts
(for managing and querying local user accounts)Bonfire.Me.Users
(for managing and querying both local and remote user identities and profiles)Bonfire.Boundaries
(for managing and querying circles, ACLs, permissions...)Bonfire.Social.FeedActivities
,Bonfire.Social.Feeds
andBonfire.Social.Activities
(for managing and querying activities and feeds)Bonfire.Posts
andBonfire.Social.PostContents
(for managing and querying posts)Bonfire.Social.Threads
(for managing and querying threads and comments)Bonfire.Social.Flags
(for managing and querying flags)Bonfire.Social.Graph.Follows
(for managing and querying follows)Bonfire.Classify
(for managing and querying categories, topics, and the like)Bonfire.Tag
(for managing and querying tags and mentions)Bonfire.Geolocate
(for managing and querying locations and geographical coordinates)Bonfire.Quantify
(for managing and querying units and measures)
Additional extensions, libraries, and modules
Bonfire.Common
andBonfire.Common.Utils
(stuff that gets used everywhere)Bonfire.Application
(OTP application)Bonfire.Me.Characters
(a shared abstraction over users, organisations, categories, and other objects that need to have feeds and behave as an actor in ActivityPub land)Bonfire.Federate.ActivityPub
andActivityPub
(ActivityPub integration)Bonfire.Search
(local search indexing and search API, powered by Meili)Bonfire.Mailer
,Bonfire.Me.Mails
, andBamboo
(for rendering and sending emails)Bonfire.Files
,Waffle
,TreeMagic
andTwinkleStar
(for managing uploaded content)Bonfire.GraphQL
(GraphQL API abstractions)Queery
andBonfire.Repo.Query
(Helpers for making queries on the database)Bonfire.Repo
(Ecto repository)Exto
(to extend DB schemas in config, especially useful for adding associations)AbsintheClient
(for querying the API from within the server)
General structure
- Bonfire app
- A flavour running
Bonfire.Application
as supervisor- Configs assembled from extensions at
flavour/$FLAVOUR/config
- Phoenix at
Bonfire.Web.Endpoint
- Routes assembled from extensions at
Bonfire.Web.Router
- Routes assembled from extensions at
- 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.*
- Extensions defining schemas and migrations (usually
Bonfire.Data.*
)- Schemas
- Migrations defined as functions in the schema modules in
lib/
- Migration templates calling those functions in
priv/repo/migrations
which are then copied into an app flavour's migrations
- Extensions implementing features or groups of features (eg.
Bonfire.Me
)- Config template which is then copied into an app flavour's config (eg
config/bonfire_me.exs
) - Contexts (eg
Bonfire.Me.Users
)- Sometimes LiveHandlers for handling frontend events in the backend (eg
Bonfire.Me.Users.LiveHandler
)
- Sometimes LiveHandlers for handling frontend events in the backend (eg
- Routes (eg
Bonfire.UI.Me.Routes
)- Controllers and/or Views (eg
Bonfire.UI.Me.CreateUserController
andBonfire.UI.Me.CreateUserLive
)
- Controllers and/or Views (eg
- API (eg
Bonfire.Me.API.GraphQL
), refer to GraphQL API documentation- Schemas
- Resolvers
- Sometimes Plugs (eg
Bonfire.Web.Plugs.UserRequired
andBonfire.Web.LivePlugs.UserRequired
)
- Config template which is then copied into an app flavour's config (eg
- Other extensions or libraries (eg
Needle
orBonfire.Common
which are used by most other extensions)
- Extensions defining schemas and migrations (usually
- Configs assembled from extensions at
- A flavour running
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
- Module names mostly begin with
Bonfire.
unless they belong to a more generic library (egNeedle
orValueFlows
) - Everything within an extension begins with the context name and a
.
(egBonfire.Social.Migrations
) - Database schemas should be named in the singular (eg
Bonfire.Data.Social.Post
) - Context modules are named in plural where possible (eg
Bonfire.Posts
) - Other modules within a context begins with the context name and a
.
(egBonfire.Posts.LiveHandler
) - Modules use UpperCamelCase while functions use snake_case
- Acronyms in module names should be all uppercase (eg
Bonfire.Social.APActivities
)
Federation libraries
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.
ActivityPub
contains the main API and is documented there.ActivityPub.Federator.Adapter
defines callback functions for the AP library.
It also contains some functionality that isn't part of the AP spec but is required for federation:
ActivityPub.Safety.Keys
- Generating and handling RSA keys and signaturesActivityPub.Federator.WebFinger
- Implementation of the WebFinger protocolActivityPub.Federator.HTTP
- Module for making HTTP requests (wrapper around tesla)ActivityPub.Instances
- Module for storing reachability information about remote instances
Also refer to MRF documentation to learn how to rewrite or discard messages.
ActivityPub.Web
This namespace contains the ActivityPub Server-to-Server REST API, the activity ingestion pipeline (ActivityPub.Federator.Transformer
) and the push federation facilities (ActivityPub.Federator
, ActivityPub.Federator.APPublisher
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
The callback functions defined in ActivityPub.Federator.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.Outgoing
and for incoming federation using the hooks in Bonfire.Federate.ActivityPub.Incoming
.