Skip to content

Commit 1cbe1db

Browse files
committed
README.md
1 parent aefc425 commit 1cbe1db

File tree

5 files changed

+212
-16
lines changed

5 files changed

+212
-16
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS_Store
2+
go.sum

.vscode/launch.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Debug",
6+
"type": "go",
7+
"request": "launch",
8+
"mode":"debug",
9+
"program": "${workspaceRoot}",
10+
"port": 8080,
11+
"host": "127.0.0.1",
12+
"showLog": true
13+
}
14+
]
15+
}

README.md

Lines changed: 178 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,178 @@
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+
![Shard Addresses](images/shards.png "Shards")
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!

images/shards.png

106 KB
Loading

main.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@ import (
1212

1313
var mongoConn *mgo.Session
1414

15+
func createConnection() (*mgo.Session, error) {
16+
dialInfo := mgo.DialInfo{
17+
Addrs: []string{
18+
"abc-shard-00-00.gcp.mongodb.net:27017",
19+
"abc-shard-00-01.gcp.mongodb.net:27017",
20+
"abc-shard-00-02.gcp.mongodb.net:27017"},
21+
Username: "MongoUser",
22+
Password: "YourVerySecurePassword",
23+
}
24+
tlsConfig := &tls.Config{}
25+
dialInfo.DialServer = func(addr *mgo.ServerAddr) (net.Conn, error) {
26+
conn, err := tls.Dial("tcp", addr.String(), tlsConfig)
27+
return conn, err
28+
}
29+
return mgo.DialWithInfo(&dialInfo)
30+
}
31+
1532
type MyEntity struct {
1633
Data []byte `json:"data" bson:"data"`
1734
}
@@ -31,20 +48,6 @@ func main() {
3148
}
3249
}
3350

34-
func createConnection() (*mgo.Session, error) {
35-
dialInfo := mgo.DialInfo{
36-
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"},
37-
Username: "MongoUser",
38-
Password: "YourVerySecurePassword",
39-
}
40-
tlsConfig := &tls.Config{}
41-
dialInfo.DialServer = func(addr *mgo.ServerAddr) (net.Conn, error) {
42-
conn, err := tls.Dial("tcp", addr.String(), tlsConfig)
43-
return conn, err
44-
}
45-
return mgo.DialWithInfo(&dialInfo)
46-
}
47-
4851
func post(w http.ResponseWriter, req *http.Request) {
4952
payload, err := ioutil.ReadAll(req.Body)
5053
if err != nil {

0 commit comments

Comments
 (0)