앞선 글에서 Higher-Order Type과 Compose Type의 개념과 사용 이유를 살펴봤다면, 이제 자연스럽게 이런 질문이 남습니다. “그래서 이걸 실제 코드 어디에 써야 하는가?”입니다.
실무에서는 고급 제네릭 자체보다도 적용 위치를 잘못 선택해서 생기는 문제를 더 자주 겪습니다.
- 도메인 모델이 제네릭으로 뒤덮여 의미를 잃는다
- 서비스 코드보다 타입 정의가 더 복잡해진다
- 타입 안정성을 얻는 대신 가독성을 잃는다
이번 글에서는 고급 제네릭 패턴을 “어디까지 써야 실무에 도움이 되는지”를 기준으로, 도메인 모델과 서비스 계층에 적용하는 전략을 정리합니다.
개념/배경 설명: 도메인과 타입은 역할이 다르다
도메인 모델의 목적은 명확합니다.
- 비즈니스 개념을 표현한다
- 의미가 코드에 드러나야 한다
- 읽는 순간 무엇을 나타내는지 이해되어야 한다
반면, 고급 제네릭은 규칙과 변환을 표현하는 도구입니다. 이 둘을 같은 층위에서 섞기 시작하면, 도메인은 점점 “타입 실험장”이 됩니다.
실무에서 가장 중요한 원칙은 이것입니다.
- 도메인은 구체적으로
- 제네릭은 추상적으로
도메인 모델에는 제네릭을 최소화한다
먼저 하지 말아야 할 예시부터 보겠습니다.
// 권장하지 않는 예시
type Entity<TId, TMeta> = {
id: TId;
meta: TMeta;
};
이 타입은 재사용 가능해 보이지만, 실제 도메인에서는 의미가 사라집니다.
type User = Entity<string, UserMeta>;
type Order = Entity<number, OrderMeta>;
이 순간부터 코드를 읽는 사람은 User가 무엇인지보다 Entity가 무엇인지부터 추적하게 됩니다.
실무에서는 이렇게 쓰는 편이 낫습니다.
type User = {
id: string;
email: string;
};
type Order = {
id: number;
amount: number;
};
실무 포인트 정리
- 도메인 타입은 구체적일수록 좋다
- 중복보다 의미 훼손이 더 큰 비용이다
- 도메인은 설명서처럼 읽혀야 한다
고급 제네릭은 서비스 경계에서 사용한다
Higher-Order Type과 Compose Type이 가장 빛나는 지점은 도메인 바깥, 즉 서비스 경계입니다.
대표적인 예는 다음과 같습니다.
- API 응답 래핑
- 권한·보안 메타 정보 추가
- 로깅·트래킹 컨텍스트 결합
type WithAudit<T> = T & {
audit: {
requestId: string;
timestamp: number;
};
};
type ServiceResult<T> =
| { ok: true; data: T }
| { ok: false; errorCode: string };
이제 도메인 타입을 직접 건드리지 않고, 필요한 규칙만 바깥에서 조합할 수 있습니다.
type UserServiceResult = ServiceResult<WithAudit<User>>;
실무 포인트 정리
- 도메인에는 손대지 않는다
- 서비스 경계에서만 타입을 감싼다
- 규칙은 바깥에서 덧붙인다
Compose Type은 “레이어 순서”를 드러내야 한다
Compose Type은 단순히 중첩하는 것이 아니라, 의미 있는 순서를 표현해야 합니다.
type SecureResponse<T> =
ApiResponse<Readonly<WithAudit<T>>>;
이 타입이 전달하는 메시지는 명확합니다.
- 도메인 데이터가 있다
- 감사 정보가 추가된다
- 읽기 전용으로 보호된다
- API 응답으로 감싸진다
반대로 순서가 뒤섞이면, 타입은 곧 이해 불가능한 퍼즐이 됩니다.
코드 예제: 서비스 계층에서의 실제 사용
다음은 서비스 함수 시그니처에서의 활용 예시입니다.
async function getUserProfile(
userId: string,
): Promise<ServiceResult<WithAudit<User>>> {
// ...
}
이 시그니처만 보고도 다음을 알 수 있습니다.
- 실패 가능성이 있다
- 도메인 User를 반환한다
- 운영을 위한 메타 정보가 포함된다
이는 문서가 아니라 타입 자체가 계약이 되는 구조입니다.
운영/실무에서 자주 겪는 문제
- 도메인 타입이 제네릭 때문에 읽히지 않는 경우
- 서비스마다 다른 Compose 순서를 사용하는 경우
- 타입 별칭이 너무 많아 추적이 어려운 구조
- 제네릭 변경이 광범위한 타입 오류로 이어지는 경우
이 문제의 원인은 대부분 하나입니다. “제네릭을 어디에 써야 하는지 기준이 없었다”는 점입니다.
실무 권장 체크리스트
- 도메인 타입이 제네릭 없이도 의미가 명확한가
- 고급 제네릭은 서비스 경계에만 존재하는가
- Compose Type의 순서가 일관적인가
- 타입 이름만 보고 의도를 추론할 수 있는가
- 팀원이 타입 정의를 따라갈 수 있는 구조인가
고급 제네릭 패턴은 실력을 드러내는 도구가 아닙니다.
도메인은 단순하게 유지하고, 복잡성은 경계에서 흡수하는 것. 이 원칙을 지킬 때, Higher-Order Type과 Compose Type은 코드를 복잡하게 만드는 요소가 아니라 구조를 안정시키는 도구가 될것입니다.
'개발 > Typescript' 카테고리의 다른 글
| [TYPESCRIPT] 타입 활용 전략: 장애 대응과 디버깅을 빠르게 만드는 TypeScript 설계 기준 (0) | 2026.01.25 |
|---|---|
| [TYPESCRIPT] 타입 규칙 운영 전략: 고급 제네릭을 팀 단위로 유지·공유하는 방법 (0) | 2026.01.24 |
| [TYPESCRIPT] 고급 제네릭 패턴: Higher-Order Type과 Compose Type을 사용하는 이유 (0) | 2026.01.22 |
| [TYPESCRIPT] 백엔드 인터페이스 설계 기준: 프론트엔드와 계약을 안정적으로 유지하는 방법 (0) | 2026.01.20 |
| [TYPESCRIPT] Nextjs 권한 테스트 전략: Authorization 로직을 테스트로 고정 방법 (0) | 2026.01.19 |
