|
1 |
| -# go-mongo-atlas |
2 |
| -Connect your go service with mongodb atlas |
| 1 | +## Preface |
| 2 | + |
| 3 | +They say, Golang is a very good choice, if you build a service for being deployed in the cloud infrastructure. And that is mostly true. However, if you have a stateful service with some database in the backend, you may find it difficult to setup such cloud infrastructure and to establish a communication between the Go service and the database server. |
| 4 | + |
| 5 | +Fortunately, there are solutions already in existence that simplify our life. For example, if you want to have your data stored in MongoDB, you may just use MongoDB Atlas - a fully [managed database service in the cloud](https://www.mongodb.com/cloud/atlas). We do not explain here, how to setup a MongoDB cluster there. It is very well done [here](https://docs.atlas.mongodb.com/getting-started/). We focus on how to create a connection to MongoDB Atlas with Go and interact with the database cluster. |
| 6 | + |
| 7 | +## Software Prerequisites |
| 8 | + |
| 9 | +You need an account on MongoDB Atlas and a running database cluster there. The tier `M0 Sandbox` would be enough, since it is free to use and allows you to store up to 512MB of data. |
| 10 | +Please make sure your IP is added to the whitelist in your MongoDB Atlas project (see Security -> Network Access). If you deploy the Go service in a Google Kubernetes Engine, you may want to take a look at our next article, which explains how to securely connect a Kubernetes cluster with the MongoDB Atlas. |
| 11 | + |
| 12 | +## Create an empty Go service |
| 13 | + |
| 14 | +Let us create a simple Go service with two endpoints: |
| 15 | + |
| 16 | +- `/save` to receive a record to store |
| 17 | +- `/read` to read the previously stored record |
| 18 | + |
| 19 | +The service will listen at the port `8080`: |
| 20 | + |
| 21 | +```package main |
| 22 | +
|
| 23 | +import ( |
| 24 | + "net/http" |
| 25 | +) |
| 26 | +
|
| 27 | +func main() { |
| 28 | + http.HandleFunc("/save", post) |
| 29 | + http.HandleFunc("/read", get) |
| 30 | +
|
| 31 | + if err := http.ListenAndServe(":8080", nil); err != nil { |
| 32 | + panic(err) |
| 33 | + } |
| 34 | +} |
| 35 | +
|
| 36 | +func post(w http.ResponseWriter, req *http.Request) {} |
| 37 | +
|
| 38 | +func get(w http.ResponseWriter, req *http.Request) {} |
| 39 | +``` |
| 40 | + |
| 41 | +## Create a connection to the MongoDB Atlas cluster |
| 42 | + |
| 43 | +[mgo.v2](https://pkg.go.dev/gopkg.in/mgo.v2) is a very useful package for interacting with Mongo and we are going to use it for the MongoDB Cluster Atlas as well. Add this function at the beginning of your code: |
| 44 | + |
| 45 | +``` |
| 46 | +func createConnection() (*mgo.Session, error) { |
| 47 | + dialInfo := mgo.DialInfo{ |
| 48 | + Addrs: []string{"abc-shard-00-00.gcp.mongodb.net:27017", "abc-shard-00-01.gcp.mongodb.net:27017", "abc-shard-00-02.gcp.mongodb.net:27017"}, |
| 49 | + Username: "MongoUser", // your mongodb user |
| 50 | + Password: "YourVerySecurePassword", // ...and mongodb password |
| 51 | + } |
| 52 | + tlsConfig := &tls.Config{} |
| 53 | + dialInfo.DialServer = func(addr *mgo.ServerAddr) (net.Conn, error) { |
| 54 | + conn, err := tls.Dial("tcp", addr.String(), tlsConfig) // add TLS config |
| 55 | + return conn, err |
| 56 | + } |
| 57 | + return mgo.DialWithInfo(&dialInfo) |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +On the MongoDB Atlas you always have not just a single database server, but a cluster with several shards. You have to replace the shard addresses `abc-shard-00-XX.gcp.mongodb.net:27017` with your own ones that are to find here: |
| 62 | + |
| 63 | + |
| 64 | + |
| 65 | +We also added a TLS config into our code, because the MongoDB Atlas denies unencrypted connections. |
| 66 | + |
| 67 | +Adding the initialization of the session variable completes the first step: |
| 68 | + |
| 69 | +``` |
| 70 | +var mongoConn *mgo.Session |
| 71 | +
|
| 72 | +func main() { |
| 73 | + var err error |
| 74 | + mongoConn, err = createConnection() |
| 75 | + if err != nil { |
| 76 | + panic(err) |
| 77 | + } |
| 78 | +
|
| 79 | + http.HandleFunc("/save", post) |
| 80 | + http.HandleFunc("/read", get) |
| 81 | +
|
| 82 | + if err := http.ListenAndServe(":8080", nil); err != nil { |
| 83 | + panic(err) |
| 84 | + } |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +## Using the mongo connection |
| 89 | + |
| 90 | +What we have now is a singleton `mongoConn` that can be used directly which is not a good idea. Why create it at all? Why cannot we establish a connection every time the client calls our endpoints? |
| 91 | + |
| 92 | +Because `mgo.DialWithInfo(...)` can take several seconds before the connection to the MongoDB Atlas is ready. There is a couple of necessary steps like sending and accepting certificates, authorization etc. that needs to be done, before your service can proceed with the next step. |
| 93 | + |
| 94 | +And of course you cannot use the singleton `mongoConn` in all your endpoints for an obvious reason (due to the side effects by using of a common connection in concurrent HTTP sessions). |
| 95 | + |
| 96 | +So, we use a copy of the singleton `mongoConn` which works perfect: |
| 97 | + |
| 98 | +``` |
| 99 | + session := mongoConn.Copy() // "session" can be used safely |
| 100 | + defer session.Close() |
| 101 | +``` |
| 102 | + |
| 103 | +Let us implement `/save` and `/read` now. We store the data in a sort of generic way: everything what the client sends us, we are going to save into the Mongo database as a byte array. |
| 104 | + |
| 105 | +``` |
| 106 | +func post(w http.ResponseWriter, req *http.Request) { |
| 107 | + payload, err := ioutil.ReadAll(req.Body) |
| 108 | + if err != nil { |
| 109 | + panic(err) |
| 110 | + } |
| 111 | +
|
| 112 | + session := mongoConn.Copy() |
| 113 | + defer session.Close() |
| 114 | +
|
| 115 | + entity := MyEntity{Data: payload} |
| 116 | + err = session.DB("test").C("data").Insert(entity) |
| 117 | + if err != nil { |
| 118 | + panic(err) |
| 119 | + } |
| 120 | +} |
| 121 | +
|
| 122 | +func get(w http.ResponseWriter, req *http.Request) { |
| 123 | + session := mongoConn.Copy() |
| 124 | + defer session.Close() |
| 125 | +
|
| 126 | + entity := MyEntity{} |
| 127 | + err := session.DB("test").C("data").Find(bson.M{}).One(&entity) |
| 128 | + if err != nil { |
| 129 | + panic(err) |
| 130 | + } |
| 131 | +
|
| 132 | + w.Write(entity.Data) |
| 133 | + w.Write([]byte{10}) // add a line break for a better look |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +_Normally you would not want to `panic` in case of an error, but return an HTTP error code in the response. However, we want to keep it simple for now._ |
| 138 | + |
| 139 | +Do not forget to close a copy of your session. MongoDB Atlas considers sessions as a resource and like every database server has a limit of opened connections. |
| 140 | + |
| 141 | +We are ready to test our endpoints! |
| 142 | + |
| 143 | +## Testing the endpoints |
| 144 | + |
| 145 | +You can start the service with following command (don't forget a point at the end): |
| 146 | + |
| 147 | +``` |
| 148 | +> go run main.go . |
| 149 | +``` |
| 150 | + |
| 151 | +If it worked, you will be able to save your data into the MongoDB Atlas cluster with `curl`: |
| 152 | + |
| 153 | +``` |
| 154 | +> curl -i -XPOST http://127.0.0.1:8080/save --data 'Here is my record' |
| 155 | +
|
| 156 | +HTTP/1.1 200 OK |
| 157 | +Date: Fri, 10 Jul 2020 13:03:03 GMT |
| 158 | +Content-Length: 0 |
| 159 | +``` |
| 160 | + |
| 161 | +...and fetch this data again via `/read` |
| 162 | + |
| 163 | +``` |
| 164 | +> curl -i -XGET http://127.0.0.1:8080/read |
| 165 | +
|
| 166 | +HTTP/1.1 200 OK |
| 167 | +Date: Fri, 10 Jul 2020 13:06:24 GMT |
| 168 | +Content-Length: 18 |
| 169 | +Content-Type: text/plain; charset=utf-8 |
| 170 | +
|
| 171 | +Here is my record |
| 172 | +``` |
| 173 | + |
| 174 | +At least, it worked for us. |
| 175 | + |
| 176 | +If you want to download the entire source code of this topic, visit us at [github.com/setlog/go-mongo-atlas](https://github.com/setlog/go-mongo-atlas) |
| 177 | + |
| 178 | +Be nosy and stay connected! |
0 commit comments