14/05/2017

[Java] Xchange me, sir - Sample REST project to get xrates to EUR

A while ago, while I was going through some interview rounds, a company that will be left unnamed, reluctantly assigned me a sample project to deliver as part of the interview process.
Very annoying though, was the fact that they had me book after some organisational pain, an hour of my time just to call and tell me in five minutes that it was highly unlikely I could deliver since I hadn't worked in Java recently.
Regardless of that quite bold and rude statement, I still asked to have the project just for practice anyway and they agreed, saying if I really wanted I could try and come back to them when I was done .
I of course did not plan to, but turning down some free exercise is never a smart choice, so I rolled with it.
Obviously they had never heard of Google as well, given that the whole project can easily be assembled just by searching code snippets and gluing them with minimal brainpower anyway if you are on a lazy mode day.
So long story short, here we are with the Xchange me, sir project.



Project description:

Implement a simple foreign exchange rate service that uses data provided by the European Central Bank. Provide a REST API to retrieve the exchange rate to EUR from any other currency provided by the ECB and allow querying current (https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml) and past (https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml) data. Use an asynchronous job to retrieve and update the xrates storing them locally in memory and have the REST API use the data in memory to return this information.

Limitations:

  • Use Java 8
  • Use Spring 4
  • Return the response in JSON format
They also provided an IntelliJ project that was essentially a Spring application with a sample REST hello world method already prepared and runnable.

Implementation description:

We need to provide two methods to retrieve the current and historical data which will be stored in two HashMaps in memory; we can do this by providing two URLs with different parameters. Current data would be retrieved with:

http://localhost:8080/api/getXrate/{currency}

and historical data would be retrieved with:

http://localhost:8080/api/getXrate/{currency}/{date}

Where currency is the 3 characters uppercase currency name (eg CHF) and date is in the format YYYY-MM-DD

When a method is called, we check if we have the data in memory, if yes, we try to retrieve the xrate and either return it or return null; if not, we return a "Retry later" message and start the async data loading process.

The loading process works in three steps:
  1. locally download the XML files from the ECB
  2. parse the XML files - which by the way have a bad structure in my opinion since they reuse the same node name multiple times with different meanings and store the values in attributes rather than the node-value format
  3. populate the HashMaps with the parsed data

Why those choices:

  • The sample files are really small so it could be possible to directly get the XMLs and populate the maps. However by storing them locally we track the actual data we were provided so we can produce it in case of complaints, we can run analytics on it and we can reload it anytime without needing to contact the ECB service again.
  • Given the structure of the files, mapping them to some intermediate entity turns out to be far more work in terms of complexity and code, than simply querying them to populate the maps.
  • We could use only a single map to track all data and query it by assuming we are requested today's data if we have no input date. However, the two methods will not be called with the same frequency so by using two separate objects we can easily scale them independently and reduce the impact of maintenance operations to a single part of the service.

What can be done to enhance the project:
  • Once the files are downloaded and processed, archive them somewhere on the filesystem or in a DB
  • Add a replay logic for the loading process
  • Add some sanitation checks on the request parameters
  • Refine the return logic to provide information that allows users to understand whether the "No data found" message means we do not and will never have the xrate for the particular input provided or simply that we are still loading the data in the system. For example by storing a list of currencies we support and checking that the input we receive matches that, this would also help with input sanitation
  • Start the data loading at system bootup instead of waiting for the first request to come in
  • Add purge logic to remove stale data (>90 days) from the system
  • Add update logic to periodically refresh the data in the system by starting the loading process. The customers can still invoke the APIs and would only have a delay if they request data from the map at the exact same time we are updating that bucket
  • Improve the insertion and retrieval mechanism to add logic which enables us to provide a list of currencies as response in case of a misspelled input for example
  • Modularize the components even more so that the loader service can for example reside on its own server and be invoked via JMS to update the distributed cache used to return the data
  • And so many other ideas, but the only hour of sun we have today really calls me out
Overall, this was an interesting exercise, not overly complicated and enjoyable enough but the best part is how long it takes to build this from scratch. Suffice to say that writing this post and uploading everything to Github took more time than implementing :)

No comments:

Post a Comment

With great power comes great responsibility.

Da grandi poteri derivano grandi responsabilità.