Adapter 패턴이 TypeScript, React, Node.js 예제를 통해 호환되지 않는 인터페이스를 연결하고 통합과 레거시 코드를 현대화하는 방법을 알아보세요.
December 19, 2025 (3mo ago) — last updated February 10, 2026 (2mo ago)
어댑터 패턴: TypeScript, React & Node.js 예제
Adapter 패턴이 TypeScript, React, Node.js 예제를 통해 호환되지 않는 인터페이스를 연결하고 통합과 레거시 코드를 현대화하는 방법을 알아보세요.
← Back to blog
어댑터 패턴: TypeScript, React & Node.js 예제
요약: 실무 예제를 통해 TypeScript, React, Node.js에서 어댑터 패턴이 호환되지 않는 인터페이스를 어떻게 연결하는지 알아봅니다.
소개
완벽히 작동하는 라이브러리나 레거시 모듈이 있는데 시스템의 나머지 부분과 맞지 않아 골치 아팠던 적이 있나요? 마치 유럽식 어댑터를 북미식 소켓에 꽂으려는 것과 같습니다 — 둘 다 작동하지만 인터페이스가 맞지 않죠. 어댑터 패턴은 한 인터페이스를 다른 인터페이스로 변환해서 기존 코드를 변경하지 않고 재사용할 수 있게 해줍니다.
이 가이드는 패턴을 설명하고 TypeScript와 React 예제를 보여주며, 콜백 기반 모듈을 현대화하는 Node.js 어댑터를 시연합니다. 즉시 적용해 통합을 정리하고 테스트 용이성을 높일 수 있는 실용적인 기법에 중점을 둡니다.
어댑터 패턴이 중요한 이유
어댑터 패턴은 호환되지 않는 객체를 감싸서 코드가 기대하는 인터페이스를 노출하는 구조적 패턴입니다. 이 패턴은 1994년에 Gang of Four에 의해 처음 문서화되었습니다1. 어댑터는 서드파티 API 통합, 레거시 코드 현대화, 이질적인 데이터 소스 통합에 필수적입니다.
어댑터가 도움이 되는 일반적인 시나리오:
- 반환되는 데이터 형태가 다른 서드파티 API 통합.
- 레거시 콜백 API를 async/await와 함께 작동하도록 현대화.
- UI 컴포넌트를 위해 여러 데이터 소스를 단일 인터페이스로 통합.
어댑터를 사용하면 비즈니스 로직을 외부 시스템과 분리하여 깔끔하게 유지할 수 있습니다.
어댑터 패턴 한눈에 보기
| Concept | Description |
|---|---|
| Type | Structural |
| Primary intent | Allow objects with incompatible interfaces to work together |
| Core idea | Wrap the adaptee to expose the target interface |
| Key problem solved | Reuse existing classes without changing their source code |
| Common use cases | Third-party libraries, legacy code, multiple data sources |
구조와 역할
이 패턴에는 네 가지 역할이 있습니다:
- 클라이언트 — 특정 인터페이스를 필요로 하는 코드.
- 타깃 인터페이스 — 클라이언트가 기대하는 계약.
- 어댑티(Adaptee) — 필요한 기능을 가진 호환되지 않는 클래스나 모듈.
- 어댑터 — 타깃을 구현하고 어댑티에 위임하며 필요한 경우 호출을 변환합니다.
두 가지 일반적인 어댑터 스타일:
- 객체 어댑터(구성): 어댑터는 어댑티의 인스턴스를 보유합니다. 가장 유연한 접근입니다.
- 클래스 어댑터(상속): 어댑터가 어댑티를 상속하고 타깃을 구현합니다. 다중 상속을 요구하며 현대의 JavaScript/TypeScript에서는 덜 일반적입니다.
실용 예제: TypeScript + React
대시보드가 서로 다른 응답 형태를 가진 두 서비스에서 사용자 프로필을 받는다고 상상해보세요. 어댑터가 없다면 컴포넌트는 조건부 로직으로 지저분해집니다.
호환되지 않는 API 형태
// Data from UserServiceA
interface UserA {
userId: number;
fullName: string;
emailAddress: string;
}
// Data from UserServiceB
interface UserB {
id: string;
name: string;
contact: {
email: string;
};
}
앱이 기대하는 타깃 인터페이스
interface UnifiedUser {
id: string;
name: string;
email: string;
}
TypeScript 어댑터들
// Adapter for UserServiceA
function adaptUserA(userA: UserA): UnifiedUser {
return {
id: userA.userId.toString(),
name: userA.fullName,
email: userA.emailAddress,
};
}
// Adapter for UserServiceB
function adaptUserB(userB: UserB): UnifiedUser {
return {
id: userB.id,
name: userB.name,
email: userB.contact.email,
};
}
변환 로직을 중앙화하면 컴포넌트가 깨끗하고 탄력적으로 유지됩니다. API가 필드 이름을 변경해도 어댑터만 변경하면 됩니다.
통합된 데이터를 소비하는 React 컴포넌트
interface UserProfileProps {
user: UnifiedUser;
}
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
return (
<div>
<h2>{user.name}</h2>
<p>ID: {user.id}</p>
<p>Email: {user.email}</p>
</div>
);
};
이 컴포넌트는 단일하고 예측 가능한 데이터 형태에 의존하므로 테스트와 재사용이 용이합니다.
예제: 콜백 기반 Node.js 모듈 현대화
레거시 모듈은 종종 에러 우선 콜백을 사용합니다. 안정적인 모듈을 수정하는 대신 Promise 기반 API를 노출하는 어댑터를 만드세요.
레거시 어댑티(수정하지 마세요)
// legacyFileProcessor.js
const fs = require('fs');
class LegacyFileProcessor {
processFile(filePath, callback) {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
return callback(err, null);
}
const processedContent = data.toUpperCase();
callback(null, processedContent);
});
}
}
module.exports = LegacyFileProcessor;
Promise를 반환하는 어댑터
// FileProcessorAdapter.js
const LegacyFileProcessor = require('./legacyFileProcessor');
class FileProcessorAdapter {
constructor() {
this.legacyProcessor = new LegacyFileProcessor();
}
processFile(filePath) {
return new Promise((resolve, reject) => {
this.legacyProcessor.processFile(filePath, (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}
}
module.exports = FileProcessorAdapter;
이것은 Node의 util.promisify 동작을 모방하지만, 적응 로직을 명시적이고 테스트 가능하게 유지합니다3.
애플리케이션 코드에서 어댑터 사용
const FileProcessorAdapter = require('./FileProcessorAdapter');
const fileProcessor = new FileProcessorAdapter();
async function handleFileProcessing() {
try {
console.log('Processing file with modern async/await...');
const content = await fileProcessor.processFile('my-file.txt');
console.log('Processed Content:', content);
} catch (error) {
console.error('An error occurred:', error);
}
}
handleFileProcessing();
이 방법은 레거시 코드를 손대지 않으면서 코드베이스의 나머지 부분에 현대적인 인터페이스를 제공합니다.
언제 어댑터를 사용해야 하나
인터페이스가 달라 직접 통신할 수 없는 두 컴포넌트가 있을 때 어댑터를 사용하세요. 전형적인 시나리오:
- 입력 또는 출력이 모델과 맞지 않는 서드파티 API를 통합해야 할 때.
- 레거시 콜백 API를 래핑하여 async/await와 함께 작동하게 해야 할 때.
- 서로 다른 형식의 여러 데이터 소스를 지원해야 할 때, 소스별로 하나의 어댑터를 만드세요.
어댑터를 사용하지 말아야 할 경우:
- 양쪽 시스템을 모두 제어하고 있고 작은 리팩터로 문제를 해결할 수 있다면 직접 리팩터링을 우선하세요.
- 복잡한 서브시스템을 단순화하려는 목적이라면 Facade(퍼사드)를 고려하세요. 퍼사드는 단순화된 고수준 진입점을 제공하고, 어댑터는 호환성에만 집중합니다.
빠른 결정 체크리스트
| Situation | Use Adapter? | Why |
|---|---|---|
| Need to use a third-party library with incompatible API | Yes | You can’t change the library, so adapt to it |
| Control both sides and change is small | No | Refactor directly to avoid extra indirection |
| Need a simplified high-level interface to a complex system | No | Facade is a better fit |
| Migrating legacy systems incrementally | Yes | Wrap old components to match new interfaces |
| Multiple differently structured data sources | Yes | Adapters unify them into one shape |
테스트와 성능
어댑터는 핵심 로직을 외부 시스템과 분리하여 테스트 용이성을 향상시킵니다. 어댑터의 인터페이스를 모킹하면 컴포넌트를 격리해 테스트할 수 있고, 어댑터 자체는 변환 로직을 별도로 검증할 수 있습니다.
어댑터로 인한 성능 오버헤드는 보통 추가 함수 호출 하나 정도로 미미하며 네트워크 I/O나 데이터베이스 쿼리와 비교하면 거의 무시할 수 있습니다. 대부분의 웹 애플리케이션에서는 유지보수와 결합도 감소의 이점이 작은 비용을 충분히 상쇄합니다. JavaScript는 Stack Overflow 개발자 설문조사에서 가장 많이 사용되는 언어로 남아 있어 개발자들이 통합 작업을 자주 다룬다는 것을 보여줍니다4.
자주 묻는 질문
Q: 어댑터 패턴은 어떤 문제를 해결하나요?
A: 클라이언트의 호출을 어댑티가 이해하는 호출로 번역하여 인터페이스 불일치를 해결함으로써 코드를 변경하지 않고 재사용할 수 있게 합니다.
Q: 어댑터는 레거시 코드에 어떻게 도움이 되나요?
A: 어댑터는 레거시 모듈을 감싸 현대적 인터페이스를 노출하므로 위험한 재작성 없이 오래된 안정적 코드를 새 애플리케이션에 통합할 수 있게 합니다.
Q: 다른 패턴보다 언제 어댑터를 선택해야 하나요?
A: 두 개의 맞지 않는 인터페이스 간 호환성이 필요할 때 어댑터를 선택하세요. 서브시스템 전체를 단순화하려면 퍼사드(Facade)를 사용하세요.
추가 읽을거리 및 내부 링크
- 다형성(polymorphism) vs 상속(inheritance) 심층 분석: /blog/polymorphism-vs-inheritance
- 레거시 시스템 현대화 전략: /blog/modernizing-legacy-systems
- 어댑터 패턴 참고 및 예제: GeeksforGeeks2
Clean Code Guy에서는 부서지기 쉬운 복잡한 코드베이스를 회복력 있고 테스트 가능하며 다루기 즐거운 자산으로 바꿔주는 실용적인 디자인 패턴 구현을 팀에 제공합니다. 레거시 시스템이나 까다로운 통합 문제로 고민 중이라면, 우리의 Clean Code Audits가 더 건강한 아키텍처로 가는 명확하고 실행 가능한 로드맵을 제공할 수 있습니다. 더 나은 코드를 더 빠르게 배포하는 방법을 알아보세요.
AI가 코드를 작성합니다.당신이 그것을 지속시킵니다.
AI 가속 시대에 클린 코드는 단순히 좋은 관행이 아닙니다 — 확장되는 시스템과 자체 무게로 붕괴되는 코드베이스의 차이입니다.