這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。在本系列的[前一部分中](https://studygolang.com/articles/12485),我們談到了使用者認證和 JWT。在這一部分中,我們將快速探索 go-micro 的代理功能。正如前面的文章提到的,go-micro 是一個可插拔的架構,它串連了許多不同的常用技術。如果你看看[外掛程式倉庫](https://github.com/micro/go-plugins),你會看到它支援多少外掛程式。在我們的例子中,我們將使用 NATS 代理外掛程式。## 基於事件驅動的架構[事件驅動的架構](https://en.wikipedia.org/wiki/Event-driven_architecture)是一個非常簡單的概念。我們通常認為好的架構是要解耦的,一個服務不應該與其他服務耦合或者感知到其他服務。當我們使用諸如 `gRPC` 協議時,在某些情況下是正確的,我們以向 `go.srv.user-service` 發布請求為例。其中就使用了服務發現的方式來尋找該服務的實際位置。 儘管這並不直接將我們與實現耦合,但它確實將該服務耦合到了其他名為 `go.srv.user-service` 的服務,因此它不是完全的解耦,因為它直接與其他服務進行互動。那麼什麼讓事件驅動架構真正的解耦呢?為了理解這一點,我們首先看看發布和訂閱事件的過程。服務 a 完成了一項任務 x,然後向系統發布一個事件 `x 剛剛發生了`。服務並不需要知道或者關心誰在監聽這個事件,或者該事件正在發生什麼影響。這些事情留給了監聽事件的用戶端。如果你期待 n 個服務對某個事件採取行動,那麼也很容易。例如,你想 12 個不同的服務針對使用 `gRPC` 建立新使用者採取行動,可能需要在使用者服務中執行個體化 12 個用戶端。而藉助事件發布訂閱或事件驅動架構,你的服務就不需要關心這些。現在,用戶端服務只需要簡單的監聽事件。這意味著,你需要中間的介質來接收這些事件,並通知訂閱了事件的用戶端。這篇文章中,我們將在每次建立使用者時建立一個事件,並且將建立一個用於寄送電子郵件的新服務。我們不會真的去實現發郵件的功能,只是類比它。## 代碼首先,我們需要將 NATS 代理外掛程式整合到我們的使用者服務中:```go// shippy-user-service/main.gofunc main() {... // Init will parse the command line flags.srv.Init()// Get instance of the broker using our defaultspubsub := srv.Server().Options().Broker// Register handlerpb.RegisterUserServiceHandler(srv.Server(), &service{repo, tokenService, pubsub})...}```現在讓我們在建立新使用者時發布事件([請參閱此處的完整更改](https://github.com/EwanValentine/shippy-user-service/tree/tutorial-5))```go// shippy-user-service/handler.goconst topic = "user.created"type service struct {repo RepositorytokenService AuthablePubSub broker.Broker}...func (srv *service) Create(ctx context.Context, req *pb.User, res *pb.Response) error {// Generates a hashed version of our passwordhashedPass, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)if err != nil {return err}req.Password = string(hashedPass)if err := srv.repo.Create(req); err != nil {return err}res.User = reqif err := srv.publishEvent(req); err != nil {return err}return nil}func (srv *service) publishEvent(user *pb.User) error {// Marshal to JSON stringbody, err := json.Marshal(user)if err != nil {return err}// Create a broker messagemsg := &broker.Message{Header: map[string]string{"id": user.Id,},Body: body,}// Publish message to brokerif err := srv.PubSub.Publish(topic, msg); err != nil {log.Printf("[pub] failed: %v", err)}return nil}...```確保你正在運行 Postgres,然後讓我們運行這個服務:```shell$ docker run -d -p 5432:5432 postgres$ make build$ make run```現在我們建立我們的電子郵件服務。 我為此建立了一個[新的倉庫](https://github.com/EwanValentine/shippy-email-service):```go// shippy-email-servicepackage mainimport ("encoding/json""log"pb "github.com/EwanValentine/shippy-user-service/proto/user"micro "github.com/micro/go-micro""github.com/micro/go-micro/broker"_ "github.com/micro/go-plugins/broker/nats")const topic = "user.created"func main() {srv := micro.NewService(micro.Name("go.micro.srv.email"),micro.Version("latest"),)srv.Init()// Get the broker instance using our environment variablespubsub := srv.Server().Options().Brokerif err := pubsub.Connect(); err != nil {log.Fatal(err)}// Subscribe to messages on the broker_, err := pubsub.Subscribe(topic, func(p broker.Publication) error {var user *pb.Userif err := json.Unmarshal(p.Message().Body, &user); err != nil {return err}log.Println(user)go sendEmail(user)return nil})if err != nil {log.Println(err)}// Run the serverif err := srv.Run(); err != nil {log.Println(err)}}func sendEmail(user *pb.User) error {log.Println("Sending email to:", user.Name)return nil}```在運行之前,我們需要啟動 [NATS](https://nats.io/)...```$ docker run -d -p 4222:4222 nats```另外,我想快速解釋一下 go-micro 的一部分,我覺得這對於理解它作為架構如何工作很重要。你會注意到:```gosrv.Init()pubsub := srv.Server().Options().Broker```讓我們來快速探索一下。當我們用 go-micro 建立服務時,`srv.Init()` 會自動去尋找所有的配置,例如所有配置的外掛程式、環境變數或命令列選項。它將會將這些整合執行個體化為服務的一部分。為了使用這些執行個體,我們需要將它們從服務中提取出來。在 `srv.Server().Options()` 中,你還可以找到 Transport (go-micro 架構的一個核心組件,傳輸是服務之間的同步請求/響應通訊的介面) 和 Registry (go-micro 架構的一個核心組件,叫註冊表,提供了一個服務發現機制來將名稱解析為地址)。在我們的例子中,會用到 `GO_MICRO_BROKER` 環境變數,會用到 `NATS` 代理外掛程式,並建立一個該外掛程式的執行個體,準備好我們串連和使用。如果你正在建立一個命令列工具,你可以使用 `cmd.Init()`,確保你匯入了 `github.com/micro/go-micro/cmd`。這會產生同樣的影響。現在構建並運行此服務:`$ make build && make run`,確保你也在運行使用者服務。然後轉到 `shippy-user-cli` 項目,並運行 `$ make run`,看我們的電子郵件服務輸出。你應該看到類似... `2017/12/26 23:57:23 Sending email to: Ewan Valentine`就是這樣!這是一個簡單的例子,因為我們的電子郵件服務隱式地收聽單個 `user.created` 事件,但希望你能看到這種方法如何讓你編寫解耦的服務。值得一提的是,使用 JSON over NATS 會比 gRPC 帶來更高的效能開銷,因為我們已經回到序列化json字串的領域。但是,對於某些使用方式,這是完全可以接受的。 NATS 非常高效,非常適合訊息最多交付一次的事件(fire and forget 有訊息最多交付一次的意思,這個[連結](http://www.enterpriseintegrationpatterns.com/patterns/conversation/FireAndForget.html)可以協助做更深入的理解)。Go-micro 還支援一些最廣泛使用的隊列 / pubsub 技術供你使用。[你可以在這裡看到它們的列表](https://github.com/micro/go-plugins/tree/master/broker)。你不需要改變你的實現因為 go-micro 為你提供了抽象。你只需要將環境變數從 `MICRO_BROKER=nats` 更改為 `MICRO_BROKER=googlepubsub`,然後將 main.go 的匯入從 `_ "github.com/micro/go-plugins/broker/nats"` 更改為 `_ "github.com/micro/go-plugins/broker/googlepubsub"`。如果你不使用 go-micro,那麼有一個 [NATS go 庫](https://github.com/nats-io/go-nats)(NATS 是用 go 寫的,所以對 Go 的支援非常穩固)。發布一個事件:```gonc, _ := nats.Connect(nats.DefaultURL)// Simple Publishernc.Publish("user.created", userJsonString)```訂閱一個事件:```go// Simple Async Subscribernc.Subscribe("user.created", func(m *nats.Msg) {user := convertUserString(m.Data)go sendEmail(user)})```我之前提到過,在使用第三方訊息代理(如 NATS)時,會失去對 protobuf 的使用。這是一種恥辱,因為我們失去了使用二進位流進行通訊的能力,這當然比序列化的 JSON 字串的開銷要低得多。 但是,像大多數人所關心的那樣,go-micro 也可以解決這個問題。內建 go-micro 是 pubsub 層,位於代理層之上,但不需要第三方代理(如 NATS)。 但是這個功能真正棒的部分在於它利用了 protobuf 的定義。 所以我們回到了低延遲二進位流的領域。 因此,讓我們更新我們的使用者服務,用 go-micro 的 pubsub 替換現有的 NATS 代理:```go// shippy-user-service/main.gofunc main() {...publisher := micro.NewPublisher("user.created", srv.Client())// Register handlerpb.RegisterUserServiceHandler(srv.Server(), &service{repo, tokenService, publisher})...}``````go// shippy-user-service/handler.gofunc (srv *service) Create(ctx context.Context, req *pb.User, res *pb.Response) error {// Generates a hashed version of our passwordhashedPass, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)if err != nil {return err}req.Password = string(hashedPass)// Here's our new publisher code, much simplerif err := srv.repo.Create(req); err != nil {return err}res.User = reqif err := srv.Publisher.Publish(ctx, req); err != nil {return err}return nil}```現在我們的郵件服務是這樣的:```go// shippy-email-serviceconst topic = "user.created"type Subscriber struct{}func (sub *Subscriber) Process(ctx context.Context, user *pb.User) error {log.Println("Picked up a new message")log.Println("Sending email to:", user.Name)return nil}func main() {...micro.RegisterSubscriber(topic, srv.Server(), new(Subscriber))...}```現在我們在我們的服務中使用我們的底層 User protobuf 定義,通過 gRPC,並且不使用第三方代理。太棒了!這是一個封裝! 接下來的教程我們將著眼於為我們的服務建立一個使用者介面,並研究 網頁用戶端如何開始與我們的服務進行互動。本文中的任何錯誤、反饋,或任何您會發現有用的東西,請給我發[電子郵件](ewan.valentine89@gmail.com)。
via: https://ewanvalentine.io/microservices-in-golang-part-5/
作者:André Carvalho 譯者:shniu 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
1282 次點擊