일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- ip
- 프로그래머스
- BFS
- 타입 챌린지
- Algorithm
- 쉬운 문제
- 소켓
- 타입스크립트
- 알고리즘
- Node.js
- 자바스크립트
- 수학
- typescript
- 프로그래머스 레벨 2
- HTTP 완벽 가이드
- 레벨 1
- dfs
- 백준
- javascript
- type challenge
- 크롤링
- 가천대
- 문자열
- dp
- 그래프
- Nestjs
- socket
- Crawling
- HTTP
- TCP
- Today
- Total
kakasoo
Nestia에서 typeof, namespace를 쓰면 안 됩니다 본문
왜 안 되는데요?
Nestia는 NestJS로 작성된 백엔드 코드를 읽고 TypeScript compiler를 이용해서 프론트에서 사용 가능한 SDK를 만든다.
그래서 지금 사용하면 곤란한 TypeScript keyword들이 있는데, 하나는 typeof고 하나는 namespace이다.
typeof의 경우 타입의 이름을 추론해낼 수 없게 되는 문제가 있다.
namespace는 한 함수가 동일 네임스페이스로부터 두 개 이상의 내부 인터페이스, 타입을 가져올 때 문제가 될 수 있다.
이유는 import 시 두 타입이 하나의 인터페이스로부터 추론된다는 것을 컴파일러가 인지하기 어렵기 때문에 하나만 갖고 오게 되서다.
https://kscodebase.tistory.com/663
따라서 내가 이전에 쓴 글에 있던 것처럼 에러를 추론하는 방법은 SDK가 부적절하게 생성될 위험이 있다.
그럼 어떻게 해야 하는데요?
export const ERROR: Record<string, ErrorResponse> = {
ALREADY_CREATED_EMAIL: { result: false, code: 4001, data: '이미 생성된 이메일입니다.' },
NO_AUTH_TOKEN: { result: false, code: 4002, data: '인증이 필요합니다.' },
IS_SAME_POSITION: { result: false, code: 4003, data: '이미지의 정렬 값이 동일한 경우가 존재합니다.' },
CANNOT_FINDONE_ARTICLE: { result: false, code: 4004, data: '게시글을 찾지 못했습니다.' },
SELECT_MORE_THAN_ONE_BODY_IMAGE: { result: false, code: 4005, data: '적어도 1장 이상의 이미지를 골라야 합니다.' },
NOT_FOUND_ARTICLE_TO_COMMENT: { result: false, code: 4006, data: '댓글을 작성할 게시글을 찾지 못했습니다.' },
TOO_MANY_REPORTED_ARTICLE: { result: false, code: 4007, data: '신고가 접수된 게시글이라 댓글 작성이 불가능합니다.' },
ALREADY_FOLLOW_USER: { result: false, code: 4008, data: '이미 좋아요를 누른 유저입니다!' },
CANNOT_FIND_ONE_DESIGNER_TO_FOLLOW: { result: false, code: 4009, data: '팔로우할 유저를 찾지 못했습니다.' },
STILL_UNFOLLOW_USER: { result: false, code: 4010, data: '아직 팔로우한 적 없는 유저에요!' },
CANNOT_FIND_ONE_DESIGNER_TO_UNFOLLOW: { result: false, code: 4011, data: '언팔로우할 유저를 찾지 못했습니다.' },
CANNOT_FIND_ONE_REPLY_COMMENT: { result: false, code: 4012, data: '답글을 달 댓글을 찾지 못했어요.' },
ALREADY_CREATED_PHONE_NUMBER: { result: false, code: 4013, data: '이미 생성된 전화번호입니다.' },
ARLEADY_REPORTED_ARTICLE: { result: false, code: 4014, data: '이미 신고한 게시글입니다.' },
IS_NOT_WRITER_OF_THIS_ARTICLE: { result: false, code: 4015, data: '이 게시글의 작성자만이 수정할 수 있습니다.' },
CANNOT_FIND_ONE_COMMENT: { result: false, code: 4016, data: '해당 댓글을 찾지 못했습니다.' },
CANNOT_FOLLOW_MYSELF: { result: false, code: 4017, data: '설마 자기 자신을 팔로우하려고 했어요?!' },
} as const;
왜냐하면 내가 만든 에러는 이러한 형태의 객체였고, 여기서 typeof ERROR.ALREADY_CREATED_EMAIL 형식을 취했기 때문이다.
이걸 변환해서 네임스페이스를 만들고, 네임스페이스 안에다가 인터페이스들을 정의할 수도 있다.
하지만 네임스페이스로 바꾸게 될 경우에도 SDK의 경로가 제대로 추적되지 않는 문제가 발생한다.
따라서 모듈을 만들기 위해서 네임스페이스를 만들기보다, 아예 모듈로 구분지을 것들을 하나의 파일로 모아 관리하는 것이 낫다.
// business-error.ts
export const ERROR: Record<string, ErrorResponse> = {
// 생략
} as const;
파일 명을 ERROR로 퉁치는 대신에, 에러를 구조를 만들 수 있도록 분리해주자.
추후 에러를 런타임에서 관리할 때를 대비해서 index.ts로 모아서 export 해주는 등 구조 관리를 해주는 것도 좋다.
하지만 타입을 관리할 때는 무조건 파일 단위로 인터페이스를 나눠서 경로를 가지게 하지 말자.
export declare namespace NAMESPACE {
interface INTERFACE {
name: string;
}
}
그 다음으로는 namespace나 typeof 키워드들을 모두 제거하는 일이 필요하다.
이런 형태의 네임스페이스를 쓰게 될 경우, 그 네임스페이스로부터 내부 인터페이스를 추론할 수는 있다.
이 경우에는 NAMESPACE.ITERFACE 와 같이 점 연산자로 표기하게 되는데, 이 상태에서는 아무런 문제가 없다.
하지만 하나의 함수의 SDK를 추론해낼 때, 동일한 네임스페이스로부터 두 개 이상의 인터페이스를 추론할 때는 에러가 발생할 수 있다.
결론만 말하면...
export interface ERROR {
type: string;
result: false;
code: number;
data: string;
}
export const isBusinessErrorGuard = (obj: any): obj is ERROR & { type: 'business' } => {
if (isErrorGuard(obj)) {
if (obj.type === 'business') {
return true;
}
}
return false;
};
export const isErrorGuard = (obj: any): obj is ERROR => {
if (obj.result === false) {
return true;
}
return false;
};
export interface ALREADY_CREATED_EMAIL extends ERROR {
type: 'business';
result: false;
code: 4001;
data: '이미 생성된 이메일입니다.';
}
1. 에러를 구조적으로 나누기 위해 네임스페이스를 사용하는 것은 금지.
2. 에러를 만들고 타입스크립트를 사용하는 것도 금지.
3. 따라서 에러 타입을 정의한 후, 그 에러 타입에 맞는 에러들을 정의하고, 내부 프로퍼티로 구조를 결정할 type 프로퍼티 정의
4. 에러를 인터페이스로 쓰게 될 경우, 이전처럼 에러를 리턴하거나 그런 행위가 불가능해지는데?
/**
* @summary 230129 - 게시글 작성 / 임시저장 기능이 추가되어야 한다. (incompleted)
* @tag articles
* @param userId 글을 쓰고자 하는 작성자의 id
* @param createArticleDto 게시글의 정보
* @returns
*/
@TypedRoute.Post()
public async writeArticle(
@UserId() userId: number,
@TypedBody() createArticleDto: CreateArticleDto,
): Promise<TryCatch<ArticleType.DetailArticle, CANNOT_FINDONE_ARTICLE | IS_SAME_POSITION>> {
const savedArticle = await this.articlesService.write(userId, createArticleDto);
if (isErrorGuard(savedArticle)) {
return savedArticle;
}
const article = await this.articlesService.getOneDetailArticle(userId, savedArticle.id);
if (!article) {
return typia.random<CANNOT_FINDONE_ARTICLE>();
}
return createResponseForm(article);
}
만들어진 에러 타입은 모든 내부 값들이 상수로 정의되어 있기 때문에 typia.random<T>()을 이용해서 리턴해주면 된다.
해당 타입에 속하는 랜덤한 객체를 만들어주는 메서드인데, 여기서 상수로만 정의되어 있기 때문에 무조건 동일한 값이 추론된다.
즉, 인터페이스를 값처럼 다룰 수 있다.
'프로그래밍 > NestJS' 카테고리의 다른 글
Clean Architecture (NestJS) (1) | 2023.06.25 |
---|---|
NestJS에서의 Redis 적용 (0) | 2023.05.09 |
모든 타입이 추론되는 API 만들기 ( feat. Nestia ) (10) | 2023.03.26 |
테스트 코드로 커뮤니케이션하기 (0) | 2023.03.12 |
Nestia를 활용한 e2e 테스트 (2) | 2023.03.01 |