Привет, Хабр! На связи Антон, руководитель Архитектурного комитета компании SimbirSoft. Вместе с моими коллегами в прошлой статье мы рассказали про особенности применения подхода Design API First. Сегодня покажем, как реализуется этот подход на практике на примере сервиса аутентификации пользователей.   

Почему мы выбрали именно этот пример? Во-первых, сервис аутентификации пользователей не из самых простых, и с его помощью можно более полно раскрыть картину проектирования API. Во-вторых, он присутствует практически в каждом проекте в различных исполнениях, что позволяет провести анализ и спроектировать общее шаблонное решение, на базе которого можно быстро разрабатывать API этого типа под конкретные проекты.

Алгоритм проектирования API состоит из 7 шагов:

Рисунок 1. Общий алгоритм проектирования структуры API
Рисунок 1. Общий алгоритм проектирования структуры API

Ниже рассмотрим каждый шаг алгоритма на примере проектирования API для сервиса аутентификации.

1. Анализ бизнес-требований 

На данном этапе необходимо провести изучение предметной области: выяснить функциональные и нефункциональные требования. Для сервисов, которые планируется использовать повторно, лучше провести полный анализ различных вариантов применения, полностью покрывающий возможные сценарии. Например, аутентификация в различных системах может выполняться по-разному: под логином может быть любая уникальная строка или email, телефон пользователя, которые он может подтвердить; вход в систему может осуществляться с помощью пароля или по коду подтверждения, отправленного на email, телефон пользователя или другими способами.

2. Выявление общих компонентов и объектов автоматизации

Можно выделить следующие компоненты и процессы, которые будут использоваться в качестве объектов автоматизации:

  • учетная запись пользователя;

  • логин пользователя (+ его изменение);

  • пароль пользователя (+ его изменение);

  • email (как логин + его изменение и подтверждение);

  • телефон (как логин + его изменение и подтверждение);

  • открытая сессия (может быть связана с конкретным устройством, IP);

  • Access-токен (коротко живущий ключ для доступа к сессии);

  • Refresh-токен (одноразовый ключ для продления сессии);

  • регистрация нового пользователя в системе;

  • авторизация пользователя;

  • восстановление доступа.

3. Описание базовых сценариев

Для описания базовых сценариев удобно использовать Sequence-диаграммы в нотации UML, хотя на данном этапе это не так принципиально, подойдет и другой удобный инструмент. В первом приближении описываем процесс в самом общем виде, для того чтобы обозначить потоки информации. На следующих итерациях осуществляется  постепенная детализация. Иногда этот процесс требует возвращения к пункту 2 для уточнения.

Рисунок 2. Пример первичной диаграммы процесса аутентификации пользователя через логин и пароль
Рисунок 2. Пример первичной диаграммы процесса аутентификации пользователя через логин и пароль

Для создания такой диаграммы использовался инструмент PlantUML.

# Аутентификация пользователя через логин и пароль

@startuml
!theme mars

skinparam {
    MaxMessageSize 250
}

skinparam sequence {
    ParticipantPadding 125
    MessageAlign center
}

participant "User" as usr order 10
participant "WebApp" as app order 20
participant "Auth" as auth order 30

title Аутентификация пользователя через логин и пароль

usr -> app: Нажатие кнопки "Аутентификация"
app -> app: Отображение формы аутентификации пользователя
usr -> app: Заполнение полей: логин, пароль; нажатие кнопки "Войти"

app -> auth: Запрос на получение доступа к аккаунту пользователя
auth -> auth: Валидация данных

alt #f0fff0 Валидация OK
  auth -> auth: Генерация access и refresh токенов
  app <-- auth: Success
  app -> app: Отображение главного экрана аккаунта
else #fff0f0 Валидация ERROR
  app <-- auth: Error
  app -> app: Отображение сообщения об ошибке
end

@enduml

4. Верхнеуровневое проектирование структуры endpoints

На данном шаге накидываются необходимые эндпоинты для осуществления  потоков, описанных в предыдущем шаге. При этом  периодически приходится возвращаться к процессам пункта 3 для обоюдной корректировки.

Чаще всего такое проектирование осуществляется с использованием известных нотаций в обычных редакторах схем или с помощью карандаша и бумаги, однако есть и специальные инструменты, позволяющие упростить данный процесс, например Visual Paradigm.

5. Детальная проработка endpoints

Здесь описываются входные и выходные параметры в виде объектов OpenAPI. При этом важно именование объектов, чтобы можно было определить, для чего они предназначены и в каком действии участвуют. И поскольку наша цель создать обобщенную спецификацию, то описываем не только конкретные объекты, но и их абстракции. OpenAPI это позволяет сделать. А уже в конкретных проектах эти абстракции будут заменены на конкретные объекты и оставлены только необходимые эндпоинты. Также на этом шаге постоянно проверяется логика взаимодействия объектов. При выявлении каких-либо новых деталей, противоречий, неполноты данных возвращаемся к пункту 4.

 /v1/signin:
    post:
      summary: Auth/R2. Метод получения доступа к аккаунту пользователя
      description: Метод предназначен для аутентификации пользователя под указанным логином/имейлом/телефоном, соответствующим аккаунту в БД
      operationId: authAccount
      tags:
        - Auth

      parameters:
        - $ref: "#/components/parameters/App.Request.Header.acceptLanguage"
        - $ref: "#/components/parameters/App.Request.Header.correlationId"
        - $ref: "#/components/parameters/App.Request.Header.platform"

      requestBody:
        required: true
        content:
          application/json;charset=UTF-8:
            schema:
              $ref: "#/components/schemas/Auth.Request.Model.AuthAccount"

      responses:
        "201": # Доступ к аккаунту пользователя разрешён
          $ref: "#/components/responses/Auth.Response.SuccessAccessAccount"
        "202": # На email/телефон было отправлено сообщение с кодом подтверждения входа в свой аккаунт
          $ref: "#/components/responses/Auth.Response.WaitingAccessAccount"
        "400": # Логин/email/телефон должен быть указан
               # Логин/email/телефон некорректен или не существует
               # Пара логин - пароль не существует
          $ref: "#/components/responses/App.Response.Error400"
        "403":
          $ref: "#/components/responses/App.Response.Error403"
        "500":
          $ref: "#/components/responses/App.Response.Error5XX"

6. Разделение endpoints на компоненты

Когда эндпоинтов становится слишком много, тогда имеет смысл разделять их на компоненты. Возвращаясь к примеру API аутентификации, мы получили следующие компоненты:

  • Auth – основной компонент для создания новых учетных записей, входа в систему, обновления токенов, восстановления доступа.

  • Auth.Change – компонент для изменения критически важных с точки зрения безопасности полей (логин, пароль, email, телефон).

  • Auth.Operations – компонент, отвечающий за подтверждение пользователем любых важных операций различными способами (по паролю, коду подтверждения отправленного на email и по SMS), начиная от регистрации аккаунта и заканчивая операциями в бизнес-логике конкретного проекта.

  • Auth.Session – компонент для отображения и управления текущими сессиями пользователя в целях контроля его захода в систему под различными устройствами.

  • Auth.Check – вспомогательный компонент для быстрой проверки: не занят ли уже логин, указанный при регистрации, или соответствует ли пароль всем требованиям системы.

7. Детализация ограничений и описание ошибок

На этом этапе детализируем ограничения для полей объектов (типы данных, размеры, обязательность). Описываем подробно все коды ошибок и связанные с ними сообщения, которые могут возвращать эндпоинты. Опять же возвращаемся к пункту 4, чтобы максимально детализировать сценарии с учетом последней информации. Описание кодов ошибок можно увидеть в Листинге 2, а пример описания ограничений приведен в Листинге 3:

  Auth.Request.Model.CreateLogin:
      type: object
      required:
        - userLogin
        - userPassword
      properties:
        userLogin:
          description: Логин пользователя, к которому будет привязан созданный аккаунт
          type: string
          format: login
          minLength: 5
          maxLength: 32
          example: "user-login"
        userPassword:
          description: Пароль от аккаунта пользователя
          type: string
          format: password
          minLength: 8
          maxLength: 32
          example: "DfK0p8Kjp19SkHEd"

    Auth.Request.Model.AccessRecoveryByEmail:
      type: object
      required:
        - userEmail
      properties:
        userEmail:
          description: email привязаный к аккаунту пользователя
          type: string
          maxLength: 128
          example: "user-email@localhost"

В итоге должна получиться API-спецификация, в которой будут уже спроектированы и учтены большинство моментов. Теперь ее можно применять на конкретном проекте, подгоняя под требования заказчика. Здесь важна обратная связь — она позволит улучшить обобщенную спецификацию для будущих проектов.

Исходя из нашего примера, получился такой набор методов:

Описание метода

Endpoint

Метод

1.

Auth/R1. Метод создания аккаунта пользователя

/v1/signup

POST

2.

Auth/R2. Метод получения доступа к аккаунту пользователя

/v1/signin

POST

3.

Auth/R3. Метод получения информации об аккаунте

/v1/auth

GET

4.

Auth/R4. Метод генерации новых access и refresh токенов

/v1/auth

POST

5.

Auth/R5. Метод генерации access и refresh токенов по токену операции

/v1/auth/{operationToken}

POST

6.

Auth/R6. Метод восстановления доступа к аккаунту

/v1/auth/access-recovery

POST

7.

Auth/R7. Метод установки нового пароля пользователя

/v1/auth/password-set/{operationToken}

PUT

8.

Auth/CH/R1. Метод изменения логина пользователя

/v1/auth/login

PATCH

9.

Auth/CH/R2. Метод изменения пароля пользователя

/v1/auth/password

PATCH

10.

Auth/CH/R3. Метод изменения PIN-кода пользователя

/v1/auth/pin

PATCH

11.

Auth/CH/R4. Метод изменения email пользователя

/v1/auth/email

PATCH

12.

Auth/CH/R5. Метод изменения телефона пользователя

/v1/auth/phone

PATCH

13.

Auth/OP/R1. Метод получения информации об операции

/v1/auth/operation/{operationToken}

GET

14.

Auth/OP/R2. Метод подтверждения операции

/v1/auth/operation/{operationToken}

PUT

15.

Auth/OP/R3. Метод отмены операции

/v1/auth/operation/{operationToken}

DELETE

16.

Auth/SS/R1. Метод получения списка сессий пользователя

/v1/auth/sessions

GET

17.

Auth/SS/R2. Метод уничтожения открытых сессий, кроме текущей

/v1/auth/sessions

DELETE

18.

Auth/SS/R3. Метод уничтожения указанной сессий

/v1/auth/sessions/{sessionHash}

DELETE

19.

Auth/CK/R1. Метод проверяет, не занят ли уже логин

/v1/auth/login-check

POST

20.

Auth/CK/R2. Метод проверки пароля на его сложность

/v1/auth/password-check

POST

21.

Auth/CK/R3. Метод проверяет, не занят ли уже email

/v1/auth/email-check

POST

22.

Auth/CK/R4. Метод проверяет, не занят ли уже телефон

/v1/auth/phone-check

POST

Заключение

Конечно, существует риск, что при разработке всплывут какие-либо неучтенные моменты, но их уже не должно быть много. Эти недочеты устраняются в рамках конкретного проекта, далее через обратную связь сообщается о выявленных недостатках, чтобы учесть их в обобщенной API-спецификации.

На основе готовой OpenAPI-спецификации можно генерировать заготовки кода сервера и клиента, оборачивая это в различные враперы или путем наследования подключать в свои проекты. Спецификации хранятся в отдельном хранилище, из которого можно быстро выяснить, как должны общаться серверы и клиенты между собой. Далее DevOps-специалисты собирают CI, при изменении спецификации перегенерируется код в соответствии с новой спецификацией. Есть возможность на этапе изменения схемы спецификации отловить несоответствия. Конечно, для этого должны быть написаны соответствующие тесты, которые бы это отслеживали. Про автоматизацию процесса тестирования на основе спецификаций мы расскажем в одной из следующих статей :)  

1 часть: Design API First как паттерн проектирования контрактов межсервисного взаимодействия

Спасибо за внимание!

Другие полезные материалы для разработчиков и архитекторов в IT публикуем в наших соцсетях — ВК и Telegram. 

Комментарии (0)