Chapter 4: Backend Design Document - Beta
Data Modeling
User
Purpose: To provide an identity for user account and information
Principle: Stores credential and username
States:
registered: set User
username, password: registered -> one String
registered: set User
username, password: registered -> one String
Profile [User]
Purpose: To store the user’s personal information
Principle: Each user has their initial profile when they registered. The profile can be updated. And will be deleted after user delete their account.
State:
nickname: one String
email: one String
dateJoined: one int (timestamp)
headshotUrl: one String
identity: one String
role: one int
region: set Float
liked: set Item (Post/Reply)
favorite: set Item (Post/Reply)
nickname: one String
email: one String
dateJoined: one int (timestamp)
headshotUrl: one String
identity: one String
role: one int
region: set Float
liked: set Item (Post/Reply)
favorite: set Item (Post/Reply)
Session [User]
Purpose: Manage and maintain the state of a user in the system over t period.
Principle: A session begins when a user login. Session maintains state s for time t the session will expire after time t. after a session starts (and before it ends), the getUser action returns the user identified at the start: start (u, s); getUser (s, u.)
State:
active: set Session
user: active -> one User
active: set Session
user: active -> one User
Relationship [User]
Purpose: To define and manage the connection states between users
Principle: The relationship has directional states. It will take a set of User object and record their relationship states.
State:
Rel : Relationship -> set String // define the relationship as a set
u1, u2: Rel -> set User // assign relationship between u1 and u2, the u1 is the origin, u2 is the target. u1 -> u2 forms a directional relationship
relationship: Rel -> set String // the type of relationship between two users can be follower, friend to partner, to activate different functions.
Rel : Relationship -> set String // define the relationship as a set
u1, u2: Rel -> set User // assign relationship between u1 and u2, the u1 is the origin, u2 is the target. u1 -> u2 forms a directional relationship
relationship: Rel -> set String // the type of relationship between two users can be follower, friend to partner, to activate different functions.
Map
Purpose: To provide a visual representation of geographic data and allow users to interact with it. It also serves as a base to display location based informations, such as markers.
Principle: On mobile platforms, users can use touch gestures to interact with maps. On desktop, they can use mouse. Map have multiple layers and can change themes (provided by api). Map can display markers on its geographical locations.
State:
centerPoint: set Float ((set [lng, lat]))
zoomLevel: Float
layers: set String (Collections of data visualized on the map (e.g., flower field))
markers: Symbols or icons indicating points of interest or events.
theme: Visual style (e.g., satellite, terrain, nighttime).
centerPoint: set Float ((set [lng, lat]))
zoomLevel: Float
layers: set String (Collections of data visualized on the map (e.g., flower field))
markers: Symbols or icons indicating points of interest or events.
theme: Visual style (e.g., satellite, terrain, nighttime).
Marker [User]
Purpose: To display specific points of interest, events, or user locations on the map.
Principle: The Marker can be interacted and expanded into a larger information card. The Marker has different types, now including User, and POI points of interest. If the density of the marker becomes too large, they can be clustered into one marker, which will show a list by activation.
State:
location: set Float
type: one String (User/POI)
cluster: set User
location: set Float
type: one String (User/POI)
cluster: set User
Favorite[User, Item]
Purpose: Save articles and answers for later review. It will also boost the mass voting system.
Principle: The user can click favorite from a list, and jump to the corresponding article. They can also delete their favorite item.
State:
items: set String
user: set String
favoriteCount: int
favoriteList: set String
items: set String
user: set String
favoriteCount: int
favoriteList: set String
Like[User, Item]
Purpose: A mass-voting mechanism to identify good quality contents from the community
Principle: User can upvote by clicking a button next to a post/answer. Good quality contents will raise to the top of the answer list
State:
items: one String
users: set String
likeCount: one int
items: one String
users: set String
likeCount: one int
Tag[User, Item]
Purpose: a general parameter to aid classification of posts, replies and users
Principle: A tag can be assigned to multiple items. An item can have multiple tags. Required tags can’t be deleted, because it will be necessary for classification purpose. Normal tags can be deleted.
The "Tag" concept contains two documents: a list storing the tags and its related
State:
name: Tag -> one String // Users who own this tag
owns: Item -> set Tag // Items which this tag is assigned to
name: Tag -> one String // Users who own this tag
owns: Item -> set Tag // Items which this tag is assigned to
Post [User]
Purpose: Post is the core concept of this app. It represents a general rich text message that applicable to use as article, question and knowledge(for optional function wiki)
Principle: Posts are created using editing tools in each functions. Posts can be edited or delete by the user, but can’t be edit by other user. Posts have visibility options. By default, it is set to public, meaning all users can view. It can also be set as private, friends-only or partner-only.
State:
author: one User
title: one String
content: one String
visibility: one String // public, private, friendsOnly, partnersOnly
postType: one String // article, question
author: one User
title: one String
content: one String
visibility: one String // public, private, friendsOnly, partnersOnly
postType: one String // article, question
Reply [User, Post]
Purpose: Reply contains two user cases: comment on a post, or answer on a question. It depends on a post to exist.
Principle: Replies can only be created after a post. It can’t independently exist. Only registered user can reply a post. Reply uses private parameter to identify types. Like Post, Reply can also be favorited or liked by users.
State:
author: one User
// title: one String // reply doesn't need title
content: one String
relatedPost: one String
visibility: one String // public, private, friendsOnly, partnersOnly
replyType: one String // article, question
author: one User
// title: one String // reply doesn't need title
content: one String
relatedPost: one String
visibility: one String // public, private, friendsOnly, partnersOnly
replyType: one String // article, question
App Definition
Write a definition of your app as a list of concepts, with type parameters instantiated appropriately, and draw a diagram (like those we looked at in lecture) showing the state of the entire app, with contours showing which subdiagrams belong to which concepts.
app WeeHive
concepts
User
Profile [User.User]
Session [User.User]
Relationship [User1.User, User2.User]
Post [User.User]
Reply [Post.Post, User.User]
Tag [Post.Post / User.User] // how to define a concept that can be applied to both?
Favorite [Post.Post]
Like [Post.Post]
Map [?User.User]
Marker[User.User] // to display the sentiment calculated from reactions for each post.
concept syncronize
Sync createUser(username: String, password: String, out user: User)
user = User.create(username, password)
profile = Profile.createForUser(user)
Sync authenticateUser(username: String, password: String, out session: Session)
user = User.verifyCredentials(username, password)
session = Session.initiateForUser(user)
Sync addReplyToPost(author: ObjectId, content: String, replyType: ReplyType, postId: ObjectId, options?: ReplyOptions)
reply = Reply.addToPost(author, content, replyType, postId, options)
Sync fetchRepliesByPostId(postId: ObjectId)
replies = Reply.listByPostId(postId)
Sync createNewTag(name: String)
tag = Tag.initialize(name)
Sync associateTagWithItem(tagId: ObjectId, itemId: ObjectId)
Tag.linkToItem(tagId, itemId)
Sync fetchTagsByItemId(itemId: ObjectId)
tags = Tag.listByItemId(itemId)
Sync initiateFollow(userInitiator: ObjectId, userTarget: ObjectId)
Relationship.follow(userInitiator, userTarget)
Sync sendFriendRequest(initiator: ObjectId, recipient: ObjectId)
Relationship.proposeFriendship(initiator, recipient)
Sync sendRequest(initiator: ObjectId, recipient: ObjectId)
if Relationship.verifyFriendship(initiator, recipient)
Relationship.proposePartnership(initiator, recipient)
Sync acceptRequest(sender: ObjectId, receiver: ObjectId)
Relationship.acceptRequest(sender, receiver)
Sync declineRequest(sender: ObjectId, receiver: ObjectId)
Relationship.declineRequest(sender, receiver)
Sync removeRelationship(user1: ObjectId, user2: ObjectId, relationType: RelType)
Relationship.removeRelationship(user1, user2, relationType)
Sync getRelationships(userId: ObjectId, relationType: RelType)
relationships = Relationship.getRelationships(userId, relationType)
Sync tagPost(targetPost: Post, tagContent: String, out tag: Tag)
Tag.createForPost(targetPost, tagContent, tag)
Sync tagUser(targetUser: User, tagContent: String, out tag: Tag)
Tag.createForUser(targetUser, tagContent, tag)
Sync favoritePost(user: User, targetPost: Post, out favorite: Favorite)
Favorite.create(user, targetPost, favorite)
Sync likePost(user: User, targetPost: Post, out like: Like)
Like.create(user, targetPost, like)
Sync CreteMarkerOnMap(post: Post, sentimentData: any, out marker: Marker)
Marker.createForPost(post, sentimentData, marker)
Sync updateMarker(user: User, locationData: any, out map: Map)
Map.updateMap(user, locationData, map)
app WeeHive
concepts
User
Profile [User.User]
Session [User.User]
Relationship [User1.User, User2.User]
Post [User.User]
Reply [Post.Post, User.User]
Tag [Post.Post / User.User] // how to define a concept that can be applied to both?
Favorite [Post.Post]
Like [Post.Post]
Map [?User.User]
Marker[User.User] // to display the sentiment calculated from reactions for each post.
concept syncronize
Sync createUser(username: String, password: String, out user: User)
user = User.create(username, password)
profile = Profile.createForUser(user)
Sync authenticateUser(username: String, password: String, out session: Session)
user = User.verifyCredentials(username, password)
session = Session.initiateForUser(user)
Sync addReplyToPost(author: ObjectId, content: String, replyType: ReplyType, postId: ObjectId, options?: ReplyOptions)
reply = Reply.addToPost(author, content, replyType, postId, options)
Sync fetchRepliesByPostId(postId: ObjectId)
replies = Reply.listByPostId(postId)
Sync createNewTag(name: String)
tag = Tag.initialize(name)
Sync associateTagWithItem(tagId: ObjectId, itemId: ObjectId)
Tag.linkToItem(tagId, itemId)
Sync fetchTagsByItemId(itemId: ObjectId)
tags = Tag.listByItemId(itemId)
Sync initiateFollow(userInitiator: ObjectId, userTarget: ObjectId)
Relationship.follow(userInitiator, userTarget)
Sync sendFriendRequest(initiator: ObjectId, recipient: ObjectId)
Relationship.proposeFriendship(initiator, recipient)
Sync sendRequest(initiator: ObjectId, recipient: ObjectId)
if Relationship.verifyFriendship(initiator, recipient)
Relationship.proposePartnership(initiator, recipient)
Sync acceptRequest(sender: ObjectId, receiver: ObjectId)
Relationship.acceptRequest(sender, receiver)
Sync declineRequest(sender: ObjectId, receiver: ObjectId)
Relationship.declineRequest(sender, receiver)
Sync removeRelationship(user1: ObjectId, user2: ObjectId, relationType: RelType)
Relationship.removeRelationship(user1, user2, relationType)
Sync getRelationships(userId: ObjectId, relationType: RelType)
relationships = Relationship.getRelationships(userId, relationType)
Sync tagPost(targetPost: Post, tagContent: String, out tag: Tag)
Tag.createForPost(targetPost, tagContent, tag)
Sync tagUser(targetUser: User, tagContent: String, out tag: Tag)
Tag.createForUser(targetUser, tagContent, tag)
Sync favoritePost(user: User, targetPost: Post, out favorite: Favorite)
Favorite.create(user, targetPost, favorite)
Sync likePost(user: User, targetPost: Post, out like: Like)
Like.create(user, targetPost, like)
Sync CreteMarkerOnMap(post: Post, sentimentData: any, out marker: Marker)
Marker.createForPost(post, sentimentData, marker)
Sync updateMarker(user: User, locationData: any, out map: Map)
Map.updateMap(user, locationData, map)
Concept Implementation Reflections
1. Concept Reduction and Modularization
2. Reduce with backend calculation
Many parameters can be calculated through backend operations. Therefore it's not necessary to store the values in separate documents. For example, when I was implementing tag concept, I was debating whether to include a list document to store all the tag names as a separate document. Eventually I realized I can get all the tags as a list by mongodb queries for the users to select. Similar situations applies to Like concept. We don't need to store and update the like counts in the database. We can just calculate it in real time as a database query. However, I don't know if it will take be less efficiency when the scale of dataset grows too big.
Consistant return formatting: I found it is important to keep the backend return a consistant format, to make frontend developers' life easier. In this assignment, I tried to make every return follow the format of:
```
{
msg: "message of the current operation and its related ids",
return: "returning values"
}
```
Design refinements of key concepts
Profile is inherently similar to posts, but it has different content format and doesn’t require reply. The key difference between “post” and “profile” is one user can only have one profile but multiple posts. One remaining question is whether to keep the profile belongs to user. The TAs said RESTFul principle requires each concept to be independent. I don't know if the profile should be independent when designing the routes.
Relationship is an extended “Friend” concept. In my app, a relationship has 4 states: no relationship, one direction follow (no need for approval), two-directional friendship, and two-directional partnership. When two user follow each other, they automatically become friends. But a partner request still need two users becomes friends.
During implementation, I found generalizing a concept may bring additional complexity to one concept design, but it will in turn make the program structure nicer. It is a trade-off between designing one concept that can be applied to multiple use cases. For this concept, I tried to make one "relationship" concept to handle user relationships, instead of separate the follow with friendship, though their behavior may be different. That's to reduce the complexity of the documents and make it easier to maintain.
- Map is a concept I eventually understand during implementation. I was always doubting the necessity of it because it is a front-end heavy feature. I found the goal of making a concept for it is to store the state of the map in the database. So the next time when user come back, they can use the same settings and zoom levels, for a smoother user experience. That's also a great lession that the user experience is also relies on good design of backend side.
Collaboration
I used ChatGPT to help me debug and guidance for some tricky codes.
Related Links
Vercel Deployment Link: 61040-backend.vercel.app
RESTful routes: https://github.com/srcJin/61040-backend/blob/main/server/routes.ts -->