拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Zulip API 设计指南:兼容性保障、Opt-in 变更机制与 API 变更审批流程

Zulip API 设计指南兼容性保障、Opt-in 变更机制与 API 变更审批流程【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulipZulip 的核心竞争力之一是其供各端客户端Web、移动端、终端应用以及大量第三方集成与机器人统一使用的 HTTP API。当新功能需要触及 API 时如何保证不破坏现有客户端、同时维持 API 的清晰与整洁是每个 API 变更必须回答的问题。本文基于 Zulip 仓库中的官方流程文档 api-design.md结合zerver/目录下真实的源码实现与测试用例完整讲解 Zulip 的 API 变更定义、审批流程、兼容性设计尤其是client_capabilities机制与 Clean API 设计原则帮助贡献者理解并遵守这一流程。为什么 Zulip 对 API 变更如此严格Zulip 的 API 由同一套规范驱动所有客户端Zulip 官方 Web/桌面应用、官方移动端应用、终端应用zulip-terminal以及由无数用户编写的第三方集成、机器人和工具。这意味着任何一个 API 变更的影响面都远超普通内部接口。因此仓库文档将 API 变更的质量要求归纳为三点兼容性compatible任何 API 变更都不能导致现有客户端例如已经安装的老版本移动端应用崩溃或行为错乱整洁clean新 API 功能应有清晰明确crisp的语义能以自然的方式支持客户端实现命名要能准确指向其真实含义不能误导文档清晰clearly documented所有变更都要遵循 Zulip 的 API 文档规范这一点在 docs/documentation/api.md 中有专门说明。本文聚焦前两条如何实现兼容的 API 变更以及如何设计出整洁的 API。API 变更的基本流程两条铁律Zulip 官方流程文档给出了两条最核心的规则在考虑任何 API 变更时必须在社区服务器的#api design频道中讨论在合并包含 API 变更的 PR 之前必须确认该变更已在该频道中获得批准。只要遵守这两条规则负责管理该频道的维护者就会引导贡献者完成流程中的其他所有环节。什么算作API 变更官方文档的定义是任何新增、删除或改变语义的行为只要涉及服务器发送给客户端的任何内容或服务器接受来自客户端的任何内容都属于 API 变更。具体包括但不限于新增或删除端点即修改 zproject/urls.py新增或删除端点的参数新增或删除初始快照即POST /register事件队列注册接口的响应或任何其他端点响应中的任何字段改变上述任何内容的类型包括向枚举类型新增枚举值重命名或移动上述任何内容改变任何现有参数或字段含义。一个实用的判断标准是几乎任何触及 API 规范文件 zerver/openapi/zulip.yaml 的改动都算 API 变更少数例外比如仅澄清该文件中既有文档的措辞。反过来对于大多数但不是全部API 变更如果你编写了覆盖该变更的测试测试套件会自动确保 OpenAPI 规范文件有对应的修改——这一点由 zerver/tests/test_openapi.py 提供的测试保障。容易被忽视的 API 变更类型文档特别列出了两类未必一眼看出来是 API 变更的改动它们同样必须走完整流程对 Zulip content HTML 结构的任何改动即服务器为消息内容以及频道描述等少数其他位置输出的 HTML。这包括新增 CSS 类新增data-*属性或其他属性现有 CSS 类或属性出现在新位置或从某些现有位置消失。对推送通知 payload 的任何改动。API 设计审批机制#api design频道有若干维护者专门承担管理该频道讨论的职责。他们通常包括 Zulip 移动团队负责人以及一到两名主要维护服务器端和/或 Web 应用的 Zulip 维护者。一个 API 变更提案被批准的标准是频道维护者之一在该变更的讨论线程中明确表示批准。通常情况下这需要移动端维护者与服务器端/Web 端维护者各自从自己视角达成一致之后才会发生。文档还提醒了一个实践细节如果某位频道维护者表示看起来不错而你不确定他是在等待另一系统视角的反馈还是表示已批准可以合并请直接追问让对方明确状态。API 兼容性设计这是本文最核心的部分所有 Zulip API 变更必须是兼容的即不能导致现有客户端出错。有些类型的变更天然容易满足这一点有些则需要额外工作。哪些变更自动兼容对任何 API 变更第一个问题就是判断它是否属于自动兼容的情形——如果是就可以省去后文的兼容性工作。维护者判断时通常依据以下原则1. 新增端点、或为端点新增参数——总是兼容的因为现有客户端不会与之交互。但有一个重要限定前提是既有请求的语义不变。例如如果新参数有一个默认值而该默认值与端点的旧行为不同那么当客户端不传该新参数时端点语义实际上已经改变这就可能不兼容。2. 为端点响应或响应中嵌套的 JSON 对象新增字段——总是兼容的因为正确的 Zulip 客户端本来就会忽略其不认识的字段。反过来也成立如果某段既有 API 开始默认包含以前不包含的数据对象即使新对象带有可区分的标记字段这种变更也不是自动兼容的——因为现有客户端会忽略那个标记字段。文档举了一个真实案例当 Zulip 开始在POST /register的频道列表中包含已归档频道时并没有简单地加入列表 附带is_archived: true标志否则老客户端会天真地把它们当成普通未归档频道进而错误地显示在频道列表界面等位置因此当时需要按后文描述的兼容性工作来处理。3. 对 Zulip content HTML 的改动新增属性是兼容的但新增 CSS 类不是自动兼容的总体而言content HTML 的大多数改动都不是自动兼容的。4. 仅影响只被 Web 应用使用、且没有其他客户端使用的 API 功能的变更——即使涉及删除或重命名字段也是兼容的。典型场景是该功能只用于设置界面settings UI。原因是 Web 应用与服务器一起部署服务器升级后客户端会很快自动重载到新版本的 Web 应用。这类变更最多需要 Zulip Cloud 团队把 Web 端变更与服务器端变更拆到两个连续部署中完成不值得为其保留永久的兼容代码。文档给出了一套确认该功能是否只被 Web 应用使用的检查清单确认移动端没有使用该功能——且在判断前必须显式确认有时应用会依赖你不预期的 API 功能。这个检查要覆盖最新版本和所有仍在支持周期内的旧版本。例如如果移动端曾经使用某功能后来停用那么在停用它的移动端版本发布约 12 个月后之前都不能移除该功能移动团队负责人会根据应用商店的版本分布数据做移除决策。快速检查 zulip-terminal 是否可能使用该功能如有嫌疑就提 issue。通常一次git grep即可。Zulip 不会因终端应用兼容性阻塞 API 变更。考虑第三方集成、机器人、工具或其他客户端是否可能使用该功能。通常可以推断/messages发送消息这类核心端点肯定有人用仅管理员可用的设置大概没人用介于两者之间则不太确定。拿不准时就假设该功能可能被使用。Zulip Cloud 维护者可以借助环境日志了解某端点被第三方代码使用的程度但自托管环境没有这类数据可查。如何把一个不兼容的变更改造成兼容变更当变更不属于自动兼容时基本策略是把变更做成opt-in选择性开启让 unaware 该变更的现有客户端完全看不到变化。方式一为受影响的端点新增一个默认关闭的参数。文档以GET /get-messages上的allow_empty_topic_name参数为例。从源码可以完整看到这条链路的实现zerver/views/message_fetch.py 中get_messages_backend将allow_empty_topic_name声明为Json[bool] False——默认值False正是opt-in、默认关闭的直接体现该参数最终透传到 zerver/lib/topic.py 中的maybe_rename_empty_topic_to_general_chat当allow_empty_topic_name为假且 topic 为空字符串时服务器会把空 topic 重写为占位名Message.EMPTY_TOPIC_FALLBACK_NAME老客户端因此永远不会收到它无法处理的空 topic声明支持该能力的客户端则能看到真实的空字符串值。方式二当变更影响事件内容时改用client_capabilities机制。具体做法在POST /register接口的client_capabilities参数中新增一个标志同样默认关闭。事件队列注册成功后该客户端能力会作用于此后所有针对该队列的GET /events请求。从源码看这个机制的类型定义在 zerver/lib/events.pyclass ClientCapabilities(TypedDict): # This field was accidentally made required when it was added in v2.0.0-781; # this was not realized until after the release of Zulip 2.1.2. (It remains # required to help ensure backwards compatibility of client code.) notification_settings_null: bool # Any new fields of client_capabilities should be optional. Add them here. bulk_message_deletion: NotRequired[bool] user_avatar_url_field_optional: NotRequired[bool] stream_typing_notifications: NotRequired[bool] linkifier_url_template: NotRequired[bool] user_list_incomplete: NotRequired[bool] include_deactivated_groups: NotRequired[bool] archived_channels: NotRequired[bool] empty_topic_name: NotRequired[bool] simplified_presence_events: NotRequired[bool] individual_emoji_changes: NotRequired[bool] # Deprecated and no longer has any effect user_settings_object: NotRequired[bool] DEFAULT_CLIENT_CAPABILITIES ClientCapabilities(notification_settings_nullFalse)几个细节值得注意源码注释明确写着client_capabilities的任何新字段都应当是可选的NotRequired并且默认值集合DEFAULT_CLIENT_CAPABILITIES把所有能力都视为关闭——未声明能力的老客户端自动落入旧行为入口处 zerver/views/events_register.py 的events_register_backend以client_capabilities: Json[ClientCapabilities] DEFAULT_CLIENT_CAPABILITIES接收参数随后解析进do_events_register并随队列状态传递能力标志直接决定服务端输出的字段形态。以 zerver/openapi/zulip.yaml 中delete_message事件的文档为例声明bulk_message_deletion能力的客户端收到message_ids数组未声明的客户端仍收到单个message_id字段simplified_presence_events能力则区分 presence 事件的新格式presences字段与旧格式user_id/email/presence字段。规范文件中大量Only present for clients that support the ... client capability的措辞正是这一机制在 API 文档层的直接映射。兼容代码的成本与TODO/compatibility机制任何 opt-in 变更都会给服务器代码库带来持续的复杂度成本服务器必须同时支持旧形态和新形态。因此文档建议等条件成熟后回到代码中移除旧形态支持并且在写入兼容代码时就添加TODO/compatibility标记说明当某条件满足后可以删除的计划。文档还要求写这类标记前先用git grep TODO/compat -A10审阅若干已有示例。仓库中确实存在这样的标记。例如 zerver/lib/events.py# TODO/compatibility: realm_uri is a deprecated alias for realm_url that # can be removed once there are no longer clients relying on it. state[realm_url] state[realm_uri] realm.url以及同文件 L746-L753 中针对已废弃字段can_create_streams的注释说明其废弃于 Zulip 5.0feature level 102并给出可移除的条件不再需要支持读取旧属性的移动端版本。文档最后强调移除兼容代码本身也是一次 API 变更必须走同样的审批流程。同时客户端可以通过尽快升级到请求并正确处理新形态的 API来帮助降低服务器端的维护成本——即使客户端团队还没有时间完整实现触发这次 API 变更的新功能。设计 Clean API 的原则文档指出API 不够整洁不像不兼容那样会立刻出问题但对项目价值很大整洁的 API 让团队在持续做变更时不把自己框死boxing ourselves in也让移动端开发者无需逆向猜测服务器行为、无需绕开别扭的特性来实现功能。此外API 能让用户写出能做官方客户端一切之事的自定义工具是 Zulip 面向用户的一大卖点要兑现这个承诺API 就必须足够清晰、没有陷阱。写一个干净的 API 依赖品味、经验与工程判断难以完全写成条文但文档给出了一组评审 API 变更时的通用检查问题推演客户端实现设想该功能在 Web 或移动端的实现会长什么样有没有 API 修订方案能让它更简单或更清晰该功能的逻辑应该放在服务器、客户端还是两边推演语义新 API 提出的语义是否说得通是否真的解决了预期问题推演数据结构与命名参数、字段、枚举值的命名是否清晰、与语义匹配、且不会意外暗示出与真实含义不同的意思推演未来扩展该功能自然的后续扩展会不会被当前设计逼得只能做不兼容变更文档举了一个教训早期一些 API 字段被实现成 tuple 而非 object后来每当想加字段时都痛苦地迁移成了 object。API 变更中的各角色职责官方文档按角色拆解了流程这是贡献者参与 API 变更时最有操作性的部分。作为 PR 作者自己主动在#api design频道开启讨论线程并把线程链接放进 PR 描述与 API 变更无关的讨论请放到别的频道例如#backend如果数据模型讨论确实影响 API可以交叉引用。保持#api design频道的聚焦很重要这样移动团队等其他参与者才能确定该频道里所有讨论都与其相关、值得跟踪。作为 PR 评审者若 PR 看起来改动了 API检查 PR 描述中是否有#api design线程链接没有就在评审中提出也可以自己发起线程若 PR 接近可合并检查#api design线程是否已以批准收尾并核对 PR 中的 API 变更与达成的一致方案是否吻合若线程尚未收尾主动去线程里推动其得出结论。作为 API 评审者API 变更在#api design批准后服务器端作者会准备或修订实现 PR。如果参与了该变更的批准——尤其是当维护 API 质量本身就是你工作的一部分比如你是移动团队负责人——那么专门评审该 PR 的 API 文档改动很有价值完全忽略代码只读api_docs/和zerver/openapi/两个目录。原因有二取决于作者的熟练度与经验草稿文档常常不清晰或不准确这方面的反馈非常有价值有时草稿文档会暴露出对已商定 API 语义的误解或沟通偏差这类问题必须被抓出来。作为#api design频道维护者每个工作日尽量跟进频道内的线程如果某线程需要你所负责系统移动、Web、服务器的输入尽量自己给出或指出该由谁给出如果变更从你的视角看没问题、但仍需另一视角例如服务器/Web vs 移动的反馈要明确说清楚避免 PR 作者或评审者误以为已获批准。文档给出的示例措辞This looks good to me, but we need someone from the mobile team to confirm ...反之如果所有视角都已到位、或你认为对该变更而言其他视角不必要就明确宣布 API 变更已批准变更批准后可以考虑为其提移动端等其他客户端的实现 issue或在线程里请别人提这对高优先级变更推动较快对大多数变更惯例是等服务器 PR 合并后由机器人在#api documentation频道自动发帖再据此跟进。作为移动团队负责人这通常意味着你同时是#api design频道维护者和大多数 API 变更的 API 评审者。此外每次 API 变更合并进服务器后机器人会在#api documentation频道发新线程你应当确保有人回复明确决定是否需要在移动端建 issue跟踪对应改动并据此建立或更新 issue。仓库中的配套保障规范校验与文档生成作为上述流程的工程侧支撑仓库为 API 规范本身提供了多重自动化保障贡献者在做 API 变更时可以借助它们自检zerver/openapi/zulip.yaml单一事实来源的 OpenAPI 规范文件覆盖所有端点、参数、事件与 schema如前所述触及它基本即 API 变更zerver/tests/test_openapi.py测试套件中针对 OpenAPI 规范的测试确保 API 行为与规范文件保持一致——这正是文档所说写了测试就会强制规范文件同步修改机制的实现tools/check-openapi.ts基于apidevtools/swagger-parser的校验脚本支持--fix参数用于校验规范 YAML 的结构合法性与格式api_docs/API 文档源文件目录api_docs/rest.md、api_docs/real-time-events.md、api_docs/changelog.md 等文件与zerver/openapi/一起构成 API 评审者只读文档目录的评审范围。此外api_docs/目录中还有与客户端约定直接相关的文档如 api_docs/roles-and-permissions.md角色与权限、api_docs/message-formatting.md消息内容 HTML 格式约定与 content HTML 兼容性讨论相关、api_docs/mobile-notifications.md推送通知 payload另一类容易被忽视的 API 变更与 api_docs/rest-error-handling.md错误处理约定。小结Zulip 的 API 设计流程可以浓缩为一张清单先判定你的改动是不是 API 变更触及 zerver/openapi/zulip.yaml、端点/参数/字段/枚举/语义、content HTML 结构、推送 payload——是就进入流程先讨论在#api design频道发起线程等移动端与服务器/Web 端双重视角达成批准未批准不合入判兼容新增端点/参数/响应字段多数自动兼容但新参数默认值改变旧语义、默认引入新数据对象、content HTML 加 CSS 类等情形都不算仅 Web 应用独占的功能可例外但需按检查清单显式确认做 opt-in不兼容的变更用默认关闭的新参数或client_capabilities新能力标志实现参考 zerver/lib/events.py 的能力类型定义与 zerver/lib/topic.py 的空 topic 兼容处理并在兼容代码处留TODO/compatibility标记写明移除条件——而移除时再次走流程审干净从客户端实现视角、语义、命名与未来扩展四个问题审查设计补文档同步更新api_docs/与zerver/openapi/让 API 评审者能只读这两个目录完成文档层评审。理解并执行这套机制是向 Zulip 提交任何涉及 API 的功能 PR 前的必修课。【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulip创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门