Go Micro Restful Service Demo

本文介紹如何從零開始,使用 Go 語言的一些有代表性的框架萝嘁,實現(xiàn)一個微服務(wù)架構(gòu)的 Restful Web Service。
由于本人沒有 Mac 電腦(因為窮),所以本文的所有環(huán)境配置和測試靡砌、運行都在 Windows 10 環(huán)境下。

本文涉及的主要框架及環(huán)境:

  • go珊楼。go語言庫
  • JetBrians GoLand通殃。IDE
  • gin。Go 的一款常用 Web 框架厕宗,類似于 SpringMVC
  • go-micro画舌。Go 的一款微服務(wù)框架堕担,類似于 SpringCloud/Dubbo
  • gorm。Go 的一款 ORM 框架曲聂,從設(shè)計風(fēng)格上有點類似于 Hibernate/MyBatis 的混合霹购。
  • go-protobuf。protobuf 的 go 語言版本
  • consul朋腋。一個類似 zookeeper 的服務(wù)治理軟件齐疙,可以參看這邊文章。這里不總結(jié)原理和環(huán)境配置乍丈,只作為工具使用剂碴。

Go 的初體驗

Go 的環(huán)境配置

Go 官網(wǎng) 下載最新版本的 Go 編譯版本安裝包,然后運行轻专,源碼安裝請見其它博客忆矛。
安裝程序會自動設(shè)置部分但非全部環(huán)境變量,所以為了保險起見请垛,檢查并設(shè)置以下所有環(huán)境變量催训。

  • GOROOT
    安裝時候的根路徑。
  • GOBIN
    %GOROOT%\bin
    //這個環(huán)境變量是安裝時候自己設(shè)置的宗收,但好像不設(shè)置也問題不大漫拭。
  • Path
    在 Path 里添加
    %GOROOT%\bin
  • GOPATH
    這個是 Go 的工作空間,可以參看這篇文章混稽。個人理解采驻,Go 相當(dāng)于集成了 Maven 和 JDK,依賴管理匈勋、編譯等等都無需額外的軟件了礼旅。
    變量值是個合法的文件夾的路徑就行,比如 C:\Users\Cachhe\go洽洁。

GoLand 設(shè)置

  1. 配置代理痘系。針對公司的內(nèi)網(wǎng)環(huán)境,在 File->Settings->Apearance & Behavior->System Settings->HTTP Proxy饿自,配置 Manual proxy configuration汰翠,具體看公司給的代理配置。
  2. 安裝 Protobuf 的插件昭雌,為了好看复唤。在 File->Settings->Plugins里,搜索 Protobuf Support城豁,安裝完后要重啟苟穆。

測試

新建一個工程,在根目錄下新建一個 test.go,操作方式與 IDEA 差不多雳旅,這里不介紹具體的操作了跟磨。
默認(rèn)創(chuàng)建出來的 go 文件,package 名與文件夾名一致攒盈,如果我們的工程名叫 xg-temp抵拘,GoLand 初始化了一個 xg-temp 文件夾,那么 test.go 的 package 名也就是 xg-temp型豁。為了讓它可執(zhí)行僵蛛,需要改為 main。這是 go 的約定迎变。



如上圖充尉,執(zhí)行,即可在控制臺看見輸出衣形。

項目背景

假設(shè)我們有一款云產(chǎn)品驼侠,我們希望提供 CRUD 功能。

整體設(shè)計

整體上采用微服務(wù)架構(gòu)谆吴,一般來說倒源,一個工程由前端、api模塊(其實也是一個服務(wù))句狼、多個服務(wù)組成笋熬。這里我們只關(guān)注后臺的邏輯,同時為了簡單腻菇,也只包括一個服務(wù)胳螟。
單個模塊采用 MVC 的結(jié)構(gòu)。從上往下開發(fā)筹吐,先確定與前端的交互協(xié)議旺隙,序列化和反序列化用 protobuf 來做,所以又要定義 proto骏令。WEB控制層通過 gin 來做,Service 與業(yè)務(wù)相關(guān)垄提,DAO 層通過 GORM 來做榔袋。
項目結(jié)構(gòu)如圖:



可見,項目 xg-temp 由 api 和 service 兩個模塊構(gòu)成铡俐,分別對應(yīng) api 模塊 和一個服務(wù)凰兑。

  • api
    api.go 是程序的入口,負(fù)責(zé)啟動 api 模塊审丘。
    client api 模塊雖然對前端體現(xiàn)的是“后端”的作用吏够,但是對于下游的服務(wù)仍然體現(xiàn)的是“客戶端”的作用。請求到了 api 后需要調(diào)下游服務(wù)來處理,雖然暴露出去的一般是 Restful 接口锅知,但是系統(tǒng)內(nèi)部一般用更方便的 rpc 來交互播急。
    handler handler 里面定義了控制層的邏輯,包括路徑映射售睹、請求轉(zhuǎn)發(fā)和返回
    proto proto 里放 proto 文件桩警,里面定義類似于 Java 的 POJO,可以通過程序自動生成 go 文件和 微服務(wù)需要的文件昌妹。這里因為只是 demo 就沒有放捶枢。
    apitest.http JetBrains 提供的一個非常方便的 HTTP 測試工具,可以非常方便地寫 HTTP 請求
  • service
    common 放公共基礎(chǔ)庫飞崖,比如配置文件
    handler 具體業(yè)務(wù)邏輯烂叔,包括操作數(shù)據(jù)庫
    proto 同上
    config.yaml 配置文件,springboot 也是用的這個
    service.go service 模塊的程序入口

安裝必要的軟件及依賴包

安裝 consul
consul 的下載地址固歪,Windows 下的 consul 是一個打包好的 .exe蒜鸡,打開 shell,執(zhí)行

consul.exe agent -dev

看見類似下面的輸出昼牛,即代表啟動了


安裝 protobuf
protobuf 的 GitHub releases 里有 Windows 版本的二進制文件术瓮,下載下來后,解壓贰健,將 protobuf 的 bin 目錄添加到 Path 環(huán)境變量里胞四。

安裝 go-micro 及一些工具

# -u 簡單來說就是遞歸下載每個庫所依賴的庫,不加只會下載這個庫伶椿,但是 -u 不會更新已經(jīng)有的庫辜伟,就算發(fā)現(xiàn)新版本
# -v 會在 shell 里輸出下載了哪些,方便看哪出問題了
# consul client
go get -u github.com/hashicorp/consul
# micro 核心庫
go get -u github.com/micro/micro
# 根據(jù) protoc 生成 micro 的代碼
go get -u github.com/micro/protoc-gen-micro
# protobuf 核心庫
go get -u github.com/golang/protobuf/proto
# 一個用 go 寫的 protobuf 生成軟件
go get -u github.com/golang/protobuf/protoc-gen-go

如果遇到下載不下來的情況下需要用代理脊另,一般是因為被墻了导狡。還是下載不了,就得通過 git 直接下載到 GOPATH 里偎痛。見 這篇文章旱捧。

Api 模塊

routers.go

routers 里定義路徑映射,即不同的 URL踩麦、不同的請求方法怎樣處理枚赡。對比 SpringMVC 中的 Controller,非常好理解谓谦。

package handler

import "github.com/gin-gonic/gin"

func NewWebService() *gin.Engine {
    router := gin.Default() // 相當(dāng)于一個 http-server

    v1 := router.Group("/v1") 
    // 路徑分組贫橙,也就是所有訪問 /v1/* 都會被這個 group 攔截 

    app := v1.Group("/app") 
    // 同上。v1 表示 api 的版本是 1反粥,app 上的操作都被攔截到這個 group 上進行處理
    appManager := new(AppManager) 
    // 定義了一個類卢肃,這樣做完全是出于工程考慮疲迂,對 app 的請求都在這個接收者的方法上進行處理,避免混亂
    app.POST("/create", appManager.CreateApp) 
    // 對 create 路徑的 POST 請求由 appManager 上的 CreateApp 方法處理莫湘,CreateApp 接收 gin.Context 參數(shù)尤蒿,這個參數(shù)中可以獲取當(dāng)前請求的信息和一些其它信息
    app.POST("/delete", appManager.DeleteApp)
    app.POST("/update", appManager.UpdateApp)
    app.POST("/query", appManager.QueryApp)

    product := v1.Group("/product")
    pdtManager := new(ProductManager)
    product.POST("/create", pdtManager.CreateProduct)
    product.POST("/delete", pdtManager.DeleteProduct)
    product.POST("/update", pdtManager.UpdateProduct)
    product.POST("/query", pdtManager.QueryProduct)

    return router
}

app_manager.go

app 相關(guān)的控制層邏輯,也可以直接寫在 routers 里逊脯,但是這樣更軟件工程月洛。product_manager.go 也是一樣撕贞,這里就不解釋了。
代碼只實現(xiàn)了一個,其它的類似醋火,不外乎更復(fù)雜的業(yè)務(wù)邏輯瘟栖。

package handler

import (
    "github.com/gin-gonic/gin"
    "xg-temp/api/client"
    appProto "xg-temp/service/proto"
)

type AppManager struct {}

func(a *AppManager) CreateApp(c *gin.Context) {
    var mobileAppCreateRequest appProto.MobileApplication // 聲明一個反序列化后的接收對象
    if err := c.ShouldBindJSON(mobileAppCreateRequest); err != nil {
     // bind 可以很方便地從 URL query params 里或者 requestBody 里反序列化出對象渊啰,具體用法請查看相關(guān)文檔
        resp := new(appProto.AppResponse)
        resp.ErrMsg = "請求錯誤"
        resp.RetCode = 400
        c.JSON(400, resp) // 出錯了贯卦,可能是請求參數(shù)不對,返回狀態(tài)碼 400甘桑,body 部分是 JSON 格式的 resp
        c.Abort() // 執(zhí)行完當(dāng)前 handler 后就不再執(zhí)行后續(xù)的 handler 了拍皮。
    // 這個有點類似于 Java Struts 中的攔截器/Filter,框架允許類似 pipe 一樣注冊多個 handler 來處理請求跑杭,比如日志铆帽、鑒權(quán)
        return
    }
    // 通過 rpc 調(diào)用下游服務(wù)的 CreateAppInfo 并獲取返回值
    resp := client.CreateAppInfo(mobileAppCreateRequest)

    c.JSON(200, resp)
}

func(a *AppManager) DeleteApp(c *gin.Context) {
    c.JSON(200, gin.H{ // gin.H 是個很方便的生成 JSON 的方法
        "error": false,
        "data": "你好啊 朋友",
    })
}

func(a *AppManager) UpdateApp(c *gin.Context) {
    c.JSON(200, gin.H{
        "error": false,
        "data": "你好啊 朋友",
    })
}

func(a *AppManager) QueryApp(c *gin.Context) {
    c.JSON(200, gin.H{
        "error": false,
        "data": "你好啊 朋友",
    })
}

api.go

api go

package main // main 是 go 中間一個特殊的 package,表示是程序入口德谅,不然的話就算有 main 方法還是不能執(zhí)行

import (
    "fmt"
    "github.com/micro/go-micro/registry"
    "github.com/micro/go-micro/registry/consul"
    "github.com/micro/go-micro/web"
    "time"
    "xg-temp/api/client"
    "xg-temp/api/handler"
)
var (
    listenPort = "localhost:8080" // Restful 接口的 IP 和端口
)

func main()  {
    serviceName := client.API_SERVICE_NAME
    serviceVersion := "latest"

    fmt.Printf("start service:%s version:%s", serviceName, serviceVersion)

    // 連接本地 consul client
    // registry.Option 是一個函數(shù)爹橱,這個函數(shù)接受 Options 為入?yún)? Options 是一個 stuct,里面有 Addr, Timeout 等字段窄做,
    // 其中 Addr 為 host:port 格式愧驱,是 consul 服務(wù)的監(jiān)聽地址,默認(rèn)的情況下是本地的 8500 端口椭盏,這個可以在前面啟動 consul 服務(wù)的時候設(shè)置组砚、看見
    // 這個設(shè)計在 Java/Android 的回調(diào)方法設(shè)計中很常見
    opts := registry.Option(
        func(opts *registry.Options) {
            opts.Addrs = []string {fmt.Sprintf("%s:%d", "127.0.0.1", 8500)}
    })

    // 注冊 webservice 到 consul
    // consul 一般是 service.NewService,web 是一種特別的 service
    service := web.NewService(
        web.Name(serviceName),
        web.Address(listenPort), // web 服務(wù)的地址
        web.RegisterTTL(time.Second * 10),
        web.RegisterInterval(time.Second * 5),
        web.Registry(consul.NewRegistry(opts))) // consul 的 Options掏颊,包括 consul 的地址
    _ = service.Init()

    // 初始化控制層
    gin := handler.NewWebService() // 拿到一個 gin Engine

    // 初始化 micro service
    client.InitApiService()

    // 綁定 gin 到 "/" 路徑上處理所有請求
    service.Handle("/", gin)

    // 啟動 service
    if err := service.Run(); err != nil {
        println(err) // 出錯了
    }
}

測試

先將前面涉及 client 的代碼注釋掉糟红,那部分是調(diào) rpc 的,我們還沒寫乌叶。然后 run api.go改化,這個時候我們是無法訪問 create 接口的,但是其它接口是可以的枉昏。在 apitest.http 里定義 HTTP 請求,運行


image.png

client.go

client.go 暫時沒法寫揍鸟,因為一般是先有 service 接口前端才能調(diào)兄裂,所以我們先切換到 service 模塊句旱,看看怎么繼續(xù)做。

Service 模塊

Service 主要處理兩件事晰奖,暴露給前端 RPC 接口谈撒,執(zhí)行具體的業(yè)務(wù)邏輯。一般情況下匾南,我們需要自己去定義接口啃匿,然后與 RPC 框架綁定起來,然后再實現(xiàn)這些接口蛆楞。proto 提供了更為方便的途徑溯乒,我們只需要在 proto 文件里聲明 Service 和 數(shù)據(jù)結(jié)構(gòu),然后就可以自動生成對應(yīng)的數(shù)據(jù)結(jié)構(gòu)定義(類似 POJO/Entity) 和 微服務(wù)代碼豹爹,我們只需要去實現(xiàn)一個個接口就行了裆悄。

在 Java 中,自動生成類已經(jīng)是很常見的框架功能臂聋,比如 MyBatis-Generator/GreenDao光稼,但 Proto 功能更強大。Java 中的 RPC 一般是通過動態(tài)代理來做的孩等,go 似乎沒這個功能艾君,所以稍微比 Java 的 RPC 框架用起來麻煩一些。

proto

首先來看 app.proto

syntax = "proto3"; // 語法版本

package xgtmp.srv.app; // 生成的 go 文件會是怎樣的 package name

// 定義 RPC 接口
service App {
    // 創(chuàng)建APP信息
    rpc CreateAppInfo (MobileApplication) returns (AppResponse) {}
    // 更新APP信息
    rpc UpdateAppInfo (MobileApplication) returns (AppResponse) {}
    // 刪除App信息
    rpc DeleteAppInfo (MobileApplication) returns (AppResponse) {}
    // 查詢APP信息
    rpc QueryAppInfo (AccessId) returns (AppResponse) {}
}
// 類似于聲明一個類
message AccessId {
    uint32 accessId = 1; // app 的ID肄方,后面的數(shù)字需要唯一冰垄,是在序列化/反序列化時候的順序
}

message MobileApplication {
    string appName = 1;    // app名稱
    string otherInfo = 2;  // 其它信息
}

message AppResponse {
    string errMsg = 1;
    int32 retCode = 2;
    MobileApplication app= 3; // 意思是 AppResponse 里面會包含一個 MobileApplication 的值,
    // 這里也可以像下面一樣用引用扒秸,雖然序列化/反序列化的時候都是 deep-copy播演,但是其它代碼中的行為會不一樣,比如賦值/讀值
    // MobileApplicaion* app = 4;
}

定義完了后伴奥,打開 bash写烤,進入 app.proto 所在目錄,執(zhí)行以下命令拾徙,應(yīng)該會額外生成兩個文件洲炊,app.pb.go 是數(shù)據(jù)結(jié)構(gòu),app.micro.go 是 RPC 接口尼啡。

protoc --micro_out=. --go_out=. app.proto
# --micro_out=. 表示在當(dāng)前目錄下生成 micro 文件
# --go_out=. 表示在當(dāng)前目錄下生成 pb 文件
# 常用的還有個 --proto_path暂衡,當(dāng)當(dāng)前的 proto 文件里 import 了不在當(dāng)前路徑的其它 proto 文件時,就需要額外指明崖瞭,比如
protoc --proto_path=$GOPATH/src:. --micro_out=. --go_out=. greeter.proto
# 表示搜索 $GOPATH/src 目錄及其子目錄狂巢。親測 windows 下這個 / 符號會導(dǎo)致出錯,:. 也會书聚,不清楚為什么唧领。

看一下 app.pb.go 里的部分關(guān)鍵代碼藻雌,其它方法被省略了。

type MobileApplication struct {
    AppName              string   `protobuf:"bytes,1,opt,name=appName,proto3" json:"appName,omitempty"`
    OtherInfo            string   `protobuf:"bytes,2,opt,name=otherInfo,proto3" json:"otherInfo,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

主要是反引號中間的部分斩个。這部分定義了 protobuf 基于字節(jié)做序列化和反序列化時候的元數(shù)據(jù)胯杭,以及 json 時候的元數(shù)據(jù),后面的 GORM 也是在這里定義受啥。這與 Java 的注解很像做个。

再來看一下 app.micro.go 里的部分關(guān)鍵代碼。

// Client API for App service
// 這部分代碼是給 RPC 客戶端的滚局,在我們的項目中居暖,也就是 api 模塊里的 client 里可以調(diào)用的接口。

type AppService interface {
    // 創(chuàng)建APP信息
    CreateAppInfo(ctx context.Context, in *MobileApplication, opts ...client.CallOption) (*AppResponse, error)
    // 更新APP信息
    UpdateAppInfo(ctx context.Context, in *MobileApplication, opts ...client.CallOption) (*AppResponse, error)
    // 刪除App信息
    DeleteAppInfo(ctx context.Context, in *MobileApplication, opts ...client.CallOption) (*AppResponse, error)
    // 查詢APP信息
    QueryAppInfo(ctx context.Context, in *AccessId, opts ...client.CallOption) (*AppResponse, error)
}

type appService struct {
    c    client.Client
    name string
}

/// 獲取一個 Service 實例
func NewAppService(name string, c client.Client) AppService {
    if c == nil {
        c = client.NewClient()
    }
    if len(name) == 0 {
        name = "xgtmp.srv.app"
    }
    return &appService{
        c:    c,
        name: name,
    }
}

// Server API for App service
// 這部分是服務(wù)需要實現(xiàn)的接口核畴,客戶端調(diào)用上面的接口膝但,rpc 到了服務(wù)端后,會實際調(diào)用下面的接口谤草,一一對應(yīng)跟束。
type AppHandler interface {
    // 創(chuàng)建APP信息
    CreateAppInfo(context.Context, *MobileApplication, *AppResponse) error
    // 更新APP信息
    UpdateAppInfo(context.Context, *MobileApplication, *AppResponse) error
    // 刪除App信息
    DeleteAppInfo(context.Context, *MobileApplication, *AppResponse) error
    // 查詢APP信息
    QueryAppInfo(context.Context, *AccessId, *AppResponse) error
}
// 服務(wù)端調(diào)用這個方法把一個 micro server 實例綁定到實現(xiàn)了 AppHandler 接口的 struct 上
func RegisterAppHandler(s server.Server, hdlr AppHandler, opts ...server.HandlerOption) error {
// 首先里面定義了一個私有的內(nèi)部接口 app,App 繼承 app丑孩。
    type app interface {
        CreateAppInfo(ctx context.Context, in *MobileApplication, out *AppResponse) error
        UpdateAppInfo(ctx context.Context, in *MobileApplication, out *AppResponse) error
        DeleteAppInfo(ctx context.Context, in *MobileApplication, out *AppResponse) error
        QueryAppInfo(ctx context.Context, in *AccessId, out *AppResponse) error
    }
    type App struct {
        app
    }
    // NewHandler 內(nèi)部做了大量的工作冀宴,通過反射創(chuàng)建了所需的元數(shù)據(jù)
    h := &appHandler{hdlr}
    return s.Handle(s.NewHandler(&App{h}, opts...))
}
// appHandler 繼承 AppHandler
type appHandler struct {
    AppHandler
}
// micro 的框架會調(diào)用 appHandler 的 這些方法,這些方法內(nèi)部本身并沒有定義什么內(nèi)容温学,
// 而是直接去調(diào) AppHandelr 里的對應(yīng)實現(xiàn)略贮,而這些實現(xiàn)在上面的 RegisterAppHandler 里由傳入的參數(shù)指定。
func (h *appHandler) CreateAppInfo(ctx context.Context, in *MobileApplication, out *AppResponse) error {
    return h.AppHandler.CreateAppInfo(ctx, in, out)
}

func (h *appHandler) UpdateAppInfo(ctx context.Context, in *MobileApplication, out *AppResponse) error {
    return h.AppHandler.UpdateAppInfo(ctx, in, out)
}

func (h *appHandler) DeleteAppInfo(ctx context.Context, in *MobileApplication, out *AppResponse) error {
    return h.AppHandler.DeleteAppInfo(ctx, in, out)
}

func (h *appHandler) QueryAppInfo(ctx context.Context, in *AccessId, out *AppResponse) error {
    return h.AppHandler.QueryAppInfo(ctx, in, out)
}

handler 模塊

看完了 proto 的生成文件可能還是一頭霧水仗岖,不知道接下來怎么做逃延,不要慌。
客戶端需要調(diào)用 RPC 服務(wù)轧拄,首先得聲明要調(diào)用哪個服務(wù)揽祥,通過服務(wù)治理軟件,也就是 consul 拿到具體得 ip:port檩电,然后聲明調(diào)用哪個接口拄丰,最后拿到返回結(jié)果。

在 api 模塊里定義 rpc_client.go

package client

import (
    "context"
    "fmt"
    goMicro "github.com/micro/go-micro"
    appProto "xg-temp/service/proto"
)

var apiService goMicro.Service // 一個全局變量保存初始化后的實例

var API_SERVICE_NAME = "xgtmp.srv.api"
var APP_SERVICE_NAME = "xgtmp.srv.app"

// 類似于靜態(tài)函數(shù)俐末,返回一個實例值料按。這里的單例與否需要上層來保證
// 這里是創(chuàng)建 api 服務(wù),也就是 api web service卓箫。
func InitApiService() {
    apiService = goMicro.NewService(
        goMicro.Name(API_SERVICE_NAME),
        goMicro.Version("latest"))
    apiService.Init()
}

// 將本地調(diào)用载矿,轉(zhuǎn)換成 rpc 調(diào)用。如果數(shù)據(jù)結(jié)構(gòu)不一致烹卒,這里會需要做數(shù)據(jù)結(jié)構(gòu)的轉(zhuǎn)換恢准,包括傳給 rpc 調(diào)用的參數(shù)魂挂,以及收到的返回結(jié)果。
func CreateAppInfo(mobileAppCreateRequest appProto.MobileApplication) * appProto.AppResponse {
    // 聲明要找哪個 RPC 服務(wù)馁筐,以及綁定到哪個 client 數(shù)據(jù)結(jié)構(gòu)上
    // apiService 做發(fā)起 rpc 請求的一方,所以是 client
    appService := appProto.NewAppService(APP_SERVICE_NAME, apiService.Client())
    fmt.Printf("Create RPC client for : %v", mobileAppCreateRequest)
    // 實際調(diào)用 rpc
    resp, err := appService.CreateAppInfo(context.TODO(), &mobileAppCreateRequest)
    if err != nil {
        println(err.Error())
    }
    return resp
}

服務(wù)端的 interface 已經(jīng)定義好了坠非,需要實現(xiàn)成具體的業(yè)務(wù)邏輯敏沉。go 里面的 OOP 很抽象,完全不像 Java 那樣清晰明顯炎码。實現(xiàn)一個接口盟迟,只需要包含所有函數(shù)且函數(shù)的名稱、入?yún)⒑统鰠⑼耆恢铝氏校退銓崿F(xiàn)了這個接口了攒菠。

定義一個 dao.go 文件,起名起的不好歉闰,因為這里應(yīng)該不只是 db 上的操作的辖众,更應(yīng)該是 service 層的東西。

type AppDao struct {
    DB *gogorm.DB // 數(shù)據(jù)庫鏡像實例和敬,類似于 SessionFactory
}

func (d *AppDao) CreateAppInfo(ctx context.Context, in *proto.MobileApplication, out *proto.AppResponse) error {
    fmt.Println("This is Cachhe and we are notified")
    // 模擬業(yè)務(wù)處理
    in.OtherInfo = "設(shè)置其它屬性"
    err := d.DB.Create(in).Error // 插入一條數(shù)據(jù)
    if err != nil {
        out.ErrMsg = err.Error()
        out.RetCode = 500
        println(err)
    } else {
        out.ErrMsg = "沒有錯誤啦"
        out.RetCode = 200
        out.App = in
    }
    // additional methods
    return nil
}

func (AppDao) UpdateAppInfo(context.Context, *proto.MobileApplication, *proto.AppResponse) error {
    panic("implement me")
}

func (AppDao) DeleteAppInfo(context.Context, *proto.MobileApplication, *proto.AppResponse) error {
    panic("implement me")
}

func (AppDao) QueryAppInfo(context.Context, *proto.AccessId, *proto.AppResponse) error {
    panic("implement me")
}

d.DB.Create(in) 能執(zhí)行成功凹炸,必須要先有數(shù)據(jù)庫、表昼弟、和數(shù)據(jù)庫連接啤它。在 Java 里,我們需要配置數(shù)據(jù)源舱痘、連接池等等变骡,go 里面也是一樣。

在 service 的根目錄創(chuàng)建一個 config.yml 文件

mysql:
  hostname: ""
  port: 3306
  user: "root"
  password: "123456"
  database: "xg"

在 common 目錄里創(chuàng)建 config.go

package common

import (
    "gopkg.in/yaml.v2"
    "io/ioutil"
    "os"
    "sync"
)

var m *AppConfig
var once sync.Once // go 的并發(fā)包 sync 里的工具類芭逝,保證某個操作只能執(zhí)行一次

type AppConfig struct {
    MySql struct{
        User string `yaml:"user"` // yaml 的元數(shù)據(jù)塌碌,定義了怎么解析 yaml 文件
        Host string `yaml:"hostname"`
        Port string `yaml:"port"`
        Password string `yaml:"password"`
        DBName string `yaml:"database"`
    }
}

// 單例模式
func CfgInstance() *AppConfig {
    once.Do(func() {
        m,_ = loadConf()
    })
    return m
}

func loadConf() (*AppConfig, error) {
    localConfPath := "C:\\Users\\CocoAdapter\\go\\src\\xg-temp\\service\\config.yaml"

    conf := &AppConfig{}
    yamlFile, err := ioutil.ReadFile(localConfPath)
    if err != nil {
        println("Error! Yaml file IOException: %s", err.Error())
        os.Exit(1)
    }
    // 從字節(jié)數(shù)組里反序列化成一個 conf 實例
    if err := yaml.Unmarshal(yamlFile, conf); err != nil {
        println("Error! Yaml unmarshal exception: %s", err.Error())
        os.Exit(1)
    }
    return conf, nil
}

在 handler 目錄里創(chuàng)建 database.go,定義數(shù)據(jù)庫連接

package handler

import (
    _ "github.com/go-sql-driver/mysql" // 這個意思是要執(zhí)行 mysql 該包下的文件里所有init()函數(shù)铝耻,但不會打包這個包誊爹,所以無法通過包名來調(diào)用包中的其他函數(shù)
    "github.com/jinzhu/gorm"
    "xg-temp/service/common"
)

func CreateMySqlConnection() (*gorm.DB, error) {
    host := common.CfgInstance().MySql.Host
    port := common.CfgInstance().MySql.Port
    if host == "" || host == "localhost" || host == "127.0.0.1"{
        host = ""
    } else {
        // 遠(yuǎn)程 ip
        host = "tcp(" + host + ":" + port + ")"
    }

    user := common.CfgInstance().MySql.User
    password := common.CfgInstance().MySql.Password
    dbName := common.CfgInstance().MySql.DBName

    return gorm.Open("mysql",
        user + ":" + password + "@" + host +"/" + dbName + "?charset=utf8&parseTime=true&loc=Local")
}

service.go

現(xiàn)在需要啟動這個 service 模塊。

package main

import (
    "fmt"
    "github.com/micro/go-micro"
    "github.com/micro/go-micro/registry"
    "github.com/micro/go-micro/registry/consul"
    "os"
    "time"
    "xg-temp/service/handler"
    proto "xg-temp/service/proto"
)

func main() {
    serviceName := "xgtmp.srv.app"

    db ,err := handler.CreateMySqlConnection()
    defer db.Close()

    if err != nil {
        println("conncet error: &s", err.Error())
        os.Exit(1)
    }

    appDao := &handler.AppDao{DB:db} // 將數(shù)據(jù)源傳給 service 對象
    // 這里是通過 gorm 的 DDL 功能瓢捉,直接創(chuàng)建表频丘。但是仍需要先手動建庫。
    // 假設(shè)要么所有表都存在要么都不存在
    if !appDao.DB.HasTable(&proto.MobileApplication{}) {
        if err := appDao.DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8",
    // gorm 能直接根據(jù) struct 生成對應(yīng)的表泡态,具體請見 gorm 文檔
            ).CreateTable(&proto.MobileApplication{}, &proto.ProductInfo{}, &proto.Product2App{},
            ).Error; err != nil {
                println("Error: Create table error")
                os.Exit(1)
            }
    }

    // 連接本地 consul
    opts := registry.Option(func(opts *registry.Options) {
        opts.Addrs = []string {fmt.Sprintf("%s:%d", "127.0.0.1", 8500)}
    })
    // New Service
    service := micro.NewService(
        micro.Name(serviceName),
        micro.Version("latest"),
        micro.RegisterTTL(time.Second * 10),
        micro.RegisterInterval(time.Second * 5),
        micro.Registry(consul.NewRegistry(opts)),
    )
    // Init & Binding handling
    service.Init()
    _ = proto.RegisterAppHandler(service.Server(), appDao)

    // Run Service
    if err := service.Run(); err != nil {
        println(err)
    }
}

一起 run 起來

api 和 service 是兩個模塊搂漠,都需要運行起來。在運行之前某弦,先要運行 consul桐汤,這樣服務(wù)才能注冊上去而克。啟動順序不重要,因為 api 模塊只有在被訪問的時候才會去調(diào) service 模塊怔毛。

在 http 文件里定義一個新的 POST 請求


查看 api 模塊的控制臺輸出


查看 service 模塊的控制臺輸出


查看數(shù)據(jù)庫


總結(jié)

本文介紹了怎樣使用 go 下面的一些框架和軟件來搭建一個微服務(wù)架構(gòu)的 Restful Service员萍。但是,各方面都沒有特別深入拣度,比如碎绎,沒有探索 consul 的原理和分布式生產(chǎn)環(huán)境下的配置方法,沒有探索 go-gin抗果、go-micro筋帖、gorm等的內(nèi)部原理,及其高級用法冤馏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末日麸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子逮光,更是在濱河造成了極大的恐慌代箭,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睦霎,死亡現(xiàn)場離奇詭異梢卸,居然都是意外死亡,警方通過查閱死者的電腦和手機副女,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門蛤高,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碑幅,你說我怎么就攤上這事戴陡。” “怎么了沟涨?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵恤批,是天一觀的道長。 經(jīng)常有香客問我裹赴,道長喜庞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任棋返,我火速辦了婚禮延都,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘睛竣。我一直安慰自己晰房,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著殊者,像睡著了一般与境。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猖吴,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天摔刁,我揣著相機與錄音,去河邊找鬼海蔽。 笑死簸搞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的准潭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼域仇,長吁一口氣:“原來是場噩夢啊……” “哼刑然!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起暇务,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤泼掠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后垦细,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體择镇,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年括改,在試婚紗的時候發(fā)現(xiàn)自己被綠了腻豌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡嘱能,死狀恐怖吝梅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惹骂,我是刑警寧澤苏携,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站对粪,受9級特大地震影響右冻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜著拭,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一纱扭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茫死,春花似錦跪但、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忆首。三九已至,卻和暖如春被环,著一層夾襖步出監(jiān)牢的瞬間糙及,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工筛欢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浸锨,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓版姑,卻偏偏與公主長得像柱搜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剥险,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 去年有段時間得空聪蘸,就把谷歌GAE的API權(quán)威指南看了一遍,收獲頗豐表制,特別是在自己幾乎獨立開發(fā)了公司的云數(shù)據(jù)中心之后...
    騎單車的勛爵閱讀 20,440評論 0 41
  • 翻譯查閱外網(wǎng)資料過程中遇到的比較優(yōu)秀的文章和資料健爬,一是作為技術(shù)參考以便日后查閱,二是訓(xùn)練英文能力么介。此文翻譯自 Pr...
    401閱讀 67,170評論 1 39
  • gRPC 是一個高性能娜遵、通用的開源RPC框架,基于HTTP/2協(xié)議標(biāo)準(zhǔn)和Protobuf序列化協(xié)議開發(fā)壤短,支持眾多的...
    小波同學(xué)閱讀 19,435評論 6 19
  • 1设拟、Micro是一個專注于簡化分布式系統(tǒng)開發(fā)的微服務(wù)生態(tài)系統(tǒng)。 2鸽扁、怎么使用micro 使用go-micro編寫一...
    __apple閱讀 7,471評論 1 2
  • 有時候node_modules包特別大蒜绽,目錄層級太深,windows系統(tǒng)刪除有時候還報沒有管理員權(quán)限的錯誤桶现。右鍵點...
    團貓咪愛吃玉米閱讀 24,062評論 0 6