Motivation
Every company that I have worked at represents and handles Money in different ways. I thought that there should be a simple library that allows for anti-bikeshedding when using Money within an organisation. Whilst the notion of storing an amount of money is simple, there are different implementations of it which can lead to some good and bad consequences.
For example, one of the problems I have seen is storing the amount as a decimal number as there can be rounding issues on either side of the intended number and especially when serialising and deserialising the amount after transporting it to another service. For instance, storing £5.21
as the decimal 5.21
means that when dividing the money object by 3 (let’s say to split the bill as an example use case), the outcome will be the decimal number 1.736666666667
. This leads to having to round the number and then handle that remainder.
Technical Implementation
I implemented the library in Kotlin, with no external dependencies and using a single class to represent the Money object which contains an amount stored as a Long
(large number) and a Currency. This is the simplest way of representing money that I have seen, it is nothing more than a given amount of a currency.
Any arithmetic operation on the class requires checking that the currency is the same in both Money objects, but the library also allows for adding and subtracting whole numbers as minor units. Division is rounded to the nearest whole number, in our example before of splitting £5.21
we would get 521 ÷ 3 = 174 (and therefore £1.74)
.
There are additional utility functions to support a variety of use cases for working with money, these include isPositive
, isNegative
, percentage
, Money.min
and Money.max
. Money objects are also sortable, but as they cannot compare currencies by value they only sort by the numeric amount.
Internationalisation
Constructing a Money object can be done through major or minor denominations, in the case of Sterling, the major unit is pounds and minor unit is pence. So calling Money.fromMajor(50, ...)
would represent £50.00
and calling Money.fromMinor(50, ...)
would represent £0.50
.
Although most currencies in the world work with major and minor units, some currencies do not have minor units, for example the Japanese Yen. The library handles this by using the Currency.getDefaultFractionDigits function to scale the input given through the Money.fromMajor
constructor. This means that in the case of Japanese Yen, both Money.fromMajor
and Money.fromMinor
will represent the same amount given to both.
The toString
method has two implementations, it can take an optional Locale
argument but defaults to the system default, which prints the number into the currency style of the specified locale. This allows extra internationalisation as some currencies are used in multiple countries where the format of writing a currency changes. For example, printing the money €7.50
in Ireland would be €7.50 EUR
but in France would be 7,50 € EUR
.
Modularisation
The library is split into multiple modules to allow only the required libraries being brought in, there is money-core which contains only the core Money class. Then there are money-gson, money-jackson, and money-kotlinx-serialization for supporting the different JSON libraries. Importing a specific JSON serialisation library module will also bring in the core module. Each of the modules is available on the Maven Central repository.
Usage
Import the library via:
// Gradle
implementation("com.abroadbent:money-core:0.3.0")
<!-- Maven -->
<dependency>
<groupId>com.abroadbent</groupId>
<artifactId>money-core</artifactId>
<version>0.3.0</version>
</dependency>
Once imported, you can access the Money
class which has all logic in. You can use the class in model classes via:
import com.abroadbent.money.Money
data class Account(
val name: String,
val opened: String,
val balance: Money
)
This can be instantiated through any of the JSON libraries (Gson, Jackson, KotlinX Serialization) or by using the static constructor method:
val account = Account(
name = "Test User",
opened = "2021-01-17",
balance = Money.fromMajor(10_000, Currency.getInstance("GBP"))
)
Performing operations on money objects is a simple and immutable operation, you can perform any of the operations previously mentioned, using the Javadoc in the library or the readme of the project. For example, adding two objects together:
val twoPounds = Money.fromMajor(2, Currency.getInstance("GBP"))
val fivePounds = Money.fromMajor(5, Currency.getInstance("GBP"))
val sevenPounds = twoPounds + fivePounds
Publish process
Deploying to the Maven Central repository means following the guide on Sonatype and Gradle. Because I built the library using Kotlin Gradle Script, I could apply the configuration to each module through subprojects
in build.gradle.kts
.
All the configuration was in the root gradle build file for the whole project, where the modules contain a build file which only supplies the required dependencies. This deploys each of the modules within the repository, taking the group and version from the main gradle build file, and uses the module name as the artifact ID.
In order to deploy an artifact to Maven Central, the sources and Javadoc must also be deployed in order to pass validation, this required adding Dokka for producing the Javadoc JAR.
Conclusion
While there are larger scale projects which solve the same problem, this library was a good proving ground for writing a simple library which solves a single and defined issue.