[面试][後端]设计 API 时会考虑哪些点?

你设计的 API 除了跑的动以外,它安全稳定吗?

既然 Junoir 跟 Senior 的後端工程师都能写出可以运作的 API;那公司为什麽要多花钱请一个比较贵的工程师呢?

不仅面试官会问这个问题,你也要时常反问自己:「我凭什麽可以开出更高的薪水?我跟其他的工程师有哪里不一样?

大纲

  1. 设计 API 时会考虑哪些点?

    • 1.1 面试官为什麽会问?
    • 1.2 面试官想从答案确认什麽?
    • 1.3 笔者提供的简答
  2. 回答问题所需具备的知识

    • 2.1 验证 Client 端身份
    • 2.2 了解後端验证的重要性,并认识常见的 Validation Rule
    • 2.3 优化後端的常见方法
    • 2.4 浅谈 Unit Test(单元测试)
  3. 衍伸问题

    • 3.1 你知道 PUT & PATCH 的差异吗?
    • 3.2 请举例 RESTful api 的命名原则
    • 3.3 GraphQL 跟 RESTful API 的差异在哪里?

1. 设计 API 时会考虑哪些点?

1.1 面试官为什麽会问?

能依照需求规格书写好 API 只是对後端工程师的基础要求;但这个 API 考虑的是否周全就看每个工程师的经验了。

这算是常见面试题,面试官想从回答中了解你後端的实作经验与基本功,并判断你在进入团队後是否需要花更多时间训练才能成为有效战力。


1.2 面试官想从答案确认什麽?

  • 你设计的 API 是否有防护的机制
  • 当 Client 端传入不符规范的参数及型态时,你的 API 是否有成为防线
  • 你有用什麽方法保证 API 的稳定性
  • 是否有思考过 API 的优化

1.3 笔者提供的简答

以过去设计的内部管理系统来说,Client 端在登入後会取得 JWT,之後在存取 API 前会用这个 JWT 来验证是否有存取的权限;为了避免 Client 端使用不符规范的参数,我会先验证每个传入参数的格式,并撰写 Test Case 确保这只 API 在不同情境下的 Response 都符合期待;再确认 API 的稳定性後,我会去思考还有什麽地方可以优化,让 Client 端有更好的体验。


2. 回答问题所需具备的知识

2.1 验证 Client 端身份

这边带读者了解两种主流验证机制,大部分的框架都有提供对应的方案供开发人员使用。

  • Session 与 Cookie
    当 Client 端登入後,Server 会产生一笔 Session 并记录(可能存在资料库或记忆体),同时将可以识别身份的 SessionID 送回 Client 端储存於 Cookie;此後 Client 端的请求,Server 只需要根据 Cookie 中的 SessionID 寻找 Session 来验证使用者即可。
    • 使用 Session 要考量的问题
      1. 因为 Session 的资料存在 Server 中,需考虑同时有很多使用者上线时要面对的效能或是记忆体问题
        • 解决方案:定期清理过期 Session。
      2. 如果采取 Cluster 部署,就要考虑不同 Server 的 Session 共享问题
        • 解决方案:Server 需设计 Session 同步机制(Replication)。
  • JSON Web Token(JWT)
    当 Client 端登入成功後,Server 会回传一个 JWT,Client 端通常会将 JWT 储存於 localstorage;当 Client 端打算访问受保护的资源时,需要在 Header 的 Authorization 使用 Bearer 模式添加 JWT,如果验证通过即可存取相关资源。
    • 使用 JWT 要考量的问题
      1. 在 Client 端取得 JWT 後,在 JWT 有效期内他都能够使用;也就是说我们这段期间无法废弃或是改变这个 JWT 的权限。就像是你不小心给了某个帐号管理员的权限,而他也刚好登入并打算在 JWT 有效期内干大事,此时你就算删除帐号也於事无补。
        • 解决方案:关键资源多设计一层 Middleware,从资料库来验证帐号是否有存取权限。
      2. 不要为了方便把 JWT 的有效期限设定太长,不然这个 JWT 泄露出去後,任何人都可以拿它来干大事。
        • 解决方案:JWT 有效期限不要太长,同时使用 HTTPS 来减少盗用。

2.2 了解後端验证的重要性,并认识常见的 Validation Rule

无论前端验证做得多完美,後端验证都是必须存在的;因为想攻击系统的人不一定会从前端执行,直接 Call API 尝试所有可能性的效率快多了;前端验证可以大幅减少不必要的 Request 产生,後端验证则是保护资料的最终防线

  • 为什麽要了解 Validation Rule?

    • 你可以节省非常多亲手撰写 Function 的时间(自己写的效能跟稳定度未必好,还很占版面)。
    • 这些不符规范的资料并不会进入後端运算的逻辑层面。
    • 验证发现错误就 Response Error,减少 Client 端等待时间。

接下来用情境来介绍几个常见的 Validation Rule,不同框架的名字可能略有不同,我这边以 Laravel 的为主。

  • 验证使用者注册系统时填写的参数

    • 此为 Laravel 验证传入参数的程序,下方依据参数进行解说。

      $validator = Validator::make($request->all(), [
        'email' =>  ['required', 'email', 'string', 'max:255', 'unique:users'],
        'name' =>  ['required', 'string', 'max:50'],
        'password' => ['required', 'string', 'max:255', 'min:8', 'regex:/^.*(?=.{8,})(?=.*?[a-zA-Z])(?=.*?[0-9]).*$/', 'confirmed']
      ]);
      
      • email
        为必填 required 栏位,同时检查传入的字串是否符合 email 格式以及长度 max 是否超过限制,并确认这个 email 并没有被注册过 unique
      • name
        为必填 required 栏位,同时检查传入的字串长度 max 是否超过限制。
      • password
        为必填 required 栏位,除了检查传入的字串长度 max 是否超过限制外,通常会限制最小长度 min;再严谨一点会要求 password 复杂度需包含英数字 regex,且内容与 password_confirmation 栏位相同 confirmed

2.3 优化後端的常见方法

这边先不讨论程序语言和资料库选择带来的影响,专注在共同可行性方案上。

  • 操作资料库时加入限制条件
    就算前端 Request 的参数没有限制条件(ex:分页、笔数、种类...),後端也要有意识的设定预设值,不然当资料量大的时候除了会跑很久浪费效能外,前端也不适合接收/呈现这麽多资讯。

  • 回传前端的资料尽量是已经计算过的
    Client 端使用的机器效能未知,如果把太多的逻辑运算交给前端可能导致不好的使用者体验;我这边建议後端最好回传计算後的资料,前端负责显示为主,不过这块也要看实际的业务量以及情境来 trade-off 。

  • 加入 CDN Cache
    如果遇到国外客户抱怨连线速度很慢,就可以考虑采用 CDN 来解决这个问题;CDN 服务商的做法是将 Server 建置在世界各地,将网站上的静态内容(图片、影音、档案)与动态内容(资料库查询)Cache 到 Server,根据访客的地理位置,从最近的 Server 提供资料;好处如下:

    • 提升网站的稳定度(如果某个 CDN Server 挂了也有替身)。
    • 节省原本 Server 的频宽(因为流量被分散了)。
    • 改善使用者体验(过去会卡住的都是大档案,有了 CDN Cache 後浏览会更流畅)。
  • 提升硬体水平
    江湖传言:「效能不够,机器来凑!」虽然听起来好像是在开玩笑;但实际业务上如果真的遇到效能瓶颈,许多公司的做法就是先靠硬体让服务稳定;服务稳定了,你才有心力去 Refactor 程序。


2.4 浅谈 Unit Test(单元测试)

  • 为什麽 Unit Test 很重要?
    • 有它保证程序码的稳定性,发生错误时可以快速找到源头,因此开发人员可以更大胆的开发。
    • 减少手动测试的时间,大幅降低相关维运成本
    • API 版本更新时(ex:v2 升级到 v3),确保旧版 API 可以正常运行
  • Unit Test 使用时机
    • 开发人员要先在本机 Run 过一遍,确认所有 Unit Test 都通过。
    • 如果有使用 Git 做版本控制,在特定 branch 合并时 CI/CD 应该要有某个 Stage 来做测试,确保合并分支的程序正确性
  • 撰写 Unit Test 基础概念
    • 测试时使用(或自动建立)Mock data,不要动到 Server 资料
      Unit Test 着重在测试程序逻辑,建议测试时使用 Mock data,不然可能导致正式环境发生意外。
    • 测试要涵盖你程序中的所有逻辑
      • 假设一个 API 有 12 种回传逻辑,你的 Unit Test 就要有 12 个;根据莫非定律,少写的测试往往上线後就会爆掉
      • 如果 API 有做 JWT 防护、Request 参数验证,我建议要把所有错误情境都做一次模拟;甚至要模拟 JWT 还在有效期,但实际权限已被移除的边缘测试

笔者听过有些公司在专案正式 Release 时没有撰写任何的 Unit Test,直接让客户人肉测试,等客户回报错误时再去修复 Bug;嗯...这种公司的离职率通常很高,因为有一堆历史业障需要修但没人敢碰。


3. 衍伸问题

3.1 你知道 PUT & PATCH 的差异吗?

考点:对於 API 基础操作的掌握程度

「PUT」是替换资源,而「PATCH」则是更新部份资源内容;以使用者举例,假设他有「姓名、简介、年龄」这几个栏位,如果想更新「姓名」的资讯:

  • 用 PUT 方法送出更新,除了「姓名」外还要填写所有的栏位,如果没填写会导致其他的栏位更新成预设值。
  • 用 PATCH 方法送出更新,只需填写要更新的「姓名」栏位即可。

3.2 请举例 RESTful API 的命名原则

考点:确认你写 API 的习惯是否符合标准

假设今天有一个 users 的 Table。

  • 取得 user list
    • 方法:GET
    • API 路径/api/users
  • 取得 user detail
    • 方法:GET
    • API 路径/api/users/{user_id}
  • 新增 user
    • 方法:POST
    • API 路径/api/users
  • 更新 user 资讯
    • 方法:PUT
    • API 路径/api/users/{user_id}
  • 删除 user
    • 方法:DELETE
    • API 路径/api/users/{user_id}

3.3 GraphQL 跟 RESTful API 的差异在哪里?

考点:确认你对市场技术的认知程度

过去 RESTful API 需要根据需求规格书给每个 API 设计回传的资料结构,如果这个 API 在很多页面被共用就有可能要回传更多的资讯,但回传的资讯中有些是只有特定页面才有需求的,假使拆成多只 API 也会导致日後维护困难;而 GraphQL 的出现正好解决了这个问题,他能够由 Client 端来定义 Server 端要回传的资料结构,不再回传冗赘资讯。

  • 补充:用范例了解两者差异
    要查询图书馆中特定书籍,回传内容须包含:「作者、书名、出版年」。

    • GraphQL

      • Client 端传送的 query

        query {
          books (id:12) {
            authors {
                firstName
                lastName
            }
            title
            yearPublished
          }
        }
        
      • Server 端 Response 的 result

        {
          "data": {
            "books": {
              "authors": [
                {
                  "firstName": "Lin",
                  "lastName": "Dean"
                }
              ],
              "title": "全端工程师生存笔记",
              "yearPublished": "2022"
            }
          }
        }
        
    • RESTful API

      • Client 端的 Request
        需要拆成GET api/books/12GET api/authors/12来分别取得书籍、作者资讯。

      • Server 端 Response 的 result
        GET api/books/12

        {
          "title" : "全端工程师生存笔记",
          "authorID": 12,
          "yearPublished" : 2022,
          "page": 168,
          "tags": ["履历"、"面试"、"职场"]
        }
        

        GET api/authors/12

        {
          "firstName": "Lin",
          "lastName": "Dean"
        }
        

    在上面的范例中,RESTful API 需要两个 Request 才能取得完整资料,如果想用一个 Request 取得完整资料就要重新建立一支 API;比较起来GraphQL 对前端工程师来说是更好使用的工具;不过转换与建立的成本较 RESTful API 高,如果想尝试 GraphQL 带来的效益,可以考虑渐进式的迁移。


感谢大家的阅读,如果喜欢我的文章可以订阅接收通知;如果有帮助到你,按Like可以让我更有写文的动力,我们明天见~

参考资源

  1. 进阶 RESTful API 讨论
  2. 透过 JWT 实作验证机制
  3. 认识 Cookie、Session、Token 与 JWT
  4. 简单聊一聊 Cookie、Session、Token、JWT 的区别和作用

我在 Medium 平台 也分享了许多技术文章
❝ 主题涵盖「MIS & DEVOPS资料库前端後端MICROSFT 365GOOGLE 云端应用自我修炼」希望可以帮助遇到相同问题、想自我成长的人。❞


<<:  Rust-定义Closure(闭包)

>>:  [Day 30] 使用ChromeDriver来做单元测试(三)

Day 14: DockerFile实作Node前後端 (下)

Node前端 今天来讲Vue前端的与postgres包入进docker,和後端编译後直接放入相比,前...

16.MYSQL搜寻特殊字元

在前一篇文章中,我们有提到如果要搜寻部分字元的话,可以使用 _ 以及 % 这两种 但是如果想要将 _...

【从零开始的 C 语言笔记】第二十六篇-变数的生命周期(1)

上一篇我们介绍了副函式的概念,一般来说我们会把特别处理某些资料的部分,另外拉出一个副函式来处理,除...

【第十八天 - Binary Tree介绍】

Q1. binary tree 是什麽 二元树 (binary tree) 是一种资料结构,应用非常...

硕士课程一问

想问一下大家觉得要是想从事软件开发或是前端web的工程师,大家会推荐念硕士课程吗?还是大家觉得实战经...