Paidy Forex Proxy
This project is an implementation of the test task from Paidy - https://github.com/paidy/interview/blob/master/Forex.md
Forex is a simple application that acts as a local proxy for getting exchange rates. It's a service that can be consumed by other internal services to get the exchange rate between a set of currencies, so they don't have to care about the specifics of third-party providers. We provide you with an initial scaffold for the application
....
Create a
live
interpreter for theoneframe
service. This should consume the one-frame API.
Assumptions and limitations
- Rates are not invertible, i.e. AUD-SGD != 1 / SGD-AUD
- Limit for calling external service is 1000 requests per day
- It is possible to get an unlimited number of rates from external service via one request
- Rate returned by the proxy shouldn't be older than 5 minutes
- Proxy should be able to serve at least 10000 requests per day (one every ≈8 seconds)
- It is possible that external service could fail or time out
Implementation
Given these assumptions, I built an application that uses caching to reduce the number of calls to the external service. With the default configuration (can be changed) the application works like this:
OneFrame
service has a maximum allowed timeout of 30 seconds and in case of external service error it will retry to make the request 3 times with a delay of 10 seconds in between themLiveCachedRatesBoard
service requests rates for all possible, pre-defined pairs of currencies usingOneFrame
service every 2 minutes.OneFrameRatesLive
service will return the cached pair usingRatesBoard
service and will check if the rate is not older than 5 minutes, otherwise will return an error.
This setup allows us to have pretty fresh data for rates: ≈2 minutes old if underlying service works correctly and in normal conditions, it makes 720 requests per day to the external service. But it also takes into consideration possible failures of underlying service. If underlying service has failed but recovered within 2 minutes - clients still could use the Proxy without seeing error responses, but with a bit older data, which is still within the allowed range of 5 minutes.
So this setup allows the clients of the Proxy have fresh data while staying beyond the limit of requests to the external service and having spare time (≈2.5 minutes per 5 minutes timeframe) and requests (280 per day) to recover in case of error.
All the errors are wraped into the ErrorResponse(message: String)
model and returned as JSON body with suitable HTTP status code.
Build and run
There are multiple ways to run the Proxy.
docker-compose (easiest, Proxy+OneFrame)
The repository provides the Dockerfile
for the project, it relies on the fat-jar file, it also provides docker-compose.yml
file for running both the Proxy and the one-frame container.
In order to run the project using this method, do the following:
- Execute
sbt clean assembly
in the shell, this will output the fat-jar file at./target/scala-2.12/forex-assembly-1.0.1.jar
- Execute
docker-compose up --build
, this will build the image for the Proxy and pull the OneFrame image. After this, it will start two containers in the same network:paidy_one_frame
andpaidy_proxy_api
. API will be exposed on the port 9000.
Dockerfile (in case of external OneFrame)
If you have an external OneFrame service you can just build the image of the Proxy and run it. In order to run the project using this method, do the following:
- Execute
sbt clean assembly
in the shell, this will output the fat-jar file at./target/scala-2.12/forex-assembly-1.0.1.jar
- Execute
docker build -t paidy_proxy_api ./
. This will build an image calledpaidy_proxy_api
- Execute
docker run -e ONE_FRAME_HOST={host} -e ONE_FRAME_PORT={port} paidy_proxy_api
. Instead of{host}
and{port}
provide valid OneFrame service host and port
Java (in case of external OneFrame)
It is also possible to just run the fat jar directly. In order to run the project using this method, do the following:
- Execute
sbt clean assembly
in the shell, this will output the fat-jar file at./target/scala-2.12/forex-assembly-1.0.1.jar
- Execute
ONE_FRAME_HOST={host} ONE_FRAME_PORT={port} java -jar ./target/scala-2.12/forex-assembly-1.0.1.jar
. Instead of{host}
and{port}
provide valid OneFrame service host and port
Accessing API
Now you can query the rates on http://127.0.0.1:9000/rates
, for example:
curl 'http://127.0.0.1:9000/rates?from=USD&to=SGD'
Possible improvements
- More tests (current coverage is ≈84%), integration tests
- Better logging, metrics
- Maybe rework the
LiveCachedRatesBoard
, I don't have that much experience with the FS2 streams, maybe it's bad practice to run multiple streams concurrently, started in Main.