당신이 현재 JavaScript를 사용하고 있는 프론트엔드 프로젝트를 개발하고 있다면, 아니면 TypeScript를 사용하고 있지만, API 응답을 위한 타입 대신 any
를 사용하고 있다면, 이 글을 통해 프로젝트의 장기적인 관점에서 TypeScript를 왜 제대로 사용해야 하는지를 설명드릴 수 있습니다.
JavaScript를 사용해 서비스를 개발하는 것은 가장 간단한 방법입니다. API 문서나 Swagger에 기재되어있는 API 응답값을 기반으로 프론트엔드를 구현하면 됩니다. 프로젝트 규모가 작을 때는 이 방법을 통해 빠르게 개발할 수 있어 유용합니다. 하지만 프로젝트 규모가 커지면서 여러 문제가 발생하게 됩니다.
특히, 한 API 의 응답을 프로젝트의 여러 곳에서 사용해야 하는 경우, API의 변경이 발생했을 때 사용처를 IDE 의 도움 없이 직접 찾아야 합니다.
API 를 호출하는 곳과 데이터를 사용하는 곳 사이의 거리가 멀어질수록 이런 관리가 더 어려워집니다.
또한 API 응답이 어떤 값이 빈 값을 어떻게 표현하는지에 따른 처리도 일일이 확인해야 합니다.
이런 문제들을 해결하기 위해 API 문서나 Swagger를 보고 TypeScript의 interface나 type alias 와 같은 타입을 만들어 사용하면, 처음에 타입스크립트를 위한 프로젝트 설정에 시간이 더 걸리고, API 가 추가될때마다 개발이 더 발생하지만, 프로젝트 규모가 커짐에 따라 장점이 분명해집니다.
API 응답이 변경되어 타입의 필드가 변경되어야 하는 경우, 이를 통해 사용처를 역으로 찾을 수 있습니다. 그렇기 때문에, API 호출하는 곳과 데이터를 사용하는 곳 사이의 거리가 멀어져도 관리 비용을 줄일 수 있습니다.
서버에서 보내주는 타입과 interface의 타입이 일치하므로, 두 타입 간에 차이가 없다면, 타입 에러가 나지 않게 됩니다. (TypeScript의 strict 모드를 사용해야만 옵셔널 필드에 대한 빌드 시점 검증이 가능합니다)
또한 코드 리팩토링에도 적용해둔 타입으로 코드에 대한 검증이 가능하기 때문에, 개발자의 부담이 감소할 수 있습니다.
프론트엔드의 예상과 다르게 API 응답이 오는 것은, 프론트엔드 입장에서 잠재적인 위험을 내포하고 있습니다. 이 경우에 몇 가지 해결책이 존재합니다.
에러 모니터링 도구인 Sentry와 같은 솔루션을 사용하면, 서버로부터 예상치 못한 응답이 왔을 때 발생하는 프론트엔드의 오류를 실시간으로 감지하고 보고받을 수 있습니다.
이는 팀이 실시간으로 문제를 인지하고, 빠르게 대응할 수 있는 기회를 제공하므로 프로덕션 환경에서 발생한 문제를 효과적으로 관리할 수 있습니다.
그러나 이 방법에는 주의할 점이 있습니다. 모든 응답의 변경이 항상 에러를 발생시키지는 않습니다. 예를 들어, 서버에서 새로운 필드를 추가하여 보내는 경우, 기존의 클라이언트 코드는 이를 무시하고 정상적으로 동작할 수 있습니다. 그러나 이런 경우 클라이언트 코드는 예상치 못한 동작을 할 수 있으며, 이러한 상황은 코드가 복잡해지고, 이후에 코드를 리팩토링하거나 기능을 추가할 때 의도치 않은 사이드 이펙트를 발생시킬 수 있습니다.
따라서 에러 모니터링 도구를 사용하는 것은 중요하지만, 이것만으로는 완전히 안전한 코드를 보장하기 어렵습니다. 그렇기 때문에 이 경우에도 타입검증과 같은 방법을 병행해야 합니다.
TypeScript는 컴파일에서만 타입을 검증합니다. 이는 IDE 나 CI 에서 타입 오류가 있는지 확인해주지만, 실제로 코드가 실행되는 런타임에서는 타입에 대한 검증이 이루어지지 않습니다.
즉, 런타임에서 받아온 데이터가 우리가 정의한 타입과 일치하는지를 자동으로 검증해주지 않는다는 것입니다. 그래서 실제로 데이터를 받아와서 사용할 때, 예상하지 못한 타입의 데이터가 올 경우 에러가 발생할 수 있습니다.
이러한 문제를 해결하기 위해, 런타임에서도 타입 검증을 수행해주는 로직을 추가할 수 있습니다. 예를 들어, ts-interface-checker 와 같은 라이브러리를 사용하면, 런타임에서도 타입을 검증하고 예상하지 못한 타입의 데이터가 올 경우 이를 즉각적으로 감지할 수 있습니다.
하지만, 이 방법에는 몇 가지 주의해야 할 점이 있습니다.
첫째, 이 로직은 사용자의 브라우저에서 실행되므로, 애플리케이션의 성능에 영향을 줄 수 있습니다. 타입 검증 로직이 복잡하거나 데이터의 크기가 큰 경우, 이 로직이 브라우저의 성능을 저하시킬 수 있습니다.
둘째, 이 방법은 런타임에서의 타입 검증을 위해 별도의 로직을 추가로 작성하고 유지해야 하는 추가적인 작업이 필요합니다.
데이터 검증을 위한 역할을 수행하는 BFF(Backend-for-frontend) 서버를 활용하여 런타임에 응답의 타입을 검증할 수 있습니다. 이 방법은 프론트엔드와 백엔드 간의 통신에서 발생하는 데이터 불일치 문제를 줄이는 데 도움이 됩니다.
즉, BFF 서버는 클라이언트와 서버 사이에 위치하여, 서버에서 오는 데이터를 클라이언트가 예상한 형태와 타입으로 변환하거나 검증하는 역할을 합니다. 이를 통해 서버의 응답이 예상한 타입과 일치하지 않을 경우 이를 런타임에 즉각적으로 감지하고 처리할 수 있습니다.
그러나 이 방법에는 한 가지 주의해야 할 점이 있습니다. BFF 서버를 통해 타입 검증을 수행하면, 타입과 더불어 실제 타입검증 로직을 관리해야 합니다. 이는 관리 포인트가 늘어나 유지보수 비용을 증가시킬 수 있습니다.
요즘 주목받고 있는 GraphQL은 RESTful API의 타입 안정성 문제를 해결할 수 있는 매커니즘이며, 응답 데이터에 대해 더 강력한 타입 검증을 제공합니다. GraphQL은 스키마를 기반으로 동작하므로, 이 스키마를 통해 API 응답의 구조와 타입이 미리 정해져 있습니다. 이렇게 되면 클라이언트에서 응답을 받을 때 해당 응답이 스키마에 정의된 타입과 일치하는지 검증이 가능하게 됩니다.
더불어 GraphQL 문서로부터 TypeScript의 interface나 type alias를 자동 생성할 수 있습니다. 이렇게 하면 API의 응답 타입을 프론트엔드에서도 안정적으로 다룰 수 있게 됩니다.
또한, ApolloServer 같은 라이브러리를 도입하면, GraphQL 스키마에 정의된 대로 응답이 왔는지에 대한 검증을 자동으로 수행해줍니다. 만약 스키마에 정의되지 않은 응답이 오게 되면, 서버는 에러를 발생시켜 프론트엔드에서 완벽하지 않은 응답을 사용하게 되는 것을 막을 수 있습니다.
이렇게 GraphQL을 통해 응답 데이터의 타입 안정성을 확보함으로써 프론트엔드에서의 타입 에러를 최소화하고, 개발 효율성을 높일 수 있습니다.
TypeScript 의 도입으로 API 응답에 대한 안정성을 확보할 수 있습니다. 하지만 프론트엔드에서의 타입 검증만으로 한계가 있기 때문에 에러 모니터링 도구를 사용하거나, 서버 사이드에서의 타입 검증을 병행하는 등 다양한 방법을 통해 안정성을 확보해야 합니다.
하지만 이는 무조건적인 것이 아니라 프로젝트의 상황과 요구에 따라 적절히 선택해야 합니다. 프로젝트의 규모, API의 복잡도, 팀의 기술 수준 등 여러 요인을 고려하여 필요한 수준의 타입 안정성을 제공하는 방법을 선택해야 합니다.