(二) 创建符合规范的 API 接口- Build API For Your Company 系列

本文内容

完成 测试数据填充 的工作,我们就需要筹划一下API究竟怎么写的事情了?或许你的项目经理会给你一份API文档,让你实现就可以。这样你就不用来做本部分的工作。更多的时候你可能也是这些接口的制订者,你需要和你的团队伙伴一起讨论并确定一个可行的API接口,由你来规范并整理出文档,或者整个项目都是你一个人负责,当然你也需要和自己聊聊改如何来处理这个阶段的事情:计划并创建符合规范的接口

我们用 Endpoint 这个词来代指我们需要处理的对象:单个的接口,
就像 GET https://api.example.com/foo/bar

处理业务需求的阶段,这么说吧,我们目前接触到得很多开发任务可能都会涉及到一系列 Resource 的 CRUD(Create, Read, Update, Delete)的操作, 如果你对 resource 这个概念还不是很了解,可能需要补充一点概念知识:

简单解释一下,Resource 可以理解为我们在应用中抽象出来的一种对象实体,比如你要开发一个基于Map的应用,你所处理的一系列地点(Places)就可以作为程序中的一种资源。你所做的是去处理一下这中资源,为其创建对应的APIs,来完成对该资源的操作。

废话不多说,我们可以实际开发中的操作。TDD (Test Driven Development)的开发流程确实是一种非常不错的开发方式,个人觉得API的开发更应该采用这种方式。我们也将会用这种方式来完成本系列的讲解。思维回到之前解释 Resource 时使用到的一个示例,假设我们有这样一些商业需求:

  • 在地图(Map)上有多个地点;
  • 每个地点(Place)的一定范围内有多个商业机构;
  • 每个商业机构(Merchant)可以提供一些商业服务(Service);
  • 用户(User)可以在地图上点击商业机构,来完成一些需求(登记、购买、预约等)

所以你的脑海中需要一个 "Places" 资源的概念存在,快速列出这个资源需要处理的 Actions 列表:

    Places
        - Create
        - Read
        - Update
        - Delete

显而易见,我们需要能够查看这些 Places,需要能够创建、更新和删除这些资源。考虑的更多一点,我们有时候需要列表展示符合一定条件的 Places 列表(分页的内容后面会谈及), 修改一下:

    Places
        - Create
        - Read
        - Update
        - Delete
        - List

如果想让这个列表更有效的辅助开发和团队交流,你可以给这个列表加上一些简单的内容:

    ...
    - List(Lat, lon, distance, name)

我们并没有在 Create 和 Update 这两个操作上面添加更多信息,是应该这两个接口应该是牵扯业务逻辑最多的接口,我们现阶段的目标只是列出需要创建出一个接口列表,没有必要去思考多么详尽的接口设计,时候未到。

考虑的再多一点,地点这个资源可能会有需要展示的图片,那就需要用户上传图片到服务器。当然,完全可以把图片上传的实现设计到 Update 操作里面去,但是上传文件可能需要我们考虑的东西更多一点,你可能需要设计如何去保存到本地,或者上传到其他云服务提供商,需要对文件的验证和进度有个把控,这些东西其实还是蛮烦人的。所以更建议把文件上传这个接口给分离出来更好一点。我们创建一个Image操作来处理上传图片的问题:

    Places
        - Create
        - Read
        - Update
        - Delete
        - List(Lat, lon, distance, name)
        - Image

根据这个思路,我们可以为这个简单的商业应用,列出一个简单、完整的 API Actions 列表:

    Places
        - Create
        - Read
        - Update
        - Delete
        - List(Lat, lon, distance, name)
        - Image
    Merchants
        - Create
        - Read
        - Update
        - Delete
        - List(Lat, lon, name, address, telephone)
    Services
        - Create
        - Read
        - Update
        - Delete
        - List
    Users
        - Create
        - Create
        - Read
        - Update
        - Delete
        - List(active)
        - Image
        - Services
        - Favorites

有了这个列表,我们其实就可以着手进行代码工作了,但是在把这些操作转换成我们熟悉的一个个 Endpoint 之前,我们还是需要了解一些理论知识:

Endpoint Theory

简单来说,就是需要你对 RESTful 这个概念有一定的了解,我简单整理了一些资料:


Best Practices 的命名习惯(注:这部分内容翻译本系列参考书籍的部分章节)
  1. Get Resources

    • GET /resources 返回一个分页的列表或者是默认指定的资源信息
    • GET /resources/X 只返回一个指定的资源信息,X可以是ID, hash值,slug,username,等等,只要这个标识对于资源来说是唯一的(unique)
    • GET /resources/X,Y,Z 客户端想同时获取多个资源信息,服务器返回包含多个资源信息的集合
  2. Embeding Data

    需要嵌套资源返回的详细我们之后的系列会聊到,在这里可以简单的看一下。其实我们经常做这种资源嵌套(nested resources or embedding resource), 当我们想要获取到某个地点的所有商业就列表的时候,你的 EndPoint 应该是这样的格式:

    • GET /places/X/merchants 找到所有该地点范围内的商人资源列表
    • GET /users/X/services 返回用户所参与的所有服务(可以通过参数指定状态)列表
    • GET /users/X/services/Y 返回用户的一个特定的服务信息

    最后一个示例可能是有争议的,有些人可能更喜欢更简短一点的写法 /services/Y

  3. Increment Id vs Uuid

    以前自己写程序的时候经常会将资源 id 这个字段设置为 increments,因为很方便,很多人也建议这么做,但是从商业和安全的角度来看,你可能不希望将你的敏感资源的 id 给暴露出去,你不想有人通过遍历 id 来获取一下信息,更不想让你的竞争对手看到这些资源的统计数据。所以使用 UUID 或许是一个可以考虑的方式,当然如果有更好的方式,可以在下方留言,咱们可以讨论一下。
    在 Github 上有很多来关于 Laravel 框架生成 uuid 的项目,如果你需要考虑这方面的问题,可以去搜索一下,下面是一个示例:

    Laravel-uuid Github
    Packagist

    安装(切换到项目目录):

    $ composer require webpatser/laravel-uuid dev-master

    使用(很简单的):

    # Edit `app/config/app.php` and add the `alias`
    'aliases' => array(
    'Uuid' => 'Webpatser\Uuid\Uuid',)
    
    # 基本的用法
    Uuid::generate()
  4. DELETE Resource

    • DELETE /places/X 删除单个的地点资源
    • DELETE /places/X,Y,Z 批处理方式删除多个资源
    • DELETE /places 这个操作会删除所有地点资源
    • DELETE /places/X/image 删除指定地点的图片
    • DELETE /places/X/images 如果选择多个图片资源的话,这个操作可以用来实现批量删除的操作
  5. POST vs PUT

    资源的创建和更新是需要谨慎对待的两个操作。许多人会建议将 HTTP verb(即HTTP方法)匹配到经常处理的CRUD操作,例如:使用 POST 来新建资源信息, PUT 来更新资源信息。但是这种做法常常并不高效和满足不了功能需求;

    通常来讲, PUT 方法适用的情况是你处理改操作之前知道整个URL,而且重复的进行该操作(多次请求)不会对造成不同的结果。例如, 当你为一个地点上传一张图片的时候,PUT 方法可能是不错的选择:

    PUT /places/1/image HTTP/1.1
    Host: example.com
    Content-Type: image/jpeg

    但是当你有多张图片要上传的时候,你就无法确定整个URL,而且多次尝试该操作会导致不同的结果产生,使用POST方法或许更好一点。

    更新用户配置 是经常需要处理的一个事情,你可以这样:

    • POST /me/settings 更新单个指定的配置信息
    • PUT /me/settings 更新全部配置信息

    这个部分确实比较多变,但是最好不要去把 HTTP method 绑定到指定的 CRUD 操作,毕竟创建 Web 应用,需要很大的灵活性。

  6. 个人喜好的问题

    有些人可能喜欢使用单数形式来命名资源名称, 例如: /user/X 之类的,但复数形式呈现给其他人的逻辑可能更好一点。当你使用 /user 这样的接口时,开发者对接口的返回值会有一个下意识的反应
    :这个返回单个用户信息?或许是返回 me 的信息等等。这时候你返回整个用户列表就会造成不必要的困惑。或许你会说,返回多个数据的时候还是可以采用 /users 这样类似的命名习惯的, 但是采用复数形式的命名习惯可以保持命名的一致性,何乐而不为呢?


Controllers and Routes

有了之前的一些理论知识,用 laravel 快速实现这些接口,首先你需要将这些接口进行一下逻辑的的分组,创建需要的 controller,每种资源对应一个控制器也是一种方案:

  • PlacesController
  • MerchantsController
  • ServicesController
  • UsersController

你可以在 laravel 手动创建这些文件,laravel 提供了非常棒的语法来快速实现这些路由:

切换到项目目录下面:

php artisan controller:make UsersController

修改 app\routes.php 文件和 app\controller\UsersController.php 文件,你可以得到下面的这张表:

Action Endpoint Route Name Controller
Create POST /users users.store UsersController@store
Read GET /users/X users.show UsersController@show
Update POST /users/X users.update UsersController@update
Delete DELETE /users/X users.delete UsersController@destroy
List(active) GET /users users.list UsersController@list
Image PUT /users/X/image users.image UsersController@uploadImage
Services GET /users/X/services users.services UsersController@services
Favorites GET /users/X/favorites users.favorites UsersController@favorites

有了这个 Users 的路由示例,其他的控制器的也可以快速的写出来,在这里就不再赘述了。


下篇文章内容

当然,我们现在有了一份经过思考和讨论的草图,但是作为新手,我们可能还需要学习一些 HTTP RequestsResponses 的知识,这个就是下个章节介绍的内容。

Remote. Open. Engineer.
本帖已被设为精华帖!
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8
Summer

三更灯火五更鸡,正是男儿发奋时。:+1:

9年前 评论
Summer

@JobsLong 背景不好, 迷彩服 + 绿叶背景, 都看不到人的轮廓了. :sunglasses:

9年前 评论
Summer

呦吼呦吼呦吼. 说好的更新呢 :sunglasses:

9年前 评论
Summer

已更新.

9年前 评论
Summer

@JobsLong 解析器出现的问题, 请见这里 http://phphub.org/topics/123 , 已经放在我的 Todo list 里面了.

9年前 评论

晓文大神啊啊啊啊啊啊:+1:

6年前 评论
StringKe

文章最后一处拼写错误 HTPP

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!