GitHub - SinglishWords/singlish-words-backend: Singlish Words Application's Backend Code
This repository consists of the working files for the backend of the SinglishWords project, written primarily in Go. The backend serves an API that provides random cues, updates answers and respondents into the MySQL database.
To deploy the SinglishWords frontend on your local system, open your terminal and first clone the repository
$ git clone https://github.com/SinglishWords/singlish-words-backend.git
$ cd singlish-words-backend # to enter the directory
Running and testing the backend on your localhost requires the installation of MySQL and Redis. The easiest way to install these is using Homebrew.
If you do not have Homebrew installed, follow the instructions on https://brew.sh as a prerequisite for installing MySQL and Redis.
$ brew install mysql
$ brew install redis
This step is important if you are setting up and initialising MySQL on your local system for local testing.
If you have already initialised MySQL before, you can skip this step.
After installing MySQL using brew
, run the following command to initialise.
$ mysqld --initialize
Take note of the temporary password that is generated once this command is run. The username to access MySQL will be root
and the password will be the random generated password.
To run MySQL, run the following command
$ mysql -u root -p YOUR_PASSWORD
In order to use MySQL with the backend server, you can use the setup_db.sh
provided in the root directory. Run the following command:
$ ./setup_db.sh
This will ask for your MySQL password. After entering your password, the script will:
-
Update the
config.yaml
file to change the username and password that the server will use to communicate with MySQL -
Create a database on your local MySQL server called
singlishwords
-
Within the
singlishwords
database, it will create the following tablesclassDiagram class respondent { int id int age string gender string education string country_of_birth string country_of_residence string ethnicity string uuid string is_native text language_spoken datetime start_time datetime end_time } class question { string id string word int enable int count } class answer { int id string association1 string association2 string association3 int time_spend int question_id int respondent_id } class email { string email string want_lucky_draw string want_update text time_on_pages }
-
Within the
question
table, it will add 3000 test entries (cues). In order to change the cues added, you can modify the./sql/questions-test-data.sql
file.
First, start the Redis server in one shell window using
$ redis-server
Then, start the backend server by running
$ make run
The server will start on localhost:8080.
The API is accessible at singlishwords.nus.edu.sg/api/v1
API controller (./controller/apiv1
) calls services (./service
) which uses the DAO (./dao
) to read, update, or write to the MySQL database.
The backend serves the following API endpoints:
URL | Type | Parameters | Description |
---|---|---|---|
/ | GET | shows a summary of the three other GET endpoints | |
/questions | GET | limit | returns limit weighted random questions in JSON format |
/answers | GET | returns all answers in JSON | |
/answer | POST | answer's attributes | updates the database with the completed responses only |
/respondents | GET | returns all respondents in JSON | |
/respondent | POST | respondent's attributes | updates the database with the respondent details only |
/answers | POST | respondent's and answer's attributes | updates the database with the answers + the respondent details |
POST | email's attributes | updates the database with the email |
flowchart LR
subgraph router
/api/v1/questions
/api/v1/answers
/api/v1/answer
/api/v1/respondents
/api/v1/respondent
extra3[...]
end
subgraph service
GetAllQuestions
GetAnswers
GetAllRespondent
PostAnswer
SaveRespondent
AddRespondentAndAnswersTogether
extra2[...]
end
subgraph questionDAO
quesGetAll[GetAll]
GetWeightedQuestions
end
subgraph answerDAO
ansGetAll[GetAll]
ansSave[Save]
end
subgraph respondentDAO
respGetAll[GetAll]
respSave[Save]
AddRespondentWithAnswers
end
/api/v1/questions-->|GET|GetAllQuestions
/api/v1/answers-->|GET|GetAnswers
/api/v1/answer-->|POST|PostAnswer
/api/v1/respondents-->|GET|GetAllRespondent
/api/v1/respondent-->|POST|SaveRespondent
/api/v1/answers-->|POST|AddRespondentAndAnswersTogether
GetAllQuestions-->quesGetAll
GetAllQuestions-->GetWeightedQuestions
GetAnswers-->ansGetAll
PostAnswer-->ansSave
SaveRespondent-->respSave
GetAllRespondent-->respGetAll
AddRespondentAndAnswersTogether-->AddRespondentWithAnswers
The respondent
, question
and answer
services have GetAll()
methods, that retrieve all entries of their respective tables in the database.
- For example, the service
respondent
has aGetAllRespondents()
function - [1] calls
respondentDAO.GetAll()
- [2] runs the SQL query
SELECT * FROM respondent;
and themodel.Respondent
object automatically converts this into a collection ofmodel.Respondent
object. - The collection
[]model.Respondent
is converted to JSON - The JSON is returned with the code 200 (OK)
In order to ensure that there is an equal distribution of responses for each cue, the selection query must be more biased towards cues with fewer responses.
In questionDAO
, the GetWeightedQuestions(limit int)
method retrieves limit
weighted random questions using weighted reservoir sampling:
by taking the top limit
entries in the table sorted by the
This is equivalent to using the SQL query SELECT * FROM question ORDER BY -LOG(RAND()) / count DESC LIMIT ?;
Redis functions have been written in the various services, in order to cache questions and allow faster retrieval. However, they are not currently being used as we wish to update the count every time a question is shown to the user.
The potential risk with not using Redis is that the database will return a 408 Request Timeout. However, this will only occur if there are very large number of requests being made to the database at the same time (10,000 - 20,000 requests in a span of minutes), which is unlikely for this project.
Answers and respondents can be updated in the database either individually or concurrently.
The answer
and respondent
services each have a method PostAnswer()
and AddRespondent()
which, when run, converts the POST parameters into a data model object and passes it to the respective DAO’s Save()
method.
- For example,
AddRespondent()
converts the POST params to amodel.Respondent
- This object is passed to the
respondentDAO.save(*mode.Respondent)
method - This method uses the SQL update
INSERT INTO respondent (...) VALUES (...)
- Returns HTTP status 201 (Created)
For example, respondentDAO.save(*model.Respondent)
The respondent
service has an extra method AddRespondentAndAnswersTogether()
.
The POST params are converted into two separate model objects (model.Respondent
, model.Answer
) and passed to the above method.
The database update is treated as a transaction. The transaction is created using tx = db.Beginx()
If both SQL commands to update the transaction are successful, the transaction is committed using tx.Commit()
At this point, the changes can be committed and pushed to the master
branch of this repo.
Remember to not commit private data such as your MySQL credentials in the config.yaml
file. As a precaution, this file has been added to the .gitignore
.
The next steps include:
- Making changes to the fronted, if any (refer to this link)
- Deploying the whole application in a Docker container (refer to this link)