# Day 18 : Golang Gin(4) - MongoDB
## 前言
MongoDB是一種NoSQL資料庫,不用像使用MySQL資料庫的時候要特別設計schema,可以自由定義document的結構,資料結構如果有需要調整的彈性比較大。此外因為資料彼此之間不一定要有關聯性,存取資料也更有效率。下面練習會參考youtube影片並搭配MongoDB Atlas,一般的MongoDB結構可分為
* database : 欲使用的資料庫名稱
* connection : 其實就像是關聯式資料庫的table,例如會員資料、文章資料可以各自成為一個table
* document : connection裡面的物件,就像是每個table裡面的資料,假設以會員資料當作connection,document可能就是會員一、會員二等等。
## 使用MongoDB 與 架構設計
在練習中使用Gin框架搭配MVC架構,使用MongoDB做簡單新增刪除修改會員的API使用。下面為完成後之檔案架構圖
:::info
│ go.mod
│ go.sum
│ main.go
│
├─controllers
│  userController.go
│
├─models
│  user.go
│
├─services
│  userService.go
│  userServiceImpl.go
:::
除了main.go以外,建立三個資料夾,說明如下
* services : 用於儲存應用程式的業務邏輯。
* controllers : 定義應用程式的路由和對應的handler function,負責處理HTTP請求,從HTTP請求中解析數據,再呼叫相應的服務(UserService)來處理業務邏輯,並返回HTTP Response
* models : 定義應用程式所需要的資料類型
其中連接MongoDB主要使用的package為 [mongodb/mongo-go-driver](https://github.com/mongodb/mongo-go-driver)
## package models
第一步先再models裡面定義所需要的資料類型,json為新增資料時,key的寫法;bson為將json檔案用二進位格式化的呈現方法,其名稱為當之後將資料存進MongoDB後,每個document裡面key的名稱
```go!
package models
type Address struct {
City string `json:"city" bson:"city"`
Pincode int `json:"pincode" bson:"pincode"`
}
type User struct {
Name string `json:"name" bson:"user_name"`
Age int `json:"age" bson:"user_age"`
Address Address `json:"address" bson:"user_address"`
}
```
## package services
這裡作者特別先透過一個interface,專門用來定義要實現UserService需要那些不同的方法,這裡包含建立使用者、取得使用者、修改使用者與刪除使用者,透過使用介面提高程式碼的**可讀性**和**可維護性**。
```go!
// userService.go
package services
import "ginMongodbPractice/models"
type UserService interface {
CreateUser(*models.User) error
GetUser(*string) (*models.User, error)
GetAll() ([]*models.User, error)
UpdateUser(*models.User) error
DeleteUser(*string) error
}
```
在實際執行業務邏輯的檔案裡面,透過NewUserService函式可以創建和初始化一個 UserServiceImpl對象,讓程式可以使用UserService這個接口的方法,例如 CreateUser()。
在(u *UserServiceImpl) CreateUser()裡,使用InsertOne增加一筆document,因為並沒有要取得新增的document物件,所以用底線替代。
```go=
//userServiceImpl.go
package services
import (
"context"
"ginMongodbPractice/models"
"go.mongodb.org/mongo-driver/mongo"
)
type UserServiceImpl struct {
userCollection *mongo.Collection
ctx context.Context
}
func NewUserService(userCollection *mongo.Collection, ctx context.Context) UserService {
return &UserServiceImpl{
userCollection: userCollection,
ctx: ctx,
}
}
func (u *UserServiceImpl) CreateUser(user *models.User) error {
_,err := u.userCollection.InsertOne(u.ctx,user)
return err
}
```
## package controllers
在controllers裡面定義路由和處裡HTTP請求和回應
首先根據New函式接收一個services.UserService作為參數,並初始化一個 UserController
接下來建立這次api的群組,包含群組的路由位置與對應的handler function,最後這個群組需要在main.go做註冊才可使用。
在(u *UserController) CreateUser()函式裡面,使用ctx.ShouldBindJSON()從請求中獲取JSON數據並將其綁定到user變數上。
綁定變數成功後,將變數資料當作參數,傳遞給userServiceImpl的CreateUser方法,在MongoDB中創建一個新的使用者。
```go=
package controllers
import (
"fmt"
"ginMongodbPractice/models"
"ginMongodbPractice/services"
"net/http"
"github.com/gin-gonic/gin"
)
type UserController struct {
UserService services.UserService
}
func New(userservice services.UserService) UserController {
return UserController{
UserService: userservice,
}
}
func (u *UserController) RegisterUserRoutes(r *gin.RouterGroup) {
userroute := r.Group("/user")
userroute.POST("/create", u.CreateUser)
userroute.GET("/get/:name", u.GetUser)
userroute.GET("/getall", u.GetAll)
userroute.PATCH("/update", u.UpdateUser)
userroute.DELETE("/delete/:name", u.DeleteUser)
}
func (u *UserController) CreateUser(ctx *gin.Context) {
var user models.User
if err := ctx.ShouldBindJSON(&user); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
}
err := u.UserService.CreateUser(&user)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
}
ctx.JSON(http.StatusOK, gin.H{"message": "success"})
}
```
## main.go
init()代表主函式執行前會執行的程序,包含連接資料庫(database:website,collection:ginPractice),和第39、40初始化的工作。
最後在main()裡面註冊群組的路由,透過Run()啟動伺服器接收客戶端的請求
```go=
package main
import (
"context"
"fmt"
"log"
"ginMongodbPractice/controllers"
"ginMongodbPractice/services"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
var (
uc controllers.UserController
ctx context.Context
mongoclient *mongo.Client
err error
)
func init() {
ctx = context.TODO() // 返回一個非nil的Context,當還未清楚要如何使用或尚不可用時可以使用TODO方式宣告。
mongoconn := options.Client().ApplyURI("mongodb+srv://root:<password>@trainingcluster.owzseic.mongodb.net/?retryWrites=true&w=majority")
mongoclient, err = mongo.Connect(ctx, mongoconn)
if err != nil {
log.Fatal("error while connecting with mongo", err)
}
// 測試是否連上,可以使用Ping
err = mongoclient.Ping(ctx, readpref.Primary())
if err != nil {
log.Fatal("error while trying to ping mongo", err)
}
fmt.Println("mongo connection established")
userc := mongoclient.Database("website").Collection("ginPractice")
us := services.NewUserService(userc, ctx)
uc = controllers.New(us)
}
func main() {
defer mongoclient.Disconnect(ctx)
server := gin.Default()
// 創建路由群組,然後將該群組傳遞給 RegisterUserRoutes函式,以註冊使用者相關的路由
api := server.Group("/api")
uc.RegisterUserRoutes(api)
log.Fatal(server.Run(":8080"))
}
```
## 實作畫面
透過POST `http://127.0.0.1:8080/api/user/create`,到`func (u *UserController) CreateUser()`接受前端傳來的JSON資訊,再呼叫業務邏輯也就是
`func (u *UserServiceImpl) CreateUser(user *models.User)`將資料寫到MongoDB。

(圖一,POST一筆資料)
在ginPractice這個collection已確實新增資料

(圖二,MongoDB Atlas)
## Referencs
1. [The Official Golang driver for MongoDB](https://github.com/mongodb/mongo-go-driver)
2. [Creating Golang REST API with Gin-Gonic Web Framework & MongoDB](https://www.youtube.com/watch?v=vDIAwtGU9LE)
3. [Quick Start: Golang & MongoDB - Starting and Setup](https://www.mongodb.com/blog/post/quick-start-golang-mongodb-starting-and-setup)
4. [Quick Start: Golang & MongoDB - Modeling Documents with Go Data Structures](https://www.mongodb.com/blog/post/quick-start-golang--mongodb--modeling-documents-with-go-data-structures)
###### tags: `About Go`