Develope/기타

TypeScript 브랜딩(Branding) 개념과 활용법

oper0116 2025. 2. 11. 23:52
반응형

TypeScript 브랜딩(Branding) 개념과 활용법

TypeScript를 사용하다 보면, 동일한 기본 타입을 공유하지만 서로 다른 의미를 갖는 값을 구분하고 싶을 때가 있다. 예를 들어, 숫자로 표현되는 UserIdOrderId를 혼동해서 사용하는 실수를 방지하고 싶다면 어떻게 해야 할까?

이런 경우에 브랜딩(Branding) 기법을 활용하면 타입을 더욱 안전하게 관리할 수 있다.


1. TypeScript 브랜딩이란?

브랜딩(Branding)은 TypeScript의 타입 시스템을 활용하여 기본 타입(primitive type)에 의미를 부여하는 기법이다. 이를 통해 동일한 기본 타입을 사용하지만, 의도치 않은 값의 혼용을 방지할 수 있다.

예를 들어, 아래와 같은 코드에서 number 타입을 직접 사용하면 UserIdOrderId가 같은 타입이므로 서로 바꿔서 사용해도 컴파일러가 오류를 감지하지 못한다.

type UserId = number;
type OrderId = number;

function getUser(id: UserId) { /* 사용자 정보를 가져오는 함수 */ }

const orderId: OrderId = 123;
getUser(orderId); //  논리적으로 잘못된 호출이지만, TypeScript는 문제를 발견하지 못함

이 문제를 해결하기 위해 브랜딩 기법을 활용할 수 있다.


2. 브랜딩 적용 방법

2.1 인터섹션 타입을 활용한 브랜딩

가장 많이 사용되는 방법은 인터섹션 타입(&)과 유니크한 속성을 추가하는 방식이다.

type Brand<T, B> = T & { __brand: B };

type UserId = Brand<number, "UserId">;
type OrderId = Brand<number, "OrderId">;

function getUser(id: UserId) { /* 사용자 정보를 가져오는 함수 */ }

const userId = 123 satisfies UserId;
const orderId = 456 satisfies OrderId;

getUser(userId); // 정상 작동
getUser(orderId); // 오류 발생: OrderId는 UserId가 아님
  • Brand<T, B>T 타입에 __brand라는 고유한 속성을 추가하여 구별한다.
  • OrderIdUserId로 전달하면 컴파일러가 오류를 발생시킨다.

2.2 Nominal Typing을 활용한 브랜딩

위 방법과 유사하지만, 인터페이스를 활용하는 방식도 가능하다.

interface UserId {
  __brand: "UserId";
}

interface OrderId {
  __brand: "OrderId";
}

function getUser(id: UserId) { /* 사용자 정보를 가져오는 함수 */ }

const userId = { __brand: "UserId" } satisfies UserId;
const orderId = { __brand: "OrderId" } satisfies OrderId;

getUser(userId); // 정상 작동
getUser(orderId); // 오류 발생

이 방법도 서로 다른 타입을 구별하는 데 유용하지만, 기본 타입(예: number)을 직접 사용할 수 없다는 단점이 발생한다.

const id: UserId = 123; // 오류 발생: 'number' 타입은 'UserId'에 할당할 수 없음

위 코드에서 UserId는 인터페이스 기반의 타입이므로 number 값을 직접 할당할 수 없습니다. 대신 객체 형태로 생성해야 하므로 사용성이 떨어질 수 있습니다.


3. 브랜딩이 유용한 경우

브랜딩 기법은 아래와 같은 상황에서 특히 유용하다.

ID, 키 값 구별

데이터베이스에서 불러온 ID 값들을 명확하게 구별할 수 있다.

type ProductId = Brand<number, "ProductId">;
type CategoryId = Brand<number, "CategoryId">;

function getProduct(id: ProductId) { /* 상품 정보를 가져오는 함수 */ }

단위가 다른 숫자 구별

단위를 가진 숫자 값도 헷갈리지 않도록 구별할 수 있다.

type Kilograms = Brand<number, "Kilograms">;
type Pounds = Brand<number, "Pounds">;

const weightKg = 70 satisfies Kilograms;
const weightLb = 154 satisfies Pounds;

보안과 안전한 API 설계

REST API 호출 시 토큰을 명확하게 구분하여 보안성을 높일 수 있다.

type AccessToken = Brand<string, "AccessToken">;
type RefreshToken = Brand<string, "RefreshToken">;

function useAccessToken(token: AccessToken) { /* API 요청 */ }

4. 브랜딩의 한계와 주의할 점

  • 런타임에는 __brand 속성이 존재하지 않음
    브랜딩은 타입 시스템에서만 동작하는 개념이며, 실제 실행 시점에서는 __brand 속성이 사라진다.

  • 타입 단언을 남용하면 의미가 퇴색
    satisfies를 활용하면 as 키워드보다 타입 안정성이 증가하지만, 여전히 남용을 피해야 한다.


5. 결론

TypeScript의 브랜딩(Branding) 기법은 동일한 기본 타입을 사용하지만, 의미적으로 다른 값들을 구별하는 데 효과적이다
ID, 단위, 보안 토큰 등의 관리가 필요한 경우, 브랜딩을 적용하면 보다 안전한 코드 작성이 가능하다.

앞으로 TypeScript 프로젝트에서 더 안전한 타입 설계를 원한다면, 브랜딩을 적극 활용해보자.

반응형