Skip to content

Instantly share code, notes, and snippets.

@thetutlage
Created May 25, 2020 14:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save thetutlage/2a2e512fcead9bc57fb28c811773f8b7 to your computer and use it in GitHub Desktop.
Save thetutlage/2a2e512fcead9bc57fb28c811773f8b7 to your computer and use it in GitHub Desktop.
Double accounting Ledger

Ledger Service

Ledger service is a living implementation of Double Entry Accounting. Using concrete principles of accounting, the ledger service ensures that all money transferred between accounts is properly recorded with trail log for each and every transaction.

Features

The features set of the Ledger service is output of the initial understanding of the functionalities required to run other parts of the application.

  1. Built around double entry accounting principles.
  2. First class support for transaction reversal without loosing any history.
  3. Maintains account's current balance for quick lookup.
  4. Allows creating nested accounts with configurable settings (defaults to 5 levels of nesting)
  5. Ability to replay transactions for a given account and ensure that the account's current balance is always accurate and not mis-calculated as part of some race conditions.
  6. View complete history of an account and how its balance has changed over time.
  7. Option to allow/disallow negative account balance with configurable limits on per account level.
  8. View financial health of the entity using balance sheet.

Why Double Entry Accounting?

The Double Entry Accounting principle came into life around Year 1573 and from then, majority of business use it for managing their accounting books.

In Relay, we will be moving a lot of money across multiple accounts. For example:

  • Paying salaries
  • Computing daily earnings
  • Lending loans
  • Reimbursement
  • and many more

One of the ways is to naively create these virtual accounts and continuously update their balance. This may work fine, but nothing is stopping us from taking wrong decisions or not considering cases in which accounts and their balance can be abused.

Just like Programming algorithms, Double entry accounting comes with some guarantees (when implemented correctly).

  1. It provides a complete record of financial transactions for a business
  2. It can help reveal data entry errors by performing accounts reconciliation with bank statements.
  3. A single transaction cannot mess up with total transaction amount, since the both sides of a transaction needs to have the same total.
  4. Replaying transactions should lead to the current balance of a given account.
  5. Help reveal the financial health of a business.

Manual Accounting Process

Before we dive into the implementation details of the Ledger service, let's get a birds eye view of manual accounting process.

The Double entry accounting is a broad term and there many ways to work with it. However, this section discusses the filtered understanding of an Engineer and not of an experienced Accountant.

Everytime a business transaction takes place, it will be recorded inside the Journal and then making it's way to Ledger.

The Journal is used to maintain a list of tranasactions, along with the amounts involved and a description. Whereas, the Ledger stores transactions in reference to the impacted account and may also compute the account current balance from it.

Journal Sample

Ledger Sample

The best part about this flow is, if you take all the journal entries and sum them up for a particular account, then the account balance should match the existing account balance.

Ledger Service Implementation

Translating accounting principles to software doesn't mean that we need to maintain the books in the same manual format. However, it means we need to follow the same principles and apply all the required constraints to provide similar guarantees as mentioned by double entry accounting.

This section covers the implementation details of the Ledger service.

Terms

  • Entity: Entity refers to the business for whom the books are maintained inside the ledger service. For example: Relay is the entity.

  • Tenant: The tenant is scope applied to a subset of accounts, to prevent money transfer across multiple tenants.

    Relay needs this feature, since our books contains the transactions of our clients (the companies using Relay) and we do not want our clients to transfer money between them. For example:

    A single transaction cannot have accounts from Company A and Company B.

    This constraint is required in softwares, since there is no manual monitoring happening with every transaction.

  • Account: Account refers to an individual account in the book of accounts. In simple words: An account always has a type like asset, expense, liability and so on. Whenever a transaction happens, it has to impact two or more than 2 accounts. Otherwise, it fails the principle of double entry accounting.

  • Balance: The balance refers to the current account balance. DO NOTE: Balance doesn't always means positive balance. For example 3000 Rs in a Liability account actually means negative balance.

Design Considerations

  1. The ledger service has no knowledge of the outside world. Which means it never stored user ids or company ids inside its database. The application consuming the ledger service has to build these relationships.
  2. Negative account balances are not allowed. We may need this feature in the future, but for now a transaction will fail, if one or more accounts in that transaction goes beyond the existing account balance.
  3. As mentioned by double entry accounting principles, the Debit and Credit sides total must be same.
  4. As mentioned by double entry accounting principles, a single account cannot be mentioned twice in a single transaction.
  5. Updates are not allowed. Just like real accounting, you need to create reversal entries (There is a dedicated API for same).

Flow

Following is the abstract flow for using the ledger service.

Creating a New Entity

Everything starts by creating a new entity. The books are always maintained from the perspective of a given entity. It means that balance sheets are only available for entities.

Adding Tenants to the Entity

Next step should be to create a tenant. Tenants allow scoping accounts within a single entity, so that cross tenant transaction cannot take place.

Think of a situation where, within a single entity, you want to disallow certain accounts from cross referring each other. For example:

Relay as an entity has two customers Acme Corporation and Umbrella Corporation. We don't want Acme Corporation and Umbrella Corporation accounts to refer each other in any way. So, we create these corporations as tenants which gives us that extra level of security by prevent cross tenant transactions.

Adding Accounts to a Tenant

The final step of the flow is to create appropriate accounts for a given tenant. Don't fear when creating multiple accounts. Big corporation has hundreds of accounts in real accounting world and hence having them in software is equally acceptable.

Performing Transactions

Once the setup has been completed, you can perform transactions. Each transaction needs to impact two or more accounts and the debit/credit sides total must be same (same as real world accounting).

Handling Concurrent Requests

Concurrent requests to store a transaction needs some extra care to ensure that the ledger service computes correct outcome.

Let's start with the moving parts of a transaction and see which of them needs extra care when handling concurrent requests.

In a nutshell, a transaction performs following steps:

  1. Verify that transaction impacts two or more accounts.
  2. Debit and credit sides are equal.
  3. Ensure all entries in a transaction has positive amount values.
  4. Disallow float values, since we accept money in the least denominator.
  5. Insert transaction entries
  6. Update account balances impacted during the transaction.

The steps from 1st to 5th are not vulnerable to any sort of race conditions. However, the 6th step can lead to wrong balance during a race condition and here's how.

  1. The first transaction hits the system in which we read the account balance as 90000 (ie. Rs. 900).
  2. The second transaction hits the system at the same time and reads the same balance 90000 (ie. 900Rs).
  3. The first transaction has an entry to increase the account balance by Rs. 300.
  4. The second transaction has an entry to increase the account balance by Rs. 500.
  5. Now since both the transactions are using the same opening balance Rs. 900, we will end up with a wrong final amount, based upon which transaction finishes later.

To overcome this situation, all we need to do is acquire Row Level Update Lock. The lock is as simple as “Lock this row from getting read until the database level transaction completes”.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment