개념
Restful API는 하나의 엔드포인트에 하나의 스키마를 내려주도록 한다. 그런데 해당 엔드 포인트를 쓰는 플랫폼이 많아지거나 요구사항이 늘어나면서 문제가 생긴다. 모두에게 맞는 엔드 포인트, 즉 모든 정보를 가진 엔드 포인트를 만들거나 (one-size fits all), 매번 비슷한 필드를 내려주면서 각 플랫폼, 디바이스, 수정된 요구사항에 따라 API 엔드포인트를 계속 만들어야하는 작업을 해야한다. 이러한 문제 의식에서부터 나온 것이 graphQL인데, 이를 도입하는 것은 큰 작업이고 새로운 기술을 도입하는 데에는 시간이 필요하다. 가능한 기존의 RESTful 한 엔드 포인트를 유지하면서 클라이언트가 서버에서 원하는 것을 다시 선택하도록 수정해보려고 한다.
적용
요구사항은 아래와 같다.
기존 API에서 3가지 정도의 필드를 내려주고 있는 데 클라이언트가 이 중에 원하는 필드를 선택해서 가져가도록 하려고 한다.
기존 API 에 ?include={원하는 필드} 에 쿼리문을 추가해 구현했다.
사실 그에 맞는 API를 생성하는 것도 한 가지 좋은 방법이다. 미리 배포해놓기도 좋다. 하지만 이렇게 비슷한 API가 많아지면 유지보수도 어려워지고 코드 중복도 높아지게 된다. 이런 장단점을 고려해봤을 때 이번에는 새로운 API 보다 호환성을 지키며 기존의 API를 수정해보려고 했다.
선택할 수 있는 필드는 USER, MENTOR, META 그리고 모든 정보를 받는 ALL 이다.
받을 수 있는 필드를 enum 타입으로 만들면 들어올 수 있는 필드들을 관리하는 데 좋을 것 같아서 enum 타입으로 만들었다.
그리고 들어오는 필드가 all 또는 특정 필드인 경우 응답값에 포함시키는 매서드를 추가했다.
@Enum('code')
export class PartialType extends EnumType<PartialType>() {
static readonly USER = new PartialType('user');
static readonly MENTOR = new PartialType('mentor');
static readonly META = new PartialType('meta');
static readonly ALL = new PartialType('all');
constructor(private readonly _code: string) {
super();
}
static isAll(value: PartialType) {
return value.code === PartialType.ALL.code;
}
static isUser(value: PartialType) {
return this.isAll(value) || value.code === PartialType.USER.code;
}
static isMeta(value: PartialType) {
return this.isAll(value) || value.code === PartialType.META.code;
}
static isMentor(value: PartialType) {
return this.isAll(value) || value.code === PartialType.MENTOR.code;
}
get code(): string {
return this._code;
}
}
그리고 이런 값들을 쿼리로 받을 수 있도록 include 값을 받았다.
@ApiProperty({ required: false, type: Boolean })
@IsOptional()
@ToEnum(PartialType)
include: PartialType;
}
다양한 필드들이 들어올 수 있는데 모든 경우의 수를 어떻게 구현할 지 고민하다가 빌더 패턴을 이용하기로 했다.
우선 defulat로 아무 필드도 포함하지 않은 response 를 만들고
여기에 들어오는 필드에 따라 값을 추가하도록 설정했다. 예를 들어 meta 정보를 알려달라는 쿼리가 들어오면 해당 정보를 추가하고 멘토 정보를 알려달라는 쿼리가 들어오면 해당 정보를 추가해서 내려줬다.
response._reviewA = course.reviewA;
response._reviewC = course.reviewC;
response._studentC = course.studentC;
response._courseC = course.courseC;
}
if (PartialType.isUser(include)) {
response._user = UserDto.by(user, hasR);
}
if (PartialType.isMentor(include)) {
response._mnformation = m ? MDto.by(mentor) : undefined;
}
다음과 같이 쿼리에 따라 원하는 값을 조회할 수 있다.



여러 값을 받도록 설정하면 다음과 같이 원하는 값을 array 형태로 넣어 전달할 수 있도록 할 수 있다.
아래는 user 와 mentor를 요청해서 해당 필드만 받도록 수정한 결과이다.
굉장히.. graphQL 스럽다.. 📈📉📊

참고
https://philcalcado.com/2019/07/12/some_thoughts_graphql_bff.html
'백엔드 개발' 카테고리의 다른 글
#039. 리팩터링: 히든 필드로 기존 API에 기능 추가하기 (0) | 2023.06.15 |
---|---|
#038. 인프라: 사이드 프로젝트 호스팅 서비스 이사 기록 (1) | 2023.06.09 |
#036. 쿼리개선: insert 성능과 테이블 구조 (Index 정리하기) (0) | 2023.05.13 |
#035. 쿼리개선 : InnerJoin / OuterJoin 과 인덱스 (0) | 2023.05.01 |
#034. 쿼리개선: "연산"을 해서 index를 사용하지 못한 쿼리 개선 (0) | 2023.04.25 |