A Simple Task Management System which supports CRUD of tasks with RESTful API and a task expiry notification service.
Originally it is a take home assessment within the interview process of a company.
The tech lead leave that company a few days after I submit the assessment, and later on the new tech lead decided to hire junior developers...
so I don't know if this actually passed the interview or not LOL.
- Single user, assumed to be deployed in private network
- so there is no user authentication.
- Task volume is not too large (e.g. <10k)
- so the task scheduler is self-implemented, running along with the API server.
- Request volume is also not too large
- so that single process of API server is enough
- Development environment: MacOS, Deployment environment: Linux
I have chosen the following Python frameworks / libraries:
- Tornado
- It is chosen because it is used in that company.
- Alternative: FastAPI, which also supports async with additional functionalities like auto-generated Swagger docs
- encode/databases
- Provides async connection to relational DB with SQLAlchemy Core expression language
- Remarks: SQLAlchemy ORM is not supported.
- If there are more DB tables and more endpoints, consider using pydantic model to act as entity object and DTO.
- Python Sorted Containers
- For the task scheduler (see "Task Expiry Notification" sub-section)
- Basically follows RESTful design principle.
- 3 endpoints:
v1/health
withGET
method: basic health checkv1/tasks
withGET
,POST
,PUT
andDELETE
methodsv1/tasks/<task_id>
withGET
,PUT
andDELETE
methods
- Originally wanted to build a swagger doc (like this), however I don't have enough time to figure out how to do it for Tornado.
- Now a very brief API docs is written in the doc string of the class
task_man.handlers.v1.tasks.TasksHandler
andtask_man.handlers.v1.tasks.TaskByIdHandler
.
-
Functionality:
- notify user by console log if the task will be expired in 15 minutes. (or notify immediately after accepting expired task from API)
-
Module:
task_man.scheduling
-
Design on implementations:
- a periodic background task running in a separate thread along with the API server
- originally want it to be an long-living async function in the same thread as the API server, however cannot figure out how to run the function right after API server main IO loop started.
- instead of periodically checking DB for next to-be-expired task, the service caches task after accepting API requests of adding new tasks / updating or deleting existing tasks.
- The cache is implemented in
task_man.scheduling.TaskCache
. It is composed of 2 parts:- A dictionary with
id
as key and(title, expiry_dt)
as value, storing latest snapshots of to-be-expired tasks. - A
SortedSet
(from sortedcontainers) storing(expiry_dt, id)
, storing all snapshots of tasks with non-nulexpiry_dt
.- e.g. if
expiry_dt
of a task is updated once, there will be 2 records in the sorted set. - chosen
SortedSet
instead of a priority queue to avoid duplicated records.
- e.g. if
- A dictionary with
- The scheduler keeps checking if the next to-be-expired task (min entry in the sorted sort) will be expired in 15mins, and if yes then process the task.
- During processing an entry
(expiry_dt, id)
from the sorted set, it will check ifexpiry_dt
matches the latest snapshot from the dictionary.- if matches, notify user and remove the task from both the dict and the sorted set
- if not matches or not found, just discard the entry (the task is updated by the user)
- The cache is implemented in
- auto-load to-be-expired tasks from DB to task cache when app start.
- CAUTION: multi-process of API server is not supported. If enabled multi-process, each process will maintain a cache and will have problem on cache invalidation. Consider remote shared cache like RQ scheduler.
- a periodic background task running in a separate thread along with the API server
- create a new conda environment and activate it. (I am using Python 3.7)
- run
cd src
andpython setup.py develop
in terminal to installtask_man
in development mode.
I have chosen MySQL as the backed database.
- Please follow standard MySQL setup guide to install MySQL DB locally.
- run
mysql/create_db.sql
and thenmysql/task.sql
using tools like DBeaver to create database and table for development.
- Choose the conda env with
task_man
dev installed in `Settings/Project/Project Interpreter - Environment variables required to run the app:
MYSQL_HOST
,MYSQL_USER
andMYSQL_PASSWORD
(default values:localhost:3306
,root
,root
)- Click
Edit Configuration
in the upper right. - Choose
src/main.py
inScript path
. - Setup the environment variables.
- Click
- Run
src/main.py
with the configuration you have just created to start the app.
- git clone from the repo
- build wheels and install
task_man
package - setup environment variables for DB connection
- run with entry point:
src/main.py
- The functional test connects to the DB synchronously, which requires
mysqlclient
to be installed.- Setup guide of
mysqlclient
: https://pypi.org/project/mysqlclient/
- Setup guide of
- Also requires
requests
. - On Mac: can do steps 1 and 2 by running
chmod 700 test_req_setup_mac.sh
and thentest_req_setup_mac.sh
. - On Ubuntu: can do steps 1 and 2 by running
test_req_setup_ubuntu.sh
.
- Test coverage is not enough.
- It only provides basic positive tests. Need to add negative test cases e.g. test with invalid inputs.
- Interaction between API server and task expiry notification is not tested.