I'm building a REST server in Go and using mongoDB as my database (but this question is actually related to any other external resource).
I want my server to start and respond, even if the database is not up yet (not when the database is down after the server started - this is a different issue, much easier one).
So my dao package includes a connection go-routine that receives a boolean channel, and write 'true' to the channel when it successfully connected to the database. If the connection failed, the go-routine will keep trying every X seconds.
When I use this package with another software I wrote, that is a just a command-line program, I'm using select with timeout:
dbConnected := make(chan bool)
storage.Connect(dbConnected)
timeout := time.After(time.Minute)
select {
case <-dbConnected:
createReport()
case <-timeout:
log.Fatalln("Can't connect to the database")
}
I want to use the same packge in a server, but I don't want to fail the whole server. Instead, I want to start the server with handler that returns 503 SERVER BUSY
, until the server is connected to the database, and then start serve requests normally. Is there a simple way to implement this logic in go standard library? Using solutions like gorilla is an option, but the server is simple with very few APIs, and gorilla is a bit overkill.
== edited: ==
I know I can use a middleware but I don't know how to do that without sharing data between the main method and the handlers. That why I'm using the channel in the first place.
I have something working, but it does based on common data. However, the data is a single boolean, so I guess it's not so dramatic. I would love to get comments for this solution:
In the dao package, I have this Connect method, that return a boolean channel. The private connect go routine, writes 'true' and exit when succeed:
func Connect() chan bool {
connected := make(chan bool)
go connect(mongoUrl, connected)
return connected
}
I also added the Ping() method to the dao package; it run forever and monitor the database status. It reports the status to a new channel and try to reconnect if needed:
func Ping() chan bool {
status := make(chan bool)
go func() {
for {
if err := session.Ping(); err != nil {
session.Close()
status <- false
<- Connect()
status <- true
}
time.Sleep(time.Second)
}
}()
return status
}
In the main package, I have this simple type:
type Connected struct {
isConnected bool
}
// this one is called as go-routine
func (c *Connected) check(dbConnected chan bool) {
// first connection, on server boot
c.isConnected = <- dbConnected
// monitor the database status
status := dao.Ping()
for {
c.isConnected = <- status
}
}
// the middleware
func (c *Connected) checkDbHandleFunc(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !c.isConnected {
w.Header().Add("Retry-After", "10")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(503)
respBody := `{"error":"The server is busy; Try again soon"}`
w.Write([]byte(respBody))
} else {
next.ServeHTTP(w, r)
}
})
}
Middleware usage:
...
connected := Connected{
isConnected: false,
}
dbConnected := dao.Connect()
go connected.check(dbConnected)
mux := http.NewServeMux()
mux.HandleFunc("/", mainPage)
mux.HandleFunc("/some-db-required-path/", connected.checkDbHandleFunc(someDbRequiredHandler))
...
log.Fatal(http.ListenAndServe(addr, mux))
...
Does it make sense?