編寫Golang Function
編寫Golang Function
本篇是指導(dǎo)大家使用go-micro的Function功能,F(xiàn)unction是執(zhí)行一次的服務(wù)。(譯者按:這里Function并不等同與平常我們編寫的函數(shù),而是只執(zhí)行一次的服務(wù)所以我沒有直接翻譯,以免引起誤解)
如果想先從更高的角度了解相關(guān)的工具集,可以查看博客https://micro.mu/blog/2016/03/20/micro.html。
先寫一個Function
Function作為頂級的接口,它是go-micro中函數(shù)式編程模型主要組件。它封裝服務(wù)接口,并提供執(zhí)行一次函數(shù)的能力。
// Function 是只執(zhí)行一次的函數(shù)
type Function interface {
// Inherits Service interface
Service
// Done signals to complete execution
Done() error
// Handle registers an RPC handler
Handle(v interface{}) error
// Subscribe registers a subscriber
Subscribe(topic string, v interface{}) error
}
1. 初始化
Function使用micro.NewFunction構(gòu)建。
import "github.com/micro/go-micro"
function := micro.NewFunction()
構(gòu)建時也可以傳入選項參數(shù)。
function := micro.NewFunction(
micro.Name("greeter"),
micro.Version("latest"),
)
可選參數(shù)參考。
Go micro也可以通過micro.Flags解析命令行的傳參。
import (
"github.com/micro/cli"
"github.com/micro/go-micro"
)
function := micro.NewFunction(
micro.Flags(
cli.StringFlag{
Name: "environment",
Usage: "The environment",
},
)
)
命令行標(biāo)記參數(shù)可以使用function.Init解析。增加參數(shù)可以使用micro.Action。
function.Init(
micro.Action(func(c *cli.Context) {
env := c.StringFlag("environment")
if len(env) > 0 {
fmt.Println("Environment set to", env)
}
}),
)
Go Micro提供了一些預(yù)定義的參數(shù)標(biāo)記,這些標(biāo)記在執(zhí)行function.Init時解析。所有預(yù)定義的標(biāo)記參數(shù)可以參考。
2. 定義API
我們使用protobuf文件來定義服務(wù)的API接口。使用protobuf可以非常方便去嚴(yán)格定義API,提供服務(wù)端與客戶端雙邊具體一致的類型。
greeter.proto
syntax = "proto3";
service Greeter {
rpc Hello(HelloRequest) returns (HelloResponse) {}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string greeting = 2;
}
我們定義了一個服務(wù)叫做Greeter的Function處理器,它有一個接收HelloRequest并返回HelloResponse的Hello方法。
3. 生成API接口
我們需要protoc和protoc-gen-go來生成protobuf代碼文件,它們負(fù)責(zé)生成定義的go代碼實現(xiàn)。
Go-micro使用代碼生成器生成客戶端存根方法,這樣可以像gRPC減少模板方法。這一步需要golang/protobuffork出來的插件github.com/micro/protobuf.
go get github.com/micro/protobuf/{proto,protoc-gen-go}
protoc --go_out=plugins=micro:. greeter.proto
生成的類現(xiàn)在可以引入handler中,在服務(wù)或客戶端來創(chuàng)建請求了。
下面是代碼生成器生成的一部分代碼。
type HelloRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
type HelloResponse struct {
Greeting string `protobuf:"bytes,2,opt,name=greeting" json:"greeting,omitempty"`
}
// 定義Greeter客戶端的接口
type GreeterClient interface {
Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error)
}
type greeterClient struct {
c client.Client
serviceName string
}
func NewGreeterClient(serviceName string, c client.Client) GreeterClient {
if c == nil {
c = client.NewClient()
}
if len(serviceName) == 0 {
serviceName = "greeter"
}
return &greeterClient{
c: c,
serviceName: serviceName,
}
}
func (c *greeterClient) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) {
req := c.c.NewRequest(c.serviceName, "Greeter.Hello", in)
out := new(HelloResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Greeter的服務(wù)端API
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler) {
s.Handle(s.NewHandler(&Greeter{hdlr}))
}
4. 實現(xiàn)處理器
服務(wù)端需要注冊handlers,這樣才能提供服務(wù)并接收請求。處理器相當(dāng)于是一個擁有公共方法的公共類,它需要符合簽名func(ctx context.Context, req interface{}, rsp interface{}) error。
通過上面的內(nèi)容,我們看到,Greeter interface的簽名的看上去就是這樣:
type GreeterHandler interface {
Hello(context.Context, *HelloRequest, *HelloResponse) error
}
Greeter處理器實現(xiàn)。
import proto "github.com/micro/examples/service/proto"
type Greeter struct{}
func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
rsp.Greeting = "Hello " + req.Name
return nil
}
處理器注冊過程和http.Handler很像。
function := micro.NewFunction(
micro.Name("greeter"),
)
proto.RegisterGreeterHandler(service.Server(), new(Greeter))
另外,F(xiàn)unction接口也提供更簡單的注冊方式。
function := micro.NewFunction(
micro.Name("greeter"),
)
function.Handle(new(Greeter))
也可以使用Subscribe方法注冊成異步的訂閱者。
5. 運行Function
運行Function可以通過function.Run。這樣它會綁定到配置中指定的地址(默認(rèn)使用RFC1918規(guī)則來分配并生成隨機(jī)端口),然后開始偵聽端口。
另外,這一步會在服務(wù)啟動時向注冊中心注冊,并在服務(wù)接收到關(guān)閉信號時卸載。
if err := function.Run(); err != nil {
log.Fatal(err)
}
有接受服務(wù)請求后,這人Function就會退出??梢允褂?a target="_blank" rel="nofollow">micro run 來管理Funtion的生命周期。完整的例子查看:examples/function.
6. 完整的函數(shù)
greeter.go
package main
import (
"log"
"github.com/micro/go-micro"
proto "github.com/micro/examples/function/proto"
"golang.org/x/net/context"
)
type Greeter struct{}
func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
rsp.Greeting = "Hello " + req.Name
return nil
}
func main() {
function := micro.NewFunction(
micro.Name("greeter"),
micro.Version("latest"),
)
function.Init()
function.Handle(new(Greeter))
if err := function.Run(); err != nil {
log.Fatal(err)
}
}
需要注意的是,要保證服務(wù)發(fā)現(xiàn)機(jī)制運行起來,這樣服務(wù)才能注冊,其它服務(wù)或客戶端才能發(fā)現(xiàn)它。快速啟動可參考。
編寫客戶端
客戶端包用于查詢服務(wù),當(dāng)創(chuàng)建服務(wù)時,也包含了一個客戶端,這個客戶端匹配服務(wù)所使用的初始化包。
查詢上面的服務(wù)很簡單:
// 創(chuàng)建greate客戶端,這需要傳入服務(wù)名與服務(wù)的客戶端方法構(gòu)建的客戶端對象
greeter := proto.NewGreeterClient("greeter", function.Client())
// 在Greeter handler上請求調(diào)用Hello方法
rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{
Name: "John",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(rsp.Greeter)
proto.NewGreeterClient 需要Function名與客戶端來請求服務(wù)。
完整例子可查看go-micro/examples/function.