/accounts-balances

Elixir solution to Nubank's accounts and balances coding challenge

Primary LanguageElixir

Accounts

This is the code challenge for manipulating the following operations on bank accounts:

  • Deposits;
  • Withdrawals;
  • Balance.

Exposing those operations over HTTP.

The stack

The language I've chose is Elixir. It is functional, fun and the syntax is really pretty. I also find the pattern macth features on Elixir incredibly easy to read, making code easier to maintain.

Instead of using a full web framework to expose only two HTTP endpoints I've chose to "build" a simple microframework on top of Plug and Cowboy.

As a data storage I've chose Elixir's built-in Agents, that are wrappers around state. Agents are processes that can be supervised and are built on top of OTP to keep state. The great thing is that it can talk to other processes. It is also fast and good dealing with concurrency.

Setting up the Elixir environment

The most reliable way to setup the environment is following the official guidelines here.

If you're on a Mac, simply run:

brew update

and then:

brew install elixir

It is generally enough.

Restoring dependencies

After installing Elixir, to restore the project's dependencies run:

mix deps.get

Running tests

To run all the tests:

mix test

To run only the Accounts module tests:

mix test tests/accounts

To run only the HTTP router tests:

mix test tests/router

Running the application from the source code

To run the application from the source code, run the following command:

mix run --no-halt

This will start the app on localhost:8080.

Endpoints

There are two endpoints available:

GET /:user/balance

Returns a JSON with the user and the available amount:

{
  "user" : "<the user>",
  "amount" : "10.00"
}
PUT /:user/operation

That accepts a JSON body containing the following fields:

Field Description
amount the amount for the operation
operation The operation to be performed. One of "withdraw" or "deposit"

amount must be a valid numeric value.

Returns

400 - For invalid params

200 - For valid params.

Deploying

It is possible to run the application using Docker. I've generated an image and uploaded it to Docker Hub. I was asked to not put anything that can identify myself in the solution, so I have omitted the docker hub url. If needed I can provide it.

The Dockerfile is something like:

FROM elixir

WORKDIR /accounts

ADD . /accounts

RUN tar -xzf accounts.tar.gz

EXPOSE 8080

CMD ["bin/accounts", "foreground"]

The accounts.tar.gz is the product from the release generated by (Distillery)[https://github.com/bitwalker/distillery], responsible for generating an Erlang/OTP release from the Elixir's Mix project.

The release generation was also made using Docker, this way the binaries are generated on the same execution environment as it will run:

FROM elixir

WORKDIR /accounts

ADD accounts /accounts

RUN mix local.hex --force
RUN mix local.rebar --force
RUN mix deps.get
RUN mix release.init
RUN MIX_ENV=prod mix release --env=prod

CMD ["iex"]

Code and design decisions

I really liked the simplicity working with Plug and the ability to easily expose a few HTTP endpoint without requiring a full framework only to do that.

In the accounts_router.ex - the responsible for handling the HTTP part of the solution - I would like to improve the route that handles the PUT operations. I don't like the idea of having a nested case, one for params validating and the other for the actual operation processing. This is one of the first points for improvement I can highlight. There is also a small duplication that could be removed from the parse_params function.

Talking about the Accounts (accounts.ex) module I relied on Elixir's pattern match for function headers and also for parameters and returns destructuring features to avoid code branches. I've put comments on all the public functions because it is idiomatic in Elixir.

Business decision were made to highlight different paths that could be followed while implementing inexistent accounts. For Balances I have opted to return a value of 0, and for a Withdraw I have opted to return an :invalid_operation, as if that account didn't exist. Those are decision that should be talked to business people, I just wanted to show that.