0. Intro

这一系列blog主要是介绍如何实现一个简单的微服务框架,主要参考的对象是go-kit,更多的关注是实现的具体细节,所以有两个问题不会涉及:

  • Microservice vs Monolithic:这个话题已经太多了,从微服务出来的第一天关于这两者的对比就层出不穷。
  • 如何根据具体的业务划分微服务:其实这个才是微服务架构的核心,但是这个会依赖很多其他东西,比如具体的业务模型,团队水平等等,所以更多时候只能各自做取舍。

因为主要的目的是介绍一个微服务框架的组成部分以及具体的实现,所以就使用一个简单的blog做example。

1. Service

先简单写个基本的post service:

type Service interface{
  Create(req Request)(Response)
  Show(req Request)(Response)
}

虽然作为微服务系统来说,可以很灵活的选择语言和实现,只需要统一通信层即可,不过在一定程度上的一致还是可以帮助我们减少工作量的。所以我们先定义一个service func的标准:

type ServiceFunc func(Request) (Response)

这里的作用有点类似go-kit里面的endpoint,在go-kit的example中,是把service func跟endpoint分开实现的,service func完全自定义实现业务逻辑,使用endpoint包裹之后可以做中间层和对具体的传输协议做封装,不过这里暂时就只使用ServiceFunc来作为基础的标准。

2. Request & Response

这里的Request和Response虽然是叫req/res,其实就是统一的作为service func 的输入和输出,统一格式便于传输层处理。

type Request struct {
	Method   string // 具体调用的方法
	Protocol string // 传输层使用的协议
	Payload     []byte // 具体的参数
	TrackID       string // 用于在多个服务间追踪
}
type Response struct {
	Data interface{} // 正常时返回的数据
	Err  error // 出错时返回的报错
}

传输层的格式化主要使用json,这样在各个协议之间都比较轻松,不管是http还是rpc活着message bus,然后由于go作为一门强类型的语言,很难对参数做太多的统一处理,比如说下面格式的json:

{
  "age": 25, // int 类型会被unmarshal成float64
}
{
  "phone":1889999222,// 如果不小心没有加双引号,这个phone也会默认unmarshal成float64
}

所以在这种情况下我们保留payload作为[]byte这样在具体的业务层就可以直接unmarshal进自定义的struct。

不过在很多时候,比如说请求show接口,往往只需要一个id作为参数,所以我们还是简单的写一个helper来帮助处理一下参数:

func (r Request) ParseParams() (Params, error) {
	params := Params{}
	err := json.Unmarshal(r.Payload, &params.V)
	return params, err
}


type Params struct {
	V   map[string]interface{}
}

func (params Params) String(key string) (string, error) {
	if reflect.TypeOf(params.V[key]).Kind() == reflect.String {
		return params.V[key].(string), nil
	} else {
    // 其实很难做自动cast这种事,因为没办法确认一整套规则,所以并没有处理任何自动cast格式
		return "", errors.New(key+"type mismatch or value is nil")
	}
}

func (params Params) Slice(key string) ([]interface{}, error) {...}

func (params Params) Int(key string) (int, error) {...}

func (params Params) Float64(key string) float64 {...}

func (params Params) Map(key string) map[string]interface{} {...}

func (params Params) Bool(key string) (b bool, ok bool) {...}

3. Impl

接下来对Service接口实现一下具体的内容

type postService struct{}

func (postService) Create(req Request) (res Response) {
	var post Post
	err := json.Unmarshal(req.Payload, &post)
	if err != nil {
		res.Err = err
		return
	}
	err = store.Save(&post)
	if err != nil {
		res.Err = err
		return
	}
	res.Data = post
	return
}
func (postService) Show(req Request) (res Response) {
	var post Post
	params, err := req.ParseParams()
	if err != nil {
		res.Err = err
		return
	}
	id, err := params.String("id")
	if err != nil {
		res.Err = err
		return
	}
	store.First(id,&post)
	res.Data = post
	return
}

这里面的store是跟db或者其他存储沟通的一个接口,并不是微服务的一部分,之后还会介绍一下在微服务中处理数据的一些问题,不过跟具体的数据库沟通跟其他架构并无区别,根据自己喜好选择即可。

4. 其他

在go-kit的demo中,go-kit是把具体的service func和endpoint分开的,当然也可以直接以endpoint作为service func,这样的好处是又抽象出了一层可以做一些其他的功能,而且在go-kit中是以endpoint作为基本单位的,也就是说可以根据不同的协议分开提供方法,比如说有5个endpoint,可以让4个支持http而最后一个只支持rpc。

还有另一点对比就是其他几个微服务框架对于service参数的处理,主要有以下几种:

  • 使用自定义struct
  • 使用protobuf2定义
  • 根据顺序处理

最后就是我们并没有对传输的内容进行验证,比如说必须有哪些field以及field具体的格式,这部分由业务逻辑自己判断处理。