types
와 interfaces
는 모두 데이터 구조를 정의하기 위한 역할을 하지만, 사용 목적과 설계 철학에서 차이가 있습니다.
NestJS와 같은 대규모 애플리케이션에서는 두 개의 패키지를 구분하여 설계하면 유지보수성과 가독성이 크게 향상됩니다.
1. types
패키지의 역할
- 주요 목적: 범용적이고 가벼운 데이터 구조를 정의합니다.
- 특징:
types
는 단순 데이터 정의를 목적으로 하며, 애플리케이션 전반에 걸쳐 재사용됩니다.- 주로 간단한 데이터 타입, API 응답/요청의 공통 타입, 범용적 유틸리티 타입을 정의합니다.
- 특정 비즈니스 로직이나 클래스에 강하게 의존하지 않습니다.
- 각종 데이터의 범위와 데이터 흐름을 일관성 있게 유지할 수 있습니다.
- 예시:
- OAuth 제공자의 토큰 응답 타입
- 사용자 프로필 데이터 구조
- 특정 라이브러리나 제공자의 데이터 모델 정의 (e.g., Kakao, Google)
2. interfaces
패키지의 역할
- 주요 목적: 구체적인 애플리케이션 계약(contract) 및 역할 정의.
- 특징:
- 특정 모듈이나 클래스에서 사용할 세부적인 데이터 계약을 정의합니다.
- 서비스와 상호작용하거나, 요청과 응답을 처리하기 위해 필요한 인터페이스를 포함합니다.
- 인터페이스는 일반적으로 **NestJS의 의존성 주입(DI)**과 결합하여 사용됩니다.
- 특정 클래스, 서비스, 모듈 등과 강하게 결합된 구조를 정의합니다.
- 예시:
- JWT 페이로드 구조
- OAuth 사용자 프로필 변환 인터페이스
- 특정 제공자용 추가 인터페이스
구분하여 설계하는 이유
1. 책임 분리(Single Responsibility Principle)
types
는 범용 데이터 구조를 제공하며, 여러 모듈이 재사용할 수 있도록 설계됩니다.interfaces
는 특정 모듈이나 클래스에 특화된 계약을 정의하며, 모듈의 명확한 책임을 분리합니다.
2. 유지보수성 향상
- 변경의 영향 범위를 줄일 수 있습니다:
types
패키지는 제공자의 API 응답이 변경될 때 조정됩니다.interfaces
패키지는 모듈 간의 데이터 전달 및 협업이 변경될 때 조정됩니다.
- 타입이 변경되더라도 모듈에 미치는 영향을 최소화하여 유지보수성을 높입니다.
3. 유연성 확보
types
는 애플리케이션 전반에 걸쳐 재사용되기 때문에, 모듈에 구애받지 않고 쉽게 확장할 수 있습니다.interfaces
는 특정 클래스와 결합된 구조를 정의하므로, 비즈니스 로직에 맞게 유연하게 조정 가능합니다.
4. 가독성 향상
- 데이터를 설계하는 의도를 명확히 전달할 수 있습니다.
types
: "전역적이고 재사용 가능한 데이터 구조를 정의한다."interfaces
: "모듈/클래스 간의 협력 및 계약 관계를 정의한다."
적합한 설계 예시
types
패키지
// src/auth/types/oauth-token-response.type.ts
export interface OAuthTokenResponse {
access_token: string;
token_type: string;
refresh_token?: string;
expires_in: number;
scope?: string;
id_token?: string; // OpenID Connect 사용 시
}
// src/auth/types/oauth-user-profile.type.ts
export interface OAuthUserProfile {
id: string;
email?: string;
name?: string | { firstName?: string; lastName?: string };
picture?: string;
provider: string;
raw?: any;
}
interfaces
패키지
// src/auth/interfaces/jwt-payload.interface.ts
export interface JwtPayload {
sub: string; // 사용자 ID
email?: string; // 이메일 주소
provider: string; // OAuth 제공자
type: 'access' | 'refresh'; // 토큰 유형
}
// src/auth/interfaces/oauth-profile-adapter.interface.ts
export interface OAuthProfileAdapter {
fromGoogle(rawProfile: any): OAuthUserProfile;
fromKakao(rawProfile: any): OAuthUserProfile;
fromApple(rawProfile: any): OAuthUserProfile;
}
types
와 interfaces
를 구분하는 것은 대규모 애플리케이션에서 필수적인 설계 방식입니다.
- **
types
**는 가벼운 범용 데이터 구조를 정의하여 여러 모듈에서 재사용합니다. - **
interfaces
**는 모듈 간의 계약을 정의하여 명확한 역할 분리를 가능하게 합니다.