외부 라이브러리를 관리하는 방법

어떻게 하면 효과적으로 외부 라이브러리를 관리할 수 있을 지 생각해보기.

외부 라이브러리를 관리 하는 방법

왜 의존성을 줄여야 할까요?

개발 프로젝트에서 외부 라이브러리를 사용하는 것은 매우 일반적입니다. 그러나 외부 라이브러리에 지나치게 의존하게 되면 문제가 발생할 수 있습니다. 특히 라이브러리가 업데이트되거나, 지원이 중단되거나, 버그가 발생했을 때, 우리의 프로젝트에도 직접적인 영향을 미치게 됩니다. 그렇기 때문에 의존성을 최소화하는 것이 매우 중요합니다.

그렇다면 외부 라이브러리 의존성을 줄이는 방법은 무엇일까요?


의존성 줄이는 방법

1. 추상화 벽(Abstraction Wall)

쏙쏙 들어오는 함수형 코딩 책에서 외부 라이브러리 의존성을 줄이는 한 가지 좋은 방법을 배웠습니다. 바로 추상화 벽(Abstraction Wall)을 활용하는 방법입니다.

추상화 벽이란?

추상화 벽이란 외부 라이브러리의 복잡한 내부 구현을 감추고, 필요한 최소한의 기능만을 제공하는 인터페이스를 만들어 사용하는 것을 말합니다. 외부 라이브러리를 직접 사용하는 대신, Wrapper 함수를 사용하여 필요한 기능만 추출하고, 나머지는 감추는 방식입니다.

이렇게 하면 외부 라이브러리에 대한 의존성을 줄일 수 있을 뿐만 아니라, 향후 라이브러리 변경이 발생하더라도 수정할 범위를 최소화할 수 있습니다.

추상화 벽 사용시, 장점

추상화 벽을 사용하면 여러 가지 장점이 있습니다.

  1. 관리 포인트 감소: 외부 라이브러리의 복잡한 내부 구조에 의존하지 않으므로 관리해야 할 포인트가 줄어듭니다.
  2. 유지보수성 향상: 라이브러리 업데이트 시, 추상화 벽만 수정하면 다른 모든 코드는 수정 없이 동작하게 됩니다. 이를 통해 유지보수가 훨씬 쉬워집니다.
  3. 복잡성 감소: 라이브러리의 복잡한 기능을 감추고, 필요한 기능만 추출함으로써 코드를 단순하게 유지할 수 있습니다.

추상화 벽 사용 예시

아래 코드는 카카오톡 공유 기능을 추상화 벽으로 감싸 간단한 인터페이스만 노출한 예시입니다.

const shareKakao = ({ title, description, imageUrl, url }) => {
  if (typeof window.Kakao !== 'undefined') {
    window.Kakao.Share.sendDefault({
      objectType: 'feed',
      content: {
        title,
        description,
        imageUrl,
        link: {
          webUrl: url,
          mobileWebUrl: url,
        },
      },
    });
  }
};
 
export default shareKakao;

위 코드를 통해 카카오 API가 변경되더라도 이 함수만 수정하면 됩니다. 이를 사용하는 코드들은 변경할 필요가 없으므로, 외부 라이브러리와의 의존성을 최소화하면서도 유지보수성을 높일 수 있습니다.


다른 추상화 벽의 예시

추상화 벽은 외부 라이브러리뿐만 아니라 다양한 개발 도구와 프레임워크에서도 사용됩니다. 이런 도구들은 복잡한 내부 구현을 감추고, 사용자가 간단한 인터페이스를 통해 필요한 작업을 할 수 있도록 도와줍니다. 대표적인 두 가지 예시를 살펴보겠습니다.

React의 Virtual DOM

React는 Virtual DOM을 통해 추상화 벽을 형성하여 실제 DOM을 직접 조작하는 복잡성을 사용자에게 감추고 있습니다. Virtual DOM은 UI를 효율적으로 업데이트하는 방식으로, 개발자는 DOM 조작을 신경 쓰지 않고도 상태 변화에 따라 자동으로 UI가 반영되도록 설계할 수 있습니다. 즉, 실제 DOM을 직접 다루는 대신 Virtual DOM이 중간에서 역할을 하기 때문에, 개발자는 상태 변경에만 집중하면 됩니다.

const [count, setCount] = useState(0);
 
return <button onClick={() => setCount(count + 1)}>{count}</button>;

위 코드에서 useStatesetState는 상태를 관리하면서 UI를 업데이트하지만, 실제로 DOM을 직접 변경하지는 않습니다. React는 상태 변화에 맞춰 Virtual DOM을 비교(diffing)하고, 필요한 부분만 실제 DOM에 반영하는 방식으로 UI를 최적화합니다

Redux의 상태 관리

Redux는 상태 관리 라이브러리로, 애플리케이션 전체의 상태를 중앙에서 일관되게 관리할 수 있도록 도와줍니다. Redux는 **액션(action)**과 **리듀서(reducer)**라는 개념을 통해 상태 변경 로직을 추상화합니다. 개발자는 상태가 변경될 때마다 직접적인 상태 조작을 하지 않고, dispatch를 통해 액션을 전달하면 리듀서가 알아서 상태를 변경해줍니다. 이 과정에서 상태가 어떻게 변경되는지에 대한 복잡한 구현은 추상화되어 감춰집니다.

import { createStore } from 'redux';
 
const initialState = { count: 0 };
 
function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}
 
const store = createStore(counterReducer);
 
store.dispatch({ type: 'INCREMENT' });
console.log(store.getState()); // { count: 1 }

위 코드에서 Redux는 상태 변경을 위한 추상화된 인터페이스만 제공합니다. 상태를 직접 수정하지 않고, dispatch와 리듀서를 통해 상태를 안전하게 관리할 수 있습니다. 이 방식은 복잡한 상태 관리를 추상화하여 애플리케이션 전체에서 일관성을 유지하도록 돕습니다.


2. 의존성 버전 고정 (Dependency Version Locking)

외부 라이브러리를 사용할 때, 프로젝트의 package.json 파일에서 라이브러리 버전을 명시적으로 고정하는 것이 좋습니다. 버전을 고정하면 예상치 못한 라이브러리 업데이트로 인한 문제를 방지할 수 있습니다.

"dependencies": {
  "axios": "0.21.1", // 버전 고정
  "react": "^17.0.2"  // 마이너 업데이트만 허용
}

버전을 고정하면 라이브러리 업데이트로 인해 발생할 수 있는 호환성 문제를 예방할 수 있습니다. 다만, 중요한 보안 업데이트나 기능 추가가 있을 때는 수동으로 업데이트하는 과정이 필요합니다.


3. 테스트 코드로 검증 (Testing with Mocking)

외부 라이브러리를 사용하는 부분은 반드시 테스트 코드로 검증하는 것이 중요합니다. 특히 라이브러리를 사용하는 주요 로직에 대해 테스트를 작성해두면, 라이브러리 업데이트로 인한 문제를 빠르게 발견하고 대응할 수 있습니다.

import axios from 'axios';
jest.mock('axios');
 
test('should fetch data', async () => {
  axios.get.mockResolvedValue({ data: 'mock data' });
  const result = await fetchData(); // 모킹된 axios로 테스트
  expect(result).toEqual('mock data');
});

4. 패키지 정기 업데이트 관리 (Regular Dependency Maintenance)

정기적으로 의존성 패키지를 점검하고 업데이트하는 것이 중요합니다. 모든 패키지를 한 번에 업데이트하기보다는, 하나씩 천천히 적용하면서 테스트하는 것이 안전합니다. npm outdated 또는 yarn outdated 명령어로 업데이트 가능한 패키지를 확인하고, 각각의 변경 사항을 검토한 후 업데이트할 수 있습니다.


느낀점

이번 개발 과정을 통해, 일상적으로 사용하던 여러 메서드와 라이브러리가 추상화 벽을 통해 복잡한 내부 로직을 감추고 있다는 사실을 새삼 깨달았습니다. 그동안 당연하게 사용하던 메서드들이 실제로는 라이브러리나 프레임워크가 제공하는 복잡한 동작을 깔끔하게 추상화해 놓은 것이라는 점을 알게 되면서, 코드를 더 깊이 이해하는 것이 중요하다는 생각이 들었습니다.

특히, 앞으로는 다른 사람의 코드를 볼 때 단순히 동작만을 파악하는 것이 아니라, 그 코드가 왜 그런 구조로 작성되었는지를 더 신중하게 고려해야겠다는 생각이 들었습니다. 또한, 내가 사용하는 라이브러리나 프레임워크의 메서드가 어떤 패턴과 목적을 가지고 설계되었는지 명확히 이해하는 것이 매우 중요하다는 점도 깨달았습니다.