Database models for internal accounts

Database modeling and processes for internal, virtual and segregated accounts in Ebury.

Disclaimer: The level of detail in this RFC will probably go deeper than the levels allowed in the process, but making an exception for this it makes sense as complexity and implications on this developments are high.

Problem Description

Internal accounts were historically created in BOS, but as we want to split up BOS into several services to scale in performance and maintainability, we want to move the accounts out of BOS to ADS service, where the accounts domain belongs to.

More specific reasons for moving the accounts are explained in EUR Payment Execution via Swift Service (Barclays Frankfurt) RFC.

Background

Please read EUR Payment Execution via Swift Service (Barclays Frankfurt) RFC before going on here.

Naming glossary

  • Payment: Outgoing money flow, debited to Ebury and credited to a beneficiary.
  • Entry: Money flow, either credit or debit, for indicating money movements in accounts.
  • Internal account: Accounts owned by Ebury and used for moving money, either payments or entries.
  • Virtual account: Account proxying a parent (internally in banks, not in BOS db) account used for reconciliation purpose. All virtual accounts are link to a client, so all money movements are going to the client linked balances.
  • Segregated account: Functionally is the same as a virtual account, but actually it is an internal account shared with one (and only one) client. This is transparent from client point of view.
  • Customer account: Virtual account and segregated accounts.
  • Settlement account (also known as collection account and pool account): Accounts to bind the entries to. They are a subtype of internal account.
  • Funding account: Account where clients send funds to for fund in deals or payments.

Current implementations

There are two models in BOS for storing accounts:

  • BankAccount: Internals, segregated, beneficiary accounts, bank account approval..
  • BankAccountMask: Virtual (automatic and manual) and segregated.

In BOS you will see filter(owner__internal=True, active=True) which actually is the definition of internal account inside BankAccount model.

There are information stored inside BankAccount which is actually not account information, it is information about how it is used, these are:

  • Private.
  • Main.
  • Manual balance in client money.
  • Sync account.
  • Sync with fxsuite.
  • Allow preload movements.

All this properties alongside with other logic and database data combined are the definition of "funding account", "source account", "settlement account", etc.

The models in ADS right now:

Current ADS ER

Note: There are missing tables about rules omitted to be focused on RFC target.

Some use cases of the accounts and potential queries in DB:

  • All queries al filtering active=True.
  • The most common operations over the accounts are actually list and get by id. This queries are performed for showing payments, entries in views, so it is queried as much as payments and other views in BOS/API/EBO are loaded.
  • Funding account calculation: This is filtering against currency filter(symbol='currency').
  • Source account calculation: This is filtering against currency filter(symbol='currency').
  • List account with "Default bank entity": filter(bank_entity__name=settings.DEFAULT_BANK_ENTITY).
  • Non segregated account: Looking for accounts that has not been already segregated with filter(bankaccountmask__isnull=True). Actually, not too relevant as this feature is not used too often.
  • Filters in list by account number or IBAN equal to some value (Q(account_number=number.text) | Q(IBAN=number.text)).
  • For company transfers: Some ordering order_by('currency', 'country').
  • Entries export: Some ordering in accounts by account number for generate an xls.

Generally the most common operations but the CRUD basic operations are filtering by currency and by bank entity.

Segregated accounts

Right now in BOS, the flow for creating a segregated account is:

  1. Go to "Internal bank accounts" view.
  2. Create an internal bank account.
  3. Go to client page and add a "Assigned account".
  4. The form allows you to search a create a "assigned account" from a internal account.

Then internally BOS, create a BankAccountMask model link to the BankAccount (internal) with a relation. Then, by code logic the coherence in the data is manage, to keep both rows in different tables equal.

Segregated ER BOS

Then the BankAccount is used as a settlement account and the BankAccountMask is used for showing to the client the funding accounts.

For syncing BOS and ADS, there is a async Django signal on BankAccountMask save which is calling ADS to update/create the data.

Current BOS ADS sync

This, when ADS project finish, the signal will disappear and the data will only exists in ADS.

Multi-currency

Because of the lack of validations on creating internal accounts, operations has started to create multi-currency accounts by duplicating the account but adding different currency for each one. Example:

ES12 1234 1234 1234 EUR
ES12 1234 1234 1234 GBP
ES12 1234 1234 1234 USD

Every field is exactly the same and there is not different data on account but the currency.

Then, when selecting accounts by currency in BOS (for selecting a funding account for example) the trick does the job and the account is used.

Solution

The v1 version mimic current behavior in BOS.

ER ADS v1

We create a new model EburyEntity just to unify what it is stored as bank entity information, so we help easily filling up accounts information.

This the most simple and straight forward solution as we don't have to redefine nor investigate anything.

Pros:

  • We don’t fight BOS logic as we keep separately customer accounts and internal accounts definitions.
  • We don’t fight BOS data inconsistency. Just a refactor in the BankEntity data.
  • Scales better to changes, as when changing internal you will only affecting internal (smaller scopes).

Cons:

  • We don’t fight BOS data inconsistency.
  • There are more definition of "Account" in ADS when actually, in real world are the same. So, we will end up with data duplication and business logic to handle it.
  • Adding new validations in accounts will need to double-implement it if affecting customer and internal accounts. For example: supporting multi-currency and potentially querying mixed data with customer and internal accounts.

Segregated accounts

Essentially we keep the same behavior we already have but it will be produced when sync data into BOS instead of in views on creation. No developments nor redefinitions need it.

Segregated with v1

Alternatives

v2 Inheritance modeling

Because of including the internal accounts we redefine the "Account" definition in ADS.

The properties that are store how accounts are used are not part of accounts domain, so it won't be move as part of model to ADS, they will be stored somewhere else.

Now customer accounts and internal accounts will be manage under the same entity "Account". So, when you request /accounts API endpoint internal accounts will be listed too.

As creating a one level inheritance refactor requires extra developments and investigation, we build a modeling with two levels which then will probably (depending on investigation and changes during time) refactored to one level. Entity relation:

New ER ADS v2

Note: don't pay too much attention to naming, this will be updating during development.

We will be removing SettlementAccount model to be replaced by InternalAccount.

Pros:

  • Managing data consistency at database constraint layer.
  • Handling different account types as the same one (querying or creating new logic with all them).
  • Actually reflecting most of the real world that we can in the models.

Cons:

  • Scope changes affecting customer and internal accounts.
  • Errors/Incidents in the data layer will affect customer and internal accounts.
  • Require extra developments and investigations to be able to do it.
  • Too many inheritance levels (potential issues with libraries and to be able to change and extend models).

Segregated accounts refactors

Because of this change and unique in the IBAN we cannot longer store duplicated rows for internal and customer accounts. For solving it, we are going to just store one Account entity in ADS and it is going to be a SegregatedAccount.

Then in BOS, we are doing to keep same modeling, just mimic what BOS do right now but managing the accounts in the client page instead of the Bank account internal page. This will require creating new views for create and update the accounts.

New BOS ADS sync v2

When ADS project finish, the signal will disappear, so the sync model will looks like:

New BOS ADS sync v2

With future refactors internal and customer accounts will be removed from BOS. So, the segregated accounts will be selectable as a settlement accounts.

v3 One level inheritance

The v3 is actually the same as the v2 solution proposed but implying more developments. ER ADS v1

This solution is the aim of the solution proposed but currently pretty conflicting to develop as there is a work in progress with the customer accounts in ADS and will need refactors in the code before being able to implement it.

Pros:

  • Avoiding too many levels of inheritance is always good for maintainability.

Cons:

  • Requires refactoring and unifying virtual, segregated and internal accounts and refactor current implementations.
  • Scope changes affecting customer and internal accounts.
  • Errors/Incidents in the data layer will affect customer and internal accounts.

Segregated accounts

Same as v2.

Caveats

Check "Cons" section in solution and alternatives.

Operation

NA

Security Impact

NA

Performance Impact

The table Account in ADS will be much longer, is will contain internal too. This can cause issues when querying the table.

Currently there are ~1500 internal accounts and ~44K customer accounts. Not a big deal even merging them into one table.

Thinking about the future, customer accounts will be growth much more than internal. Right now we have ~50K clients. Let's say we grow x10 in internal accounts and we create 10 accounts for each client in Ebury: 15K internal account and 500K customer accounts. No critical numbers for ProstgreSQL.

The inclusion of account internal in Account table will not be a problem for customer accounts but the opposite may be a problem in the future, as customer accounts will be always much more than internal accounts, so we are moving the potential issues in customer accounts to internal accounts.

Developer Impact

Dealing with customer accounts will mean dealing to internal accounts and vice-versa.

Data Consumer Impact

NA

Deployment

NA

Dependencies

NA

References