codegen - 아직도 직접짜 ? 기준을 만들어 줄게

Doodream·2023년 2월 4일
9

TypeScript

목록 보기
7/7
post-thumbnail

타입, 어떤게 기준이야?

Typescript를 사용하다 보면 내가 짠 코드에 대한 최소한의 정적 타입을 만들어주는 이점을 누릴 수 있습니다. 하지만 더불어 타입을 코드로 직접 짜야 하죠. 그럼 아주 빈번하게 아래와 같은 상황이 발생합니다.

util/product.ts

type ProductType { 
	mileage: { 
    	purchase: number,
        confirm: number,
         review: number,
    }
    name: string,
    amount: number,
    type: 'row' | 'column'  
}
    
const getProductData = (data: ...):Product => {
	...
	return productData
}  

product-card-badge.ts

type ProductCardBadgeType {
	name: string
	amount: number
	type: 'row' | 'column'
}

const ProductCardBadge = ({...}: ProductCardBadgeType):Jsx.Element => {                   
	...
}

위와 같은 상황에서 ProductCardBadgeType은 각 속성별로 새로 만들어질 필요가 없습니다.
ProductCardBadgeType은 ProductType의 속성들을 부분적으로 가져와 타입을 정의해야합니다. 하지만 개발자들은 이미 ProductType이 있는지 잘 모르고 기준이되는 타입도 없기에 때에 따라 적당히 타입을 만들어 버립니다. 즉, 타입정의가 기준이 없어 적당한 타입으로 정의가 되어버리면 그것은 적당한 정적타입을 중복해서 만들게 되며 해당 속성의 정체가 정말 무엇인지에 대해 결국 가장 처음 데이터를 받았던 쿼리까지 타고 올라가 정체를 까발려야 합니다.

Response 타입까지도 적당한 타입으로 정의되었다면,,, 백엔드 스키마에 정의된 데이터 타입을 보게되겠죠.

기준은 백엔드

자. 기준은 결국 백엔드 스키마에 적용된 데이터 타입입니다. 가장 밑단의 컴포넌트까지 내려가는 props 데이터가 response로 부터 오게된다면 결국 타입스크립트의 타입추론은 백엔드 스키마로부터 와야한다는 이야기가 됩니다. 하지만 사람이 짜는 코드인데다가 협력하는 개발자로서 타입 하나하나 끝까지 추적해서 타입을 정의하기란 매우 귀찮고 리소스가 많이 드는 일 입니다.

추가로 네이밍또한 사람이 짜게되느라 누군가 알아보기 어려운 네이밍으로 타입을 정의한다면 그 또한 코드적인 낭비로 이어지거나 가독성이 어려워집니다. 리뷰할 때도 까다롭고 귀찮게 되겠죠.

그래서 이러한 타입정의의 기준이 필요하며 더 나아가 타입을 스키마로부터 자동 생성해주는 자동화 시스템이 필요합니다.

네이밍 고민도 덜 수 있지

훅도 선택사항입니다만 훅 네이밍을 짓는 고민이 필요하기에 제네레이트된 data fetch 훅을 사용한다면 따로 data fetch 훅을 만들때 네이밍 고민을 할 필요는 없을 것 입니다.

codegen 의 필요성이 바로 그렇습니다.
이는 rest api 형식을 사용하든, graphql 방식을 사용하든 마찬가지 입니다.

Rest api codegen - Orval

먼저 rest api를 사용할 때 codegen은 기준점이 swagger 문서입니다. 백엔드 개발자가 정의해놓은 swagger 문서를 기준으로 타입, 훅을 생성합니다.

https://orval.dev/

src/orval.config.js


module.exports = {
  fitpet: {
    input: {
      target: 'https://.../swagger/?format=openapi',
    },
    output: {
      mode: 'split',
      target: './src/api/generated/hooks.ts',
      schemas: './src/api/generated/types',
      client: 'react-query',
      override: {
        mutator: {
          path: './src/plugins/custom-instance.ts',
          name: 'customInstance',
        },
        useDates: false,
        query: {
          useQuery: true,
          useInfinite: true,
        },
      },
    },
  },
}

src/api/generated/types/category.ts

export interface Category {
  readonly id?: number
  name: string
  readonly children?: SubCategory[]
}

src/api/generated/hooks.ts

export const useApiCategoriesListInfinite = <
  TData = Awaited<ReturnType<typeof apiCategoriesList>>,
  TError = ErrorType<unknown>
>(
  params: ApiCategoriesListParams,
  options?: {
    query?: UseInfiniteQueryOptions<Awaited<ReturnType<typeof apiCategoriesList>>, TError, TData>
    request?: SecondParameter<typeof customInstance>
  }
): UseInfiniteQueryResult<TData, TError> & { queryKey: QueryKey } => {
	...
  return query
}

이렇게 생성된 타입, 훅들은 swagger api 문서 기준으로 작성되어 해당 타입을 기준으로 필요한 타입을 생성하면 타입 스크립트의 타입추론 기능으로 밑단까지 타입 자동완성이 될 수 있습니다.

제너레이트된 훅을 사용하면 쉽게 react-query 의 정말 필요한 설정만을 건들어 사용할 수 있습니다. 그외에 코드를 짤 필요가 없죠.
네이밍, 타입, 훅까지 한번에 codegen을 하게되어 리소스를 크게 아낄 수 있습니다.

단점.

  • 단점은 하나 백엔드 기준의 네이밍이라 훅의 네이밍이 이상할 수 있고 길어질 수 있습니다. 하지만 이는 얻는 리소스 감축에 비하면 사소한 수준입니다.
  • 백엔드 개발자가 api문서를 잘못 작성했다면? 제너레이트 하면 안됩니다. 이는 백엔드 개발자에게 맡깁시다 ...

Graphql 에서의 codegen

사실 codegen은 graphql에서 훨씬 더 강력한 성능을 발휘합니다. 기준이 graphql schema 입니다.
백엔드에서 이 스키마 설계를 할때 드는 리소스가 swagger api 문서를 작성할 때보다 훨씬 덜 하며 실수 할 확률이 적습니다.

apollo client 기준으로 이야기 하자면 사용자가 fragment, query, mutation을 작성하면 알아서
fragment는 타입을 만들고, query는 useLazyQueryHook, useQueryHook mutation 또한 같은 방식의 fetch 훅을 생성합니다.

웬만하면 데이터를 뿌려주는 작업에서 타입을 사람 손으로 만들일이 없습니다.

백엔드 에서 정의한 특별한 타입, 예를 들자면 python의 Decimal 타입은 graphql에서는 스칼라 타입으로 받게됩니다. 이를 codegen 하면서 Decimal타입을 number로 정의하게 gen 규칙을 커스텀 할 수 있습니다.

graphql/querys/refund.ts

export const canRefund = gql`
  query canRefund {
    refund {
      canRefund
    }
  }
`
// 위 쿼리를 짜게되면 타입, 훅이 generate 됩니다. 

graphql/generated/operations.ts

export type CanRefundQuery = {
  __typename?: 'Query'
  refund?: { __typename?: 'RefundType'; canRefund?: boolean }
}

graphql/generated/hooks.ts

export function useCanRefundQuery(
  baseOptions?: Apollo.QueryHookOptions<Types.RefundQuery, Types.CanRefundQueryVariables>
) {
  const options = { ...defaultOptions, ...baseOptions }
  return Apollo.useQuery<Types.CanRefundQuery, Types.CanRefundQueryVariables>(
	CanRefundDocument,
    options
  )
}

codegen.yml 파일을 수정함으로서 gen 규칙을 수정할 수 있습니다.
https://the-guild.dev/graphql/codegen/docs/config-reference/codegen-config

효과는 매우 대단했다.

  • 더이상 타입을 헤메이거나 네이밍 고민을 하지 않았습니다.
  • 훅 또한 따로 생성하거나 정의하는 리소스를 덜었으며 response 타입 정의가 잘되어 타입추론이 매우 잘되었습니다.
  • 타입을 손으로 만들때에도 기준점이 되는 타입이 있어 허술한 타입생성이 되지 않았습니다.

단점은 ?

  • 백엔드가 선 개발되어야 합니다. 하지만 이점은 front 입장에서 퍼블리싱을 하며 mock data를 만들어 백엔드 api가 나올 때까지 기다리는 형태로 리소스 관리가 되어 괜찮았고, 더구나 원래 백엔드 선 배포 후 프론트 반영 순인 개발 방식이었기 떄문에 큰 문제는 없었습니다.

  • codegen 되어 사용되지 않는 코드들이 제너레이트 되며 번들링 파일크기를 늘립니다. 이점 또한 사람이 중복으로 만들어내는 타입과 훅들을 덜어낸다면 오히려 이득입니다.

여러분 더이상 삽질 하지말고 codegen 하시길 바랍니다.

profile
일상을 기록하는 삶을 사는 개발자 ✒️ #front_end 💻

1개의 댓글

comment-user-thumbnail
2023년 2월 6일

두드림님 항상 열심히 하는 모습이 보기 좋으네요.

답글 달기