Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using Go To Build a REST Service on Top of MongoDB

DZone's Guide to

Using Go To Build a REST Service on Top of MongoDB

· Java Zone
Free Resource

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

I've been following go (go-lang) for a while now and finally had some time to experiment with it a bit more. In this post we'll create a simple HTTP server that uses mongoDB as a backend and provides a very basic REST API.

In the rest of this article I assume you've got a go environment setup and working. If not, look at the go-lang website for instructions (https://golang.org/doc/install).
Before we get started we need to get the mongo drivers for go. In a console just type the following:

go get gopkg.in/mgo.v2

This will install the necessary libraries so we can access mongoDB from our go code.

We also need some data to experiment with. We'll use the same setup as we did in my previous article (http://www.smartjava.org/content/building-rest-service-scala-akka-http-a...).

Loading data into MongoDB

We use some stock related information which you can download from here (http://jsonstudio.com/wp-content/uploads/2014/02/stocks.zip). You can easily do this by executing the following steps:

First get the data:

Start mongodb in a different terminal

    mongod --dbpath ./data/

And finally use mongoimport to import the data

     unzip -c stocks.zip | mongoimport --db akka --collection stocks --jsonArray

And as a quick check run a query to see if everything works:

jos@Joss-MacBook-Pro.local:~$ mongo akka      
MongoDB shell version: 2.4.8
connecting to: akka
> db.stocks.findOne({},{Company: 1, Country: 1, Ticker:1 } )
{
        "_id" : ObjectId("52853800bb1177ca391c17ff"),
        "Ticker" : "A",
        "Country" : "USA",
        "Company" : "Agilent Technologies Inc."
}
> 

At this point we have our test data and can start creating our go based HTTP server. You can find the complete code in a Gist here: https://gist.github.com/josdirksen/071f26a736eca26d7ea4

In the following section we'll look at various parts of this Gist to explain how to setup a go based HTTP server.

The main function

When you run a go application, go will look for the main function. For our server this main function looks like this:

func main() {
 
server := http.Server{
Addr: ":8000",
Handler: NewHandler(),
}
 
// start listening
fmt.Println("Started server 2")
server.ListenAndServe()
 
} 

This will configure a server to run on port 8000, and any request that comes in will be handled by the NewHandler() instance we create in line 64. We start the server by calling the server.listenAndServe() function.

Now lets look at our handler that will respond to requests.

The myHandler struct

Lets first look at what this handler looks like:

// Constructor for the server handlers
func NewHandler() *myHandler {
h := new(myHandler)
h.defineMappings()
 
return h
}
 
// Definition of this struct
type myHandler struct {
// holds the mapping
mux map[string]func(http.ResponseWriter, *http.Request)
}
 
// functions defined on struct
func (my *myHandler) defineMappings() {
 
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
 
// make the mux
my.mux = make(map[string]func(http.ResponseWriter, *http.Request))
 
// matching of request path
my.mux["/hello"] = requestHandler1
my.mux["/get"] = my.wrap(requestHandler2, session)
}
 
// returns a function so that we can use the normal mux functionality and pass in a shared mongo session
func (my *myHandler) wrap(target func(http.ResponseWriter, *http.Request, *mgo.Session), mongoSession *mgo.Session) func(http.ResponseWriter, *http.Request) {
return func(resp http.ResponseWriter, req *http.Request) {
target(resp, req, mongoSession)
}
}
 
// implements serveHTTP so this struct can act as a http server
func (my *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h, ok := my.mux[r.URL.String()]; ok {
// handle paths that are found
h(w, r)
return
} else {
// handle unhandled paths
io.WriteString(w, "My server: "+r.URL.String())
}
} 

Lets split this up and look at the various parts. The first thing we do is define a constructor:

func NewHandler() *myHandler {
h := new(myHandler)
h.defineMappings()
 
return h
} 

When we call this constructor this will instantiate a myHandler type and call the defineMappings() function. After that it will return the instance we created.

How does the type look we instantiate:

type myHandler struct {
// holds the mapping
mux map[string]func(http.ResponseWriter, *http.Request)
} 

As you can we define a struct with a mux variable as a map. This map will contain our mapping between a request path and a function that can handle the request.

In the constructor we also called the defineMappings function. This funtion, which is defined on out myHandler struct, looks like this:

func (my *myHandler) defineMappings() {
 
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
 
// make the mux
my.mux = make(map[string]func(http.ResponseWriter, *http.Request))
 
// matching of request path
my.mux["/hello"] = requestHandler1
my.mux["/get"] = my.wrap(requestHandler2, session)
} 

In this (badly named) function we define the mapping between incoming requests and a specific function that handles the request. And in this function we also create a session to mongoDB using the mgo.Dial function.
As you can see we define the requestHandlers in two different ways. The handler for "/hello" directly points to a function, an the handler for the "/get" path, points to a wrap function which is also defined on the myHandler struct:

func (my *myHandler) wrap(target func(http.ResponseWriter, *http.Request, *mgo.Session), mongoSession *mgo.Session) func(http.ResponseWriter, *http.Request) {
return func(resp http.ResponseWriter, req *http.Request) {
target(resp, req, mongoSession)
}
} 

This is a function, which returns a function. The reason we do this, is that we also want to pass our mongo session into the request handler. So we create a custom wrapper function, which has the correct signature, and just pass each call to a function where we also provide the mongo session. (Note that we also could have changed the ServeHTTP implementation we explain below)

Finally we define the ServeHTTP function on our struct. This function is called whenever we receive a request:

func (my *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h, ok := my.mux[r.URL.String()]; ok {
// handle paths that are found
h(w, r)
return
} else {
// handle unhandled paths
io.WriteString(w, "My server: "+r.URL.String())
}
} 

In this function we check whether we have a match in our mux variable. If we do, we call the configured handle function. If not, we just respond with a simple String.

The request handle functions

Lets start with the handle function which handles the "/hello" path:

func requestHandler1(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello world!")
} 

Couldn't be easier. We simply write a specific string as HTTP response. The "/get" path is more interesting:

func requestHandler2(w http.ResponseWriter, r *http.Request, mongoSession *mgo.Session) {
c1 := make(chan string)
c2 := make(chan string)
c3 := make(chan string)
 
go query("AAPL", mongoSession, c1)
go query("GOOG", mongoSession, c2)
go query("MSFT", mongoSession, c3)
 
select {
case data := <-c1:
io.WriteString(w, data)
case data := <-c2:
io.WriteString(w, data)
case data := <-c3:
io.WriteString(w, data)
}
 
}
 
// runs a query against mongodb
func query(ticker string, mongoSession *mgo.Session, c chan string) {
sessionCopy := mongoSession.Copy()
defer sessionCopy.Close()
collection := sessionCopy.DB("akka").C("stocks")
var result bson.M
collection.Find(bson.M{"Ticker": ticker}).One(&result)
 
asString, _ := json.MarshalIndent(result, "", " ")
 
amt := time.Duration(rand.Intn(120))
time.Sleep(time.Millisecond * amt)
c <- string(asString)
} 

What we do here is that we make use of the channel functionality of go to run three queries at the same time. We get the ticker information for AAPL, GOOG and MSFT and return a result to the specific channel. When we receive a response on one of the channels we return that response. So each time we call this service we either get the results for AAPL, for GOOG or for MSFT.

With that we conclude this first step into go-lang :)

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Published at DZone with permission of Jos Dirksen, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}