일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- TypeScript
- gradle
- terraform
- chai
- 온라인강의
- Mocha
- typeorm
- IAC
- java
- docker
- Nestia
- Redis
- 이더리움
- blockchain
- nestjs
- 블록체인
- spring
- class-transformer
- 글또
- terraform cloud
- nodejs
- 리뷰
- 유데미
- restdocs
- corretto
- 도서
- mysql
- 백엔드
- Database
- ChatGPT
- Today
- Total
끄적끄적
백엔드 DTO 를 공유받고 싶어요. 본문
백엔드에서 다른 팀에게 API 문서를 전달하는 방법에는 여러가지가 있다. Swagger 를 사용해서 문서를 전달할 수도 있고 Postman 을 이용해서 API 문서화를 시킬 수도 있다. 자바(스프링) 진영에서는 Rest Docs 와 같이 테스트 코드를 강제화 해서 문서를 만들어내는 방식이 유행하는 것 같다. 하지만 어떻게 문서를 넘기던지간에 프런트 입장에서는 백엔드가 만들어진 문서를 보고 API DTO 들을 다시 만들어 내는 과정이 필수적이며 여간 귀찮은 작업이 아니다.
그런데 우리 회사는 백엔드는 node.js(Nest.js), 프런트는 react-native & react 로 모든 개발 언어를 Typescript 로 맞춰서 개발중에 있다. 그래서 작년 초에 프런트 개발자가 이런 요구를 해왔다.
어차피 같은 언어로 만든 DTO 들인데 이거 공유해주시면 안되나요?
생각해보면 같은 언어로 만든 건데 당연한 요구사항인 것 같다. 그래서 이번 포스팅에서는 필자의 회사에서 어떤 방식으로 DTO 공유를 해 오고 있는지 설명할까 한다.
DTO 공유하기
1. DTO 패키지 분리
우선 프런트에서 공유를 원하는 부분은 단순히 DTO 들이다. 또한 서버 코드가 프런트에 있다면 보안적으로도 좋지 않은 형식이 될 수 있다. 그렇기 때문에 회사에서는 프런트에 공유해야 하는 DTO 파일들을 yarn workspace + lerna 를 이용해 모노레포로 분리하고 있다. yarn workspace + lerna 를 이용한 모노레포의 경우 시간이 나면 한번 정리하겠지만 이미 많은 블로그에서 잘 설명해주고 있으니 참고하면 좋을 듯 싶다. 이 포스팅에서는 생략하고자 한다. 개인적으로 이런 DTO 패키지는 내 소스 파일이 아니라 외부와 소통하는 일종의 "계약" 이라고 생각하여 "contract" 라는 이름을 사용한다.
아래는 이 DTO 파일들을 모아둔 패키지 예시이다. (commands 모듈은 request 를 의미하고, views 모듈은 response 를 의미한다)
그 후 api 를 개발할때는 모노레포로 개발하듯이 contract 모듈을 불러와서 개발하면 된다.
import { Body, Controller, Get, HttpCode, Param, Post } from '@nestjs/common';
import { CreateUser, UserView } from '@pawmi/app-sample-contract'; // 분리한 dto 를 불러온다.
import { UserService } from '../services/UserService';
@Controller('api/users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post('')
@HttpCode(201)
async createUser(@Body() command: CreateUser): Promise<UserView> {
await this.userService.createUser(command);
return this.userService.getUserById({ userId: command.id });
}
@Get(':userId')
@HttpCode(200)
async getUser(@Param('userId') userId: string): Promise<UserView> {
return this.userService.getUserById({ userId });
}
}
2. DTO 를 NPM 패키지로 공유
DTO 패키지 분리까지는 했는데 이를 어떻게 공유할까 고민을 했었다. 고려했던 선택지는 크게 3가지였다.
- npm package 로 배포
- S3 에 배포
- git submodule 활용
이 중에서 git submodule 은 이전에 branch 등 때문에 크고 작은 이슈들이 많이 생긴 경험 때문에 선택하지 않았다. 특히나 yarn workspace 로 분리한 경우에는 더더욱 어울리지 않을 것 같았다.
회사에서는 S3 를 이용해서 이것저것 빌드 파일들 관리를 이미 하고 있어서 이를 이용하는게 어떨까 고려해보았었다. 특히나 앱이 커지면서 CI 속도가 점점 느려지다 보니 성능 향상을 위해서 빌드 파일들을 S3 에 미리 캐시해두고 있다. 그러다보니 이미 DTO 패키지에 대한 빌드 파일이 업로드 되어 있어 프런트에서는 개발 전에 S3 를 다운로드 할 수 있는 스크립트 하나만 있으면 되겠다는 생각이 들었다. 물론 제목에서 나와 있듯이 결정하지 않았는데 그 이유는 프런트에서 파일을 다운로드 받고 사용하는 작업이 생각보다 매끈하게 이루어지지 않았기 때문이다. 하지만 결정하지 않은 가장 큰 이유는 프런트에서 첫번째 방법인 npm package 로 활용하는 것을 더 선호하기 때문이였다.
그래서 github 의 private npm package 를 이용해서 DTO 패키지를 공유했다. 이미 lerna 를 설정했다면 배포하는건 lerna publish from-package 명령어만 입력해주면 되니 배포도 편하게 이루어졌다.
아래는 배포했을때 github 에 나오는 예시이다.
그리고 이제 이를 프런트에 공유해주면 프런트에서는 아래 코드처럼 편하고 type-safe 하게 api 를 만들어 낼 수 있다.
import { CreateUser, UserView } from '@pawmi/app-sample-contract';
import axios, { AxiosResponse } from 'axios';
// type safe api
const postCreateUser = async (body: CreateUser): Promise<UserView> => {
const result = await axios.post<UserView, AxiosResponse<UserView>, CreateUser>(
`http://localhost:3000/api/users`,
body
);
return result.data
}
// 사용하기
await postCreateUser({
id: "514a1115-f21e-49ee-b6a6-4cd3fab75255",
type: "customer",
age: 25,
email: "sample@kscory.com",
password: "1234"
});
SDK 공유하기(Nestia)
지금의 회사에서는 메인으로 사용하는 서버는 지금과 같이 npm package 로 DTO 만을 공유하고 있었다. 그런데 최근 Nest js Korea 밋업에서 발표자 분이 직접 만드신 흥미로운 라이브러리를 소개해 주셨다. 프런트에 DTO 뿐만 아니라 API 요청을 할 수 있는 SDK 를 자동으로 만들어주는 라이브러리다. 즉, 현재 우리 회사에서 하는 방식보다 더 업그레이드 된 방식이라고 생각되었다. 또한 Nestia 를 사용하면 Json 상하차 성능도 비약적으로 상승시킬 수 있다고 하니 일석이조가 아닐 수 없을 것 같았다. 그래서 일단 회사의 다른 간단한 내부 api (프런트 api 가 아니라 서버끼리 소통하는 api) 를 새롭게 개발할때 사용해봤다. 결론부터 이야기하자면 메인으로 사용하는 서버도 전부 바꾸고 싶다는 생각이 들었다.
Nestia 를 세팅할 때 문서 자체가 아직 자세히 나와있지 않아서 애를 좀 먹었었는데 그래도 한번 세팅하고 나니 딱히 건드릴 것이 없이 편하게 개발할 수 있었다. 나중에 시간나면 포스팅으로 남겨봐야 겠다.
우선 Nestia 를 사용하기 위해서는 Controller 의 데코레이터를 변경해 주어야 한다.
import { Controller, HttpCode } from '@nestjs/common';
import { TypedBody, TypedParam, TypedRoute } from '@nestia/core'; // 이 부분들과 관련된 부분들을 변경해준다.
import { CreateUser, UserView } from '../dto';
import { UserService } from '../services/UserService';
@Controller('api/users')
export class UserController {
constructor(private readonly userService: UserService) {}
@TypedRoute.Post('')
@HttpCode(201)
async createUser(@TypedBody() command: CreateUser): Promise<UserView> {
await this.userService.createUser(command);
return this.userService.getUserById({ userId: command.id });
}
@TypedRoute.Get(':userId')
@HttpCode(200)
async getUser(@TypedParam('userId', 'uuid') userId: string): Promise<UserView> {
return this.userService.getUserById({ userId });
}
}
그리고 nestia.config 를 세팅하고
// nestia configuration file
import type sdk from "@nestia/sdk";
const NESTIA_CONFIG: sdk.INestiaConfig = {
input: "src/controllers",
output: "lib/api", // lib 디렉토리에 생성한다.
primitive: false,
};
export default NESTIA_CONFIG;
"npx nestia sdk" 명령어를 실행시키면 자동으로 api sdk 가 생성된다. (lib/api 디렉토리 하위에 여러 파일들이 생성된다.)
그리고 이 파일을 공유해주면 아래처럼 사용할 수 있다. 이전과 달라진 점이라면 프런트에서는 dto 를 불러올 필요도 없고 직접 api 를 정의할 필요도 없어지고 단순히 내부 라이브러리 사용하듯 사용하면 된다.
import * as userApi from '../lib/api/functional/api/users' // 공유한다면 공유된 방식으로 불러오면 된다.
const con = { host: "http://127.0.0.1:3000" };
await userApi.createUser(con, {
id: "514a1115-f21e-49ee-b6a6-4cd3fab75255",
type: "customer",
age: 25,
email: "sample@pawmi.com",
password: "1234"
})
내부 서버끼리 소통할 때도 API 를 주로 활용하는데 이를 위해서 이것저것 정의하지 않고 통일된 규격으로 쉽게 활용할 수 있다는 점이 편리했다. 물론 현재 회사는 npm package 로 DTO 를 공유하는 방식을 주로 사용하고 있지만 시간이 날 때마다 조금씩 바꿔보는 것도 좋을 듯 싶다. 다만 모노레포를 적용하는 방법을 잘 몰라서 테스트는 좀 더 필요할 것 같다.
결론
nodejs 를 활용하는 많은 회사들은 대부분 DTO 를 어떤 방식이던지 공유하고자 하는 니즈가 있을 것 같다. 만약 백엔드와 프런트가 다른 언어를 사용한다면 이런 방식을 활용하기는 어려울 것이다. 그래서 이런 부분때문에 grpc 같은 프로토콜이 유행하고 있는게 아닐까 싶다.
하지만 nodejs 를 사용한다면 누릴 수 있는 얼마 안되는 특권인 DTO 공유는 무조건 활용하는 방식으로 가는게 생산성 향상에 있어 정말로 큰 도움이 될 것이다.
'개발 > js & ts & node.js' 카테고리의 다른 글
Repository 의 추상화 (2) | 2023.12.23 |
---|---|
[NestJS] custom parameter decorator (6) | 2023.10.27 |
Typescript type 을 활용해 BIP44 Path 를 컴파일 타임에 검사하기 (0) | 2023.07.16 |
class-transformer 에 의한 Nest.js 성능 문제 (0) | 2023.06.18 |
ChatGPT API 사용해보기 (0) | 2023.03.26 |