19. Про backend документацію. Open API. Swagger. Тести Open API. Json:API vs GraphQL vs REST.

Відео версія
(YouTube - для зворотнього зв'язку)

Посилання

Документація проєкту - це дуже дорого. Саме тому на більшості проєктів її нема.

Код краще підходить для документації, ніж будь-яка мова.

Підтримка документації - це дуже дорого.

Правильно написані тести - це ще один дуже гарний шар документації.

Якщо ви все ж пишете документацію - її ніхто не прочитає. Читати документацію - це складна навичка.

Якщо розробляєте щось, від чого залежить життя або фінанси людей, - документація обов'язкова. Це також питання юридичної безпеки.

"Реальної" документації потребує не код, а бізнес-логіка. Код вже показує "як працює". Документація повинна дати підказку на питання "чому працює так".

OpenAPI (Swagger) - специфікація для опису REST вебсервісів. Він нормально конвертується в інші специфікації!

PHP8 Attributes роблять опис OpenAPI дуже компактним. Атрибути - це звичайні класи, тому їх можна вільно наслідувати або створювати свої, роблячи досвід опису ще компактнішим і більш орієнтованим до потреб проєкту.

Spectator дозволяє тестувати OpenAPI специфікацію. Це просто ідеальне рішення - розробники виконують той же самий обсяг роботи, що й для тестування, і паралельно отримують завжди актуально протестовану документацію.

OpenAPI - рішення краще, ніж Postman у кожного локально. Розробник робить той же набір дій, що й для себе локально, але одразу для всієї команди (QA, frontend devs, інші розробники).

Swagger дуже зручний у випадку "чистих" backend розробників. Він дозволяє швидко тестувати виконану роботу і мінімізувати втрати на комунікацію з розробниками клієнта.

REST або Json:API обирайте самі. Ви повинні знати про специфікації API.

GraphQL - специфічне рішення з купою проблем. Добре подумайте, чи дійсно воно вам потрібне.

Modules/Auth/Transformers/CurrentUserInfoResource.php
#[BaseScheme(
    resource: CurrentUserInfoResource::class,
    properties: [
        new PropertyString(property: 'name'),
        new PropertySlug(),
    ]
)]

Modules/Auth/Transformers/LoginUserResource.php
#[BaseScheme(
    resource: LoginUserResource::class,
    properties: [
        new PropertyString(property: 'token'),
    ]
)]

Modules/Auth/Requests/LoginRequest.php
#[BaseScheme(
    resource: LoginRequest::class,
    properties: [
        new PropertyString(
            property: 'email',
            example: 'example@example.com'
        ),
        new PropertyString(
            property: 'password',
            example: 'Pas$word1'
        ),
    ]
)]

Modules/Auth/Actions/GetCurrentUserInfo.php
#[ApiGet(
    path: '/api/v1/me',
    tags: ['Auth'],
    description: 'Get current user info',
    auth: true
)]
#[ResponseJsonSuccess(CurrentUserInfoResource::class)]

Modules/Auth/Actions/LoginUser.php
#[ApiPost(
    path: '/api/v1/login',
    tags: ['Auth'],
    description: 'Login user'
)]
#[ResponseUnauthorized]
#[ResponseJsonSuccess(LoginUserResource::class)]
#[RequestJson(LoginRequest::class)]

Modules/Auth/Actions/GetCurrentUserInfo.php
#[ApiGet(
    path: '/api/v1/me',
    tags: ['Auth'],
    description: 'Get current user info',
    auth: true
)]
#[ResponseJsonSuccess(CurrentUserInfoResource::class)]