System Design: Building a Simple Social Media Platform in Go
Saloni Agarwal
Posted on November 7, 2024
In this article, we'll walkthrough the designing of a simplified social media platform using Go, focusing on low-level system design principles. Our platform includes core features like user registration, creating posts, handling likes and comments, and a notification system to keep users updated. This example illustrates how these features can be built into a system that’s modular, scalable, and efficient.
We'll use Go's concurrency capabilities and design patterns like the facade to create a streamlined and maintainable structure, allowing the platform to handle various user interactions seamlessly.
Key Components of Our Design
The social media platform we’re building focuses on these main features:
User Management: Registering and managing user profiles.
Post Creation and Interactions: Creating posts, liking, and commenting.
Notifications: Alerting users to relevant actions like likes and comments.
Concurrency: Efficiently handling simultaneous user actions.
Core Components of the System
Let’s break down the key components of our platform and see how each part integrates into the system.
User Management
The UserManager component is responsible for user registration and profile management. Each user has essential profile details like ID, name, and bio, and the manager ensures users can be added and retrieved efficiently. Some key functions are:
typeUserstruct{typeUserstruct{IDintNamestringEmailstringPasswordstringDisplayPicture*stringBio*stringfriendsmap[int]*Userposts[]*Post}typeUserManagerstruct{usersmap[int]*Usermusync.RWMutex}func(um*UserManager)AddUser(user*User){um.mu.Lock()deferum.mu.Unlock()um.users[user.ID]=userfmt.Printf("User added: %d\n",user.ID)}func(um*UserManager)GetUserByID(userIDint)(*User,error){um.mu.RLock()deferum.mu.RUnlock()user,ok:=um.users[userID]if!ok{returnnil,fmt.Errorf("user not found")}returnuser,nil}func(um*UserManager)AddFriend(requesterID,receiverIDint)error{requester,err:=um.GetUserByID(requesterID)iferr!=nil{returnerr}receiver,err:=um.GetUserByID(receiverID)iferr!=nil{returnerr}requester.AddFriend(receiver)receiver.AddFriend(requester)fmt.Printf("Friendship added between users: %d and %d\n",requesterID,receiverID)returnnil}
In a real-world application, the UserManager would connect to a database, but here we use a map for simplicity.
Post Management
The PostManager handles user-generated content by managing posts, likes, and comments. This component allows users to create posts, like others’ posts, comment, and retrieve posts. Some key functions are:
typePoststruct{IDintUserIDintContentstringIsPublishedboolURLs[]*stringLikesintComments[]*CommentPublishedAttime.TimeCommentsEnabledboolHiddenFromUsersmap[int]bool}typePostManagerstruct{postsmap[int]*Postmusync.RWMutex}func(pm*PostManager)GetPost(postIDint)(*Post,error){pm.mu.RLock()deferpm.mu.RUnlock()post,exists:=pm.posts[postID]if!exists{returnnil,fmt.Errorf("post not found")}returnpost,nil}func(pm*PostManager)AddPost(post*Post,user*User){pm.mu.Lock()deferpm.mu.Unlock()pm.posts[post.ID]=postuser.AddPost(post)}func(pm*PostManager)LikePost(postIDint)(*Post,error){pm.mu.Lock()post:=pm.posts[postID]pm.mu.Unlock()ifpost==nil{returnnil,fmt.Errorf("post not found")}pm.mu.Lock()deferpm.mu.Unlock()post.Like()returnpost,nil}
The PostManager could interact with a database to store and retrieve posts, allowing for filtering by various criteria.
Notification Management
The NotificationManager is responsible for keeping users updated on platform activities, such as receiving a like or comment on their posts. Each notification type (like, comment, friend request) is sent through this manager, ensuring users are informed in real-time. Some key functions are:
typeNotificationstruct{IDstringTypeNotificationTypeContentstringUserIDint}typeNotificationManagerstruct{notificationsmap[int][]*Notificationmusync.RWMutex}func(nm*NotificationManager)AddNotification(userIDint,notificationTypeNotificationType,messagestring){nm.mu.Lock()defernm.mu.Unlock()notification:=NewNotification(fmt.Sprintf("notification-%d",time.Now().UnixMicro()),notificationType,message,userID)nm.notifications[userID]=append(nm.notifications[userID],notification)}func(nm*NotificationManager)GetNotificationsForUser(userIDint)([]*Notification,error){nm.mu.RLock()defernm.mu.RUnlock()notifications,ok:=nm.notifications[userID]if!ok{returnnil,fmt.Errorf("user not found")}returnnotifications,nil}
With NotificationManager, we can notify users of interactions related to their posts, allowing for a more engaging experience. In a production system, notifications could be sent via channels or push notifications.
Using the Facade Pattern with ActivityFacade
To simplify interactions between different components, we use the Facade pattern. ActivityFacade combines the functionalities of UserManager, PostManager, and NotificationManager, providing a unified interface for our social media app.
typeActivityFacadestruct{userManager*UserManagerpostManager*PostManagernotificationManager*NotificationManager}func(af*ActivityFacade)SendFriendRequest(requesterID,receiverIDint)error{_,err:=af.UserManager.GetUserByID(receiverID)iferr!=nil{returnerr}af.NotificationManager.AddNotification(receiverID,FriendRequestNotificationType,fmt.Sprintf("%d has sent you a friend request",requesterID))fmt.Printf("Friend request sent to user %d\n",receiverID)returnnil}func(af*ActivityFacade)CommentPost(userID,postIDint,contentstring)error{user,err:=af.UserManager.GetUserByID(userID)iferr!=nil{returnerr}post,err:=af.PostManager.CommentPost(user,postID,content)af.NotificationManager.AddNotification(post.UserID,CommentNotificationType,fmt.Sprintf("%d has commented on your post: %d",userID,post.ID))fmt.Printf("Comment added to post: %d\n",post.ID)returnnil}func(af*ActivityFacade)HidePostFromUser(postIDint,userIDint)error{_,err:=af.UserManager.GetUserByID(userID)iferr!=nil{returnerr}returnaf.PostManager.HidePostFromUser(postID,userID)}
With ActivityFacade, we can streamline user interactions with the platform, reducing the complexity of directly managing each subsystem. This approach makes the code more modular, maintainable, and easier to expand.
Handling Concurrency
In any social media platform, multiple users perform actions simultaneously. Go’s concurrency tools, particularly sync’s RWMutex, are ideal for handling concurrent reads and writes in a safe way.
Using RWMutex, we ensure that multiple users can read posts concurrently, but only one can like or comment at a time, preventing race conditions and data corruption.
Conclusion and next steps
Our low-level system design for a social media platform in Go provides a strong foundation for expanding features making it scalable and easy to maintain.
Potential areas for future enhancement include:
Real-time Notifications using WebSockets or push notifications.
Advanced Privacy Controls for friend requests and posts.
Persistent Data Storage with a database to replace the in-memory maps.
For full code implementation, please check the following repository:
Welcome to the Low-Level System Design in Go repository! This repository contains various low-level system design problems and their solutions implemented in Go. The primary aim is to demonstrate the design and architecture of systems through practical examples.
Low-level system design involves understanding the core concepts of system architecture and designing scalable, maintainable, and efficient systems. This repository will try to cover solutions of various problems and scenarios using Go.
Parking Lot System
The first project in this repository is a Parking Lot System. This system simulates a parking lot where vehicles can be parked and unparked. It demonstrates:
Singleton design pattern for managing the parking lot instance.
Handling different types of vehicles (e.g., cars, trucks).