LY Corporation Tech Blog

LY Corporation과 LY Corporation Group(LINE Plus, LINE Taiwan and LINE Vietnam)의 기술과 개발 문화를 알립니다.

모두가 행복해지는 API 문서 통합과 자동화

들어가며

안녕하세요. LINE Plus에서 LINE Monary와 MyDashboard 서비스의 백엔드를 맡고 있는 조성빈입니다. 이번 글에서는 백엔드 서비스 개발 및 운영 업무를 담당하면서 REST API 기반의 인터페이스 문서를 가지고 다양한 파트와 커뮤니케이션을 진행하면서 겪었던 문제들과, 그 문제들을 해결한 방법을 소개하려고 합니다. 제가 말씀드리는 게 정답이라고 할 수는 없겠으나 REST API의 스펙을 효과적으로 관리하는 방안 중 하나라고는 생각하며, 이 글을 읽는 분들에게 도움이 되기를 바랍니다.

API 문서를 제대로 관리하지 못할 때 발생하는 문제

REST API 기반의 서비스에서 API마다 명세를 직접 문서로 작성한다고 하면, API를 추가하거나 수정할 때마다 관리 비용은 기하급수적으로 늘어납니다. 예를 들어, MSA 환경 기반의 서비스에서 관리하는 서비스 도메인이 10개일 때 각 서비스마다 30개의 API가 있다면 총 300개의 문서를 만들어야 하는 불상사가 생깁니다. '에이, 실제로 누가 그렇게 API를 문서화해서 관리해?'라고 생각할 수 있겠지만, 실제 업무에서는 다양한 이해 관계자와 커뮤니케이션할 때 API 문서가 필수 요소가 될 수 있으며, 저 역시 300개까지는 아니어도 몇 십개 수준까지는 만들어 본 기억이 있습니다. 이런 경우 API를 업데이트할 때마다 관리해야 하는데요. 당시 개발만 하기에도 바쁜 와중이어서 정말 큰 부담으로 다가왔습니다.

자동 API 문서화 도구를 사용하는 것만으로 해결될까?

서버 개발자가 REST API 기반에서 생성하는 API 문서로는 OpenAPI 스펙이 가장 많이 쓰이고 있습니다. OpenAPI 스펙이 구현된 springdoc 라이브러리를 이용해 Swagger UI를 도입해서 사용하면 코드 수준에서 별다른 설정 없이 API 문서를 자동으로 생성할 수 있으며 API 실행 테스트까지 할 수 있습니다.

하지만! MSA 환경에서는 서비스 도메인별로 모두 도메인 URL이 다릅니다. 따라서 서비스 도메인이 10개라면 한 사람에게 10개의 Swagger URL을 알려줘야 하는 문제가 발생합니다. 만약 알파와 베타 환경이 다르다면 URL은 20개가 될 수도 있습니다.

이렇게 URL이 많아지면 API 명세가 어떤 URL에 속해 있는지 찾기도 어려워 Swagger로만 커뮤니케이션하기에는 한계가 있었습니다. 또한 코드 작업을 완료했다고 해도 코드 리뷰를 거쳐 머지된 후 배포까지 완료되지 않으면 해당 스펙이 문서에 반영되지 않습니다. 따라서 문서 배포까지의 텀이 상당히 길어질 수 있기 때문에 개발 초기에 스펙을 공유해야 할 때에는 사용하기 어렵다는 이유로 문서를 다시 수동으로 작성하는 방식으로 돌아가는 문제도 발생했습니다.

아래 예시를 보면, MSA 환경의 한 서비스에서 각 하위 도메인마다 각각 문서 URL이 생성되고 있습니다. 또한 문서 URL에 들어가보면 하나의 문서에 내부용, 프런트엔드용, 관리자용 등 다양한 목적의 API가 섞여 들어가 있습니다. 이 때문에 이를 참고하려는 개발자는 어떤 URL의 문서를 봐야할지, 이 문서에서 내가 확인해야 하는 API가 이것이 맞는지 헷갈릴 수 있습니다.

API 문서를 통합하기

위와 같은 문제를 해결하기 위해 MSA 환경에서 다양한 서비스의 문서를 하나의 URL에서 한눈에 보여줄 수 있고, API 문서 페이지를 일일이 수동으로 관리하지 않아도 되며, 기존처럼 약간의 코드를 수정하는 것만으로 빠르게 API 문서를 자동으로 생성할 수 있는 방법은 없을지 고민했습니다.

참고로 앞으로 말씀드릴 방법은 Spring 기반의 환경을 전제하고 있으나, OpenAPI 스펙을 만족한다면 다른 환경에서도 기본적으로 동일한 흐름으로 문서를 작성할 수 있습니다.

요구 사항 정의

문제를 분석해 도출한 요구 사항은 아래와 같습니다.

  • API 스펙별로 그룹화한 페이지 생성
    • 하나의 API 문서에 여러 목적의 API가 혼재되지 않게 구현
    • 외부 인터페이스나 내부 인터페이스 등 용도별로 문서를 추출할 수 있게 구현
    • MSA 환경 내 다양한 서비스의 문서를 하나로 통합해 제공할 수 있게 구현
  • 유지 보수 비용을 최소화하기 위해 코드 수정만으로 변경 사항이 문서에 반영될 수 있게 구현
  • 변경 사항이 문서에 반영되는 속도를 높이기 위해 특정 시점의 코드를 기반으로 문서를 배포할 수 있게 구현
    • API 스펙 관련 사항만 코드로 정의 후 리뷰 없이 바로 작업 브랜치로 배포할 수 있게 구현
      • 기존 Swagger로 작업 시 API 관련 개발을 모두 진행한 후 팀원 리뷰 후에 배포해야만 문서에 반영되던 점을 개선
    • 특정 브랜치로 서비스 배포 없이 문서만 배포할 수 있게 구현
    • 관련 부서와의 커뮤니케이션이라는 문서의 용도에 맞게 쉽게 문서에 변경 사항을 반영할 수 있도록 구현

아키텍처 설계

위 요구 사항을 고려해 아래와 같이 아키텍처를 설계했습니다.

아키텍처의 작동 방식은 간단합니다.

  1. 개발자가 GitHub Actions 워크플로를 실행해 특정 시점의 코드로 문서 생성 요청
  2. API 문서 생성
    1. 대상 Git 리포지터리들에서 OpenAPI 기반의 API 문서 데이터를 JSON 형태로 추출
    2. 추출된 데이터를 하나의 JSON 파일로 병합
    3. 병합된 JSON 데이터에서 HTML 문서 추출
    4. 추출된 HTML 문서를 문서 리포지터리로 커밋
  3. GitHub Pages를 이용해 문서 웹사이트 호스팅

개발자가 코드 작업 후 GitHub Actions를 활용해 원하는 시점에 특정 버전에 대한 문서 배포를 요청하면, 별도의 추가 작업 없이 문서 페이지(예: https://{github_url}/pages/petstore)에 바로 반영됩니다. 이 페이지 URL을 활용해 업무 관계자와 커뮤니케이션할 수 있습니다.

API 문서 통합에 사용한 도구와 스펙

다음으로 문서를 통합하기 위해 사용한 도구를 소개하겠습니다.

GitHub Actions 워크플로

문서 추출 작업의 트리거 및 문서 추출 작업 수행에는 GitHub Actions를 사용했습니다. GitHub Actions는 Git에서 발생하는 이벤트를 감지하고 처리하기 용이하며, 소스 코드에 쉽게 접근할 수 있고, 기능을 커스터마이징할 있다는 장점이 있습니다. 또한 '코드'를 문서화한다는 관점에서 가장 직관적인 도구라고 판단했습니다.

OpenAPI 스펙 채택

API를 설계해 문서화하고 관리하기 위한 스펙으로는 OpenAPI 외에도 RAML이나 API Blueprint 등이 있는데요. 가장 널리 사용되며 현재까지도 활발히 업데이트되고 있고, 개인적으로 Springfox 시절부터 사용해 왔기 때문에 사용법 등 여러 면에서 친숙한 OpenAPI를 채택했습니다. 

Redoc

문서 페이지 추출 및 생성 도구로는 Redoc을 채택했습니다. OpenAPI 스펙 기반 문서화 도구로 Swagger와, Redoc, RapiDoc 등을 검토했는데요. 제공하는 문서의 가독성이 높고, 사용자 측면에서의 편의성도 좋았으며, 함께 제공되는 Redocly CLI를  문서 관리 기능을 활용할 수 있다는 점을 고려해 Redoc으로 결정했습니다. Redoc 데모 사이트를 보면 Redoc에서 어떻게 API 문서를 구성할 수 있는지 살펴볼 수 있습니다. 

API 문서 통합 구현

앞서 소개한 아키텍처와 선택한 도구 및 스펙으로 실제로 어떻게 문서 통합을 구현했는지 살펴보겠습니다. 

OpenAPI 스펙을 충족하기 위한 기본 코드 작업

아래는 이 작업을 위해 의존성을 추가한 세 가지 라이브러리입니다. 

Springdoc-openapi 라이브러리는 OpenAPI 3 스펙을 충족하고 최신 버전의 Spring Boot를 지원합니다. 또한 Swagger UI를 포함하고 있기 때문에 의존성을 추가하는 것만으로 쉽게 API 문서를 생성할 수 있으며, 약간의 코드 작업을 추가하면 API의 명세를 더욱 상세하게 표시할 수 있습니다. 현재 프로젝트가 웹 MVC 기반에 Kotlin을 사용하고 있기 때문에 springdoc-openapi-kotlin 라이브러리도 추가했으며, 문서 데이터를 추출하기 위해 springdoc 기반에서 OpenAPI 스펙 기반의 문서 생성을 지원하는 springdoc-openapi-gradle-plugin 라이브러리를 추가했습니다. 

심각한 보안 이슈가 발생하는 것을 방지하기 위해 Swagger UI는 개발 환경에서만 노출되도록 설정해야 합니다.

대상 독자에 따라 API 문서 그룹화

서비스에서 제공하는 API 문서는 어떤 이해 관계자와 커뮤니케이션하는지에 따라 그룹화할 필요가 있습니다. 예를 들어 API를 프런트엔드 개발자용 API와 관리 콘솔 화면 개발자용 API, 다른 서비스와 연계하기 위한 API의 세 그룹으로 나눌 수 있다고 할 때, 하나의 문서에 모든 API를 다 나열하면 문서를 읽는 독자는 어떤 API가 내가 사용해야 하는 API인지 파악하기 어려울 것입니다. 이때 문서를 세 그룹으로 분류(grouping)해서 분류된 문서끼리 통합하면 독자의 혼란을 방지할 수 있습니다.

아래는 펫 스토어 서비스에서 각 API 연계 대상에 맞게 문서를 그룹화한 예시입니다. 아래와 같이 그룹화하면 프런트엔드 개발자는 FrontEnd 문서만, 관리 콘솔 화면 개발자는 Admin 문서만 보면 됩니다. 각 문서는 내부에서 다시 각 서비스 도메인에 맞게 API를 그룹화해 표시하기 때문에 독자는 필요한 API를 쉽게 찾을 수 있습니다. 

아래는 pet 서비스에서 springdoc을 이용해 그룹화한 예시입니다. Springdoc 설정에서 API의 경로나 패키지 정보를 이용해 그룹화할 수 있는데요. 아래 예시에서는 패키지 정보를 이용해 그룹화했습니다. 

springdoc:
  webjars:
    prefix:
  api-docs:
    path: /pet/api-docs
    groups:
      enabled: true
  swagger-ui:
    urls:
      - url: /pet/api-docs
        name: all
      - url: /pet/api-docs/front-end
        name: front-end
      - url: /pet/api-docs/admin
        name: admin
      - url: /pet/api-docs/internal
        name: internal
    path: /pet/swagger
    configUrl: /pet/api-docs/swagger-config
    disable-swagger-default-url: true
  group-configs:
    - group: front-end
      packages-to-scan:
        - com.linecorp.petstore.pet.api.entry.fe
    - group: internal
      packages-to-scan:
        - com.linecorp.petstore.pet.api.entry.internal
    - group: admin
      packages-to-scan:
        - com.linecorp.petstore.pet.api.entry.admin

API 문서 작성

문서는 코드에서 컨트롤러 컴포넌트와 익셉션 핸들러, API에서 다루는 DTO 객체들 위에 어노테이션 기반으로 작성합니다. 이를 통해 API의 목적과 요청/응답 객체의 설명 및 예시 값까지 표현할 수 있기 때문에 꼼꼼하게 작성할수록 더욱 완성도 높은 API 문서를 추출할 수 있는데요. 이 글은 문서 자동화에 초점을 맞춘 글이기에 문서 작성법은 이 정도로만 설명하고 넘어가겠습니다. 

API 문서를 JSON 형태로 추출

앞서 언급한 springdoc-openapi-gradle-plugin을 활용하면 Gradle 빌드를 통해 API 문서를 JSON 형태로 추출할 수 있습니다.

이를 위해 먼저 build.gradle에 관련 설정을 추가해야 합니다. 아래는 프런트엔드와 내부용 문서를 추출하기 위해 설정한 예시입니다. 

openApi {
    apiDocsUrl.set("http://petstore-fe-doc.linecorp.com") // document url
    outputDir.set(file("$buildDir/docs")) // build result path
    outputFileName.set("pet-fe.json") // build result file name
    groupedApiMappings.set(
        mapOf(
            "http://localhost:8081/pet/api-docs/front-end" to "pet-fe.json",  // frontend grouping doc
            "http://localhost:8081/pet/api-docs/internal" to "pet-internal.json", // internal grouping doc
        ),
    )
    waitTimeInSeconds.set(60) // set timeout
    customBootRun {
        args.add("--spring.profiles.active=local")
    }
}

설정을 완료한 후 다음 Gradle 명령어를 실행하면 앞서 API의 경로나 패키지 정보를 이용해 Springdoc 설정에 그룹화한 대로 각 문서가 도출돼 JSON 파일이 생성됩니다. 

./gradlew :petstore-pet:clean :petstore-pet:generateOpenApiDocs

추출된 JSON 파일을 통합 후 HTML 파일로 전환

이제 JSON 파일을 어떻게 통합해 HTML 문서로 전환하는지 살펴보겠습니다. 먼저 build.gradle에 사용자(user)와 가게(store), 마케팅(marketing) 서비스 관련 설정도 추가해 Gradle 빌드 실행 후 아래와 같은 JSON 파일이 추출됐다고 가정해보겠습니다.

  • FE
    • pet-fe.json
    • marketing-fe.json
  • Internal
    • pet-internal.json
    • user-internal.json
    • store-internal.json
  • Admin
    • user-admin.json
    • store-admin.json
    • marketing-admin.json

이제 목표는 이 JSON 파일들을 fe.html, internal.html, admin.html, 이렇게 세 개의 HTML 문서로 통합 및 변환하는 것입니다. 이 작업은 문서 통합 및 추출 기능을 지원하는 Redocly CLI를 활용해 처리하며, 처리 과정은 아래와 같이 2단계로 구분할 수 있습니다.

  1. join 명령어로 JSON 파일 통합
    redocly join pet-fe.json marketing-fe.json --output fe.json
  2. build-docs를 활용해 통합된 JSON 파일을 HTML 파일로 변환
    redocly build-docs fe.json --output fe.html

여기서 주의할 사항은 두 서비스의 문서를 합칠 때 API 문서에 선언된 태그명이 동일한 경우 충돌이 발생해 작업에 실패할 수 있다는 것입니다. 이를 방지하려면 아래와 같이 join 명령 실행 시 --prefix-tags-with-filename=true 옵션을 추가합니다.

redocly join pet-fe.json --prefix-tags-with-filename=true marketing-fe.json --prefix-tags-with-filename=true --output fe.json

이렇게 여러 문서를 결합한 뒤 JSON 파일을 확인해 보면 문서의 제목이나 설명이 빠져 있습니다. 통합한 문서의 제목이나 설명을 넣어주는 과정이 없었기 때문인데요. 이를 입력하기 위해 결과 파일을 YAML 형태로 변환한 뒤 yq 명령어를 활용해 관련 값을 추가한 후 최종 문서를 추출했습니다.

위 과정을 종합해서 간단히 정리하면 아래와 같습니다. 아래 명령어를 실행하면 최종 문서가 추출됩니다.

redocly join pet-fe.json --prefix-tags-with-filename=true marketing-fe.json --prefix-tags-with-filename=true --output fe.json
redocly bundle fe.json --output fe.yaml --ext yaml
yq -i '.info.title = "PetStore Backend API Document for FrontEnd"' fe.yaml
yq -i '.info.description = "Please contact Petstore backend if there are any issues with API"' fe.yaml
redocly build-docs fe.yaml --output petstore-fe.html

GitHub Pages로 웹 호스팅

위와 같은 과정을 거쳐 프런트엔드 개발자와 협력할 때 유용하게 사용할 수 있는 petstore-fe.html을 완성했습니다. 이제 이 문서를 어떻게 전달하는 것이 좋을까요?

GitHub에서는 특정 브랜치 경로에 HTML 파일이 존재할 경우 자동으로 이 파일을 참조해 호스팅해 주는 GitHub Pages라는 웹 호스팅 서비스를 제공하고 있습니다. 추출한 HTML 파일을 원하는 경로에 커밋하는 것만으로 자동으로 URL이 생성되며, 이 URL로 최종 완성된 API 문서를 전달할 수 있습니다.

예를 들어 Pet Store 리포지터리의 메인 브랜치로 GitHub Pages를 설정하고 문서 경로에 petstore-fe.html 파일을 올려놓으면 https://{github_url}/LINE/petstore/document/petstore-fe이라는 URL로 API 문서를 전달할 수 있습니다. 

GitHub Actions를 이용해 자동화

지금까지 API 명세를 JSON 형태로 추출해 하나의 파일로 통합한 뒤 YAML 형태로 변환해 제목과 설명을 추가하고 이를 다시 HTML 파일로 변환해서 GitHub Pages를 이용해 웹 호스팅하는 과정을 살펴봤습니다. 그런데 만약 이 과정을 계속 수동으로 직접 진행해야 한다면 어떨까요? 문서 작업이 얼마나 고된 작업이 될지 상상이 가지 않습니다.

저는 앞서 아키텍처 설계에서 살펴본 그림처럼 사용자가 간단히 요청하기만 하면 모든 작업이 자동으로 진행되기를 바랐고, 이를 유기적으로 수행할 도구로 GitHub Actions를 선택했습니다. 문서 작업의 시작이 GitHub에 저장된 코드이고, 최종 결과를 볼 수 있는 페이지도 GitHub이기 때문에 GitHub Actions가 이 작업을 수행할 최적의 도구라고 생각했습니다.

아래 그림은 GitHub Actions 워크플로를 수행해서 최종적으로 어떻게 문서를 서비스하는지 간단하게 도식화한 그림입니다. Pet Store 리포지터리에서 문서를 추출해 Document 리포지터리로 문서를 올리는 구조로 설계했습니다. 

위와 다르게 Document 리포지터리 없이 Pet Store 리포지터리에서 직접 서비스하는 것도 가능하나, 현재 담당하는 여러 서비스의 문서를 모두 같은 도메인에서 제공하고 싶었기 때문에 별도로 문서용 리포지터리를 만들어 거기서 워크플로를 작성해 문서 통합 및 업로드까지 수행되도록 구성했습니다.

워크플로는 위 그림과 같이 그룹화할 대상별로 각각 생성했는데요. 타깃만 다를 뿐 로직은 동일합니다. workflow.yaml 파일은 다음과 같은 단계로 간단히 구성했습니다.

  1. 코드 체크아웃: Pet Store 리포지터리로부터 입력받은 브랜치 기반으로 코드 체크아웃
  2. JDK 설정: 프로젝트에 맞게 JDK 설정
  3. API 문서 추출: Gradle 명령어를 활용해 문서 추출
  4. 문서 통합 및 HTML 파일 생성: Redocly CLI를 활용해 문서 통합 및 변환
  5. HTML 문서 커밋 후 푸시: 추출된 문서를 커밋하고 푸시해 자동으로 문서 갱신 후 웹 서비스로 제공

아래는 실제 workflow.yml 파일을 단순화해 가져온 샘플입니다. 

name: Petstore FE Doc

on:
  workflow_dispatch:
    inputs:
      branch:
        description: 'Input branch info you want to generate'
        required: true
        default: 'release/document'

jobs:
  document:
    runs-on: self-hosted
    steps:
      - name: Checkout code - ${{github.event.inputs.branch}}
        uses: actions/checkout@v3
        with:
          repository: 'LINE/petstore'
          ref: ${{ github.event.inputs.branch}}
          token: ${{ secrets.doc_secret }}

      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'zulu'

      - name: get pet FE doc
        run: |
          ./gradlew :petstore-pet:clean :petstore-pet:generateOpenApiDocs
      - name: get marketing FE doc
        run: |
          ./gradlew :petstore-marketing:clean :petstore-marketing:generateOpenApiDocs

      - name: generate document html
        run: |
          cp petstore-pet/build/generated/source/kapt/main/docs/pet-fe.json ./
          cp petstore-marketing/build/generated/source/kapt/main/docs/marketing-fe.json ./
          redocly join pet-fe.json --prefix-tags-with-filename=true marketing-fe.json --prefix-tags-with-filename=true --output fe.json
		  redocly bundle fe.json --output fe.yaml --ext yaml
		  yq -i '.info.title = "PetStore Backend API Document for FrontEnd"' fe.yaml
		  yq -i '.info.description = "Please contact Petstore backend if there are any issues with API"' fe.yaml
		  redocly build-docs fe.yaml --output petstore-fe.html

      - name: save build result to tmp dir
        run: mv petstore-fe.html ../../_temp
      - name: Checkout document branch
        uses: actions/checkout@v3
        with:
          ref: 'main'
      - name: Move build artifacts
        run: |
          mkdir -p petstore  # replace with your target path
          cp -R -f ../../_temp/petstore-fe.html  petstore/fe.html

      - name: Commit and push build artifacts
        run: |
          git config user.name "GitHub Actions"
          git config user.email "line-document@linecorp.com"
          git add .
          git commit -m "Commit document artifacts"
          git push

GitHub Actions를 사용하면 Git에서 발생하는 이벤트를 트리거로 사용해 특정 액션을 수행할 수 있지만, 여기서는 문서를 생성하고자 하는 브랜치를 직접 입력해서 실행하는 쪽을 선택했습니다.

이와 같은 선택을 한 주요 이유는, 신규 개발 스펙이 기획돼 API를 설계한 후, 실제로 구현하기 전에 연계되는 API를 먼저 공유하기 위해서입니다. 그래야 개발이 같이 진행되는 일정 속에서 시간을 조금 더 효율적으로 활용할 수 있습니다. 만약 API 구현이 완료된 후에야 공유할 수 있다면, 해당 API를 활용해 개발을 진행해야 하는 부서에서는 커다란 구멍을 남겨 놓고 개발을 진행하거나, 혹은 API가 전달된 후에야 개발을 시작할 수 있기 때문에 일정과 관련해 이슈가 발생할 여지가 높습니다. 이런 문제가 발생하지 않도록 API 문서에 해당하는 코드만 단순하게 작성해 커밋한 후 즉시 배포해 프로젝트 초반에도 빠르게 API 문서를 공유할 수 있게 만들기 위한 조치였습니다.

GitHub Actions를 도입하고 나서는 문서를 수동으로 수정하거나 추가로 작성할 필요가 없어졌습니다. 코드만 간단하게 작성해 워크플로를 수행한 뒤 확인하고 공유하면 되는 단순한 절차로 바뀌었습니다. 게다가 Redoc 기반의 UI가 적용된 문서이기 때문에 보기에 깔끔한 것은 물론 API 검색 기능까지 지원돼 작성자와 독자 모두 편리함이 배가 되었습니다.

리포지터리가 여러 개인 환경에도 문서 통합 적용하기

지금까지 설명한 자동화는 하나의 리포지터리 안에 여러 서비스가 다중 모듈로 구성돼 있는 경우가 대상이었는데요. 여러 리포지터리의 문서를 하나로 문서화해야 하는 경우도 있었습니다. 각 리포지터리별로 생성되는 문서는 서로 다르지만 그룹화할 대상은 하나로 단일화돼 있다고 가정할 때, 아래와 같이 구성해서 처리할 수 있었습니다.

위 구성의 처리 과정은 다음과 같습니다.

  1. {animal}.yml 워크플로가 dog/bird/cat/panda 중 해당하는 문서를 추출합니다.
  2. 추출한 문서를 Document 리포지터리에서 그 문서에 해당하는 경로에 저장합니다.
  3. 첫 번째 워크플로의 문서 추출 잡이 정상적으로 수행됐다면 다음 워크플로인 통합 워크플로가 호출됩니다.
  4. 통합 워크플로는 dog/bird/cat/panda 각 문서를 모두 가져와서 animal.json이라는 통합 문서를 생성하고 HTML 파일로 변환 후 Document 리포지터리에 커밋하고 푸시해 문서를 갱신합니다. 갱신된 문서는 웹 서비스를 통해 자동으로 제공됩니다. 

이와 같은 구성에서는 panda 워크플로만 수행하더라도 기존에 생성돼 있던 문서들과 새로운 panda 문서가 결합돼 결국 새로운 문서가 생성되므로, 여러 리포지터리로 구성된 구조에서도 문서 충돌 없이 워크플로만으로 간단하게 문서를 갱신할 수 있습니다.

앞서 하나의 리포지터리의 경우와 비교했을 때 가장 다른 점은, 워크플로를 두 개 이상으로 구성해 서로 호출해서 처리하는 구조를 만들어서, 리포지터리가 추가되더라도 각 프로젝트에 맞는 워크플로만 구현한 뒤 통합 워크플로만 호출하면 처리할 수 있는 구조로 구성했다는 점입니다.

아래는 {animal}.yml과 integration.yml의 예시로, 워크플로 간 호출이 어떻게 수행되는지 살펴볼 수 있습니다.

# {animal}.yml
name: pando doc

on:
  workflow_dispatch:
    inputs:
      branch:
        description: 'Input branch info you want to generate'
        required: true
        default: 'release/document'

jobs:
  document:
    runs-on: self-hosted
    steps:
      - # generate & upload document 

  document-integration: # integration workflow call
    needs: document
    uses: ./.github/workflows/integration.yml

# integration.yml
name: Integrate Document

on:
  workflow_call:

jobs:
  document:
    runs-on: self-hosted
    steps:
      - name: Checkout document repository
        uses: actions/checkout@v3
        with:
          ref: 'main'

      - name: generate document html
        run: |
          cd animal
          redocly join dog/dog.json --prefix-tags-with-filename=true cat/cat.json --prefix-tags-with-filename=true bird/bird.json --prefix-tags-with-filename=true panda/panda.json --prefix-tags-with-filename=true -o ./animal.json
          redocly bundle animal.json --output animal.yaml --ext yaml
		  yq -i '.info.title = "Animal Backend API Document"' animal.yaml
		  yq -i '.info.description = "Please contact Animal backend if there are any issues with API"' animal.yaml
          redocly build-docs animal.yaml --output animal.html

       - name: Commit and push build artifacts
        run: |
          git config user.name "GitHub Actions"
          git config user.email "line-document@linecorp.com"
          git add .
          git commit -m "Commit document artifacts"
          git push

마치며

기술적인 디테일을 살리면서 개발하는 것은 개발자의 기본이며, 이에 더해 타 부서와 얼마나 효율적으로 명확하게 커뮤니케이션하느냐도 개발자의 업무 능력에서 중요한 부분을 차지한다고 할 수 있습니다. 그 과정에서 불편한 부분을 캐치해 개선해 나가는 작은 노력들이 모여 서로의 시간을 아끼고 퍼포먼스를 극대화한다고 생각하는데요. 그런 차원에서 이번 문서 자동화 작업을 수행했고, 이 글에서 소개한 방법으로 여러 프로젝트에서 API 문서화 문제를 해결해 현재까지 잘 사용하고 있습니다. 이를 통해 정돈된 API 문서를 읽기 쉽고 사용하기도 쉬운 환경에서 제공할 수 있었기에 동료들의 반응도 좋았으며, 팀 차원에서 문서 관리 비용도 많이 줄일 수 있었습니다. 최근 gRPC나 GrpahQL 등의 사용이 증가하면서 REST API 기반의 문서 작성이 줄어드는 추세이긴 하지만, 그럼에도 REST API는 계속 사용될 스펙이기에 앞으로도 비슷한 케이스에 지속적으로 적용할 예정입니다. 이 글이 저와 같은 고민을 하고 계신 분들께 조금이나마 도움이 돼 실제로 업무 개선에 보탬이 될 수 있기를 바라며 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다.