A Reddit-Style Comment System in Golang
Published:
Preparing for job interviews, I found this question on Glassdoor. The problem was to design a commenting system similar to Reddit’s. Here, I use this problem to show how to problem solve using Golang’s ease of HTTP/API development. I will present my Golang implementation which is based on grpc.
Source Code
The complete source code for this project can be found here.
Features
Looking at Reddit’s user interface, there are a few design features that stand out. The first and most prominent is the nesting of comments. Second, there is the voting system. Third, there is the “masking” of comments. Some comments are displayed while others are minimized.
In this post I will consider the design of the first two features. Deciding which comments to “shrivel” will remain outside the scope of this post.
Tools
For this program I will be writing the majority of the code in Golang. In the github repository, you can find a basic javascript client that does nothing but pretty-print the comment tree as a JSON. I will connect the server code to a MySQL database through Golang’s database/sql
library.
API
Let’s first design the API for the commenting service. To fulfill our basic needs, the comment system needs two features:
/Comment/New
- to create a new comment/Comment/List
- to display comments
There is another endpoint, /Comment/Delete
, which we will not cover here. However, if we were to implement this we could simply change the text of the comment to “[deleted]” as it is done on Reddit. It is critical to keep the comment to retain the structure of the comment tree, so a delete feature will most likely not destroy all traces of the comment.
Implementation
To implement an API server in Go, w can use the net/http
package which provides a server multiplexer, ServeMux. Next, for every endpoint we wish to serve, we must provide the muxer with a mapping of path to a Handler object. While this could be an empty struct, I want to keep a cached view of the comment tree in server memory. So, my server object will have a data structure to do just that:
type CommentServer struct {
tree map[int]*comment.Comment
}
We then create a ServeMux object and redirect http requests to our server object:
cs := CommentServer{}
mux := http.NewServeMux()
mux.Handle("/Comment/New", cs)
mux.Handle("/Comment/List", cs)
// Run the server
log.Fatal(http.ListAndServe(":8888", mux))
Finally, we create code to handle an http request for the server object (which implements the Handler
interface.
func (s *CommentServer) ServeHttp(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/Comment/New":
// handle new
case "/Comment/List":
// handle list
}
}
New Comment
To create a new comment, a new comment id must be generated and the Id inserted into the comment map. We will create the comment with a default “score” of 1 and add it to the list of its parent’s children. We will finally insert the new comment into the map of [commendId]->Comment.
List Comments
For this we will exploit Golang’s recursive marshaling of objects. In Go, it is pretty easy to annotate arbitrary structs to be marshaled as JSON. The marshal/demarshal routines also have the ability to do this recursively. So, if we have a Comment struct of this form,
type Comment struct {
Text string `json:"text"`
Parent int `json:"parent"`
Id int `json:"id"`
Children []*Comment `json:"children"`
Score int `json:"score"`
mux sync.Mutex
}
the json library will auto-magically recurse down the Children field and construct recursively the inner JSON objects.
Note that the mutex is call mux
, lowercase. Since lowercase symbols are not exported in Golang, the marshaller will ignore this field. Sweet!
Conclusion
Easy! Hopefully now you have enough understanding to start building APIs in Go. Happy hunting :)