Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

记 Friends 团队两次关于 API 设计的讨论

在过去的两周,我们 friends 经历两次关于 API 设计的讨论,但遗憾的事情是,在讨论的最后虽然方案确定下来了,但实际上最终并非达成一致,方案是妥协的结果,可想而知在讨论的过程中互相之间也没有足够的事实和依据说服对方。

所以我们通过邮件的形式将这个问题抛出来,希望得到大家的意见和建议,希望有更多的讨论和反馈。希望避免下一次再遇到相似问题时,陷入同样的讨论中。

接下来我将描述这两个 case,因为各位不一定有相关的业务知识,所以我会尽可能的简化和抽象这两个 case,著重于把主要矛盾点暴露出来。如果在省略业务知识之后有描述不准确甚至误导的地方,还请 friends 团队的各位同学以及了解上下文的同学指正和补充

问题一

上图就是目前我们需要实现的前端页面,页面上陈列了一系列字段,每个字段有一些属性(是否必填,是否展示)可供配置,字段的展示按照类型(属于 employee 还是 assignment)进行了分组。设置最终会决定它是否会出现在另一个表单中供用户填写,也就是说上图其实是一个用于控制表单的表单。

前端要做的非常简单,从 API 拿到接口,接口里有字段的 ID 、属性、属性的默认值和用户设置的值等等,然后根据接口信息将字段表单渲染出来。

但复杂的地方在于字段其实被划分为 basic 类型和 custom 类型,因为业务上的需求后端对于这两种类型和相关属性的数据存储使用了不同的表和结构。这一切对前端都是不透明的,至少在这个页面中你不用关心哪些是 basic 类型,哪些是 custom 类型。虽然字段需要按照 employee 和 assignment 进行分组展示,他们更类似于一个 tag,不足以对数据库的存储设计产生影响

那么问题来了,当我们设计 API 时,出现了两类不同的意见:

  1. 数据的返回应该是以 { basicFields: {}, customFields: {} }进行组织划分。因为这样更贴近于原始数据,更贴近后端的设计,对接口消费方的兼容性更强,将来改动较小。因为消费方是易变的需求也是多元的,这样倾向于一劳永逸的解决问题。
  2. 数据的返回结果应该是以消费方为中心的,因为页面中需要以 employee 和 assignment 的维度进行划分,那为什么不直接以 { employee: {}, assignment: {} } 的组织结构返回给前端?明知道前端需要 A 接口数据,但还是返回给 B 接口数据。

注意在讨论这个设计的当下,我们需要的是一个新的接口,一个只为这个页面需求服务的接口,

问题二

这个问题的来源仍然是问题一的需求,你可能留意到在UI设计图中每个字段有自己的名称和自己的固定顺序。但实际上后端是没有存储这些信息的,这些信息存储在前端的代码里。当 API 返回之后,前端根据字段 ID 将顺序等信息和后端数据 join 起来,最终才能把页面渲染出来

于是我们在探讨另一种可能,为什么不能把字段的名称和顺序,将来还有可能例如校验规则等信息都存储在后端呢?这样前端只需要无脑渲染就可以了。这样一来所有逻辑的修改都集中在同一处,维护的成本能够降低。

反对的声音:

  • 字段在展示阶段的名称和顺序后端不需要关心,所以不应该在后端维护
  • 这样的接口将不同的业务逻辑(例如 basic 类型、custom 类型、表现层信息)混淆在一块处理,指责不清不是一种好的实践
  • 前端永远不可能无脑,后端的字段的改动始终会牵涉到前端的修改

但我们不可否认这是一种可行的模式:表现层的信息真的不应该存储在后端吗?这样的接口如果放在一个真正的 BFF (毫无疑问目前 mymobility-apps 不是) 上是不是就不存在职责不清的问题了?如果我们能够把散弹式修改的 effort 降到最低是否也是一种成就?

我们应该如何看待这种模式?我们是否应该在任何情况下都杜绝这种模式?这种模式的问题究竟在哪?

关于这两个问题,你的选择是什么?我们期待你的意见,我们希望听到你的声音

个人意见

在发表我的个人意见之前,请允许我引入另两个著名的 API 设计模式:Chatty API 和 Chunky API。这两个模式和我们的方案选择有异曲同工之妙

Chatty API

例如你需要创建一个用户,理想情况下你只需要调用一个 /user 的接口即可,将用户的所有信息一次性传给后端。但是在 Chatty API 模式下,你需要拆分为多步接口进行调用:

  • 你首先需要调用/user创建一个空的用户实体
  • 调用/user/:id/avatar创建用户头像
  • 调用/user/:id/address创建用户住址
  • 调用/user/:id/pay创建用户的支付方式

Chunky API

假设你现在需要获取用户的信息,你只需要用户的名称和头像,但是后端提供的唯一接口/user/:id 包含了几乎所有的用户信息 avatar、name、address 等等

这两种模式的例子,以及我们真实遇到的两个例子,让我不禁怀疑有些疑惑:

  • API 设计是否真的有好坏之分?
  • API 设计是否能够遵循某些设计原则?
  • API 是否应该是基础的不易变的,还是应该以消费者为中心
  • 还是应该抛弃原则,每个 case 都独立思考解决方案?

当然即使设计原则存在于现实中也可能因为种种原因也无法遵守,但是如果它们存在的话应该成为开发者中的一根弦

我个人没有更好的意见,所以我查阅了很多资料。以下进入“掉书袋”环节,或许我找到的都是我想听到的,我也欢迎更多向左的意见和参考提出

Designing Web APIs

在 Designing Web APIs 一书的第四章里,作者提出了他(们)认为的 Design Best Practices. 包括以下几点:

  • Designing for Real-Life Use Cases
  • Designing for a Great Developer Experience
    • Make it Fast and Easy to Get Started
    • Work Toward Consistency
    • Make Troubleshooting Easy
    • Make Your API Extensible

在实际的叙述过程中,他举了几个来自专业人士的建议:

When we asked Ido Green, developer advocate at Google, what makes an API good, his top answer was focus: “The API should enable developers to do one thing really well. It’s not as easy as it sounds, and you want to be clear on what the API is not going to do as well.”

No matter how carefully we design and build our core API, developers continue to create products we’d never expect. We give them the freedom to build what they like. Designing an API is much like designing a transportation net‐ work. Rather than prescribing an end state or destination, a good API expands the very notion of what’s possible for developers. —Romain Huet, head of developer relations at Stripe

Don’t overcomplicate your API and don’t future-proof it too much. Often by future-proofing your API, you make it too generic and/or too complex. Developers building applications on (or using) your platform are building stuff for the “now.” They like to move quickly and are not always thinking 10 steps ahead. Your API should cater to this mindset. —Yochay Kiriaty, Azure principal program manager at Microsoft

他们更倾向于关注当下,关注开发者。而并非以提供者为中心

APIs: A Strategy Guide

本书第五章的标题是 Key Design Principles for APIs。作者也同样花费了一章的篇幅来表述他认为的 API 设计原则。其中非技术性的原则有

  • Design APIs for Specific Audiences
    • Designing for Developers
    • Designing for Application Users
  • Best Practices for API Design
    • Differentiate Your API
    • Make Your API Easy to Try and Use
    • Make Your API Easy to Understand
    • Don't Do Anything Weird
    • Less is More
    • Target a Specific Developer Segment

在这两本书中,作者们都提倡一种“渐进式”和“基于反馈”的开发模式:

Successful APIs often start with the absolute minimum amount of functionality, and then add functions slowly over time, as feedback is collected.

When talking to companies in the midst of launching an API, we often ask “Who are your target audiences?” If they answer by saying “Everybody,” we get worried. Similarly, if we ask them “What kinds of apps do you expect to see built?”, we get worried if their answer is, “All kinds.” Why? It’s really hard to create an API to meet the demands of every possible constituent and every possible use case. And even if you had the perfect API, you can’t market effectively to all these segments.

Embracing the Differences : Inside the Netflix API Redesign

但真实世界的 API 设计是什么样的情况,我们可以通过 Netflix 早期2012年这篇博客文章 Embracing the Differences : Inside the Netflix API Redesign 看出点端倪。你可以自行把整篇看完,但我在这里只稍做总结:

在早期 Netflix 的接口依然是 one-size-fits-all (OSFA) ,即一劳永逸的解决一切。这一个 API 需要同时满足至少 800 种设备的需求,那么问题就来了:

While effective, the problem with the OSFA approach is that its emphasis is to make it convenient for the API provider, not the API consumer. Accordingly, OSFA is ignoring the differences of these devices; the differences that allow us to more optimally take advantage of the rich features offered on each. To give you an idea of these differences

最终,他们通过在前后端之间加入 Adapter 层(在我看来是 BFF 的雏形)允许不同的设备团队维护自己的 API,将 API 以服务化或者说平台化的方式暴露出来

The future of API design: The orchestration layer

Daniel Jacobson,Netflix API 的负责人,上面 APIs: A Strategy Guide 书的作者,在他的文章 The future of API design: The orchestration layer 更详细的阐述了 OSFA 的问题:

At that time, many emerging APIs were being built as open or public APIs, targeting a large set of unknown developers (LSUDs) external to the providing company.

Because of the (hopefully) vast numbers of external developers using the API representing different use cases, the most sensible way to design the API for this audience is to have the providing API team design it in a very clean, concise, and resource-oriented way that closely represents the data model and/or features of its source(s).

For some of these implementations, the engagement with the developers is different. The audience is a small set of known developers (SSKDs). They may be engineers down the hall from the API team, a contracted company hired to develop an iPhone app, or an engineering team in a partnering company. In all of these cases, however, the API team knows who these people are (at least in the abstract sense).

More importantly, however, the API team and the providing company care about the success of these implementations in a different way than they might care about the applications developed by the LSUDs. In fact, the success of the SSKDs may very well be paramount to the success of the business as a whole, a model that is becoming increasingly more pervasive.

总结一下,至少在我看来,我能够收集到的信息都在告诉我,接口应该是以消费者为中心的,应该是关注当下的,应该是精细化运营的。当然可以提供“一劳永逸”的接口方案,但它并不是最佳的选择

关于关注当下

另一个我们在 API 设计中的讨论点是

  • 为什么 API 设计不能看远一点?
  • 我不否认写代码时应该看的更远,但问题是应该看到多远,以什么为界限?

对于第一个问题,我个人的理解这本质上其实是一个关于 overengineering 的问题,关于如何定义 overengineering,我引用我非常喜欢的 Code Simplicity 的作者 Max Kanat-Alexander (实际上我这个周末才知道的他)的文章 What Is Overengineering? 的内容回答这个问题

Essentially, somebody imagined a requirement that they had no idea whether or not was actually needed. They designed too far into the future, without actually knowing the future.

没错我们应该看到更远,但是看到更远的方式不是预测未来,而是考虑未来,而是应该让代码更有扩展性以至于有能力应付将来的需求。在 API 的问题上,如果你的代码、数据结构能够足够应付将来的扩展,就足够了

对于第二个问题,我同样引用他另一篇文章 Designing Too Far Into The Future 的内容来回答:

It should be designed for the requirement that you have right now, without excluding the possibility of future requirements. If you know for a fact that you need it to do X, and just X, then just design it to do X.

Some people think design means, “Write down exactly how you’re going to implement an entire project, from here until 2010.” That won’t work, brother. That design is too rigid–it doesn’t allow your requirements to change. And believe me, your requirements are going to change.

最后最后,我不禁想到一个问题:为什么我们需要大费周章写出好的代码,做出好的设计,甚至做出好的产品。我在他的书 Code Simplicity (简约之美:软件设计之道)得到了我认可的答案:

To help people.

Even when you’re writing libraries, you’re writing to help programmers, who are people. You are never writing to help the computer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.