프로젝트 내 사용되지 않고 있는 파일들(번들에 포함되지 않는 파일들)을 검출하여

  1. 하위 컴포넌트의 GraphQL Fragment가 상위 컴포넌트의 Query 혹은 Fragment에 연결되지 않은 레거시 코드를 정리하고,
  2. 공통 라이브러리의 인터페이스 변경 작업과 같은 리팩토링 작업에서 사용되지 않는 파일들을 리팩토링 하지 않아도 될 수 있도록 삭제하여,

레거시 코드를 정리한 경험을 공유합니다.

npm: esbuild-plugin-unused-modules

GraphQL Data Masking


Data Masking은 GraphQL 언어를 개발한 facebook에서 GraphqlQL의 철학을 담은 데이터 관리 라이브러리인 relay에서 thinking-in-relay 에 언급하고 있는 개념으로, 적용 시 컴포넌트의 데이터 의존성을 캡슐화하여 컴포넌트 내부에서는 자신이 사용하고 있는 데이터에만 관심사를 가지고 부모 컴포넌트와 자식 컴포넌트에는 어떤 데이터를 사용하고 있는지 숨김(캡슐화)으로써 다른 컴포넌트와의 데이터 의존성을 느슨하게 가져갈 수 있게됩니다.

Apollo vs Relay


GraphQL 진영에서 클라이언트의 데이터 관리를 위한 라이브러리로 크게 Apollo와 Relay가 존재합니다. 많은 특징과 차이점이 존재하겠지만, 이 중에서도 가장 큰 차이점이라고 볼 수 있는 점은 바로 Data Masking을 강제하느냐 아니냐 입니다. Apollo도 GraphQL 라이브러리이기 때문에 충분히 Data Masking 전략을 취하여 컴포넌트 간 데이터 의존성을 느슨하게 가져갈 수 있습니다.

그러나, Apollo는 Relay와 다르게 Data Masking 을 반드시 지켜야하도록 강제하지 않습니다. 아래 예시는 relay로 작성된 컴포넌트입니다. 각 컴포넌트는 컴포넌트와 연결된 fragment 및 query에서 정의한 데이터만 사용이 가능하도록 캡슐화하여 컴포넌트 간 데이터 의존성을 낮춰줍니다. relay에서는 CarItem 컴포넌트에서 fragment로 정의하지 않은 데이터인 wheel 필드를 참조하려고 하면 에러를 터트립니다.

const CAR = graphql`
	query car($id: Int!) {
		car(id: $id) {
			id
			color
			wheel {
				id
				name
			}
			...CarItem_car
		}
	}
`;

const Parent = ({ carId }) => {
	const data = useQuery(CAR, {
		variables: { id }
	});
	return (
		...
		<CarItem .../>
	)
}
const carFragment = graphql`
	fragment CarItem_car on Car {
		id
		name
		size
	}
`;

const CarItem = ({
	car: propCar
}) => {
	const car = useFragment(carFragment, propCar);
	car.wheel // 에러 발생. fragment에 정의한 데이터만 참조할 수 있음
}

위와 비교하기 위해 아래는 Apollo로 작성된 컴포넌트입니다. relay 코드와 별반 다를게 없어보이지만, relay와 다르게 이번에는 CarItem 컴포넌트에서 fragment로 정의하지 않은 데이터인 wheel 필드를 참조하려고 할 때 아무 에러도 터트리지 않습니다. 이는 Apollo에서는 Data Masking을 강제하지 않는 것으로 판단할 수 있습니다.

const CAR = gql`
	query car($id: Int!) {
		car(id: $id) {
			id
			color
			wheel {
				id
				name
			}
			...CarItem_car
		}
	}
`;

const Parent = ({ carId }) => {
	const data = useQuery(CAR, {
		variables: { id }
	});
	return (
		...
		<CarItem .../>
	)
}
const carFragment = gql`
	fragment CarItem_car on Car {
		id
		name
		size
	}
`;

const CarItem = ({
	car: propCar
}) => {
	car.wheel // 참조 가능. 부모 컴포넌트에서 호출한 query에 데이터가 포함되어 있기 때문.
}