平台能力容器
容器定义
容器是指无强制外部依赖与外部接口平台无关化。因此我们需要设计一套容器间的交互协议用于去除强制外部依赖,同时通过定义一套统一的调用接口来完成外部接口对平台端差异的屏蔽性。
容器模板设计
容器本身内部的业务能力还是由对应平台端来实现,但是由于除了业务能力自身,我们还需要对业务内容进行一些通用型的处理,如多语言支持,性能数据监控与管理,同时对未来上层业务的容器化支持提供扩展的可能性。因此我们需要定义一套模板来对其进行具体约束。
平台能力容器管理器
跨容器交互协议
基于类似 ALPC(Advanced/Asynchronous Local Procedure Call)规则,交互模式采用 C/S 机制,交互实体分为请求(Request)与响应(Response),协议结构采用 Header-Body 形式,Header 中包含路由规则与协议类型,Body 则包含具体业务数据。
ALPC 的基本请求概念如下:
可以从图上看出,ALPC 类似于将 RPC 的调用概念使用在本地,是一种本机 RPC 的 C/S 技术模型。
client port
与server port
相连形成 ALPC Connection。Client 将message deliver
到message queue
。
由于我们是单 APP 结构,不需要考虑跨进程的访问场景,因此数据交换部分可以在此协议基础上进行简化,精简后简单流程如下:
- A 容器向路由表中注册,使 Router 规则生效
- B 容器尝试通过 Router 由访问 A 容器,如果允许访问,则向 A 容器发送一个请求实体,请求实体由 Router 封装
- A 容器接收到来自 B 容器的请求实体,尝试通过接口绑定参数调用绑定接口,如果该接口不存在则进入通用处理流程,如无法处理则返回异常,如调用成功则将请求结果包装为响应实体返回
- B 容器通过 Router 拿到响应实体,通过解析响应实体获取调用结果数据
该流程的时序图如下:
通过该流程,我们可以得出该交互协议的完整结构如下:
- 所有容器向路由进行注册
- 每个容器既是客户端,也是服务端,整体结构更接近 P2P
- 每个容器访问其他容器,只允许通过路由进行访问
- 每个容易处理请求错误,应以标准异常的形式返回结果
- 数据以请求实体和响应实体的方式进行传输
- 请求实体和响应实体都包含 Header,Body 按需求定义可选
- 消息实体中的数据封装理论可以使用任意数据类型,但是我们只使用 ProtoBuf 的序列化与反序列化工具
- Header 中包含了目标模块、目标能力、Body 数据类型、请求 id、数据长度
- Body 中的具体数据内容根据业务需求自行定义
- Body 内容原则上不允许传输超大数据,超大数据将在未来有需求时另行提供数据流接口
- 请求与响应模型的生命周期由路由控制,封装完成后是外部不可操作对象,容器接收到数据后应对其进行 copy,以转移其生命周期管理权限
数据结构
syntax = "proto3";
import "google/protobuf/any.proto";
import "google/protobuf/descriptor.proto";
package moqipobing;
option java_package = "com.moqipobing.ALPC";
option java_outer_classname = "ALPCMessage";
// 消息体类型枚举
enum BodyType {
// 无消息体
NOBODY = 0;
// urlencoded类型消息体,字符串形式key1=value1&key2=value2
URLENCODED = 1;
// Json数据类型
JSON = 2;
// ProtoBuf类型
PROTOBUF = 3;
// 其他类型
OTHER = 4;
}
// 请求实体
message ALPCRequest {
// 请求id,由Router分配
int32 request_id = 1;
// 请求的模块
int32 request_module = 2;
// 响应的模块
int32 response_module = 3;
// 响应的功能
int32 response_func = 4;
// 消息体类型
BodyType body_type = 5;
// 保留字段序号
reserved 6, 7, 8, 10 to max;
// 如果body_type=NOBODY,则该值不存在
// 如果body_type=URLENCODED,则该值为字符串类型
// 如果body_type=JSON,则该值为json object
// 如果body_type=PROTOBUF,则该值为ProtoBuf map
// 如果body_type=OTHER,则该值需要自行序列化
google.protobuf.Any body = 9;
}
// 响应实体
message ALPCResponse {
// 请求id,由Router分配
int32 request_id = 1;
// 请求的模块
int32 request_module = 2;
// 响应的模块
int32 response_module = 3;
// 响应的功能
int32 response_func = 4;
// 消息体类型
BodyType body_type = 5;
// 响应码
// 0为成功
// 负值为路由异常
// 正值为能力异常
int32 code = 6;
// 响应消息
// 在异常状态下可能包含报错信息
string msg = 7;
// 保留字段序号
reserved 8, 10 to max;
// 如果body_type=NOBODY,则该值不存在
// 如果body_type=URLENCODED,则该值为字符串类型
// 如果body_type=JSON,则该值为json object
// 如果body_type=PROTOBUF,则该值为ProtoBuf map
// 如果body_type=OTHER,则该值需要自行序列化
google.protobuf.Any body = 9;
}
跨层通讯机制
业务层与平台层之间的调用则通过平台能力网关完成,关于平台能力网关的具体实现框架见下文平台能力网关。 业务层通过直接调用平台能力网关提供的协议接口进行直接调用, 容器与业务层业务交互协议: 业务层调用容器,直接调用 容器调用业务层,使用基础容器提供的回调接口进行调用
业务/外部通讯协议
业务层之间通过 URI 的形式进行调用,其规则以 URI 资源标准化的形式进行定义,大体上与现在的统一跳转的结构一致。 规则如下:
scheme_id
://bundle_id
:target
/action
?params
scheme_id
代表在 App 内注册的 scheme,功能与 bundle_id 等价bundle_id
为应用名,功能与 scheme 等价target
为请求的目标模块action
为请求模块的具体功能params
为可选参数,拼接方式等同 x-www-form-urlencoded,例如:key1=value1&key2=value2
以 passport 为例,调用公司内的登陆功能则为
wanba://wanba.wodidashi.com:passport/login?username=wangshiyu&password=wangshiyu
wanba://:passport/login?username=wangshiyu&password=wangshiyu
https://wanba.wodidashi.com:passport/login?username=wangshiyu&password=wangshiyu
这三者调用作用相同,该消息进入 app 后将由 Router 进行托管,Router 解析协议内容后,自动将其转为 ALPC 调用进行内部调用。