포스트맨(postman)을 이용한 API TDD 개발 2 - API 버전관리

APIs를 이용한 API 버전관리 - 변경사항 트래킹

들어가기에 앞서

아랫글은 포스트맨의 기본적인 사용법을 학습한 사람을 대상으로 작성하였습니다. 포스트맨을 처음 접하는 분들은 아래 링크를 통하여 학습할 수 있습니다.

APIs을 이용한 버전관리

API 생성

tests scripts 작성예시

유저가 자신의 노트를 작성하여 사용할 수 있는 서비스의 API를 개발한다고 가정하겠습니다.

먼저 APIs 탭을 클릭하면 나오는 Create an API 버튼을 클릭해 새로운 API를 만들겠습니다. Name, Version, Schema type, Schema format을 차례로 입력합니다.

Schema type은 OpenAPI, RAML, GraphQL 등을 선택할 수 있는데 OpenAPI 3.0을 선택하도록 하겠습니다. Schema format은 YAML, JSON 중에서 선택할 수 있습니다. YAML을 선택합니다.

API 정보 입력

tests scripts 작성예시

만들고자 하는 API에 대한 정보를 입력합니다. 해당 정보를 통해 협업하는 동료들에게 API에 대한 정보를 공유할 수 있습니다.

API 버전 정보 입력

tests scripts 작성예시

생성된 버전에 대한 정보를 입력합니다. 현재 버전의 status를 골라 개발진행 상황을 공유할 수 있습니다. 지금은 설계 중이므로 IN DESIGN을 선택하였습니다.

그리고 0.1.0 버전에 대해 작성합니다. 현재 버전에서는 유저관리에 관한 기능을 만들고자 합니다.

API Definition 작성

tests scripts 작성예시

openapi: 3.0.0
info:
  version: '0.1.0'
  title: 'API TDD Development'
  license:
    name: woogie
servers:
  - url: 'localhost:3000'
paths:
  /users:
    post:
      summary: 'create a user'
      operationId: createUser
      tags:
        - user
      requestBody:
        description: body for creating a user
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RequestCreateUser'
      responses:
        '200':
          description: 'return created user data'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponseGetUser'
        default:
          description: Unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    RequestCreateUser:
      type: object
      required:
        - userName
      properties:
        userName:
          type: string
    ResponseGetUser:
      type: object
      required:
        - userId
        - userName
      properties:
        userId:
          type: integer
        userName:
          type: string
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
security:
  - BasicAuth: []

Definition 탭으로 이동해 스키마를 작성합니다. 우선 유저를 생성하는 API에 대해 작성했습니다.

  • http method: post
  • path: /users
  • request body: userName

유저를 생성해서 userIduserName을 리턴 해주는 간단한 API로 설계하였습니다.

Documentation 확인

tests scripts 작성예시

Documentation 탭으로 이동합니다. 해당 화면에서는 Definition 탭에서 작성한 스키마가 문서화되어 화면에 나타납니다. API를 개발하는 개발자 본인은 물론, 협업하는 동료들도 해당 문서를 보고 API에 대해 파악하고 개발할 수 있습니다.

컬렉션 생성 및 request 작성

tests scripts 작성예시

tests scripts 작성예시

현재 버전의 status로 IN DEVELOPMENT를 선택하여 설계가 끝나고 개발 중이라는 것을 표시합니다. 그리고 Generate Collection을 클릭하여 해당 버전의 컬렉션을 만들어 줍니다.

tests scripts 작성예시

컬렉션으로 들어가면 Definition에서 작성한 Open API 문서에 맞춰 자동으로 request가 생성된 것을 볼 수 있습니다. 해당 request에 대한 테스트 스크립트를 작성합니다.

Body

{{dynamicBody}}

Pre-request Script

pm.collectionVariables.set("dynamicBody", JSON.stringify({ userName: "woogie" }))

Tests

const inputUser = JSON.parse(pm.collectionVariables.get("dynamicBody"));
const createdUser = pm.response.json();

pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

pm.test("return created user data: check property", function () {
    pm.expect(createdUser.hasOwnProperty('userId')).to.be.true;
    pm.expect(createdUser.hasOwnProperty('userName')).to.be.true;
});

pm.test("return created user data: check detail data", function () {
    pm.expect(createdUser.userId > 0).to.be.true;
    pm.expect(createdUser.userName).to.eql(inputUser.userName);
});

pm.collectionVariables.unset("dynamicBody");

API 개발

nodejs express로 간단하게 API를 만듭니다. 폴더구조는 아래와 같으며 users.json에 유저 데이터를 저장하였습니다.

tdd2
  ├── node_modules
  ├── index.js
  ├── package-lock.json
  ├── package.json
  └── users.json

index.js의 코드는 아래와 같습니다. 현재 users.json에 저장된 유저 데이터 중 가장 큰 userId를 뽑아 그 값에 +1을 더해 새로 생성 될 유저의 userId가 되도록 코드를 작성하였습니다. 그리고 앞서 설계한 대로 userIduserName을 return 하도록 하였습니다.

const express = require('express');
const fs = require('fs');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json()); // Body parser use JSON data

const USERS_FILE = './users.json';

app.post('/users', function(req, res) {

  const createUserRequest = req.body;

  let nextUserId = 1;
  const existingUsers = JSON.parse(fs.readFileSync(USERS_FILE));
  
  if (existingUsers && existingUsers.length > 0) {
    const userIds = existingUsers.map((user) => user.userId).sort((a, b) => a - b);
    nextUserId = userIds[userIds.length - 1] + 1;
  }

  const createdUser = { 
    userId: nextUserId,
    userName: createUserRequest.userName,
  };

  existingUsers.push(createdUser);
  fs.writeFileSync(USERS_FILE, JSON.stringify(existingUsers));

  res.json(createdUser);
});

const port = 3000;
app.listen(port);
console.log('Express started on port %d ...', port);

앞서 만든 request를 사용해 API를 테스트 합니다. 테스트 결과 성공적으로 API가 작동함을 확인할 수 있습니다.

tests scripts 작성예시

Release

유저 CRUD 기능 중 유저 생성 기능이 완성되었습니다. 테스트를 통해 유저 생성 기능의 설계가 올바르고 개발이 완료된 것을 확인하였으므로 이를 다른 개발자와 공유하기 위해 릴리즈를 만들도록 하겠습니다.

다시 APIs의 버전 탭으로 돌아옵니다. 오른쪽의 시계모양 아이콘을 클릭합니다. Create Release 버튼을 클릭합니다.

tests scripts 작성예시

tests scripts 작성예시

해당 릴리즈에 대한 정보를 작성하고 Create Release 버튼을 클릭합니다.

tests scripts 작성예시

성공적으로 완료되면 현재 버전에 릴리즈가 생성된 것을 볼 수 있습니다. 해당 릴리즈에는 릴리즈 직전까지 작성한 항목이 백업됩니다.

버전 탭에서는 언제든 새로운 기능을 위한 데이터가 추가되고 삭제 될 수 있으므로 참고하는 다른 개발자 입장에서는 혼란이 있을 수 있습니다. 하지만 릴리즈에는 현재 개발이 완료된 신뢰할 수 있는 데이터로 이루어져 있어 신뢰할 수 있으며 해당 내용은 수정이 불가능하기 때문에 실수로 내용을 수정되거나 하는 상황을 방지 할 수 있습니다.

tests scripts 작성예시

버전 변경

tests scripts 작성예시

개발을 진행하여 버전 0.1.0에서 계획한 유저 CRUD 기능이 완성되었습니다. 그리고 프론트앤드 개발자가 API를 붙히면서 나온 몇몇 버그를 fix하였습니다. 그 때마다 새로운 릴리즈를 만들어 수정이력을 트래킹할 수 있도록 하였습니다.

이제 버전을 바꿔 새로운 기능을 개발할 때가 왔습니다. APIs 탭으로 이동해 새로운 버전을 생성하겠습니다.

tests scripts 작성예시

tests scripts 작성예시

0.2.0 버전을 새롭게 만들며 0.1.0 버전에서 작성한 항목들 중 필요한 것을 골라서 새로운 버전에 Copy 합니다. 입력이 완료되면 Create Version 버튼을 클릭합니다.

tests scripts 작성예시

새로운 버전이 생성되었습니다. 이제 앞서 거쳤던 과정을 반복하여 해당 버전에 계획한 기능을 완성하면 됩니다.

글을 마치며

먼저 시간 내어 읽어주셔서 감사합니다. 저번 글에 이어서 이번에도 생각했던 것 보다 글이 길어졌습니다.

API를 개발하다보면 종종 당초 기획한 설계에서 수정이 필요할 때가 생깁니다. 시나리오 검토나 기획이 꼼꼼하지 못한 탓도 있겠지만 실제로 서비스에 적용해 보면 발견하는 예상치 못한 상황도 있기 때문입니다.

이전에 시간에 쫒기고 빠른 수정이 요구되는 상황에서 구두와 메신저로 수정사항이 전달되고 처음에 작성한 API 설계서는 어느새 관리가 안되었는데 동료 개발자는 해당 문서를 참고하여 혼란에 빠지는 상황을 경험한 적이 있습니다.

저는 이러한 상황의 원인이 API 라이프 사이클이 제대로 돌지 않아 생긴 문제였다고 생각합니다. 추가/수정 등의 이벤트가 발생했을 때 그 내용이 기록되고 수정이력을 통해 트래킹 할 수 있으며 관련된 테스트나 문서로 연결이 되어 있었다면 앞서 말한 혼란은 피할 수 있었을 것입니다.

항상 부족했던 기억은 머릿속에 남는 것 같습니다. 이를 잊지 않고 해결하기 위한 노력을 지속하는게 중요한 것 같습니다. 좀더 완성도 있는 API와 원활한 협업을 위해 연구하는 새로운 글로 돌아오겠습니다.

감사합니다.

디지엠유닛원 주식회사

  • 대표이사 권혁태
  • 개인정보보호책임자 정경영
  • 사업자등록번호 252-86-01619
  • 주소
    서울특별시 금천구 가산디지털1로 83, 6층 601호(가산동, 파트너스타워)
  • 이메일 unit1@dgmit.com