[CODING CHALLENGE FROM MARCH22]
Simple Flask app created for XXXX Coding Challenge.
It runs in a docker container exposed on localhost:5000 and implements the 4 required endpoints:
- Create an account
- Deposit money on a specific account
- Withdraw money from a specific account
- Get balance of a specific account
This was my first time writing a backend in python (my experience with python is mostly script-based). Luckily I found the Flask official documentation well written so the proccess was quite enjoyable. My implementation here is based pretty much straight from the Quickstart and Tutorial sections, though I wrote the actual code making sure to only include what I found neccesary.
To get the app up and running,
docker-compose -f dc-canon.yaml -f dc-dev.yaml upThis will start 2 containers. One running a postgres database , and a second container running the flask application via the Werkzeug development server.
A simple landing page can be checked to verify the api is reachable and functional.
http://localhost:5000/
Two types of tests are implemeneted. Unit tests (app/tests/test_unit_business.py) as well as functional test (app/tests/tests_api.py). Given that the functional tests make connections to the database, it is neccesary to start up a postgres container for these.
docker-compose -f dc-canon.yaml -f dc-test.yaml upThis command will handle the neccesary container setup, init a temporary postgres database, run existing tests, and print coverage to the console.
Note: after the tests have run the database container will still be running. You will need to run docker-compose down to clean up. This is not ideal and should be refactored.
Seperation of concerns here is simple: a controller layer, and a business logic layer, a database connection layer. In the case of this app these are represented by modules residing in a single file.
- Controller -
accounts.pywhich binds into the main app as aBlueprint - Business -
business.pywhich is responsible for the business logic regarding accounts - Database -
db_connect.pywhich handles requests to the database
Connection to the postgres DB is, for simplicities sake, implemented with psycopg2 directly, rather than using a ORM like SQLAlchemy. This would possibly change in further development.
The database contains a single table accounts. Fields are id, account_number, balance_cents. A minimal setup for the requested functionality. Balance is in cents to avoid floating points.
The image used for the Flask application is built on python:3.6-slim and subsequently installs python dependencies for this project via requirements.txt. One additional dependency is installed: netcat to handle scanning for the postgres service before starting mondux.
Since development was exclusively in containers, for flexibility I implemented an extends pattern allowing for different setups when running the containers.
For example, a volume is mounted in the dc-dev config but not in the dc-test config, meaning for tests the database is always in a virgin state and can be seeded accordingly.
Note: While writing tests I had the api command set to tail -f /dev/null so that I could docker exec into the container and manually run tests as needed.
Init of the database is handled by db/init-user-db which postgres runs when the database is first inited. This script creates at database for the app, as well as an app user. The variables for these are set in api_database.env which are passed into the container via docker-compose.
Further steps here would be to create db-prod.yaml. For a local deployment this could spin up an additional nginx container pointing to the web service. The web service would run from a custom image (without the test libraries for example) and run the flask app behind a WSGI server like gunicorn.
Responses are all in JSON format.
Create: creates a new account
/api/accounts/create
response: {
"account_number": "{account_number}"
}
Deposit: deposits transfer sum to the specified account and returns new balance
/api/accounts/{account_number}/deposit?sum={transfer_sum}
response: {
"balance": 0
}
account_number required
transfer_sum required
Withdraw: withdraws transfer sum from the specified account and returns new balance
/api/accounts/{account_number}/withdraw?sum={transfer_sum}
response: {
"balance": 10
}
account_number required
transfer_sum required
Get Balance: returns balance for the specified account
/api/accounts/{account_number}/balance
response: {
"balance": 10
}
account_number required
The API will explicitly return errors regarding non-existent accounts as 404, and transfer sums that cannot be parsed to an integer as 400. The response will return the corresponding error code, as well as a JSON with an error message.
response: {
"error": "{error message}"
}
If no account number is provided the API will return a 404 without additional error description.
- Testing the database could be more thorough.
- ORM implementation is probably advised.
- Seperation of concerns: Queries are written directly in the
db_connectclass. In subsequent developements this would be refactored.