# 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 │&emsp;&emsp;userController.go │ ├─models │&emsp;&emsp;user.go │ ├─services │&emsp;&emsp;userService.go │&emsp;&emsp;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。 ![](https://i.imgur.com/jSBX4f8.png) (圖一,POST一筆資料) 在ginPractice這個collection已確實新增資料 ![](https://i.imgur.com/NmpOPLF.png) (圖二,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`