<aside> 🔗

GitHub 예시 코드

</aside>

들어가는 말


전 회사에서 A 프로젝트를 진행할 때였다. 프로젝트의 규모가 커서 프론트엔드 개발자뿐 아니라 백엔드 개발자도 꽤 다수였다. 사용하는 모델도 많았고, API도 많았다.

초반에는 순조로웠다. API 스펙이 나오면 그에 따라 프론트엔드 개발을 했다. 문제는 프로젝트 중반 이후에 발생했다. 어떤 이유에서인지, API 응답 모델이 자꾸 바뀌었다.

다행이었던 건, Swagger(OpenAPI 규격)와 Codegen 조합을 쓰고 있었기 때문에 개발 시점에 오류를 잡아낼 수 있었다는 것이다. 다만 그러기 위해, 프론트엔드 개발자들은 API 모듈을 업데이트를 하는 순간 프로젝트 전반에 발생하는 오류에 대해 영향 범위 체크, 업무 분담, 전파 등 본 업무 외에 추가적인 부담을 떠안아야 했다. 여러모로 쉽지 않은 프로젝트였다.

이후 B 신규 프로젝트를 시작할 때 동료분이 “API 응답 모델을 그대로 쓰지 말고 변환 레이어를 두자”는 아이디어를 제안했다. 그렇게 하면 API 응답 모델이 변경되더라도 영향 범위를 제한할 수 있다는 것이었다. 아이디어는 설득력 있었고, 우리는 신규 프로젝트에 곧바로 적용했다.

이번 글에서는 그때의 경험을 바탕으로, 그 과정을 되짚어보면서 내용을 다지고 그 효과를 정리하는 시간을 가져보기로 했다. 이 내용은 앞으로의 실무에서도 분명 많은 도움이 될 것이다.

API 응답 모델 그대로 쓰기


API 응답 모델 변경 전

API 스펙에 따라 userName, userEmail을 내려주는 API 응답 모델과 API 함수를 작성하고, UI 컴포넌트에서 이를 사용한다.

예시 코드

// api 함수

export interface GetUserResponse {
  userName: string
  userEmail: string
}

export const getUser = async (): Promise<GetUserResponse> => {
  return await get({
    userName: 'John Doe',
    userEmail: '[email protected]',
  })
}
// UI 컴포넌트

export const TryWithoutAdapter = () => {
	const [user, setUser] = useState<GetUserResponse>()

  // ...

  return <UserProfile name={user?.userName} email={user?.userEmail} />
}

API 응답 모델 변경 후

이제 API 응답 모델이 변경되어 userNameuser_name, userEmailuser_email 로 필드명들이 바뀌었다.

프론트엔드에서는 API 응답 모델 타입과 이를 소비하는 UI 컴포넌트까지 수정을 해야 한다. 만약 여러 컴포넌트에서 소비중이라면, 수정해야 할 라인 수는 기하급수적으로 늘어날 것이다.

예시 코드

// api 함수

export interface GetUserResponse {
  user_name: string // 수정
  user_email: string // 수정
}

export const getUser = async (): Promise<GetUserResponse> => {
  return await get({
    user_name: 'John Doe',
    user_email: '[email protected]',
  })
}
// UI 컴포넌트

import { useEffect, useState } from 'react'
import { getUser, type GetUserResponse } from '../api/getUserWithoutAdapter'
import { UserProfile } from './UserProfile'

export const TryWithoutAdapter = () => {
  const [user, setUser] = useState<GetUserResponse>()

  const fetchUser = async () => {
    const user = await getUser()

    setUser(user)
  }

  useEffect(() => {
    fetchUser()
  }, [])

	// 수정
  return <UserProfile name={user?.user_name} email={user?.user_email} /> 
}