译文 | A poor man's API

标签: api设计 apisix postgresql | 发表时间:2022-12-02 10:14 | 作者:Apache_APISIX
出处:https://segmentfault.com/blogs
作者:Nicolas Fränkel
翻译:Sylvia
https://blog.frankel.ch/poor-man-api/

在 API 日渐流行的年代,越来越多的非技术人员也希望能从 API 的使用中获利,而创建一套成熟的 API 方案需要时间成本和金钱两方面的资源加持。在这个过程中,你需要考虑模型、设计、REST 原则等,而不仅仅是编写一行代码。

如何打造一个具有高性价比且能持续迭代的产品,成为越来越多技术团队的目标。 本文将展示如何在不编写任何代码的情况下,简单实现一个 API 实践。

方案初试

该解决方案主要使用的是 PostgreSQL 数据库,PostgreSQL 是一个开源 SQL 数据库。同时我们没有编写 REST API,而是使用了 PostgREST 组件。

PostgREST 是一个独立的 Web 服务器,它可以将 PostgreSQL 数据库直接转换为 RESTful API。如果你想了解 PostgREST 的使用方法,可以参考 入门指南文档,内容非常全面且开箱即用。

接下来,我们将它应用到一个简单的示例中。

具体步骤

以下过程你可以在 GitHub 上找到完整源代码。
下方展示了一个通过 CRUD API 公开的 product 表。

由于我没有找到任何现成的 Docker 镜像,所以我单独创建了一份新的 Dockerfile。其中主要涉及依赖项的安装和参数化数据生成。

Dockerfile

  FROM debian:bookworm-slim                                                   
ARG POSTGREST_VERSION=v10.1.1                                               
ARG POSTGREST_FILE=postgrest-$POSTGREST_VERSION-linux-static-x64.tar.xz     
RUN mkdir postgrest
WORKDIR postgrest
ADD https://github.com/PostgREST/postgrest/releases/download/$POSTGREST_VERSION/$POSTGREST_FILE \
    .                                                                       
RUN apt-get update && \
    apt-get install -y libpq-dev xz-utils && \
    tar xvf $POSTGREST_FILE && \
    rm $POSTGREST_FILE

之后,Docker 镜像在 /postgrest 文件夹中会包含一个名为 postgrest 的可执行文件。这里可以通过 Docker Compose 来部署:

docker-compose.yml

  version: "3"
services:
  postgrest:
    build: ./postgrest                                   
    volumes:
      - ./postgrest/product.conf:/etc/product.conf:ro    
    ports:
      - "3000:3000"
    entrypoint: ["/postgrest/postgrest"]                 
    command: ["/etc/product.conf"]                       
    depends_on:
      - postgres
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: "root"
    volumes:
      - ./postgres:/docker-entrypoint-initdb.d:ro

接下来可以执行以下命令,查询前文提到的 product 表:

  curl localhost:3000/product

得到如下结果反馈:

  [{"id":1,"name":"Stickers pack","description":"A pack of rad stickers to display on your laptop or wherever you feel like. Show your love for Apache APISIX","price":0.49,"hero":false},
 {"id":2,"name":"Lapel pin","description":"With this \"Powered by Apache APISIX\" lapel pin, support your favorite API Gateway and let everybody know about it.","price":1.49,"hero":false},
 {"id":3,"name":"Tee-Shirt","description":"The classic geek product! At a conference, at home, at work, this tee-shirt will be your best friend.","price":9.99,"hero":true}]

方案优化

尽管上文提到的这套解决方案有效,但仍存在很大的改进空间。比如数据库用户不能更改数据、实际操作中每个人都可以访问相关数据等。这对于与产品相关的数据来说,可能不是一个大问题,但如果是医疗数据呢?

PostgREST 的官网 使用文档中提到了这一点,并明确提出:建议用户使用反向代理。

提到反向代理,就不得不将目光转向到 API 网关行列。与 NGINX 不同,这里我选取了 开源领域非常活跃的 API 网关产品 — Apache APISIX。APISIX 是一个动态、实时、高性能的 API 网关,提供了负载均衡、动态上游、灰度发布、精细化路由、限流限速、服务降级、服务熔断、身份认证、可观测性等数百项功能。

首先,我们可以在 Docker Compose 文件中补充 APISIX 相关信息,包括 APISIX 及其依赖的存储 etcd,而 etcd 主要用于存储 APISIX 的路由、插件等配置信息。

docker-compose.yml

  version: "3"
services:
  apisix:
    image: apache/apisix:2.15.0-alpine                              
    volumes:
      - ./apisix/config.yml:/usr/local/apisix/conf/config.yaml:ro
    ports:
      - "9080:9080"
    restart: always
    depends_on:
      - etcd
      - postgrest
  etcd:
    image: bitnami/etcd:3.5.2                                       
    environment:
      ETCD_ENABLE_V2: "true"
      ALLOW_NONE_AUTHENTICATION: "yes"
      ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2397"
      ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2397"

然后将 APISIX 配置为 postgrest 的代理进行调用。

  curl http://apisix:9080/apisix/admin/upstreams/1 -H 'X-API-KEY: 123xyz' -X PUT -d ' 
{
  "type": "roundrobin",
  "nodes": {
    "postgrest:3000": 1                                                             
  }
}'
curl http://apisix:9080/apisix/admin/routes/1 -H 'X-API-KEY: 123xyz' -X PUT -d '    
{
  "uri": "/*",
  "upstream_id": 1
}

现在再来查询端点,会得到与上文一致的返回结果。

  curl localhost:9080/product

添砖加瓦

虽然到目前为止,我们还没有添加任何实际项目,但准备工作已经全部就绪了。接下来就让我们为这个 API 添加一些其他功能,让其更安全有效,易于追踪。

DDoS 保护

API 作为一个连接属性的组件,必然要保证其过程中的传输安全。因此,在这里我们对 API 增加一些防护,让其免受 DDoS 攻击。APISIX 提供了非常多的官方插件,涉及鉴权、流量处理、监控等等。为了防止 DDoS,我们可以使用 APISIX 的 limit-count 插件。

在 APISIX 中使用插件时,你可以在创建特定路由时在每个路由上设置某个插件。如果你想让某个插件在每个路由上都生效,则可以使用全局规则。如下方所示,我们希望在默认情况下可以保护每个路由,所以使用全局规则设定 limit-count 插件。

  curl http://apisix:9080/apisix/admin/global_rules/1 -H 'X-API-KEY: 123xyz' -X PUT -d '
{
  "plugins": {
    "limit-count": {                 
      "count": 1,                    
      "time_window": 5,              
      "rejected_code": 429           
    }
  }
}'

现在,如果我们执行太多的请求,APISIX 将会保护上游。

  curl localhost:9080/product
  <html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>openresty</center>
</body>
</html>

增加鉴权

PostgREST 还在根端提供了一个 OpenAPI endpoint。因此,我们现在有两条路由: / (控制 Open API 规范)和 /product (控制产品)。

假设我们现在需要制定一套限制访问的操作,即不允许未经授权的人访问数据。普通用户可以访问产品端信息,而管理员用户可以访问 Open API 规范和产品端信息。

APISIX 提供了几种 身份验证方法,这些身份认证方式都可以通过插件进行实现。这里我们选取 APISIX 中最常用也是最简单的认证插件 key-auth,它依赖于 Consumer(消费者)抽象。 key-auth 插件的使用中需要一个特定的 header,这样插件就可以根据值数据进行反向查找,并找到其对应的 Consumer。

以下代码展示了如何新建一个 Consumer:

  curl http://apisix:9080/apisix/admin/consumers -H 'X-API-KEY: 123xyz' -X PUT -d '    
{
  "username": "admin",                                                               
  "plugins": {
    "key-auth": {
      "key": "admin"                                                                 
    }
  }
}'

同样的,我们需要对 Consumer user 和 Key user   进行相关操作。现在可以创建一个专用路由来配置它们,以便只有来自 admin 的请求才能通过:

  curl http://apisix:9080/apisix/admin/routes -H 'X-API-KEY: 123xyz' -X POST -d ' 
{
  "uri": "/",
  "upstream_id": 1,
  "plugins": {
    "key-auth": {},                                                             
    "consumer-restriction": {                                                   
      "whitelist": [ "admin" ]                                                  
    }
  }
}'

然后使用以下命令测试一下:

  curl localhost:9080

发现并没有起作用。这是因为我们没有通过 API 密钥的 header 进行身份验证。

  {"message":"Missing API key found in request"}

添加 header 后再次进行测试:

  curl -H "apikey: user" localhost:9080

{"message":"The consumer_name is forbidden."}

发现仍然没有效果。这是因为这里 API key 为 user ,我们前边仅为 admin 也就是管理员设置了相关权限。所以如果更换为 admin ,就会如期返回 Open API 规范的相关信息。

配置监控

在软件系统中,总有一个被人们低估重要性的功能 —— 可观测性。在生产环境中部署了任何组件,都需要监控其运行状况。

如今,很多服务都提供了可观测性的功能,比如 Prometheus。得益于 Prometheus 的开源属性,它被广泛应用于实践中。因此,这里我们也选用 Prometheus 进行相关数据的监控。

为了通过图表等形式显示数据,我们也同时需要依赖于 Grafana。接下来,将这些组件添加到 Docker Compose 文件中。

docker-compose.yml

  version: "3"
services:
  prometheus:
    image: prom/prometheus:v2.40.1                                    
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml    
    depends_on:
      - apisix
  grafana:
    image: grafana/grafana:8.5.15                                     
    volumes:
      - ./grafana/provisioning:/etc/grafana/provisioning              
      - ./grafana/dashboards:/var/lib/grafana/dashboards              
      - ./grafana/config/grafana.ini:/etc/grafana/grafana.ini         
    ports:
      - "3001:3000"
    depends_on:
      - prometheus

以上操作需注意:APISIX 的默认监控方案中自带 Grafana,因此只需从 APISIX 中获取 相关配置 即可。同时将默认端口从 3000 更改为 3001 是为了避免与 PostgREST 服务发生冲突。

一旦监控基础设施到位,我们只需要指示 APISIX 以 Prometheus 期望的格式提供数据即可。可以通过配置插件和新的全局规则来实现这一目标:

config.yaml

  plugin_attr:
  prometheus:
    export_addr:
      ip: "0.0.0.0"             
      port: 9091
  curl http://apisix:9080/apisix/admin/global_rules/2 -H 'X-API-KEY: 123xyz' -X PUT -d '
{
  "plugins": {
    "prometheus": {}
  }
}'

此时发送几个查询请求,并打开 Grafana 仪表板,可看到类似数据。如果运行较多请求,则会出现更丰富的数据仪表。

总结

创建一个成熟的 RESTful API 是一项巨大的投资。你可以通过 PostgREST 将数据库暴露在 CRUD API 中来快速测试一个简单的 API。但是,这样的体系结构不适用于实际生产。要想使其更具实践性,就需要在 PostgREST 前设置一个 facade、一个反向代理,或者更好的 API 网关。

Apache APISIX 作为云原生 API 网关,提供了广泛的特性,从流量处理到认证授权和可观测性等。有了 APISIX,你就可以用较低的成本快速验证你的 API 需求。锦上添花的是,当你验证需求完成之后,还可以保留现有的 facade,并用自定义开发的 API 来替换 PostgREST。

相关 [译文 poor man] 推荐:

译文 | A poor man's API

- - SegmentFault 最新的文章
作者:Nicolas Fränkel. 在 API 日渐流行的年代,越来越多的非技术人员也希望能从 API 的使用中获利,而创建一套成熟的 API 方案需要时间成本和金钱两方面的资源加持. 在这个过程中,你需要考虑模型、设计、REST 原则等,而不仅仅是编写一行代码. 如何打造一个具有高性价比且能持续迭代的产品,成为越来越多技术团队的目标.

Ubuntu 里使用中文 Man

- charleszhou - Wow! Ubuntu
[提问] Mysql导入sql的乱码问题. 都知道 man 特别给力,有什么不会了就 man 一下,甚至 man 自己也能 man. 不过 Ubuntu 默认的 man 是英文的,看着总归有些累,将 man 改为中文的就易读多了. # 使用方法:打开终端,输入以下命令安装中文 man 手册. 安装完了不够,还要看一下中文 man 手册安装到哪里了,在终端输入.

This man is your FRIEND系列海报

- Edward - hUrR DuRr
Fallout: New Vegas乱入:. Team Fortress 2乱入:. reddit上真是什么奇葩的subreddit都有:r/PropagandaPosters.

Linux中文man在线手册

- Hobbes - C++博客-首页原创精华区
     摘要:   阅读全文. Gezidan Studio 2011-08-18 21:58 发表评论.

今天你Man了没有?!男人节特辑

- AWard - 果壳网 guokr.com - 果壳网
身为男人,最感兴趣的自己的零件,大概要数那话儿了,本篇也将以这个分类起头. 会“骨折”,能预报心脏病,受吸烟影响,硬太久会坏掉,有很多种形状,量法不对还会显短,这不是什么奇怪的东西,这是男人的那话儿. 看了 《你真的了解你“兄弟”吗. 》 就会发现,你恐怕还真不了解你“兄弟”啊……. 在男人心目中,宁折不弯,一柱擎天才是真爷们,其实在做爱做的事时,那儿还真不一定总是直的.

早熟孩童專用!吸起來超MAN超討喜的「翹鬍子奶嘴」

- fx_wonder - 大人物
每個做父母的朋友最大的心願,就是希望孩子能夠快快長大,不只是一眠大一吋,最好能明天一醒來就給我大學畢業出門去賺錢回來孝敬我啊. (怨念很深XD)但是空想也只是空想,如果你希望你的孩子看起來成熟穩重一點,除了禮儀教育之外,這裡還有個法寶,讓他戴上立馬超齡個20歲不成問題啊XD.

pman – 官方的PHP Man,热爱命令行的PHP程序员有福了

- Platinum - Chen Ze的日志
有unix/linux开发或者管理经验的程序员或者系统管理员都知道man是超级好用的一个命令. 例如man malloc就可以看到malloc函数的用法,而man ssh则可以看到ssh客户端的各个参数的详细说明. 因为觉得man非常好用,所以我很早之前就想写一个php man,但是一直都没去做. 07还是08年我写过一个php的ctags,通过解析php的html文档来生成tags文件,使得可以在vim方便地查看php函数定义.

并发框架Disruptor译文

- - 酷壳 - CoolShell.cn
(感谢同事 方腾飞投递本文). Martin Fowler在自己网站上写了一篇LMAX架构的文章,在文章中他介绍了LMAX是一种新型零售金融交易平台,它能够以很低的延迟产生大量交易. 这个系统是建立在JVM平台上,其核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单. 业务逻辑处理器完全是运行在内存中,使用事件源驱动方式.

Paul Graham:撼动硅谷的人(译文)

- puppyguy - 阮一峰的网络日志
为《黑客与画家》写"译者序",遇到一个棘手的问题. "应该如何介绍Paul Graham,才能让中国读者了解,这是一个在美国互联网界如日中天的教父级人物. 正在发愁的时候,老天帮忙,让我看到了上个月《福布斯》杂志有一篇Paul Graham长篇专访,写得非常棒,是迄今最佳的关于他的介绍. 这么难得的材料,怎么能错过呢.

HTTPS的七个误解(译文)

- danbao - 阮一峰的网络日志
开发网页的时候,往往需要观察HTTP通信. 我使用的工具主要有两个,在Firefox中是Firebug,在IE中是Fiddler. 但是,一直听别人说,付费软件HttpWatch是这方面最好的工具. 前几天,HttpWatch的官方网志刊登了一篇好文章,澄清了一些HTTPS协议容易产生误解的地方. 学习之后,我增长了不少网页加密通信的知识.