You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Alfred 46d32659ab Solution 6 months ago
app Solution 6 months ago
docker Solution 6 months ago
tests Solution 6 months ago
.gitignore Solution 6 months ago
README.md Solution 6 months ago
agent.py Solution 6 months ago
config.yml Solution 6 months ago
requirements.txt Solution 6 months ago
statement.py Solution 6 months ago

README.md

Code test ─ July 13, 2020


Hello there,

Welcome to [-]. In order to evaluate your skills and verify your fit within the company we will ask you to perform a simple test.

You need to develop a Python 3 based application. Please, try to keep everything simple, but always using the best practices. Don’t try to over complicate yourself, use whatever technology or framework you find most suitable. For example, you could use Flask and Sqlalchemy. You don’t need to work with a real database engine, using sqlite as the persistence layer would be enough for this test. Using project generators such as Cookiecutter may help you to setup your project faster from an existing template, but it is not required.

It is quite important for this test that you develop your solution applying the software engineering patterns that you know and perform a test driven development or at least to submit your code with tests. The test should take you between 3-4h, although there isn’t a time limit to complete it all. When you have finished the test, please let us know an estimate of how much time did you dedicate to complete it.

Submit a zip or tgz file with:

  • Documentation and notes about your design.
  • The full source code of your project.
  • Instructions of how to setup and run your test.

We will evaluate very positively any tests that are capable of running inside a docker container. If you have any Questions, please let us know.

Definition

The software you will write in this test will be used for banks. Banks have accounts. Accounts hold money. Transfers can be made between accounts. Banks store the history of transfers. There can be two types of transfers:

  • Intra-bank transfers, between accounts of the same bank. They don’t have commissions, they don’t have limits and they always succeed.
  • Inter-bank transfers, between accounts of different banks. They have 2,5€ commissions, they have a limit of 2000€ per transfer. And they have a 33% chance of failure.

Part 1

Define a set of data structures to accurately reflect:

  • Banks. A bank will hold a set of accounts.
  • Accounts. Holds money of a person.
  • Transfers. Represents a money movement between two accounts (intra-bank or inter-bank).

Make sure that new types of accounts and transfers can be added to the bank with minimal effort. Build up a REST back-end application that may perform the following actions:

1) Method to query movements of local account. Method: GET URI: /account_id/list This end-point will send a formatted json with all account movements.

2) Method to perform an intra-bank transfer. Method: PUT URI: /transfer BODY:

{
“source” : “source_account_id”,
“destination” : “destination_account_id”,
“amount” : Quantity (Decimal),
“info”: “Short string describing the purpose of the transfer”
}

This operation will move money from one to another account, please consider cases were account does not have funding, or any other kind of possible errors.

3) Method to add funds to the account. Method: PUT URI: /account_id/add BODY:

{
“amount” : Quantity (Decimal)
“src_bank”: “source bank identifier”, (Optional in case the money comes from another
bank)
“info”: “Short string describing the purpose of the transfer”
}

4) Method to remove funds from the account. Method: PUT URI: /account_id/retire BODY:

{
“amount”: Quantity (Decimal),
“dst_bank”: “destination bank identifier”, (Optional in case the money goes to another
bank)
“info”: “Short string describing the purpose of the transfer”
}

Part 2

Create an initial system consisting of two banks with at least two different accounts each. Consider each bank a separate invocation of your back-end application listening at a different port and using a separate database.

Now, let’s start the fun part of the test. Jim has an account on the first bank and Emma has an account on the second bank. Jim owns Emma 20000€. Emma is already a bit angry, because she did not get the money although Jim told her that he already sent it.

Help Jim send his money by developing a transfer agent. You have to build up a transfer agent client application that will consume your API, and perform transfers between those two banks. This agent assures that everybody gets their money. When the agent receives an order to transfer money from account A to account B, he issues transfers considering commissions, transfer limits and possibility of transfer failures.

Now that Emma has received the money, please help her to pay her rent of 2500€ by issuing a transfer to her landlord Steve who has an account at the first bank. She also wants to send a transfer of 3000€ to her sister Sara who has an account at the second bank.


Solution

Here it is presented my final solution to the [-] code test. It took me a bit more than expected. However, I’m pretty satisfied with the final solution.

You can easily run it from the docker directory. From there you would launch three bank containers with:

docker-compose up

From here you can execute the agent with:

docker-compose run app python agent.py

Also you can execute the part 2 statement check with:

docker-compose run app python statement.py

Design

The whole business logic is inside the app.banks.bank.Bank class. All the actions and options for money movements are managed there.

You can create an instance of a Bank using the init_bankfunction. For this you can send a dictionary parameter with the concrete case definition. If you do not do it, the config.yml will be read and use it to define the bank.

The model is as simplest as I could. It has only two classes: app.banks.models.Account and app.banks.models.Transfer. The Transfer class really represents money movements. You create a Transfer instance with its functions create_in_transfer or create_out_transfer. You can also check a Transfer rightness with its validate method.

If a transfer fails it has to be undone. For implementing this I used the command pattern for the required steps (to take and to send money). You perform an step with the do method of a Command instance and revert it with the undo method.

Gateways represent bank connections. A bank has a dictionary of registered bank gateways to interact with them. An ExternalGateway instance will make a network connection; a LocalGateway will link a bank with itself.

To check and transport data there are DTO structures inside the app.banks.dtos module. I decided to use the Pydantic library for make validations and conversions; that made really easy the whole data management.

The REST API is in the module app.api. It’s quite simple and there is nothing to remark. There is a function, manage_exceptions, for managing exceptions (oh surprise!). It unifies the exception conversion to a response object in all the end points, it also makes easy to test the exception dealing.

In the whole solution I tried to use an style as declarative as possible. It makes easy to follow the code execution and easy to fix bugs.

Time

I designed the solution on the first 30 minutes. At that point I knew how to manage external bank connections, had clear the data model, and how to do-undo transfers.

The first think I coded was the banks package. The first blueprint worked with lists, dictionaries, and Python data classes. I did a mess when decided to add the data base. Specially when loaded SqlAlchemy as I haven’t use it for a long time and did not remember its functioning. At the end, when I added Flask, I decided to not make it a bigger mess adding another library and keep things as they were at that point.

I finished the adventure of the data base adding a singleton pattern in the DataBase class. I Also crossed fingers wishing the examiner does not take into account how sloppy is to add that pattern.

About the API. At first I wanted to use FastAPI library instead of Flask and take the most from the asyncio Python library. It could give me some points on the test. And with all of this asynchronous programming, try to emulate the time that could take a whole transfer pipeline. Returning transfer identifiers to query transfer statuses later. At that moment I had pass those 4 expected hours, so decided to let it like this and use Flask.

The last part was the agent. Having a well functioning API, a solid structure, and a well tested solution I did not find difficulties to make it. However, as the code test had took me so much time, I decided to not add tests for it (sorry not-sorry).