Barrel File 을 활용한 캡슐화(Capsulation) 및 추상화(Abstraction)

Barrel File 을 활용한 캡슐화(Capsulation) 및 추상화(Abstraction)

Summary

본 포스트는 자바스크립트(JS, JavaScript) 와 타입스크립트(TS, TypeScript) 에서 모듈 시스템(Module System) 을 활용할 때, 강제적인 정보 은닉(Encapsulation) 이 어려운 구조적 한계를 Barrel File 이라는 Pattern 을 통해 어떻게 보완할 수 있는지를 학습하고 이해한 부분을 정리한 내용을 담고 있습니다.
학습해본 내용을 제 관점에서 재정리한 것이며, 학습이나 실무 경험을 통해 이후 일부 내용이 변경될 수 있음을 감안하고 읽어주시기 바랍니다.
 

Barrel File?

저는 평소에 import 할 Module 을 제한하기 위한 목적으로 위와 같은 코드를 자주 작성해봤는데, 이걸 정확히 무엇이라고 부르는 지도 모르고 사용해오고 있었습니다.
위와 같이 index.ts 에서 export 할 모듈을 관리하는 구조인데요. 이런 방식의 구조를 Barrel File 방식이라고 부른다고 합니다. index.ts 파일이 일종의 Barrel(통) 역할을 하여 여러 개를 하나의 통에 모아 담는다는 의미에서 유래했다고 하는데요.
Barrel File 방식은 앞에서 설명한 제가 기존에 사용했던 목적처럼, 여러 개의 모듈을 하나의 진입점(Index) 에서 통합적으로 export 하는 방식으로, 보통 index.ts 또는 index.js 파일로 사용되며, 해당 디렉토리에 있는 여러 모듈을 한 곳에서 모아서 외부에 노출합니다.
 

Why?

JavaScript, TypeScript 의 ES Module System 은 기술적으로는 접근 제어자가 존재하지 않아 내부 구현을 완전히 감추기 어렵습니다.
즉, export 된 모든 모듈은 import 문을 통해 외부에서 무조건 접근이 가능하므로, import 문을 잘못 사용하여 내부에서만 사용되는 모듈을 선언한다던지, 의도하지 않은 의존성을 만들기 쉽습니다.
이러한 구조적 한계를 보완하기 위해, Barrel File 방식을 캡슐화와 추상화를 위한 인터페이스(Interface) 역할로 활용할 수 있습니다.
자체적으로도 한 depth 라도 아낄 수 있다는 장점이 있지만, 이외에도 별칭(alias) 기능을 함께 활용하면 depth 가 멀리 떨어진 파일을 import 할 때도 ../../../../ 처럼 .. 을 도배하지 않고 깔끔하게 작성하기도 좋습니다.
 

캡슐화(Encapsulation) 및 추상화(Abstraction)

  • 특정 모듈의 구현 세부사항은 Barrel File 내부에서만 직접 import 하고, 외부에는 노출하지 않음으로써, 외부 의존성(External Dependency) 을 통제할 수 있습니다.
  • Barrel File 을 통해 상위 Level API 만 외부에 제공함으로써, 내부 구현의 복잡도를 숨기고, 더 명확한 인터페이스를 구성할 수 있습니다.
  • 사용자는 Barrel File 에서 제공하는 export 만 사용하여, 내부 구조에 대한 직접적인 접근을 지양하도록 일종의 Guideline 을 제공할 수 있습니다.
  • 즉, Barrel File 로 구성된 Module 을 사용하는 사용자(개발자) 의 입장에서는 설계 원칙 상 실제 그 Module 의 내부 구현을 알 필요가 없고, 따라서 내부 구현이 Refactoring 되더라도, 이에 영향을 받지 않습니다.
  • 더불어 ESLint 같은 Linting 도구를 활용하여 이를 아예 방지하는 Rule 을 추가하면, 간접적으로 강제 은닉도 가능합니다.
 

근본적인 한계

일부 개발자들은 Barrel File 을 단순한 import / export 정리 도구로만 보고, 캡슐화 도구 목적으로 사용하는 것을 과도한 추상화라고 생각할 수도 있습니다.
  • 실제로 ESLint Rule 을 사용하더라도 경고, 오류를 표시하는 게 전부입니다.
  • husky, lint-staged 같은 Local CI 나, DevOps Pipeline 내에서 설정을 따로 해주는 것 역시 형상 관리 지점에서 이런 규칙을 동기화하는 목적이지, 실질적으로 Runtime 에서 강제적으로 접근을 제어하는 것은 근본적으로 불가능합니다.
  • 따라서, 개발 문화와 다소 복잡한 컨벤션(Convention) 설정으로 이를 구현해야 하는 점에서 한계가 존재합니다.
 

그래도 무조건 사용하는게 좋을까?

Barrel File 을 무조건 사용하는 것이 좋다 라고는 볼 수 없습니다.
  • 컨벤션을 바탕으로 필요한 설정을 섬세하게 해두면, 분명 개발자 경험(DX, Developer eXperience) 관점에서 장점을 가져오는 것이 분명합니다.
  • 하지만, 때때로 IDE Intellisense 등 자동화(Automation) 기능과 충돌할 수 있습니다. 이는 문제(Problem), 버그(Bug) 로 보는 것이 맞지만, 이에 대한 해결의 책임자는 누구일까요?
  • 대규모 프로젝트에서의 순환 참조(Circular Dependency), 부수 작용 모듈(Side-Effect Module) 이 섞일 때, 코드 스플리팅(Code Splitting) 및 트리 쉐이킹(Tree Shaking) 에 악영향을 줄 수 있다는 의견도 있습니다.
 

추가로 연관지어서 생각해볼 주제

  • TypeScript 5.0+ 에서의 모듈 해석 전략(Module Resolution) 과의 관계
 

마무리

그동안 이게 무엇인지도 모르고 사용해왔다는 점에서 조금 충격을 먹기도 했지만, 이렇게 하나를 알아간다는 점에서 참 유익했던 학습 과정이었습니다.
Barrel File 방식은 그 자체로 언어나 런타임 Level 에서 제 역할을 수행하는 것은 아니지만, ES Module System 의 구조적인 한계를 실무적으로 보완하는 데에 꽤 현명하게 해결하는 방식이라고 느꼈습니다.
명시적인 인터페이스(Interface) 설계가 어려운 JavaScript, TypeScript 생태계에서, Barrel File 방식을 통한 추상화는 일종의 Convention 기반의 방어선 처럼 제 역할을 간접적으로 수행한다고 결론지을 수 있었습니다.
본 포스트은 제가 학습하고 이해한 내용을 바탕으로 작성한 정리이며, 앞으로 얼마든지 내용이 변경될 수 있다는 점을 감안하고 읽어주시기 바랍니다. 더불어 혹시 다른 의견이나 피드백이 있다면 언제든지 댓글로 남겨주시기 바랍니다.
 

끗!