声明是一切的契约
SERVORA = Servora Enables Reliable, Versioned, Observable Resource APIs
Servora 是一个以 ProtoBuf 为契约、高性能、模块化的 Go 快速开发框架。无论您想构建一个单体应用还是微服务项目,Servora 都将是您的不二之选。
跑一对 master + worker 微服务,看看 servora 起一个项目长什么样。前置要求:Go 1.26.1+、Docker。
git clone https://github.com/Servora-Kit/servora-example
cd servora-example
make compose.up.infra # 拉起 Consul / Jaeger / OTel Collector
# 终端 A:worker
cd app/worker/service && make run
# 终端 B:master
cd app/master/service && make run
# 验证:HTTP 200 + {"reply":"master relay -> worker says hello, hi"}
curl 'http://127.0.0.1:8001/v1/hello?greeting=hi'
make run直接go run启动;如需 air 热重载请改用make dev(需先make init安装 air)。
完整流程(全容器化 / 热重载 / 端口约定 / 目录结构)见 servora-example。
Servora 提供了极为方便的 HTTP、gRPC 服务端的浅层封装。WithConfig 直接接收从配置文件 scan 出来的 corev1.Server,无需手工拼端口/网络/超时等参数。
import (
"log/slog"
corev1 "github.com/Servora-Kit/servora/api/gen/go/servora/core/v1"
svrgrpc "github.com/Servora-Kit/servora/transport/server/grpc"
pb "myapp/api/gen/go/myapp/user/v1"
kgrpc "github.com/go-kratos/kratos/v3/transport/grpc"
)
func NewGRPCServer(c *corev1.Server, l *slog.Logger, svc *UserService) *kgrpc.Server {
return svrgrpc.NewServer(
svrgrpc.WithConfig(c),
svrgrpc.WithLogger(l),
svrgrpc.WithServices(func(s *kgrpc.Server) {
pb.RegisterUserServiceServer(s, svc)
}),
)
}Servora 提供了极为方便的 HTTP、gRPC 客户端的浅层封装。Dialer 内部接管服务发现、负载均衡、连接池与中间件链,业务侧仅需 Dial(ctx, "service.name")。
import (
"context"
"log/slog"
corev1 "github.com/Servora-Kit/servora/api/gen/go/servora/core/v1"
clgrpc "github.com/Servora-Kit/servora/transport/client/grpc"
pb "myapp/api/gen/go/myapp/user/v1"
"github.com/go-kratos/kratos/v3/registry"
)
func CallUser(ctx context.Context, l *slog.Logger, data *corev1.Data, d registry.Discovery) error {
dialer := clgrpc.NewDialer(
clgrpc.WithData(data),
clgrpc.WithDiscovery(d),
clgrpc.WithLogger(l),
)
conn, err := dialer.Dial(ctx, "user.service")
if err != nil {
return err
}
_, err = pb.NewUserServiceClient(conn).GetProfile(ctx, &pb.GetProfileRequest{})
return err
}配置文件、proto type<->go type映射、认证、授权、审计...在 Servora 的世界观下,这一切都可以从一个 proto 文件定义。Servora 提供了很多 protoc 插件来代码生成,以实现尽量以 proto 文件来规定除了普通接口请求以外的所有行为。
以下是 Servora 提供的 Proto 插件,多数采用代码生成 + 运行时解析配合的方式工作。
通过 (section) 标记 message 在外部 yaml/json 配置中的定位键,通过 (field) 声明字段的默认值与必填语义。Plugin 自动生成 SectionKey() / ApplyDefaults() / CheckRequired() / ApplyConf(),配合 bootstrap.Scan 在 kratos config 中定向 scan。该契约只在启动期加载或业务显式调用 bootstrap.Scan 时生效;Kratos Config.Watch 不会自动调用这些生成方法。
import "servora/conf/v1/annotations.proto";
import "google/protobuf/duration.proto";
message Redis {
option (servora.conf.v1.section) = { key: "redis", optional: true };
string addr = 1 [(servora.conf.v1.field) = { required: true }];
string network = 2 [(servora.conf.v1.field) = { default: "tcp" }];
google.protobuf.Duration timeout = 3 [(servora.conf.v1.field) = { default: "5s" }];
}运行时通过 Scan(rt, targets...) 统一扫描整份配置或指定 section;配置文件中缺失的 optional section 静默跳过,ApplyConf 自动完成必填校验 + 默认值填充:
import (
"github.com/Servora-Kit/servora/core/bootstrap"
redispb "github.com/Servora-Kit/servora/api/gen/go/servora/contrib/db/redis/v1"
)
redisCfg := &redispb.Redis{}
if err := bootstrap.Scan(rt, redisCfg); err != nil {
return err
}
// redisCfg 已通过必填校验并自动填充默认值(由 ApplyConf 编排)配置中心热更新属于 Kratos Config.Watch 的底层能力。业务如需在 watch 回调中复用上述契约,需要自行对新的 Value 执行 Scan 并调用 ApplyConf();Servora 不会对远端配置变更自动重 scan、校验或回调。
通过 service_default 声明服务级默认认证策略,方法级 rule 可整段覆盖。schemes 接受 jwt / apikey / mtls / aksk 等任意字符串,支持业务自定义引擎。Plugin 生成 AuthnRules 表,由 authn.Server 中间件运行时分发。
import "servora/authn/v1/annotations.proto";
service UserService {
option (servora.authn.v1.service_default) = {
mode: MODE_REQUIRED
schemes: ["jwt"]
};
// 继承 service_default:要求 jwt 通过
rpc GetProfile(GetProfileRequest) returns (User);
// 方法级覆盖:完全公开
rpc Login(LoginRequest) returns (LoginResponse) {
option (servora.authn.v1.rule) = { mode: MODE_PUBLIC };
}
}Plugin 生成 AuthnRules() 方法表,业务侧装一次中间件即可——Multi + Named 注册多种认证引擎,规则表由 WithRulesFuncs 注入:
import (
"github.com/Servora-Kit/servora/security/authn"
authjwt "github.com/Servora-Kit/servora/security/authn/jwt"
"github.com/Servora-Kit/servora/security/authn/apikey"
pb "myapp/api/gen/go/myapp/user/v1"
)
mw := authn.Server(
authn.Multi(
authn.Named(authjwt.Scheme, authjwt.NewAuthenticator(authjwt.WithVerifier(verifier))),
authn.Named(apikey.Scheme, apikey.NewAuthenticator(apikey.WithStore(keyStore))),
),
authn.WithRulesFuncs(pb.AuthnRules),
)跟认证同构:service_default 声明服务级默认授权策略,方法级 rule 可整段覆盖。授权检查由 action × resource_type × resource_id_field(从请求消息中提取资源 ID)三元组定义。默认接入 OpenFGA,可替换为任意实现 Authorizer 接口的后端。
import "servora/authz/v1/authz.proto";
service VideoService {
option (servora.authz.v1.service_default) = {
mode: AUTHZ_MODE_CHECK
action: "can_read"
resource_type: "video"
resource_id_field: "id"
};
// 继承 service_default:检查 can_read on video[req.id]
rpc GetVideo(GetVideoRequest) returns (Video);
// 方法级覆盖:换成 can_delete
rpc DeleteVideo(DeleteVideoRequest) returns (DeleteVideoResponse) {
option (servora.authz.v1.rule) = {
mode: AUTHZ_MODE_CHECK
action: "can_delete"
resource_type: "video"
resource_id_field: "id"
};
}
// 方法级覆盖:完全跳过授权
rpc ListPublicVideos(ListPublicVideosRequest) returns (ListPublicVideosResponse) {
option (servora.authz.v1.rule) = { mode: AUTHZ_MODE_NONE };
}
}Plugin 生成 AuthzRules() 规则表,业务侧把 Engine 实现(OpenFGA / 自研后端)连同规则表交给 authz.Server 即可:
import (
openfgaconfpb "github.com/Servora-Kit/servora/api/gen/go/servora/security/authz/openfga/v1"
"github.com/Servora-Kit/servora/security/authz"
"github.com/Servora-Kit/servora/security/authz/openfga"
pb "myapp/api/gen/go/myapp/video/v1"
)
client, err := openfga.NewClient(&openfgaconfpb.Config{
ApiUrl: "http://openfga:8080",
StoreId: "store-id",
})
if err != nil {
return err
}
mw := authz.Server(openfga.NewAuthorizer(client), authz.WithRulesFuncs(pb.AuthzRules))Servora 提供了声明式、 AIP 风格约束的 CRUD 生态框架,用户只需要在 API Proto 中定义 AIP 规范的资源 message ,Servora 就会利用 protoc-gen-servora-crud 插件生成 Go 、 TypeScript 关于 分页、排序以及过滤的辅助函数,以及关于 proto 生成的代码与 data 层所使用的 orm框架字段的绑定关系代码。通过 CRUD 生态,用户可以非常规范、方便地完成增删改查的 API 设计与业务开发。
具体使用示例可见:servora-platform/example.service
通过 audit_rule 或 service_default 声明 RPC 是否进入通用审计。注解只表达开关;事件类型、业务目标、详情 data 和扩展属性由事件生产者负责,例如 authn/authz middleware、后续 CRUD generator 或业务显式 emit。通用 RPC 审计事件以 CloudEvents 投递。
import "servora/audit/v1/annotations.proto";
service ResourceService {
option (servora.audit.v1.service_default) = { mode: AUDIT_MODE_ENABLED };
rpc CreateResource(CreateResourceRequest) returns (Resource) {
option (servora.audit.v1.audit_rule) = {
mode: AUDIT_MODE_ENABLED
};
}
}Plugin 生成 AuditRules() 规则表,业务侧把 Auditor 实现(默认 Kafka)跟规则表一起挂到 middleware:
import (
"github.com/Servora-Kit/servora/obs/audit"
pb "myapp/api/gen/go/myapp/resource/v1"
)
mw := audit.Middleware(auditor,
audit.WithRulesFuncs(pb.AuditRules),
)通用 middleware 发出的事件固定为 servora.audit.rpc.v1,source 为 "//" + app.Name,subject 为 Kratos transport operation(如 /myapp.resource.v1.ResourceService/CreateResource)。业务资源事件可直接用 audit.NewEvent() 构造自定义 CloudEvent 并调用 Auditor.Emit。
Servora 复用 Kratos 的 registry.Registrar / registry.Discovery 接口,并在 core/registry/ 与 core/config/ 下内置了主流后端,可在 yaml 中按 key 切换:
| 能力 | 内置后端 |
|---|---|
| 服务注册与发现 | Consul / Etcd / Nacos / Kubernetes |
| 配置中心 | Consul / Etcd / Nacos |
配置中心通过 Kratos source/watch 接入远端配置;Servora 只保证启动期 LoadBootstrap / bootstrap.Scan 的配置契约,不承诺远端配置变更自动触发 servora-conf 生成方法。
Servora 默认接入 OpenTelemetry SDK,开箱即用:
- Metrics —
obs/metrics.New通过 OpenTelemetry Metrics SDK + Prometheus exporter 暴露/metricsendpoint;业务自定义指标通过metrics.Meter("your/import/path")创建 OTel instruments,默认 Prometheus registry 上的promauto指标不会自动并入 Servora/metrics - Tracing —
obs/tracing.InitTracerProvider通过 OTLP gRPC exporter 推送到 OTel Collector,再由 Collector 转发到 Jaeger / Tempo 等任意后端 - Logging —
obs/logger提供基于slog的结构化日志,支持 stdout/file/OTel fanout - Audit — 详见上文「Proto 契约化 → 审计」段,事件以 CloudEvents 格式投递
本地起一套 Prometheus + Jaeger UI + Grafana 即可看到全链路指标与 trace(参考 servora-platform 的 compose 配置;servora-example 只内置 Consul / Jaeger / OTel Collector,可直接 curl /metrics 做 smoke)。
- 特别感谢 go-kratos,为 servora 提供了核心能力的支撑。
- 特别感谢 go-wind-admin,为 servora 的组织架构提供了灵感。
- 感谢所有用户的建议和反馈。
- 感谢开源社区的所有贡献者和支持者。
Apache License 2.0,详见 LICENSE。本仓库依赖的第三方组件版权声明见 THIRD_PARTY_LICENSES。
