Backend System Design - cheehongw/functional_expressionism GitHub Wiki
Backend application design
On the backend side, the NodeJS server exposes a set of API endpoints, using ExpressJS to handle routing. Internally, the application follows a layered design, separating the HTTP layer from the service layer and the mongoDB clusters.
HTTP Layer
Within the HTTP layer contains routers and controllers, each with their own purpose.
Type | What it does |
---|---|
Routers | Defines the API endpoints/routes an external caller can use to query the application, and routes each HTTP request to the appropriate controller. |
Controllers | Receives the HTTP request object from the Routers, pull out request parameters and passes them as relevant calls to services. |
Due to the lack of experience and time in meeting milestone 2, we have merged the controllers with the services in the early stage of our implementation. After milestone 3, we have separated the concerns between new controllers and services.
Business Logic Layer
The business logic layer is ideally free from any HTTP/ express logic, and are functions/files that exist outside the HTTP context of our application. The business logic layer contains the logic of fetching and modeling data, and manipulating the data if need be. This now-useful data will then be returned to the controller, for the controller to send out as a HTTP response.
Type | What it does |
---|---|
Services | Performs the task of fetching data from the database and manipulating the fetched data to a more useful form |
Models | Defines the data structures and objects used in our application. This determines the logical structure of our database and how data is related across collections. |
Services
In the early stages of our development, it seemed unnecessary to us to abstract out the service aspect from the controllers. However, the usefulness of separating the services and controllers became apparent when it came to testing:
just run
node services/getLikeStatus.js
!
For example, unit tests can be performed easily on a service function as seen above, since we can just run the js file of the function itself and console.log()
the output. If we had combined the controllers and services, we would need to run the whole application and send mock HTTP requests to localhost (as we did previously 😔) to console.log()
the output, which is time consuming and tedious.
Moreover, by separating concerns, we can pinpoint where the errors occurred without having to second-guess whether it was a HTTP problem or a mongoose problem. Being able to perform unit tests easily made developing with mongoose much less stressful and improved morale considerably.
Models
Model Diagram
As it stands, our application contains 5 data models, which encapsulate the 5 types of data that we have: Location
, Stall
, Dish
, Likes
and Ratings
, and how they relate to each other, as seen in the diagram above. For example, there is a one-to-many relationship from locations to stalls and from stalls to dishes. In a non-relational database, this relationship is modeled by having each dish reference one stall, and each stall referencing one location.
The diagram also gives an idea of what fields are available in each model.
Initially, we intended to have a user
model that contains a likes
and ratedItems
field to model the relation between liked/rated items and a user. However, deep nesting is difficult to work with using mongoose/mongoDB, and might not be the right mindset to have when using a non-relational database. As such, we have a Likes
and Ratings
model with relevant fields to represent the concept of a "Like" and "Rating", even though such things might be unnecessary in a relational database.