前后端数据常用传输格式有:json、xml 和 proto等,不管是mvc还是ddd,都会在表现层的对不同的格式进行转化下层依赖所需的类、Object、 aggregate等。
Kratos 表现层
在实际开发场景中,前后端传输采用json。 但kratos使用proto定义Api, 那么我们以一个Http请求为例,来看看kratos的表现层是如何处理的。
以下是一个Kratos-Example,由编写好的proto文件,proto-go 等自动生成表现层的代码。 大概步骤:
- 编写proto,包括Service,Req,Reply
- 生成表现层代码
- 实现下层逻辑等
Example完整代码如下
func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error {
return func(ctx http.Context) error {
var in HelloRequest
if err := ctx.BindQuery(&in); err != nil {
return err
}
if err := ctx.BindVars(&in); err != nil {
return err
}
http.SetOperation(ctx, "/helloworld.v1.Greeter/SayHello")
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.SayHello(ctx, req.(*HelloRequest))
})
out, err := h(ctx, &in)
if err != nil {
return err
}
reply := out.(*HelloReply)
return ctx.Result(200, reply)
}
}
可以看到:
- 这里对具体的格式无感,输入In还是输出Out都是一个proto生成的类。
- 具体的实现交给了ctx 上下文去实现 查看 ctx.Result的源码:
func (c *wrapper) Result(code int, v interface{}) error {
c.w.WriteHeader(code)
return c.router.srv.enc(&c.w, c.req, v)
}
这里的enc,没有特殊设置,使用默认的DefaultResponseEncoder,部分源码如下
func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
...
codec, _ := CodecForRequest(r, "Accept")
data, err := codec.Marshal(v)
if err != nil {
return err
}
w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
_, err = w.Write(data)
...
}
通过codc接口隔离具体实现,调用接口的 codec.Marshal序列化。
codec包
Codec定义Transport用于编码和解码消息的接口。它的实现包括 json,xml,proto,yaml等
type Codec interface {
// Marshal returns the wire format of v.
Marshal(v interface{}) ([]byte, error)
// Unmarshal parses the wire format into v.
Unmarshal(data []byte, v interface{}) error
// Name returns the name of the Codec implementation. The returned string
// will be used as part of content type in transmission. The result must be
// static; the result cannot change between calls.
Name() string
}
这里我们聚焦正在使用的Json
type codec struct{}
func (codec) Marshal(v interface{}) ([]byte, error) {
switch m := v.(type) {
case json.Marshaler:
return m.MarshalJSON()
case proto.Message:
return MarshalOptions.Marshal(m)
default:
return json.Marshal(m)
}
}
这里Marshal()使用第三方库““google.golang.org/protobuf/encoding/protojson”
实现proto转json。
protojson第三方包
protojson是Google提供的proto和json的转化
package api_test
import (
"google.golang.org/protobuf/encoding/protojson"
"testing"
v1 "helloworld/api/helloworld/v1"
)
func TestToJson(t *testing.T) {
reply := &v1.HelloReply{
Message: "Jobs",
}
replyJson, err := MarshalOptions.Marshal(reply.ProtoReflect().Interface())
if err != nil {
t.Errorf("Marshal Error: %v", err)
}
t.Logf("replyJson: %v", string(replyJson))
}
更多的接口请参考“https://google.golang.org/protobuf/encoding/protojson”
小结
本文主要以kratos的表现层的组织方式,来介绍常用的框架表现层处理方式。 其中:
- kratos通过proto来生成表现层代码的类,包括http和grapc,内部对具体实现无感。
- kratos通过解码器codec实现不同传输格式的解耦。
- 第三方包protojson实现proto和json转换。
框架的主要功能之一就是标准化处理一下公共的问题场景,从源码中可以学习:通过接口隔离具体的实现,而使框架与具体实现解耦,且具备更好的拓展性。