浅谈 API 设计
Posted 漂流的江湖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈 API 设计相关的知识,希望对你有一定的参考价值。
写了几年(没几年)代码,大部分时间不是在用别人设计的 api 就是在考虑怎么设计自己的 api,然后让自己用得相对舒服一点点,如果不舒服,默默互道 fuck 之后,包装一层让自己舒服的 api 完事,再也不用看里面是什么了——大概是写代码的日常(不是)。
我的一点点理解
API 设计其实是个挺烧脑的事情,它应该有几个方面的表现:
能看出设计者对用户的约束是什么,副作用是什么
能很好地约束限制用户的行为,比如所有权转移
能清晰告知用户,需要什么类型的入参及出参是什么类型
大部分情况下,自己写给自己用的 API 都仅仅只是可以用而已,谈不上约束和限制,只有在写库的时候,才会碰到上诉的烧脑问题,以及,如果不是静态类型系统,那就谈不上强约束类型,void**
大法其实也可以,就是用户日常不懂调用它的假设前提是什么而已(狗头
相对满意的设计
直到前几天,我才终于写了一个自己比较满意的 API 设计,一个非常小的 PR ,实现的是 jsonrpc pubsub client,功能简单到没朋友,但 Client 与 Handle 的相互转换及泛型的处理,我觉得我可以吹半个月(实际上已经过去了一个月,因为懒的缘故,现在才把东西写出来,延时吹)。
好,我开始讲设计(吹)了,pubsub client 是一个长连接,向 server 订阅某个奇怪的 topic,在发生这个事件的时候,server 返回 event 给 client,长连接的类型可以是各种奇怪的协议,比如 TCP,Websocket,IPC等。同时,因为是静态类型的原因,每个 topic 对应的返回值类型大概率是不一样的,这里就需要两个泛型类型的约束,一个是底层 io 类型,一个是 topic 类型,并且这两个泛型的约束时间是不一样的,一个是在建立连接的时候必须指定,一个是建立订阅关系的时候必须指定。
那么自然而然,就有了类似于代理模式的设计,有两个结构出现了,一个表达当前已经建立了长连接,一个表达当前已经进行了订阅动作:
pub struct Client<T> {
inner: Framed<T, StreamCodec>,
id: usize,
}
pub struct Handle<T, F> {
inner: Framed<T, StreamCodec>,
topic: String,
sub_id: String,
output: PhantomData<F>,
rpc_id: usize,
}
而且两者可以通过订阅和取消订阅相互转换,达到 io 复用的目的,接口就类似如下:
impl impl<T> Client<T>
where
T: AsyncWrite + AsyncRead + Unpin,
{
pub async fn subscribe<F: for<'de> serde::de::Deserialize<'de>>(
mut self,
name: &str,
) -> io::Result<Handle<T, F>>
}
impl<T, F> Handle<T, F>
where
T: AsyncWrite + AsyncRead + Unpin,
{
pub async fn unsubscribe(mut self) -> io::Result<Client<T>>
}
上面的 F
就是订阅的瞬间,需要明确返回类型是什么,同时使用了一个一般不太用得上的 Rust 语法 —— HRTB,ok,上述的 API 完成了一切我想要的核心功能:
通过 io 泛型让 client 能适应任何底层协议的实现
通过两个结构的相互转换让用户能复用 io
通过 ownership 的迁移,强制实现状态的迁移(订阅或未订阅)
通过延时绑定泛型让
Handle
能够适应任何想要的返回值
当然,上述设计也有没有覆盖的地方,理论上说,jsonrpc 的订阅是可以单连接订阅多个 topic 的,而这里的设计并没有实现,而是强制要求一个 io 只能订阅一个 topic,在上述的核心 API 设计里,并不是不能实现,而是当前来说,暂时不需要,为了简化代码,而遭到了遗弃。实现的方法很简单,订阅接口上将 &str
变成 &[&str]
,订阅过程中出现 notification
事件返回的话,暂时缓存起来,直到转换为 Handle
结构,才允许释放给用户,相应的,需要缓存多个 sub_id,用来取消订阅。
结束
ok,我吹完了,讲道理,写了几年代码,终于实现了一个相对满意的设计,我觉得吧,挺不容易的(逃
以上是关于浅谈 API 设计的主要内容,如果未能解决你的问题,请参考以下文章
浅谈四种API设计风格(RPCRESTGraphQL服务端驱动)
Express实战 - 应用案例- realworld-API - 路由设计 - mongoose - 数据验证 - 密码加密 - 登录接口 - 身份认证 - token - 增删改查API(代码片段