Persisting Money into the database. Design decision
-
06-07-2019 - |
Question
I need a table to store the state of a financial transaction. The state of this transaction can be roughly modelled by this class.
class FinancialTransaction
{
Integer txId,
Money oldLimit,
Money newLimit,
Money oldBalance,
Money newBalance,
Date txDate
}
class Money
{
Currency curr,
BigDecimal amount
}
My initial design of the schema looks like this:
CREATE TABLE tx
(
txId bigint(20) unsigned NOT NULL,
oldlimit_currency varchar(3) NULL,
oldlimit_amount decimal(7,5) default 0.00,
newlimit_currency varchar(3) NULL,
newlimit_amount decimal(7,5) default 0.00,
----snipped----
PRIMARY KEY (txId)
)
Two things worry me:
- Each transaction occurs based on one Currency. I haven't thought far enough as to whether I might need to support transactions that may happen in multiple currencies. Assuming that it doesn't change; then isn't it more space-efficient to just maintain one currency column ? Will I regret this simplistic solution ?
- Since each Money item is a value object, should I instead save all Money objects into a separate Money table and have the original table use moneyIds as foreign keys to the Money table ?
That is,
CREATE TABLE tx
(
txId bigint(20) unsigned NOT NULL,
oldlimit_money_id int NOT NULL,
newlimit_money_id int NOT NULL,
----snipped----
PRIMARY KEY (txId),
FOREIGN KEY (oldlimit_money_id) REFERENCES MONEY(id) ON DELETE NO ACTION ON UPDATE NO ACTION,
FOREIGN KEY (newlimit_money_id) REFERENCES MONEY(id) ON DELETE NO ACTION ON UPDATE NO ACTION
)
Are there alternative designs ?
Thanks lazyweb.
Solution
The currency and money value are two different concepts so would be better to separate them. There is no need to make a separate table for 'values' but would be better to have one for currencies because these are separate entities. The new design would look like:
CREATE TABLE tx
(
id bigint(20) unsigned primary key,
old_limit_currency_id int not null references CURRENCY(id),
old_limit_value decimal(7,5) not null,
new_limit_currency_id int not null references CURRENCY(id),
new_limit_value decimal(7,5) not null
)
Also, check if decimal(7,5) has enough space for your scenarios, it looks a little low. There is an old saying: "Better safe than sorry" :)
OTHER TIPS
- If in future you do need to support transactions between two currencies, it ought to be possible to model this as two transactions with one of each currency.
- It looks like your Money objects are, semantically, values rather than entities. Thus I don't see the need to separate them out as entities.
What about a third idea:
CREATE TABLE tx
(
txId bigint(20) unsigned NOT NULL,
currency varchar(3) NOT NULL,
oldlimit decimal(7,5) default 0.00,
newlimit decimal(7,5) default 0.00,
----snipped----
PRIMARY KEY (txId)
)
All money values of one transaction need to be of the same currency, right?
If it's not an overkill, go one step further and store all amounts/values in a single currency and maintain an exchange rates table.