-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathQuiz App Explanation.txt
302 lines (248 loc) · 15.6 KB
/
Quiz App Explanation.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
Quiz App
---------
Goals:
User accessing the appplication will get a set of 10 MCQ questions which they would need
to answer. They will be scored based on their answers to the quiz.
This is completely Backend Web Application.
Hence, to verify the APIs, we'll be using Postman.
Tech Stacks Used:
1. Spring Boot
2. Java 11
3. PostgreSQL
Dependencies Used:
1. Spring Web : Used to create the Web Application
2. PostgreSQL Driver : It helps access data in PostgreSQL DB.
3. Spring Data JPA: It allows us to connect to the Spring app and the DB.
4. Lombok: For reducing Boilerplate code.
Procedure:
This Quiz web application is a Restful WebService.
The questions being asked to the user, is stored in the PostgreSQL DB.
The application has three layers, each with their own functions:
1. Controller Layer:
- It is responsible for interacting with the users and accept their requests.
- Sends the requests to be processed in the service layer.
- Returns the output from the service layer to the User.
2. Service Layer:
- All the business logic required by the application stays here.
- Processing is done here.
- Controller Layer passes on the requests from the user to this layer, for processing
3. DAO Layer:
- This layer is responsible for interacting with the DB (perform CRUD operations)
- DAO layer is used to decouple the Service layer from the DB.
To implement the three layers mentioned above, there are three packages:
com.example.quizwebapp.controller
com.example.quizwebapp.service
com.example.quizwebapp.dao
which contains all of the Controller, Service and DAO classes respectively.
The annotations used:
1. @RestController
- It tells the Spring framework that this class will be a Controller in the Spring MVC.
- Since we are building a RESTful Web Application capable of serving REST APIs (GET, POST, PUT etc),
all the controllers in this application will be annotated with @RestController.
- This annotation also combines @controller and @ResponseBody annotatios, so we don't need to use them seperately.
2. @Service
- It tells the Spring framework that this class will belong in the Service layer.
- Signifies that the class is holding business logic.
3. @Autowired
- This annotation allows for automatic dependency injection
- It automatically scans the components present in the current package and the sub packages.
- Components are usually marked with @Component, @Service, @Repository or @Controller annotations.
4. @Entity
- It is used to show that a class can be mapped to a table.
- In JPA, @Entity is used to show a table that's stored in a DB.
5. @Table
- Used to mention which table will be mapped to the Entity/Model class.
6. @Id
- Denotes which variable will serve as the Primary Key of the Entity class.
- In case a table is not already present, the variables of the Entity class will create columns.
- Column marked with @Id annotation will serve as the primary key of the table.
7. @Data
- This is present in the Lombok dependency.
- It is used to add Getter and Setter methods for all the fields mentioned in a class.
8. @Repository
- It tells the Spring framework that this class/interface will belong in the DAO layer.
- It signifies this class contains code for Database used for storage, retrieval,
update, delete and search operations.
9. @GetMapping()
- Used to handle GET HTTP requests
- This annotation tells the method of the controller to fetch something from the database.
10. @GetMapping("category/{category}")
- Here, {category} is the templated part of the URL. User can input a value to this part.
- This templated part is then mapped to the method's variable using
@PathVariable annotation.
10. @PathVariable
- This annotation maps the templated part of a URL to a variabe of the method.
- In the below example, {category} gets mapped to String category:
@GetMapping("category/{category}")
public List<Question> getQuestionByCategory( @PathVariable("category") String category) {}
- Since the templated part and the variable name is same, we can use
(@PathVariable String category) instead of (@PathVariable("category") String category)
11. @PostMapping
- Used to handle POST HTTP requests.
- This annotation tells the method of the controller to accept JSON format data from user.
11. @RequestBody
- When a user sends a request for adding something in the DB, it is accepted as a POST request.
- The request stores the data sent by the user in it's body, in JSON format.
- This annotation tells the Spring framework to extract the data stored in the body and convert it into class object.
- This conversion is known as deserialization.
12. @RequestParam
- It allows us to extract parameters and files from the request URL.
- We can accept URL variables using this annotation.
13. @ManyToMany
- This annotation is used to symbolise the many-to-many relationship mapping.
- Many-to-many relationships define entities for which both side of the relationship can have multiple references to each other
14. @Query
- It is used to define a Query that we can execute
15. @GeneratedValue()
- It is used to generate the value of a column in an model/entity class.
- Usually it is paired with the Primary Key and used to generate a value for the primary key.
Now coming to the DB, for each table we use in the application, we create one class.
This class is called an Entity or a Model and contains the data of the application.
The fields (variables) used in the Entity/Model class, represents the columns present in the table.
SQL uses "snake_casing" while Java uses "camelCasing".
For a column in the table named "difficult_level", we can use the variable name "difficultyLevel" in our model class.
This is becasue we are using JPA, whose object-relational mapping (ORM) framework makes sure this happens.
Make sure the PostgreSQL Table name uses the following naming conventions: https://www.geeksforgeeks.org/postgresql-naming-conventions/
For example, if the table name is "table_name", we can use @Table(name = "TableName")
We are adding a method in the QuestionController class to fetch all the questions of a particular category only.
For this, the method accepts the category and sends it to the QuestionService class which in turn calls QuestionDAO class.
Now we have created a custom method in the QuestionDAO class named: findByCategory(String category).
In our DB we have a column named "category" and this is why we named the method findByCategory().
DataJPA can understand that we want to fetch the values based on the the category column.
Next, we are taking data from the user to add in our table.
Data from the user has to be in the JSON format.
By using @RequestBody annotation, we can convert the JSON format data into a class object and then store it in our DB.
QuestionDAO interface implements JPARepository interface, due to which we are able to access the save() method.
This method is used to save the class objects.
In the service layer, we will invoke the save() method which will save the user's question to the DB.
We are going to add HTTP Response Status Codes and handle exceptions.
For that we would need to return the Data as well as the Status Codes and till now we were returning only Data.
This is where "ResponseEntity" saves the day. We are using ResponseEntity to configure the HTTP Response.
We can add ResponseEntity in both the Controller and the Service Layer.
Since we are trying to return a Status Code as well as handle exceptions, we will use it in Service Layer.
Before ResponseEntity was used and exceptions were handled getAllQuestions() looked like this:
----------------------------------------------------------------------------------------------
Service Layer:
--------------
public List<Question> getAllQuestions()
{
return qDAO.findAll();
}
Controller Layer
----------------
@GetMapping("allQuestions")
public List<Question> getAllQuestions()
{
return qService.getAllQuestions();
}
After Adding ResponseEntity and handling exceptions, the functions look like this:
----------------------------------------------------------------------------------
Service Layer:
--------------
public ResponseEntity<List<Question>> getAllQuestions()
{
try
{
// If the request sent by user is correct, we will return Status Code OK.
return new ResponseEntity<>(qDAO.findAll(), HttpStatus.OK) ;
}
catch(Exception e)
{
// printStackTrace() can pin-point exactly which line is causing the error to appear.
e.printStackTrace();
}
// If the request sent by user is incorrect, we will return Status Code Bad Request.
return new ResponseEntity<>(qDAO.findAll(), HttpStatus.BAD_REQUEST) ;
}
Controller Layer:
-----------------
@GetMapping("allQuestions")
public ResponseEntity<List<Question>> getAllQuestions()
{
return qService.getAllQuestions();
}
The Controller Layer's getAllQuestions() now should return an object of ResponseEntity<List<Question>> i.e., the Data and the Status Code.
Hence, we changed it's type from "List<Question>" to "ResponseEntity<List<Question>>". Takes the input from the user and send it to the
Service Layer's getAllQuestions(), which sends Data and the Status Code back.
In the service layer, first the request is checked if it's a valid one or not, returning 200 OK and 400 Bad Request respectively.
Next we check if there's any error/exception which is handled by the try-catch block.
*************************************************************************************************************************************************************
Next we will move to the Quiz, starting with Quiz Controller Layer.
We will give users the ability to create a quiz from the questions present in the Questions Table: question_copy_table.
Users can choose a topic and create multiple quizzes each of which can contain different number of questions.
The questions used in the quizzes, will be taken from the Questions Table and they might be repeated i.e.,
Let's say there are two quizzes titled: Quiz1, Quiz2 and Quiz3, with 10 questions each.
Few of the questions used in Quiz1, Quiz2 and Quiz3 might be same (Repeated questions).
Quizzes created, will be stored/persisted in the DB, for future use.
For persisting, we'll create a Quiz Table and map it to the Question Table.
We can have 3 types of relationship mapping:
1. OneToOne - One Quiz will have only one Question
2. OneToMany - One Quiz can have multiple Questions, but can't have the same Question(s) in different Quiz i.e, Questions can't be repeated
3. ManyToMany - Multiple Quizzes can have multiple Questions each. Different Quizzes can have the same Question(s).
From this, it is clear that we want a ManyToMany mapping. This will create an extra table.
The Quiz model class will contain three variables:
1. Id: It will serve as the primary key. Will be auto incremented.
2. Title: The title of the Quiz [Quiz1, Quiz2.. as told in the exapmple above. Could be anything else]
3. List of Questions: It will have the list of questions for the quiz. This will have ManyToMany mapping.
Due to the ManytoMany relationship mapping, the application will create 2 tables upon running for the first time:
1. quiz:
- This contains the list of quizzes that have been created. (Quiz1, Quiz2, etc)
- Has two columns: id (auto incremented Primary key) and title (the title of the quiz)
2. quiz_questions:
- This table maps which questions are present in which quiz.
- It will store the quiz_id (id from quiz Table) and the question_id (id of the questions from question_copy_table)
- This table doesn't have any primary key. Instead it maps the primary key of quiz Table with the primary key of question_copy_table.
The URL syntax for creating a quiz is:
localhost:8080/quiz/create?category=<category>&numQ=<numQ>&title=<title>
Example of an acceptable URL for quiz creation:
localhost:8080/quiz/create?category=Java&numQ=3&title=FirstQuiz
Users would need to provide 3 details before creating the quiz:
1. category: Category of the questions used in the Quiz.
2. <numQ>: Number of questions the user wants in their quiz.
3. title: Name (Title) of the quiz.
These details can be sent to the controller via the address bar or via JSON format. Currently we are doing it via the address bar.
The details get forwarded to the Quiz Service Layer where the a new Quiz is being created.
This is done by createQuiz() and the below mentioned process is followed:
1. Since we have the category and the number of questions in the quiz, we call the QuestionDAO layer for acessing the question_copy_table.
From the table, we take the questions in a randomised order. Native SQL query is written for this.
2. Next we create a new instance of the Quiz model class. This creates a new quiz.
3. After this, we set a title and the list of questions we just polled from QuestionDAO layer to the newly created quiz.
4. Finally, we save this new quiz instance which will make it persist for future use.
*************************************************************************************************************************************************************
Now that a quiz has been created and saved in the DB, our next step is to serve the questions of the quiz to the user.
For this we create a method in the Quiz Controller that accepts the Quiz ID.
It returns the List of Questions present in the mentioned Quiz ID.
The questions present in the Quiz will contain all of the below mentioned attributes:
1. Question ID
2. Question Title
3. Difficulty Level
4. Options (Option 1 - Option 4)
5. Right Answer
Since this is a Quiz which we are sending tot the user, we don't need to send all of the above mentioned attributes.
We'll only send the Question ID, Question Title and the Options.
For this, we create another model / entity class, QuestionWrapper. It will have only the above mentioned parameters i.e.,
Question ID, Question Title and Options (Option 1 - 4)
We fetch the quiz instance associated with the mentioned Quiz Id.
Then extract the List of questions present inside the quiz instance. They are objects of Questions.java (Entity/Model class).
This is followed by converting each of the object to an object of QuestionWrapper.java (Entity/Model class).
After that we send the converted value to the user.
Users have to submit their response as a list of answers in JSON format. We use this to calculate the score.
The syntax for the response is:
[
{ "id": "2",
"answer" : "Sample Answer 1"
},
{ "id": "3",
"answer" : "Sample Answer 2"
}
]
The ordering needs to be the same as the ordering of the questions. For example if question with id 2 was at the top of the list,
the response list should have that question's answer first.
For this, we have created another model class: Response.java, in the model package. This is not linked to ResponseEntity in any way.
It has two attributes: Integer id and String answer.
The response sent by the user is a List of Response objects.
In the service class we find the questions in the quiz and get the correct answers associated with the questions and compare them
with the answers of the response objects.
Since both Response and Questions are Lists, and the order in which the response was given is the same as the questions sent, we can
simply iterate through the lists, comparing the answer we got with the right answer for the questions.
Then we send back how many questions the user got correct as the score.