As your product and back-office requirements evolve you'll need to update your Schema and migrate existing data. Your Schema is immutable and versioned, and once deployed to a Ledger FRAGMENT will enforce backwards compatibility checks. Use migrations to safely change your Schema and Ledger data while ensuring compatibility with your code, zero down-time, and data that is always immutable and auditable.
By deploying a new Schema version, you can:
The exact process for performing a migration depends whether you need to migrate all or some of your existing data, and whether affected balances need to stay accurate during the migration.
When you create a new Entry Type it will automatically be marked as V1. Once deployed, Entry Type versions are immutable and cannot be deleted from your Schema. Editing Entry types in the Dashboard will automatically create new versions as needed.
In the Schema JSON Entry Types have a type
and typeVersion
field that together must be unique. To ensure backwards compatibility all deployed Entry Type versions are always kept on the Schema.
{
"key": "schema-key",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account",
"typeVersion": 1,
"lines": [
{
"account": {
"path": "assets/banks/user-cash"
},
"key": "funds_arrive_in_bank",
"amount": "{{funding_amount}}"
},
{...}
]
},
{
"type": "user_funds_account",
"typeVersion": 2,
"lines": [
{
"account": {
"path": "assets/banks/user-cash"
},
"key": "funds_arrive_in_bank",
"amount": "{{funding_amount}} + {{fee}}"
},
{...}
]
}
]
}
}
When posting you can specify typeVersion
to select which version of an Entry Type to use. If not set, 1
will be used as the default.
Once you have created a new Entry Type version, you can prevent postings of the previous version by setting the old Entry Type versions's status
field to disabled
in your Schema.
{
"key": "schema-key",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "user_payment",
"typeVersion": 1,
"status": "disabled",
"lines": [...]
},
{
"type": "user_payment",
"typeVersion": 2,
"lines": [...]
}
]
}
}
When an Entry Type is disabled:
BadRequestError
You can archive an Entry Type version to trigger the migration process for historical data. Archiving an Entry Type version:
disabled
for 45 secondsLedgerEntryDataMigration
(see below) for each Ledger using the Schema to query for its entries to migrateTo archive an Entry Type, set its status
field to archived
in your Schema:
{
"key": "schema-key",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "user_payment",
"typeVersion": 1,
"status": "archived",
"lines": [...]
},
{
"type": "user_payment",
"typeVersion": 2,
"lines": [...]
}
]
}
}
Once archived, you can un-archive an Entry Type version by settings its status
back to active
. This will set the LedgerEntryDataMigration
created for the archival to status
inactive
. The Entry Type version will then be available again for use in your application.
When you deploy a Schema with a newly archived Entry Type version a LedgerEntryDataMigration
will asynchronously be created for each Ledger using the Schema. To migrate existing entries that were posted with the archived Entry Type version:
ledgerEntries
on the relevant LedgerEntryDataMigration
. Since the Entry Type version was previously disabled, no new Ledger Entries will be added to this list.migrateLedgerEntry
for each Ledger Entry with the new version of the Ledger Entry. This will remove the Ledger Entry from the LedgerEntryDataMigration
. The migration is complete when the ledgerEntries
array is empty.query GetEntryMigrations(
$ledger: LedgerMatchInput!
$filter: LedgerEntryDataMigrationsFilterSet
) {
ledger(ledger: $ledger) {
ledgerEntryDataMigrations(filter: $filter) {
nodes {
entryType
typeVersion
ledgerEntries {
nodes {
id
type
posted
parameters
}
pageInfo {
hasNextPage
endCursor
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
{
"ledger": {
"id": "ledger-id"
},
"filter": {
"entryType": {
"equalTo": "user_payment"
}
}
}
Use the migrateLedgerEntry
mutation to migrate your Ledger Entries. This mutation's input takes an id
of the Ledger Entry to be migrated, and the newLedgerEntry
to migrate to. Under the hood this performs a reversal of the entry to migrate followed by a repost using the new Entry.
mutation MigrateLedgerEntry($input: MigrateLedgerEntryInput!) {
migrateLedgerEntry(input: $input) {
... on MigrateLedgerEntryResult {
reversedLedgerEntry {
id
ik
}
reversingLedgerEntry {
id
ik
}
newLedgerEntry {
id
ik
type
typeVersion
posted
created
lines {
nodes {
amount
}
}
}
}
}
}
{
"input": {
"id": "entry-to-migrate-id",
"newLedgerEntry": {
"ledger": {
"id": "ledger-id"
},
"type": "user_payment",
"typeVersion": 2,
"parameters": {
"amount": "10000",
"fee": "100"
}
}
}
}
Like Entries, your Ledger Accounts are generally immutable and safeguarded by FRAGMENT's backwards-compatibility rules. However a number of Account migrations are supported to ensure flexible Schema design.
Accounts can be migrated between strongly and eventually consistent by updating the consistencyConfig
field for the Account on the Schema. Once deployed a LedgerMigration
will be created for each of the Schema's Ledgers that will apply the update.
Single-currency Accounts can be upgraded to multi-currency Accounts by updating the currencyMode
field for the Account on the Schema. Once the Schema change is deployed a LedgerMigration
will be created for each of the Schema's Ledgers that will apply the update.
Note:
currency
argument when reading the balance
, ownBalance
, or childBalance
fields of the now multi-currency Account. No change is required if you're reading the balances
, ownBalances
, or childBalances
fields of the Account.When you need to prevent new entries from being posted to a Ledger Account you can disable it by setting the Account's status
field to disabled
in your Schema. This is useful when:
When an Account is disabled:
BadRequestError
{
"key": "schema-key",
"chartOfAccounts": {
"accounts": [
{
"key": "legacy-user-funds",
"name": "Legacy User Funds",
"template": "assets/banks/legacy-user-funds",
"status": "disabled"
},
{
"key": "user-funds",
"name": "User Funds",
"template": "assets/banks/user-funds"
}
]
}
}
Once an Account is disabled, you can archive it to trigger the migration process for historical data. Archiving an Account:
disabled
for 45 secondsLedgerAccountDataMigration
for each Ledger using the Schema to query for its entries to migrateTo archive an Account, set its status
field to archived
in your Schema:
{
"key": "schema-key",
"chartOfAccounts": {
"accounts": [
{
"key": "legacy-user-funds",
"name": "Legacy User Funds",
"template": "assets/banks/legacy-user-funds",
"status": "archived"
},
{
"key": "user-funds",
"name": "User Funds",
"template": "assets/banks/user-funds"
}
]
}
}
When you archive an Account, FRAGMENT automatically creates a LedgerAccountDataMigration
that tracks all entries posting to that account. You can query for these migrations and filter by specific structuralPath
s:
query GetAccountMigrations(
$ledger: LedgerMatchInput!
$filter: LedgerAccountDataMigrationsFilterSet
) {
ledger(ledger: $ledger) {
ledgerAccountDataMigrations(filter: $filter) {
nodes {
accountPath
ledgerEntries {
nodes {
id
type
typeVersion
posted
parameters
lines {
nodes {
account {
path
}
amount
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
{
"ledger": {
"id": "ledger-id"
},
"filter": {
"accountPath": {
"equalTo": "assets/cash"
}
}
}
Before migrating a Ledger Account it is important to consider whether or not you can take downtime on reading the Account balance. This will inform whether you should run an online or offline migration.
For offline migrations your Account balance will be in flux while the migration is running. To perform one:
ledgerEntries
list on the LedgerAccountDataMigration
.For online migrations your Account balance will stay accurate throughout the process. To perform one:
migrateLedgerEntry
to migrate each of the old Ledger Entries to the new dual-write Entry Type version. Now both old and new Accounts will have matching balances.ledgerEntries
list on the LedgerAccountDataMigration
.migrateLedgerEntry
to migrate old Entries to final Entry Type versions. Once completed, the old Account will have no lines.