타입스크립트의 브랜디드 타입

TypeScript로 엔티티를 모델링할 때, 이와 같은 인터페이스를 얻는 것은 매우 일반적입니다.

TypeScript

 

문제점

속성의 타입은 의미론적 의미가 없습니다. 타입 측면에서 User.id, Order.id, Order.year 등은 모두 동일합니다: 숫자이며, 숫자로서 상호 교환 가능하지만, 의미상으로는 그렇지 않습니다.

앞의 예를 따라, 엔티티에 대해 작업을 수행하는 함수 집합을 가질 수 있습니다. 예를 들어:

TypeScript

 

이 함수들은 숫자의 의미론적 의미에 관계없이 어떤 인수로든 숫자를 받아들입니다. 예를 들어:

TypeScript

 

당연히, 이는 큰 실수이며, 코드를 읽지 않고 피하기 쉬워 보일 수 있지만, 코드가 항상 예제처럼 단순하지는 않습니다.

같은 일이 getOrdersFiltered에도 발생합니다: 우리는 일과 월의 값을 바꿀 수 있으며, 경고나 오류를 받지 않습니다. 오류는 일이 12보다 클 때 발생하지만, 결과가 기대한 대로 되지 않을 것은 분명합니다.

해결책

객체 칼리스테닉스의 규칙은 이를 위한 해결책을 제공합니다: 모든 원시 데이터와 문자열을 래핑합니다 (관련 원시 집착 반패턴). 규칙은 원시 데이터를 의미론적 의미를 나타내는 객체로 래핑하는 것입니다 (DDD에서는 이를 ValueObjects로 설명합니다).

하지만 TypeScript에서는 이를 위해 클래스나 객체를 사용할 필요가 없습니다: 우리는 타입 시스템을 사용하여 연도를 나타내는 것과 다른 숫자가 연도를 대신 사용할 수 없도록 보장할 수 있습니다.

브랜디드 타입

이 패턴은 의미론적 의미를 보장하는 속성을 추가하기 위해 타입의 확장성을 사용합니다:

TypeScript

 

이 간단한 라인은 숫자로 작동할 수 있는 새로운 유형을 만들지만 숫자가 아니라 연도입니다.

TypeScript

 

해결책 일반화

브랜드 유형마다 유형을 작성하는 것을 피하기 위해 다음과 같이 유틸리티 유형을 만들 수 있습니다:

TypeScript

 

브랜드 속성 이름으로 고유한 심볼을 사용하여 속성과 충돌을 피하고 원래 유형과 브랜드를 일반 매개변수로 사용하는 유틸리티 유형입니다.

이를 통해 모델과 함수를 다음과 같이 리팩터링할 수 있습니다:

TypeScript

 

이제 이 예에서 IDE는 idUserId이고 deleteOrderOrderId를 예상하는 것으로 오류가 표시됩니다.

TypeScript

 

Trade-Offs

작은 희생으로 BrandX를 사용해야 합니다. 예를 들어, 원시값에서 새 값 생성 시 const year = 2012 as Year를 사용해야 하지만 값 객체를 사용하는 경우 new Year(2012)와 동등합니다. “생성자” 역할을 하는 함수를 제공할 수 있습니다:

TypeScript

 

브랜드 유형으로 유효성 검사

브랜드 유형은 데이터가 유효한지 확인하는 데 유용하며 유효성이 검증된 데이터에 대한 특정 유형을 가질 수 있기 때문에 사용자가 유효성이 검증되었음을 신뢰할 수 있습니다:

TypeScript

 

Readonly는 필수는 아니지만 데이터를 유효성 검사한 후에 데이터가 변경되지 않도록 보장하려면 매우 권장됩니다.

요약

브랜드 유형은 다음을 포함하는 간단한 해결책입니다:

  • 코드 가독성 향상: 각 인수에 어떤 값이 사용되어야 하는지 명확해집니다.
  • 신뢰성: 코드에서 감지하기 어려운 실수를 피하는 데 도움이 됩니다; 이제 IDE(및 타입 검사)가 값이 올바른 위치에 있는지 감지하는 데 도움을 줍니다.
  • 데이터 검증: 브랜드 타입을 사용하여 데이터가 유효한지 확인할 수 있습니다.

브랜드 타입은 ValueObjects의 일종의 버전으로 생각할 수 있지만, 클래스를 사용하지 않고 단지 타입과 함수로만 구성됩니다.

타입의 힘을 즐기세요!

Source:
https://dzone.com/articles/branded-types-in-typescript