### Refactor Simplify Design
# 代码简化设计文档 (Refactor & Simplify)
本文档记录对最近一轮重构(god-object 文件拆分 + 插件错误日志 + 方法白名单注册)
的**简化 review** 与落地改动。目标是**质量**,不涉及正确性 bug 修复。
## 1. 背景
最近的提交(`HEAD~11...HEAD`)主要做了两件事:
1. **文件拆分**:把两个"上帝对象"拆成职责内聚的小文件。
- `client/xclient.go` (1212 行) → `xclient_broadcast.go` / `xclient_call.go`
/ `xclient_discovery.go` / `xclient_transfer.go`
- `server/server.go` (799 行) → `server_conn.go` / `server_dispatch.go`
/ `server_response.go` / `server_shutdown.go`
2. **行为增强**:把之前 `_ = plugin.DoXxx(...)` 吞掉的错误改为记录日志;新增
`RegisterWithMethods` 方法白名单注册;空白名单守卫。
拆分本身是**逐行搬移**(byte-for-byte),四位 reviewer 一致确认搬移无漂移。
真正需要简化的是**新增逻辑**。
## 2. 发现与处理
四个维度(Reuse / Simplification / Efficiency / Altitude)并行 review,去重后
落地如下改动。
### 2.1 已修复
| # | 文件 | 问题 | 简化后 |
|---|------|------|--------|
| 1 | `server/service.go` | **三重空白名单守卫**:`RegisterWithMethods`、`RegisterNameWithMethods` 各有一次 `len(methods)==0` 检查,`register()` 内部 `else` 分支又重复了第三次(且错误文案不同)。第三处对白名单路径不可达(两个公开入口已拦截)。 | 删除 `register()` 内的死分支,改为注释说明:空 slice 会自然落到下方"no suitable methods"检查。规则收敛,文案不再三份。 |
| 2 | `server/service.go` | **`RegisterWithMethods` 缺少 nil-Plugins 守卫**:`RegisterName`/`RegisterNameWithMethods` 在 `DoRegister` 前都有 `if s.Plugins == nil` 兜底,唯独 `RegisterWithMethods` 没有,与同族方法不一致(潜在 nil 解引用)。 | 补齐 `if s.Plugins == nil { s.Plugins = &pluginContainer{} }`,与同族方法对齐。 |
| 3 | `server/server_shutdown.go` | **拆分留下的接缝垃圾**:`Serve` 的文档注释被搬到 shutdown 文件却无对应函数(悬空注释);`getDoneChan` 被搬来后全仓零调用(死代码)。 | 删除死函数 `getDoneChan` 与悬空注释;把 `Serve` 的文档注释还给 `server.go` 中 `Serve` 函数上方。 |
| 4 | `server/service.go` | `reflect.PtrTo` 已废弃(编译器 diagnostic)。 | 替换为 `reflect.PointerTo`。 |
| 5 | `server/plugin_test.go` | 新增的 `waitServerReady`(返回 bool,goroutine 安全)已用于消除 goroutine 内的 `t.Fatalf`,但 `TestPluginHeartbeat` 的子 goroutine 里还残留一处 `t.Fatalf`(`go vet` 报警)。 | 改为 `t.Errorf` + `return`,补全该模式,`go vet` 干净。 |
### 2.2 已评估但跳过(避免改变意图或超出 diff 范围)
- **插件错误日志"每调用点手写"而非收敛到 `pluginContainer`**(Altitude 提出):
reviewer 建议把"吞错并记日志 vs 返回错误"的策略下沉到 container 内部,让调用点
统一 `_ =` 或统一返回。这是**合理的深层改进**,但涉及重新设计 `Do*` 方法族的
错误契约,波及所有插件调用点,**远超本次 review 的 diff 范围**,且属于行为/接口
变更(`/simplify` 明确排除)。留作后续独立重构项。当前每处日志的 message 各有
上下文(`servicePath.method`),并非可折叠的 copy-paste。
- **方法白名单未复用/泛化 `suitableMethods`**(Altitude/Reuse 提出):白名单路径
自己重走 `all` 建 `picked`,并额外 `MethodByName` 探测以区分"不存在"与"签名不合适"。
reviewer 建议给 `suitableMethods` 加过滤器参数统一两条路径。这会改动核心注册机制
的签名与语义,属于设计层变更,**跳过**——现有实现正确且已复用 `suitableMethods`
的产出,`MethodByName` 探测仅用于生成更友好的错误文案,代价可接受。
- **`nil` vs `len==0` 语义**:`register` 用 `methods == nil` 表示"注册全部",非 nil
表示白名单。这是刻意的哨兵语义,公开 API 已在边界拦住空 slice,改为显式 intent
参数属于 API 变更,**跳过**。
- **doc-only / 注释冗长**(`share/context.go`、`client/discovery.go`):均为文档
措辞层面,不影响逻辑,**跳过**。
- **`xclient_discovery.go` `watch()` 去掉了 `sort.Slice`**:这是**减少**工作量而非
回归,且属搬移期的既有行为变更(前序提交 `cd34c37` 已单独处理),**不在本次范围**。
## 3. 拆分边界评估(Altitude)
四位 reviewer 一致认为新文件边界**内聚合理**:
- Server 侧:`conn`(监听/连接/单请求读取/鉴权)、`dispatch`(请求分发/函数调用/错误)、
`response`(发送响应)、`shutdown`(生命周期/优雅关闭/重启)——四个真实职责簇。
- Client 侧:`broadcast`(Broadcast/Fork/Inform 扇出)、`call`(Call/Go/SendRaw/wrap)、
`discovery`(watch/选点/缓存客户端)、`transfer`(SendFile/DownloadFile/Stream)。
无 awkward 的跨文件依赖,无"拆错位置"的接缝(除已修的 #3)。
## 4. 验证
```
go build ./... # 通过
go vet ./server ./client ./share # 干净(原 t.Fatalf 报警已消除)
go test ./server -count=1 # ok (11.7s)
```
## 5. 结论
拆分本身干净,无需改动。新增逻辑修掉了 5 处质量问题(死代码、不一致守卫、接缝
垃圾、废弃 API、测试 vet 报警),核心收益是**空白名单规则从三处收敛到边界一处**、
**同族注册方法的 nil-Plugins 守卫对齐**。两项更深的设计改进(插件错误策略下沉、
白名单泛化 `suitableMethods`)超出 `/simplify` 范围,记录留待后续。
---
### CHANGELOG
# [rpcx](http://rpcx.io)
## 1.9.4
- switch the experimental `rdma` transport from rsocket to gordma's rdmanet.Conn (build tag `rdma`)
- add RegisterWithMethods/RegisterNameWithMethods to register only a whitelist of a struct's methods (#581)
- document server plugin extension points (execution order, parameters, return values) in godoc (#645)
- fix printf argument mismatches surfaced by `go vet`
## 1.9.0
- unregister all services on close automatically
- add PostHTTPRequestPlugin
- support io_uring
- add CacheDiscovery
- add Oneshot method for XClient
- support RDMA
## 1.8.0
- supports distributed rate limiter based on go-redis/redis-rate
- move zookeeper plugin to https://github.com/smallnest/rpcx-zookeepr
- move consul plugin to https://github.com/smallnest/rpcx-consul
- move redis plugin to https://github.com/smallnest/rpcx-redis
- move influxd/opentelemetry plugin to https://github.com/smallnest/rpcx-plugins
- you can write customized error, for example `{"code": 500, err: "internal error"}`
- server support the work pool by `WithPool`
- support to write services like `go std http router` style without reflect
- simplify async write for service
- improve performance
## 1.7.0
- move etcd support to github.com/rpcxio/rpcx-etcd
- Broken API: NewXXXDiscovery returns error instead of panic
- support AdvertiseAddr in FileTransfer
- support Auth for OneClientPool
- support Auth for XClientPool
- Broken API: add meta parameter for SendFile/DownloadFile
- support streaming between server side and client side
- support DNS as service discovery
- support rpcx flow tracing
- support websocket as the transport like tcp,kcp and quic
- add CMuxPlugin to allow developing customzied services by using the same single port
- re-tag rpcx to make sure the version is less than 2 (for go module)
- support visit grpc services by rpcx clients: https://github.com/rpcxio/rpcxplus/tree/master/grpcx
- support configing grpc servicves in rpcx server side
- improve rpcx performance
- add Inform method in XClient
- add memory connection for unit tests
- supports opentelemetry
## 1.6.0
- support reflection
- add kubernetes config example
- improve nacos support
- improve message.Encode performance
- re-register services in etcd v3
- avoid duplicated client creation
- add SelectNodePlugin that can interrupt the Select method
- support TcpCopy by TeePlugin
- support reuseport for http invoke
- return reply even in case of server errors
- Change two methods' name of client plugin!
- Broken API: add error parameter in `PreWriteResponse`(#486)
- Broken API: change ReadTimeout/WriteTimeout to IdleTimeout
- Support passing Deadline of client contexts to server side
- remove InprocessClient plugin
- use heartbeat/tcp_keepalive to avoid client hanging
## 1.5.0
- support jsonrpc 2.0
- support CORS for jsonrpc 2.0
- support opentracing and opencensus
- upload/download files by streaming
- add Pool for XClient and OneClient
- remove rudp support
- add ConnCreated plugin. Yu can use it to set KCP UDPSession
- update client plugins. All plugin returns error instead of bool
- support ETCD 3.0 API
- support redis as registry
- support redis DB selection
- fix RegisterFunction issues
- add Filter for clients
- remove most of build tags such as etcd, zookeeper,consul,reuseport
- add Nacos as registry http://nacos.io
- support blacklist and whitlist
## 1.4.0
- Support utp and rudp
- Add OneClient to support invoke multile servicesby one client
- Finish compress feature
- Add more plugins for monitoring connection
- Support dynamic port allocation
- Use go module to manage dependencies
- Support shutdown graceful
- Add [rpcx-java](https://github.com/smallnest/rpcx-java) to support develop raw java services and clients
- Support thrift codec
- Setup rpcx offcial site: http://rpcx.io
- Add Chinese document: http://cn.doc.rpcx.io or https://smallnest.gitbooks.io/go-rpc-programming-guide
## 1.3.1
- Add http gateway: https://github.com/rpcxio/rpcx-gateway
- Add direct http invoke
- Add bidirectional communication
- Add xgen tool to generate codes for services automatically
fix bugs.
## 1.3.0
- Rewrite rpcx. It implements its protocol and won't implemented based on wrapper of go standard rpc lib
- Add go tags for pluggable plugins
- Add English document: https://github.com/smallnest/rpcx-programming
- Add rpcx 3.0 examples: https://github.com/rpcxio/rpcx-examples
rpcx 3.0 is not compatible with rpcx 2.0 and below
---
### README
- **stable branch**: v1.7.x
- **development branch**: master
Official site: [http://rpcx.io](http://rpcx.io/)
[](https://opensource.org/licenses/Apache-2.0) [](http://godoc.org/github.com/smallnest/rpcx) [](https://github.com/smallnest/rpcx/actions) [](https://goreportcard.com/report/github.com/smallnest/rpcx) [](https://coveralls.io/github/smallnest/rpcx?branch=master) [](_documents/rpcx_dev_qq3.jpg)
**Notice: etcd**
since rpcx 1.7.6, some plugins have been moved to the independent project:
- `etcd` plugin has been moved to [rpcx-etcd](https://github.com/rpcxio/rpcx-etcd)
- `zookeeper` plugin has been moved to [rpcx-zookeeper](https://github.com/rpcxio/rpcx-zookeeper)
- `consul` plugin has been moved to [rpcx-consul](https://github.com/rpcxio/rpcx-consul)
- `redis` plugin has been moved to [rpcx-redis](https://github.com/rpcxio/rpcx-redis)
- `influxdb` plugin has been moved to [rpcx-plugins](https://github.com/rpcxio/rpcx-plugins)
- `opentelemetry` plugin has been moved to [rpcx-plugins](https://github.com/rpcxio/rpcx-plugins)
## Announce
**Stable branch: v1.9.4**
A tcpdump-like tool added: [rpcxdump](https://github.com/smallnest/rpcxdump)。 You can use it to debug communications between rpcx services and clients.
## Cross-Languages
you can use other programming languages besides Go to access rpcx services.
- **rpcx-gateway**: You can write clients in any programming languages to call rpcx services via [rpcx-gateway](https://github.com/rpcxio/rpcx-gateway)
- **http invoke**: you can use the same http requests to access rpcx gateway
- **Java Services/Clients**: You can use [rpcx-java](https://github.com/smallnest/rpcx-java) to implement/access rpcx services via raw protocol.
- **rust rpcx**: You can write rpcx services in rust by [rpcx-rs](https://github.com/smallnest/rpcx-rs)
> If you can write Go methods, you can also write rpc services. It is so easy to write rpc applications with rpcx.
## Installation
install the basic features:
`go get -v github.com/smallnest/rpcx/...`
If you want to use `quic`、`kcp` registry, use those tags to `go get` 、 `go build` or `go run`. For example, if you want to use all features, you can:
```sh
go get -v -tags "quic kcp" github.com/smallnest/rpcx/...
```
**_tags_**:
- **quic**: support quic transport
- **kcp**: support kcp transport
- **rdma**: support the experimental RDMA transport (built on gordma's rdmanet.Conn; requires libibverbs on Linux)
## Which companies are using rpcx?
## Features
rpcx is a RPC framework like [Alibaba Dubbo](http://dubbo.io/) and [Weibo Motan](https://github.com/weibocom/motan).
**rpcx** is created for targets:
1. **Simple**: easy to learn, easy to develop, easy to integrate and easy to deploy
2. **Performance**: high performance (>= grpc-go)
3. **Cross-platform**: support _raw slice of bytes_, _JSON_, _Protobuf_ and _MessagePack_. Theoretically it can be used with java, php, python, c/c++, node.js, c# and other platforms
4. **Service discovery and service governance**: support zookeeper, etcd and consul.
It contains below features
- Support raw Go functions. There's no need to define proto files.
- Pluggable. Features can be extended such as service discovery, tracing.
- Support TCP, HTTP, [QUIC](https://en.wikipedia.org/wiki/QUIC) and [KCP](https://github.com/skywind3000/kcp)
- Support multiple codecs such as JSON, Protobuf, [MessagePack](https://msgpack.org/index.html) and raw bytes.
- Service discovery. Support peer2peer, configured peers, [zookeeper](https://zookeeper.apache.org), [etcd](https://github.com/coreos/etcd), [consul](https://www.consul.io) and [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS).
- Fault tolerance:Failover, Failfast, Failtry.
- Load banlancing:support Random, RoundRobin, Consistent hashing, Weighted, network quality and Geography.
- Support Compression.
- Support passing metadata.
- Support Authorization.
- Support heartbeat and one-way request.
- Other features: metrics, log, timeout, alias, circuit breaker.
- Support bidirectional communication.
- Support access via HTTP so you can write clients in any programming languages.
- Support API gateway.
- Support backup request, forking and broadcast.
rpcx uses a binary protocol and platform-independent, which means you can develop services in other languages such as Java, python, nodejs, and you can use other prorgramming languages to invoke services developed in Go.
There is a UI manager: [rpcx-ui](https://github.com/smallnest/rpcx-ui).
## Performance
Test results show rpcx has better performance than other rpc framework except standard rpc lib.
The benchmark code is at [rpcx-benchmark](https://github.com/rpcx-ecosystem/rpcx-benchmark).
**Listen to others, but test by yourself**.
**_Test Environment_**
- **CPU**: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz, 32 cores
- **Memory**: 32G
- **Go**: 1.9.0
- **OS**: CentOS 7 / 3.10.0-229.el7.x86_64
**_Use_**
- protobuf
- the client and the server on the same server
- 581 bytes payload
- 500/2000/5000 concurrent clients
- mock processing time: 0ms, 10ms and 30ms
**_Test Result_**
### mock 0ms process time
| Throughputs | Mean Latency | P99 Latency |
|---|
 |  |  |
### mock 10ms process time
| Throughputs | Mean Latency | P99 Latency |
|---|
 |  |  |
### mock 30ms process time
| Throughputs | Mean Latency | P99 Latency |
|---|
 |  |  |
## Examples
You can find all examples at [rpcxio/rpcx-examples](https://github.com/rpcxio/rpcx-examples).
The below is a simple example.
**Server**
```go
// define example.Arith
……
s := server.NewServer()
s.RegisterName("Arith", new(example.Arith), "")
s.Serve("tcp", addr)
```
#### Registering only selected methods
`Register`/`RegisterName` expose every suitable exported method of a struct as
RPC. To expose only some of them, use `RegisterWithMethods` /
`RegisterNameWithMethods` with a whitelist of method names:
```go
// Arith has exported methods Mul, Add and Sub, but only Mul/Add
// should be callable remotely.
s := server.NewServer()
err := s.RegisterWithMethods(new(example.Arith), []string{"Mul", "Add"}, "")
// err := s.RegisterNameWithMethods("Arith", new(example.Arith), []string{"Mul", "Add"}, "")
s.Serve("tcp", addr)
```
Rules of the whitelist:
- **Whitelist only**: methods not listed are not registered, so a newly added
method stays private unless you opt it in.
- **Errors on bad names**: a name that does not exist on the receiver, or that
exists but is not a suitable RPC method, makes registration fail (the two
cases give different error messages). Nothing is partially registered.
- **Empty whitelist errors**: pass a non-empty list, or use `Register` /
`RegisterName` to register all methods.
**Client**
```go
// prepare requests
……
d, err := client.NewPeer2PeerDiscovery("tcp@"+addr, "")
xclient := client.NewXClient("Arith", client.Failtry, client.RandomSelect, d, client.DefaultOption)
defer xclient.Close()
err = xclient.Call(context.Background(), "Mul", args, reply, nil)
```
## Contributors
## Contribute
see [contributors](https://github.com/smallnest/rpcx/graphs/contributors).
Welcome to contribute:
- submit issues or requirements
- send PRs
- write projects to use rpcx
- write tutorials or articles to introduce rpcx
## License
Apache License, Version 2.0
---