API优先发展 - 理论与实践
#教程 #python #tdd #openapi

API第一种方法是一种软件开发技术,在该技术中,首先设计和开发API(应用程序编程接口),然后完成API的实现。这种方法与构建应用程序的传统方法不同,在该应用程序中,UI(用户界面)更为重要,稍后将开发API。

在这篇文章中,我们将介绍为什么API-First是一个很好的做法,以及如何在Python中实施它。
此外,我们将涵盖以下实践,例如:API文档和TDD。

为什么首先?

API第一种方法比传统方法具有多个优点。以下是使用API​​第一种方法的一些好处:

  1. 更好的文档:
    由于首先开发了API,因此更容易正确记录它。这使开发人员更容易理解如何使用API​​,并使其他团队更容易使用API​​。

  2. 改进的集成:
    使用API​​的第一种方法,整合不同的系统和应用程序会更容易。这是因为API从一开始就被设计为其他系统和应用程序。

  3. 可重复使用的组件:
    API的第一种方法允许开发可重复使用的组件,这些组件可用于不同应用程序。这节省了时间和精力,因为可以在多个应用程序中使用相同的组件。

  4. 更快的开发:
    使用API​​的第一种方法,可以与API开发并行进行UI开发。这可以同时开发API和UI,因此可以更快地开发。

  5. 改进的安全性:
    由于首先开发了API,因此在API级别实施安全措施更容易。这使应用程序更加安全,因为API充当应用程序和外部系统之间的保护层。

我们可以更进一步,并首先使用诸如定义API规格和文档之类的实践,并首先使用TDD依靠测试。这些实践使我们能够确保我们正在构建一个完全符合我们最初的计划的应用程序。

要使用Python将API首次方法付诸实践,您可以使用名为Connexion的库。连接是一个Python库,可让您使用OpenAPI规范定义API,然后自动生成基于烧瓶的应用程序。

我们该如何将其放在实践中?

首先,请确保已安装本教程的所有必需工具:

要求:

  • python 3^
  • Poetry
  • 您选择的代码编辑

现在您拥有所有必要的工具,让我们继续构建此应用程序!

以下是使用连接和Python实现API首次方法的步骤:

1.用诗歌创建一个新应用:

poetry new habit_tracker
cd habit_tracker
poetry add connexion

2.使用OpenAPI规范定义API:

此规范定义了API的各种端点,请求和响应参数以及API的其他详细信息。您可以使用Swagger Editor之类的工具使用OpenAPI规范来定义API。

这是此习惯跟踪器应用程序的OpenAPI规范文件的示例:

openapi: 3.0.0
info:
  title: habits.openapi
  version: '1.0'
  license:
    name: MIT
  contact:
    name: Thiago Pacheco
    url: pacheco.io
    email: hi@pacheco.io
  description: A simple API to manage Habits
servers:
  - url: 'http://localhost:5000'
paths:
  /:
    parameters: []
    get:
      summary: List Habits
      tags:
        - Habits
      responses:
        '200':
          description: List of Habits
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Habit'
              examples:
                Example 1:
                  value:
                    - id: 1
                      description: Learn how to use API-First
                      user_id: 1
                      created_at: '2023-01-01 10:00'
                      updated_at: '2023-01-01 10:00'
      operationId: habit_tracker.list
      description: Retrieve the list of habits of a given user
    post:
      summary: Create a Habit
      operationId: habit_tracker.create
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Habit'
              examples:
                Example 1:
                  value:
                    id: 1
                    description: Learn how to use API-First
                    user_id: 1
                    created_at: '2023-01-01 10:00'
                    updated_at: '2023-01-01 10:00'
      description: ''
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HabitCreate'
            examples:
              Example 1:
                value:
                  description: Learn how to use API-First
                  user_id: 1
        description: ''
      tags:
        - Habits
  '/{habit_id}':
    parameters:
      - schema:
          type: integer
        name: habit_id
        in: path
        required: true
    get:
      summary: Get a Habit by ID
      operationId: habit_tracker.get
      responses:
        '200':
          description: OK
        '404':
          description: Not Found
      tags:
        - Habits
    put:
      summary: Update a Habit
      operationId: habit_tracker.update
      responses:
        '202':
          description: Accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Habit'
        '404':
          description: Not Found
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HabitCreate'
    delete:
      summary: Delete a habit
      operationId: delete-habit_id
      responses:
        '204':
          description: No Content
        '404':
          description: Not Found
components:
  schemas:
    HabitCreate:
      title: HabitCreate
      type: object
      x-examples:
        Example 1:
          description: Learn how to use API-First
          user_id: 1
      properties:
        description:
          type: string
        user_id:
          type: integer
    Habit:
      title: Habit
      type: object
      properties:
        id:
          type: integer
        description:
          type: string
        user_id:
          type: integer
        created_at:
          type: string
        updated_at:
          type: string
      x-examples:
        Example 1:
          id: 1
          description: Learn how to use API-First
          user_id: 1
          created_at: '2023-01-01 10:00'
          updated_at: '2023-01-01 10:00'

3.使用连接生成烧瓶应用程序:

使用OpenAPI规范定义了API后,您可以使用连接来生成基于烧瓶的应用程序。连接读取OpenAPI规范,并生成必要的路由,请求和响应解析以及为您的API处理错误。

让我们以我们的期望来快速测试

# tests/test_habit_tracker.py

from habit_tracker.app import create_app


def test_can_create_app():
    app = create_app()
    assert app is not None

让我们添加逻辑以进行测试。

为应用程序创建初始化代码:

/habit_tracker文件夹下,创建一个名为app.py的文件,带有以下内容:

# /habit_tracker/app.py
from flask import Flask
import connexion


def create_app() -> Flask:
    app = connexion.FlaskApp(__name__, specification_dir='../openapi/')
    app.add_api('habit.openapi.yaml')
    return app.app

/habit_tracker下创建一个名为resources.py的文件,其中包含以下内容:

# /habit_tracker/resources.py
from typing import Dict

from flask import jsonify


def list_habits():
    """
    Return a list of habits
    """


def create_habit(body: Dict):
    """
    Create a new habit
    """


def get_habit_by_id(habit_id: int):
    """
    Get a habit by ID
    """


def update_habit(habit_id: int, body: Dict):
    """
    Update a habit
    """


def delete_habit(habit_id: int):
    """
    Delete a habit
    """

快速运行测试以确保它通过:

poetry run pytest tests/

如果不通过,请再次快速查看之前的步骤,以确保它们都完成。

4.实施API逻辑:

拥有生成的烧瓶应用程序后,您可以为API的每个端点实现逻辑。您可以使用Python函数来处理请求并返回适当的响应。

请注意,OpenAPI文件中定义的每个端点都有operationId属性。此属性指向烧瓶应用程序内应定义功能的位置。

让我们开始添加一些测试以使用我们的预期终点:

# tests/test_habit_tracker.py
import pytest
from habit_tracker.app import create_app

# ...


@pytest.fixture
def client():
    app = create_app()
    client = app.test_client()
    return client


def test_list_habits(client):
    response = client.get("/")
    assert response.status_code == 200
    assert response.json == [
        {
            "id": 1,
            "description": "Drink 8 glasses of water a day",
            "user_id": 1,
            "created_at": "2020-01-01 12:00",
            "updated_at": "2020-01-01 12:00",
        },
    ]


def test_create_habit(client):
    payload = {
        "description": "Drink 8 glasses of water a day",
        "user_id": 1,
    }
    response = client.post("/", json=payload)
    assert response.status_code == 201
    assert response.json == {
        "id": 1,
        "description": "Drink 8 glasses of water a day",
        "user_id": 1,
        "created_at": "2020-01-01 12:00",
        "updated_at": "2020-01-01 12:00",
    }


def test_get_habit_by_id(client):
    response = client.get("/1")
    assert response.status_code == 200
    assert response.json == {
        "id": 1,
        "description": "Drink 8 glasses of water a day",
        "user_id": 1,
        "created_at": "2020-01-01 12:00",
        "updated_at": "2020-01-01 12:00",
    }


def test_update_habit(client):
    payload = {
        "description": "Workout 45 minutes a day",
        "user_id": 1,
    }
    response = client.put("/1", json=payload)
    assert response.status_code == 202
    assert response.json == {
        "id": 1,
        "description": "Workout 45 minutes a day",
        "user_id": 1,
        "created_at": "2020-01-01 12:00",
        "updated_at": "2020-01-01 12:00",
    }


def test_delete_habit(client):
    response = client.delete("/1")
    assert response.status_code == 204

现在,让我们通过实现API逻辑进行测试:

# /habit_tracker/resources.py

from typing import Dict
from flask import jsonify


def list_habits():
    """
    Return a list of habits
    """
    return jsonify([
        {
            "id": 1,
            "description": "Drink 8 glasses of water a day",
            "user_id": 1,
            "created_at": "2020-01-01 12:00",
            "updated_at": "2020-01-01 12:00",
        },
    ])


def create_habit(body: Dict):
    """
    Create a new habit
    """
    return jsonify({
        "id": 1,
        "description": body["description"],
        "user_id": body["user_id"],
        "created_at": "2020-01-01 12:00",
        "updated_at": "2020-01-01 12:00",
    }), 201


def get_habit_by_id(habit_id: int):
    """
    Get a habit by ID
    """
    return jsonify({
        "id": habit_id,
        "description": "Drink 8 glasses of water a day",
        "user_id": 1,
        "created_at": "2020-01-01 12:00",
        "updated_at": "2020-01-01 12:00",
    })


def update_habit(habit_id: int, body: Dict):
    """
    Update a habit
    """
    return jsonify({
        "id": habit_id,
        "description": body["description"],
        "user_id": body["user_id"],
        "created_at": "2020-01-01 12:00",
        "updated_at": "2020-01-01 12:00",
    }), 202


def delete_habit(habit_id: int):
    """
    Delete a habit
    """

现在每个测试都应该通过:

poetry run pytest tests/

5.运行烧瓶应用程序:

要运行该应用程序,让我们在/habit_tracker下创建一个main.py文件并用诗歌运行:

# /habit_tracker/main.py

from habit_tracker.app import create_app


def run():
    app = create_app()
    app.run()

添加一个poetry脚本以运行应用程序。

# pyproject.toml
# ...
[tool.poetry.scripts]
start = "habit_tracker.main:run"
# ...

您可以运行烧瓶应用程序以启动API服务器并使API可用。

poetry run start

继续使用您喜欢的HTTP客户端测试您的端点。您可以使用失眠或邮递员。

附加(可选)步骤

此外,您还可以通过向您的项目添加以下额外软件包来包括Swagger UI:

poetry add connexion[swagger-ui]

包含此软件包,您可以重新运行应用程序,然后转到url http://localhost:5000/ui

您可以使用一个漂亮而有光泽的Swagger UI!

使用API​​首次方法与连接和Python允许您轻松设计和实现API。您可以使用OpenAPI规范来定义API,然后使用连接生成烧瓶应用程序。这使您可以专注于API的逻辑,而不是设置API服务器所需的样板代码。

总而言之,API第一种方法比传统的构建应用程序具有多个优点。它允许更好的文档,改进的集成,可重复使用的组件,更快的开发以及改进的安全性。因此,值得考虑在构建任何应用程序时使用API​​的第一种方法。

可以找到源代码here