The basic idea behind the implementation is to cache the exchange rates pulled from 1Forge API in memory. These rates are kept for a given amount of time and new exchange ratest are pulled only after it passes. This mechanism allows for keeping the total amount of requests not going over the daily quota (implemented here). At the same time, it also improves the response latency of our service.
The downside is that the exchange rates can (and probably will) diverge from the cached values while they are being kept in memory. As such, this is not ideal for use cases where high temporal precision is required. The proper way how to solve such a problem would obviously be to upgrade the 1Forge plan.
Alternatively, a considerably less polite way how to circumvent the limits is to create multiple indenpendent free-tier accounts and rotate their API keys (e.g. round robin) when sending the requests to 1Forge and thus "increase" the quotas. Though, this will probably be in conflict with T&C of 1Forge so it's not recommended as it might lead to a ban, it is an option, though...
The live interpreter uses sttp as the http client, which provides an integration with monix tasks (used by the skeleton implementation).
I've also introduced enumeratum which provides a convenient way to achieve Enum-like functionality over case object
s. This is useful in the context of Currency
s as these are used as a whitelist of supported currencies. Adding a new Currency case object will automatically translate to obtaining its exchange rates when populating the cache.
Finally, I've also extended the Error
] types (here and here) to account for more specific error cases and failures. These also translate into more specific HTTP error codes
The service can be started with the default configuration reference.conf (with 2-minute cache refresh interval) by:
sbt run
To change the TTL before repopulating the cache modify the oneforge.max-age
property to the desired FiniteDuration
, e.g.
app {
...
one-forge {
max-age = 2 minutes // free tier limits to 1000 a day, which translates to up to one request per 1.44 minutes
...
}
...
}
The test suite can be run by a standard
sbt test
This executes tests for:
- 1forge client (OneForgeClientImplTest)
- JSON schema validation for 1forge API responses (QuotaTest and QuoteTest)
- 1forge service classes (OneForgeServiceTest and CachedOneForgeServiceTest)
- the API routes (RoutesTest)
The unit tests are implemented using Scalatest with property-based tests using Scalacheck (as defined in ModelFactory).