Skip to content

Commit

Permalink
doc: improve document for api plugin (#429)
Browse files Browse the repository at this point in the history
  • Loading branch information
seeflood authored Mar 22, 2022
1 parent 891c928 commit 428540f
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 37 deletions.
2 changes: 1 addition & 1 deletion cmd/layotto/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp
}
return actuator.NewGrpcServerWithActuator(server)
}),
// register your grpc API here
// register your gRPC API here
runtime.WithGrpcAPI(
default_api.NewGrpcAPI,
),
Expand Down
6 changes: 3 additions & 3 deletions cmd/layotto_multiple_api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,11 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp
}
return actuator.NewGrpcServerWithActuator(server)
}),
// register your grpc API here
// register your gRPC API here
runtime.WithGrpcAPI(
// default grpc API
// default GrpcAPI
default_api.NewGrpcAPI,
// a demo to show how to register your own API
// a demo to show how to register your own gRPC API
helloworld_api.NewHelloWorldAPI,
// support Dapr API
// Currently it only support Dapr's InvokeService and InvokeBinding API.
Expand Down
4 changes: 3 additions & 1 deletion docs/zh/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
- [Pub/Sub API](zh/building_blocks/pubsub/reference.md)
- [Configuration API](zh/building_blocks/configuration/reference.md)
- [RPC API](zh/building_blocks/rpc/reference.md)
- 可扩展性
- [API插件](zh/design/api_plugin/design.md)
- [grpc API 文档](https://github.com/mosn/layotto/blob/main/docs/en/api_reference/api_reference_v1.md)
- SDK文档
- [java sdk](https://github.com/layotto/java-sdk)
Expand Down Expand Up @@ -72,7 +74,7 @@
- [RPC设计文档](zh/design/rpc/rpc设计文档.md)
- [分布式锁API设计文档](zh/design/lock/lock-api-design.md)
- [Sequencer API设计文档](zh/design/sequencer/design.md)
- [file API设计文档](zh/design/file/file-design.md)
- [File API设计文档](zh/design/file/file-design.md)
- [FaaS 设计文档](zh/design/faas/faas-poc-design.md)
- [API插件](zh/design/api_plugin/design.md)
- [支持Dapr API](zh/design/api_plugin/dapr_api.md)
Expand Down
192 changes: 161 additions & 31 deletions docs/zh/design/api_plugin/design.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Hierarchical API design
# API 插件设计文档 & 使用指南
本文前半部分讨论 API 插件解决什么问题、为什么这样设计,后半部分介绍 API 插件怎么用。

如果您只关心 API 插件的用法,可以直接跳到 [2.4. 使用指南](zh/design/api_plugin/design?id=_24-使用指南)

## 1. 需求分析
### 1.1. 解决什么问题
解决扩展性问题。不管是Dapr还是开源Layotto的API,目前都无法完全满足生产需求。
Expand Down Expand Up @@ -45,52 +49,162 @@ Dapr的扩展性是通过Binding API解决,但是这种非结构化的API有


### 2.2. 设计目标
1. 让有定制开发需求的开源用户通过import Layotto的方式使用Layotto,而不是Fork
1. 让有定制开发需求的开源用户通过 import Layotto 的方式使用 Layotto,而不是 Fork

2. 开发api plugin足够简单
2. 开发 api plugin 足够简单

3. 配置文件公用同一个json,新增api plugin无需新增配置文件
3. 配置文件公用同一个 json ,新增 api plugin 无需新增配置文件

### 2.3. 用户扩展开发时的编程界面
### 2.3. 功能设计

![image](https://user-images.githubusercontent.com/26001097/131614952-ccfc7d11-d376-41b0-b16c-2f17bfd2c9fc.png)

#### step 1. 实现自己的私有API
Layotto 新增若干扩展点。

企业用户在使用 Layotto 时,可以自己维护一个项目、import Layotto。自己的项目里存放各种扩展插件、组件。如果您熟悉 Java,这就类似于 Java 社区想要用 [Eureka](https://github.com/Netflix/eureka) 时,可以 import Eureka、然后做扩展。

当用户想新增一类 API 时,可以在自己的项目里开发一个 package(包括自己的 proto,pb文件,自己的 API实现),然后在 `main.go` 里调用 Layotto 的扩展点、将自己的 API 注册进 Layotto。

### 2.4. 使用指南
如何添加自己的 proto、添加自己的私有API?

一个示例是 [项目中提供的 helloworld 包](https://github.com/mosn/layotto/tree/main/cmd/layotto_multiple_api/helloworld) ,实现了自定义的 API, `SayHello`

以此为例,解释下写 API 插件的步骤:

#### step 0. 定义自己的 proto 文件、编译成 pb
比如用户想新增一个自己的 `Greeter` API,提供 `SayHello` 方法,那么需要先写个 proto:
(这个例子是我从 [grpc 官方示例](https://github.com/grpc/grpc-go/blob/master/examples/helloworld/helloworld/helloworld.proto) 粘贴过来的)
```protobuf
syntax = "proto3";
option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
如果需要自己的私有API,用户需要实现GrpcAPI interface,以及相应的构造函数。
// The response message containing the greetings
message HelloReply {
string message = 1;
}
```

然后把它编译成 `.pb.go` 文件。

[项目中提供的 helloworld 示例包](https://github.com/mosn/layotto/tree/main/cmd/layotto_multiple_api/helloworld) 偷了个懒,直接 import 了 grpc 官方编译好的 .pb.go 文件:

<img src="https://gw.alipayobjects.com/mdn/rms_5891a1/afts/img/A*9VnARJimj90AAAAAAAAAAAAAARQnAQ" width = "40%" height = "40%" alt="score" align=center />

#### step 1. 为刚才定义的 API 编写实现
protoc 编译工具会根据 proto 文件帮你编译出 go 语言的 interface `helloworld.GreeterServer`,但是 interface 的具体实现还是需要自己写。

比如,示例中我们编写的 `server` 实现了 `helloworld.GreeterServer` interface, 有 `SayHello` 方法:

```go
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
```

这个GrpcAPI就是您自己的API,它需要实现一些生命周期管理方法。目前只定义了Init和Register。
#### step 2. 实现 [`GrpcAPI` interface](https://github.com/mosn/layotto/blob/main/pkg/grpc/grpc_api.go) ,管理 API 插件的生命周期
现在你已经有了自己的 API 实现,下一步需要把它注册到 Layotto 上。
> **回忆一下**:如何把 API 注册到原生的 grpc server 上?
>
> 只需要写这么一行代码:
>
> pb.RegisterGreeterServer(s, &server{})
想要把自己的 API 注册到 Layotto 上,需要:

- 实现 [`GrpcAPI` interface](https://github.com/mosn/layotto/blob/main/pkg/grpc/grpc_api.go) 、实现一些生命周期钩子

这个 GrpcAPI 负责管理您的 API 的生命周期、提供了各种生命周期钩子。目前生命周期钩子有 Init 和 Register 。

```go
// GrpcAPI is the interface of API plugin. It has lifecycle related methods
type GrpcAPI interface {
// init this API before binding it to the grpc server. For example,you can call app to query their subscriptions.
Init(conn *grpc.ClientConn) error
// Bind this API to the grpc server
Register(s *grpc.Server, registeredServer mgrpc.RegisteredServer) mgrpc.RegisteredServer
// init this API before binding it to the grpc server.
// For example,you can call app to query their subscriptions.
Init(conn *grpc.ClientConn) error

// Bind this API to the grpc server
Register(s *grpc.Server, registeredServer mgrpc.RegisteredServer) (mgrpc.RegisteredServer, error)
}
```

- 实现相应的构造函数 `NewGrpcAPI`,用来创建你的 `GrpcAPI`
```go
// NewGrpcAPI is the constructor of GrpcAPI
type NewGrpcAPI func(
appId string,
hellos map[string]hello.HelloService,
configStores map[string]configstores.Store,
rpcs map[string]rpc.Invoker,
pubSubs map[string]pubsub.PubSub,
stateStores map[string]state.Store,
files map[string]file.File,
lockStores map[string]lock.LockStore,
sequencers map[string]sequencer.Store,
sendToOutputBindingFn func(name string, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error),
) GrpcAPI
type NewGrpcAPI func(applicationContext *ApplicationContext) GrpcAPI

// ApplicationContext contains all you need to construct your GrpcAPI, such as all the components.
// For example, your `SuperState` GrpcAPI can hold the `StateStores` components and use them to implement your own `Super State API` logic.
type ApplicationContext struct {
AppId string
Hellos map[string]hello.HelloService
ConfigStores map[string]configstores.Store
Rpcs map[string]rpc.Invoker
PubSubs map[string]pubsub.PubSub
StateStores map[string]state.Store
Files map[string]file.File
LockStores map[string]lock.LockStore
Sequencers map[string]sequencer.Store
SendToOutputBindingFn func(name string, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error)
SecretStores map[string]secretstores.SecretStore
}
```

举个例子,在[helloworld 示例中](https://github.com/mosn/layotto/blob/main/cmd/layotto_multiple_api/helloworld/grpc_api.go), `*server` 实现了 `Init``Register` 方法:

```go
func (s *server) Init(conn *rawGRPC.ClientConn) error {
return nil
}

func (s *server) Register(grpcServer *rawGRPC.Server, registeredServer mgrpc.RegisteredServer) (mgrpc.RegisteredServer, error) {
pb.RegisterGreeterServer(grpcServer, s)
return registeredServer, nil
}
```

也有相应的构造函数:

```go
func NewHelloWorldAPI(ac *grpc_api.ApplicationContext) grpc.GrpcAPI {
return &server{}
}
```

#### step 2. 将自己的API注册进Layotto
##### `GrpcAPI` 的生命周期
Layotto 会在启动过程中回调上述生命周期钩子和构造函数。调用顺序大致为:

用户可以把Layotto的main复制粘贴出来,按需修改,去掉用不到的东西(比如用不到etcd的分布式锁组件,可以在自己的main里删掉它)
`Layotto 初始化好所有组件` ---> 调用`NewGrpcAPI`构造函数 ---> `GrpcAPI.Init` ---> `Layotto 创建 grpc 服务器` ---> `GrpcAPI.Register`

如果用户写了自己的API,可以在main里将它注册进Layotto
图示如下:

<img src="https://gw.alipayobjects.com/mdn/rms_5891a1/afts/img/A*7_NyQL-FjigAAAAAAAAAAAAAARQnAQ" width = "40%" height = "40%" alt="score" align=center />

#### step 3. 将自己的 API 注册进Layotto
按照上文的步骤实现自己的私有 API 后,可以[在 main 里将它注册进Layotto](https://github.com/mosn/layotto/blob/5234a80cdc97798162d03546eb8e0ee163c0ad60/cmd/layotto_multiple_api/main.go#L203) :

```go

Expand All @@ -100,11 +214,11 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp
// 3. run
server, err := rt.Run(
runtime.WithGrpcOptions(opts...),
// register your grpc API here
// register your GrpcAPI here
runtime.WithGrpcAPI(
// default grpc API
// default GrpcAPI
default_api.NewGrpcAPI,
// a demo to show how to register your own API
// a demo to show how to register your own GrpcAPI
helloworld_api.NewHelloWorldAPI,
),
// Hello
Expand All @@ -114,7 +228,23 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp
// ...........
```
#### step 3. 编译运行Layotto
我们推荐用户在自己的项目中定制 main 函数、定制启动流程。
具体来说,您可以把 Layotto 的 main 复制粘贴到自己的项目里,按需修改,去掉用不到的东西(比如用不到etcd的分布式锁组件,可以在自己的main里删掉它)
#### step 4. 编译运行Layotto
准备就绪,可以启动 Layotto 了。
以 helloworld 为例:
```shell
cd ${projectpath}/cmd/layotto_multiple_api
go build -o layotto
# run it
./layotto start -c ../../configs/config_in_memory.json
```
Layotto启动过程中,会回调每个注册进来的API的生命周期方法(Init,Register)
启动完成后,您的API就会对外提供 grpc 服务
6 changes: 5 additions & 1 deletion pkg/grpc/grpc_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,19 @@ import (

// GrpcAPI is the interface of API plugin. It has lifecycle related methods
type GrpcAPI interface {
// init this API before binding it to the grpc server. For example,you can call app to query their subscriptions.
// init this API before binding it to the grpc server.
// For example,you can call app to query their subscriptions.
Init(conn *grpc.ClientConn) error

// Bind this API to the grpc server
Register(s *grpc.Server, registeredServer mgrpc.RegisteredServer) (mgrpc.RegisteredServer, error)
}

// NewGrpcAPI is the constructor of GrpcAPI
type NewGrpcAPI func(applicationContext *ApplicationContext) GrpcAPI

// ApplicationContext contains all you need to construct your GrpcAPI, such as all the components.
// For example, your `SuperState` GrpcAPI can hold the `StateStores` components and use them to implement your own `Super State API` logic.
type ApplicationContext struct {
AppId string
Hellos map[string]hello.HelloService
Expand Down

0 comments on commit 428540f

Please sign in to comment.