Architecture
On this page you'll find information about the internal architecture of Firefly III. It's not meant as an explanation of where everything is but rather as a means to show you how everything is structured. If you ever want to develop something these pages should show you where you need be.
Database
These images show you a graphical overview of the most important parts of the database. There are many more tables, but these are the important objects you may want to learn about. If you see something in the database you're curious about, please let me know.
Transactions
In the picture, objects related to transactions are shown in shades of red. A "transaction group" ties multiple "transaction journals" together. Each transaction journal contains exactly two transactions. A transaction either removes or adds money from an account. A basic withdrawal or expense in Firefly III consists of:
- One transaction group to tie everything together.
- One transaction journal to link two transactions.
- One transaction removing money from account A.
- One transaction adding money to account B.
A split transaction is one "transaction group" that contains X "transaction journals", one for each split. Each transaction journal contains two transactions. The amount of the withdrawal is stored in the transaction.
Accounts and transaction journals can have a currency assigned to it. Transaction journals may also have a "foreign" currency assigned to it, which is another reference to a currency object.
Transaction journals have metadata like categories, budgets and bills and tags.
Accounts are linked to transactions and to piggy banks. A transfer (see transaction type) may link a transaction journal to a piggy bank in a piggy bank event. Together they form the history of the piggy bank. A piggy bank event may exist without a link to a transaction journal.
Rules
Rule groups contain rules. Rules have actions and triggers. Although actions and triggers may reference to budgets, categories and tags and other things, there is no database relationship.
Recurring transactions
A recurring transaction has some basic information, and an X number of transactions, which means it's possible to make a "split" recurring transaction. It's also possible to create multiple "repetitions", that define when a recurrence fires. This data is used by the recurring cronjob to create the exact transaction as defined in the transaction definition, whenever a repetition says it's time.
Webhooks
Webhooks have a related object called the "webhook message" that contains the content of that particular moment the webhook fired and if it succeeded.
Code
Firefly III is based on Laravel, and uses the MVC model. There is a number of design patterns used throughout the code. Listed here are the most important directories and what they contain. Some design patterns use more directories than what you see here, so be sure to also check out the Design Patterns below.
Models
Models are stored in Models/
. Everything in the Firefly III database has an associated model. Most models also have a routeBinder
function that tells if a URL parameter is valid. Some tables in the database are used to link data together, which means they don't have a model.
Views
All the views are stored in views/
. There is a v1 and a v2 folder. The v1 folder contains the HTML-based views you know and love. The v2 folder contains the views for the new layout, which will be a Vue app. As such the v2 views are fairly basic (empty). Views are Twig files.
Controllers
Most pages are generated by controllers in Controllers/
. They're grouped around objects (rules, transactions, etc.) and mostly follow a CRUD-model.
- IndexController
- CreateController
- etc...
There are specific controllers for the charts, some JSON controllers and some notable exceptions. Most of this code will have to move to the API, because I plan to make the new layout (v2) API-only. That means that the controllers in the app/Api/V1
directory will become leading.
API
The API code is stored in app/Api/V1
and consists of controllers, some basic middleware and request classes. The API code is pretty consistent: store or update objects, make some lists and transform the data into JSON. The requests can be interesting because in those requests the rules are stored that define what you can submit. The API will become the main interaction method for ALL frontend code in Firefly III, which means it will have to be greatly expanded.
Generators
- The Chart generators in
app/Generator
render specific arrays into chart.js compatible arrays (which in turn, are returned to the user as JSON). - The Report generators in
app/Generator
are kind of deprecated. They used to generate one huge array with the report in it that the user requested. This got slow, so now the generators only do the basic stuff. Each box on your favorite report is requested using AJAX / JSON from a dedicated controller inapp/Http/Controllers/Report
. This feels a lot snappier. - The Webhook generator is a small piece of code that generates the actual message used in webhooks.
Helpers
The app/Helpers
contains some generic code used for reports, the help, and updates. It's a place for all of those things I couldn't think of a better place for. The transaction collector is kind of special and is detailed in another section.
Jobs
Some of the work that Firefly III needs to do is complex or time-consuming, so it's packed in a job. In app/Jobs
you'll find some things either triggered by cron jobs or by other things.
Validation rules
Validation rules are stored in app/Rules
and app/Validation
. It's one thing to validate if a number is larger than zero. But some validation rules for transactions used by Firefly III are pretty complex, because there's a lot of context.
Services
The idea of app/Services
was that any external service would have its code and interfaces in this folder. This applies to the update checker, the webhooks and the password verifier. However, there's also a few "internal" services. Creating a transaction or account from beginning to end is complex so these services tie it all together.
Support classes
In the app/Support
directory you will find a bunch of support code for all kinds of functions and things in Firefly III. Just like the app/Helpers
folder it seems to collect all kinds of code snippets that have nowhere else to go.
Translations
All languages are stored in resources/lang
. The main language is en_US
, all other translations are derived from the English text. If you want to support the translations, check out Crowdin.
Design Patterns
Apart from the code in the previous section there are some design patterns in use that don't really focus on specific folders specifically but rather have a design that spans more than one part of the code base.
Transaction collection, search and rules
The single biggest piece of code in Firefly III is the transaction collector. This is an interface with lots of functions that is designed to collect (you guessed it) transactions. In the code you give the collector parameters like date-ranges, accounts or search queries. You then collect the results and use it in lists, or in rule actions or whatever. This is basically one big database query.
There are two schools of thought when it comes to transaction collection. Each page, each view and each report has its own set of transactions it wants to show and a specific set of meta-data it needs. In earlier times each controller would have its own code to grab transactions from the database. This is very hard to maintain, although it makes each query very efficient. A transaction collector is less effective than writing dedicated queries, but it does save a lot of code.
Check out the GroupCollectorInterface.php
for all the possibilities.
There is an OperatorQuerySearch
class stored in app/Support/Search
that converts the search query of the user into a transaction collector. The collector does its work and the result is shown to the user.
Stored in the folder app/TransactionRules/Engine
is the rule engine. It works just like the search does. It uses the rule triggers to build a search query. The query is executed by the transaction collector. The transactions that are found are then handled by the actions in the Actions
folder. The engine ties all of this together.
Events
Some events in Firefly III trigger more code. File EventServiceProvider.php
shows you all events (also listed in app/Events
) and the class + method that will trigger if the event happens. Some events trigger multiple handlers. The code of the handlers is in app/Handlers
.
Factories
Most objects in Firefly III are complicated things. Just look at the structure of a transaction. In a worst-case scenario a new split withdrawal results in 13 new objects. The various factories in app/Factory
handle this. If you start with the TransactionGroupFactory
you can go down the rabbit hole of transaction creation in Firefly III.
Webhooks
When selected events happen (currently: when a transaction is stored or updated) Firefly III creates a new StandardMessageGenerator
that will generate a new webhook message for every active webhook that is applicable to the current event.
After the generator generates the messages another trigger is fired that will send the messages. The WebhookEventHandler
will select a maximum of 3 webhook messages that haven't failed too often. This means that message generation, message selection, and message sending are three separate processes.
So actually sending a webhook message is another separate job (see SendWebhookMessage
) which means that it can be done asynchronously. The StandardWebhookSender
will use the Sha3SignatureGenerator
to sign the message and send it (using Guzzle).
The result is stored in the original webhook message in the database.
Repositories
The repository design pattern basically says that you don't query your models directly but ask a repository to list or create what you need, and then the repository takes care of it. It's a weird pattern because usually you have this repository interface and just a single implementation making the interface kind of overhead. But I like the idea.
These repositories do a lot of the heavy lifting when it comes to specific models and their demands from various parts of the code (search for, list, summarize). The "store"- and "update"-methods that these repositories contain are mostly taken over by factories.
Frontend
The /frontend
folder contains the code of the new Vue frontend. Check out src
for all the details. It's a Vue based frontend build using Quasar.