Documentation
Introduction
This comprehensive setup provides a solid foundation (starter
) for building compositional, safe, ergonomic and event-driven applications. It includes an Accounting information system, demonstrating how to implement a domain model and how to run it.
Accounting is absolutely essential to the financial domain. It serves as the backbone of financial management, providing crucial insights into the financial health of individuals, businesses, and organizations. Without accounting, it would be nearly impossible to track income, expenses, assets, and liabilities accurately. It plays a pivotal role in decision-making processes, financial reporting, compliance with regulations, and overall strategic planning.
Accounting domain is a perfect fit for event-driven architecture. It is a natural fit for event sourcing and event streaming, integrating with other systems and services in your organization. We demonstrate how to implement a domain model and how to integrate it with Customer management, Order management and Payment systems.
Accounts are categorized into types. The 5 main types are `asset`, `liability`, `equity`, `income`, and `expense`. Depending on the type of account, an increase is recorded as either a debit or a credit.
- Assets and expenses are increased with debits, decreased with credits
- Liabilities, equity, and income are increased with credits, decreased with debits
All transfers consist of two entries, a debit and a credit. Double-entry bookkeeping ensures that all funds come from somewhere and go somewhere.
Accounting Equation: Assets - Liabilities = Equity + Income − Expenses
Asset Scale
To maximize precision and efficiency (calculations are performed on the integers to avoid loss of precision due to floating-point approximations), Account debits/credits and Transfer amounts are 128-bit integers. However, currencies are often denominated in fractional amounts. To represent a fractional amount, map the smallest useful unit of the fractional currency to 1. Consider all amounts in `faccounting` as a multiple of that unit.
In USD, $1 = 100 cents. So for example,
- The fractional amount $0.45 is represented as the integer 45.
- The fractional amount $123.00 is represented as the integer 12300.
- The fractional amount $123.45 is represented as the integer 12345.
Technology
The application is built with TypeScript, NextJS, TailwindCSS, Supabase, and Fmodel. It is designed to be a solid foundation for building event-driven applications that require a high level of composability, safety, and ergonomics.
Fmodel
aims to bring functional, algebraic and reactive domain modeling to TypeScript. It is inspired by DDD, EventSourcing and Functional programming communities, yet implements these ideas and concepts in idiomatic TypeScript.
Fmodel
promotes clear separation between data and behaviour.
Data:
- Command - An intent to change the state of the system.
- Event - The state change itself, a fact. It represents a decision that has already happened.
- State - The current state of the system. It is evolved out of past events.
Behaviour:
- Decide - A pure function that takes command and current state as parameters, and returns the flow of new events.
- Evolve - A pure function that takes event and current state as parameters, and returns the new state of the system.
- React - A pure function that takes event as parameter, and returns the flow of commands, deciding what to execute next.
Why Supabase
Supabase is an open-source alternative to Firebase that provides a complete backend solution for your applications. It offers a range of features that make it a powerful and flexible choice for your development needs.
Scalable and Reliable
Supabase is built on top of PostgreSQL, one of the most robust and scalable databases available. This ensures that your application can handle growing amounts of data and traffic with ease.
Open-Source and Customizable
As an open-source platform, Supabase allows you to customize and extend its functionality to fit your specific needs. You can also contribute to the project and help shape its future development.
Comprehensive Feature Set
Supabase offers a wide range of features, including authentication, real-time updates, storage, and more, all in a single platform. This helps you save time and reduce the complexity of your application.
Cost-Effective
Supabase`s pricing model is designed to be more affordable than traditional backend services, especially for small to medium-sized projects. This makes it an attractive option for startups and independent developers.
Features
The data model consists of Accounts
, Transfers
, and ledgers
. Ledgers partition accounts into groups that may represent a currency or asset type or any other logical grouping. Only accounts on the same ledger can transact directly. Ledgers are only stored as a numeric identifier on the account and transfer data structures.
Transfers
Financial transactions are called transfers
instead of transactions
because the latter term is heavily overloaded in the context of databases. Transfers debit a single account and credit a single account. However, you`ll probably run into cases where you want transactions with multiple debits and/or credits. For example, you might have a transfer where you want to extract fees and/or taxes. For that purpose we support executing multiple/linked transfers in a single transaction. This is supported by the aggregate.handleList(Commands[])
method.
There are four concrete variations of the transfer commands which are part of the public API:
- 1. Single-Phase / CreateTransferCommand
- Two-Phase
- 2. Pending / CreatePendingTransferCommand
- 3. Accept-Pending / AcceptPendingTransferCommand
- 4. Reject-Pending / RejectPendingTransferCommand
Transfers can either be:
- Single-Phase
, where they are executed immediately or
- Two-Phase
, where they are first put in a Pending state and then either Accepted or Rejected / Perfect primitive for atomic transfers across different systems.
Accounts
We track each account's cumulative posted debits and cumulative posted credits. In double-entry accounting, an account balance is the difference between the two — computed as either debits - credits
or credits - debits
, depending on the type of account. It is up to the application to compute the balance from the cumulative debits/credits.
- For every transfer to an account, there is an equal and opposite transfer from a different account.
- Accounts may enforce net balance limits, such as `debits may not exceed credits`, or `credits may not exceed debits`.
- We distinguish between inflight reserved amounts and committed/accepted amounts to control inflight liquidity.
There are two variations of the account commands which is part of the public API:
- CreateAccountCommand
- CloseAccount
Event sourcing
With event sourcing, we delve deeper by capturing every decision or alteration as an event. Each new transfer or modification to the account state is meticulously documented, providing a comprehensive audit trail of all financial activities.
This affords you a 100% accurate historical record of your financial domain, enabling you to effortlessly traverse back in time and review the state of all accounts at any given moment.
History is always on!
Event sourincing is implemented with a single database table, supporting optimistic concurrency control. We aslo provide a set of SQL functions to fully support event sourcing.
This implementaion is very general and can be used for any domain model, not just accounting.
Recepies
We provide a set of recipes to help you get started with the most common use cases, demonstrating how to implement a domain model and how to use it/run it.
- Income Tracking
- Expense Tracking
- Payments
- Investor Funding
These recepies demonstrate how to use the domain model from the UI perspective.
Domain model is implemented in TypeScript and can be used in the edge functions, as well as in the UI. Domain core model is separated from the infrastructure and application layers! This flexibility allows you to build event-driven applications that require a high level of composability, safety, and ergonomics.
Installation
The project sutructure includes two main parts/directories:
- root (THIS APPLICATION): NextJs 14 (Tailwind, shadcn/ui) application to manage your finantials.
- supabase: Supabase/Postgres SQL migrations and TypeScript/Deno edge functions.
To get started with our product, follow these simple steps:
Step 1: Run the Supabase
The start command uses Docker to start the Supabase services:
supabase start
You can use the
supabase stop
command at any time to stop all services:supabase stop
You can now visit your local Supabase Dashboard at
http://localhost:54323
.Step 2: Run the application
Rename the
.env.local.example
to.env.local
, and specify your `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` variables. Yourapi url
andanon key
will be printed in the terminal as a result ofsupabase start
command.Run it:
yarn dev
The application will be available at
http://localhost:3000
.
Usage
Once you have the application installed, you can start using it to its full potential. Here are some tips to get you started:
Sign in / Sign Up
Sign Up a new user on the log-in page, and login to the application. It will auto-confirm the user in the local development environment. In production environment, the user will receive an email with a confirmation link!
Recipes
Recipes guide you through the most common use cases, demonstrating how to integrate accounting into your context.
Dashboard
Dashboard provides a comprehensive overview of your financial data, with a summary of your accounts, transfers, and ledgers.
Reports
Reports enable a detailed analysis of your financial data wuth four type of reports: Balance Sheet, Income Statement, Cash Flow, and Anual Report.
Database
Database changes are managed through migrations
. This way, you can keep track of all changes to your database schema.
These migration scripts are foundational to the event sourcing and event streaming API, that your applications(edge functions) are going to use. They are located in the supabase/migrations
directory:
20231223160131_event_sourcing.sql
: Tables, Index, Rules and Triggers for Event Sourcing20231223160131_event_sourcing_api.sql
: SQL functions for Event Sourcing20231223160229_event_streaming.sql
: Tables, Index, Rules and Triggers for Event Streaming20231223160229_event_streaming_api.sql
: SQL functions for Event Streaming- ...
Feel free to create your own migration scripts:
supabase migration new add_department_to_employees_table
You can apply migrations manually by running the following command(s) in your terminal:
# To apply the new migration to your local database:
supabase migration up
# To reset your local database completely:
supabase db reset
Multi-Tenant
All tables have tenant_id
column, and the application is multi-tenant ready. Every user/email is a tenant, and can only see their own data. You might want to extend this functionality to support teams and organizations.
Check the actions.ts / signUpAction
and change the way the tenant is extracted, to match your needs. Check the supabase/migrations/20231223160130_utils.sql
for `auth.tenant()` sql function that is used to specify our Row-Level Security policies.
Row-level security (RLS) is a feature that allows you to restrict rows returned by a query based on the user/tenant executing the query.