Go是一种很好的通用语言,但是微服务需要一定量的专门支持。RPC安全性、系统可观察性、基础设施集成,甚至程序设计等都需要认真考虑。go-kit工具包填补了标准库留下的空白,使Go成为任何组织编写微服务的一流语言。为了快速入门,我们先用go-kit工具包实现一个简单的整数乘法计算服务。
项目的架构分层(官方推荐使用):
|─ go-kit-microservice. # 项目名称
├── cmd # 服务入口
│ ├── client ## 测试服务 Client
| ├── http_client.go
│ └── main.go ### 服务main函数入口
├── internal # 业务代码
│ ├── endpoint ## endpoint 层
| ├── endpoint.go
│ ├── service ## service 层, 用来存放我们的业务逻辑代码
| ├── service.go
│ └── transport ## transport 层
| ├── transport.go
首先,我们在 internal/service包下service.go 里面定义我们的服务接口,同时定义请求和响应的结构体,并且对Service接口进行了一个实现。
// service interface
type Service interface {
Multiply(ctx context.Context, in MultiplyRequest) MultiplyResponse
}
// request struct
type MultiplyRequest struct {
A int `json:"a"`
B int `json:"b"`
}
// response struct
type MultiplyResponse struct {
Res int `json:"res"`
}
// Service struct, has an implementation of Service interface
type baseService struct {
}
func NewService() Service {
return &baseService{}
}
func (bs baseService) Multiply(ctx context.Context, in MultiplyRequest) MultiplyResponse {
return MultiplyResponse{
Res: in.A * in.B,
}
}
go-kit通过一个抽象层EndPoint来提供它的大部分功能。这里定义的EndPoints对象就是我们暴露的一个出口,通过这个出口来暴露一个MultiplyEndPoint的端点提供了整数乘法计算的功能。
package endpoint
import (
"context"
"github.com/go-kit/kit/endpoint"
"go-kit-microservice/internal/service"
)
type EndPoints struct {
MultiplyEndPoint endpoint.Endpoint
}
func NewEndpoints(svc service.Service) EndPoints {
var multiplyEndpoint endpoint.Endpoint
{
multiplyEndpoint = makeMultiplyMultiplyEndPoint(svc)
}
return EndPoints{MultiplyEndPoint: multiplyEndpoint}
}
func makeMultiplyMultiplyEndPoint(s service.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(service.MultiplyRequest)
resp := s.Multiply(ctx, req)
return resp, nil
}
}
由于我们服务可能是给内部用的一些接口,也有可能是给外部调用接口,所以我们还需要一个transport层来抽象这一部分的细节。一般情况下,不同的协议其实只是在请求参数的解码和响应内容的编码上存在差异。 大部分情况可能我们的接口既要支持grpc, 又要支持使用http来访问。为了简单,我们的服务目前仅支持http来访问。
package transport
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
endpoints "go-kit-microservice/internal/endpoint"
"go-kit-microservice/internal/service"
"net/http"
"strconv"
)
func MewHttpHandler(endpoints endpoints.EndPoints) http.Handler {
options := []httptransport.ServerOption{
httptransport.ServerErrorEncoder(errorEncoder),
}
m := http.NewServeMux()
m.Handle("/multiply", httptransport.NewServer(
endpoints.MultiplyEndPoint,
decodeMultiplyRequest,
encodeMultiplyResponse,
options...,
))
return m
}
// decode the request to service layer request params
func decodeMultiplyRequest(_ context.Context, r *http.Request) (interface{}, error) {
var (
in service.MultiplyRequest
err error
)
if in.A, err = strconv.Atoi(r.FormValue("a")); err != nil {
return in, err
}
if in.B, err = strconv.Atoi(r.FormValue("b")); err != nil {
return in, err
}
return in, nil
}
// encode the response data to user
func encodeMultiplyResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if f, ok := response.(endpoint.Failer); ok && f.Failed() != nil {
errorEncoder(ctx, f.Failed(), w)
return nil
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
// error EncodeHandler
func errorEncoder(ctx context.Context, err error, w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
fmt.Println("response failed: ", err.Error())
e := json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()})
if e != nil {
fmt.Println("json encode failed: ", e.Error())
}
}
type errorWrapper struct {
Error string `json:"error"`
}
所有的细节都准备好了,我们只需要把服务注册到一个端口上跑起来就可以了
package main
import (
"fmt"
"github.com/oklog/oklog/pkg/group"
"go-kit-microservice/internal/endpoint"
"go-kit-microservice/internal/service"
"go-kit-microservice/internal/transport"
"net"
"net/http"
)
func main() {
svc := service.NewService()
endpoints := endpoint.NewEndpoints(svc)
httpHandler := transport.MewHttpHandler(endpoints)
var g group.Group
{
httpListener, err := net.Listen("tcp", ":8082")
if err != nil {
fmt.Println("http server start failed:" + err.Error())
}
g.Add(func() error {
return http.Serve(httpListener, httpHandler)
}, func(e error) {
err = httpListener.Close()
if err != nil {
fmt.Println("http server close failed", err.Error())
}
})
}
_ = g.Run()
}
go提倡我们为每个接口都写一个客户端去验证启动后接口的情况, 所以我在cmd/client下面新增了一个客户端代码,用来验证程序启动后实际的效果
package client
import (
"fmt"
"io/ioutil"
"net/http"
)
const baseUrl = "http://localhost:8081/"
func main() {
multiplyUrl := baseUrl + "multiply?a=4&b=9"
resp, err := http.Get(multiplyUrl)
if err != nil {
fmt.Println(err.Error())
} else {
resp := getHttpResponse(resp)
fmt.Println("get multiply result:" + resp)
}
}
func getHttpResponse(resp *http.Response) string {
data, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
fmt.Println(err.Error())
}
return string(data)
}
$ go build http_client.go
$ ls
http_client http_client.go
$ ./http_client
get multiply result:{"res":36}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。