Facade 패턴으로 레거시 시스템 단순화하기
Wenhao Wang
Dev Intern · Leapcell

서론
빠르게 발전하는 소프트웨어 개발 환경에서 백엔드 시스템은 시간이 지남에 따라 상당한 기술 부채가 쌓이는 경우가 많습니다. 이는 특히 레거시 시스템이나 깊게 통합된 복잡한 하위 시스템을 다룰 때, 방대하고 복잡한 코드베이스로 자주 나타납니다. 이러한 하위 시스템과 직접 상호 작용하는 것은 개발자의 악몽이 될 수 있으며, 내부 작동 방식, 모호한 API 및 복잡한 워크플로우에 대한 깊은 지식이 필요합니다. 이러한 복잡성은 개발 속도를 늦출 뿐만 아니라 버그 발생 가능성을 높이고 향후 유지보수를 어려운 과제로 만듭니다. 다행히도 디자인 패턴은 일반적인 아키텍처 문제에 대한 우아한 솔루션을 제공합니다. 이 중에서 Facade 패턴은 복잡성을 추상화하는 강력한 도구로 두각을 나타냅니다. 이 글에서는 백엔드 프레임워크 내에서 Facade 패턴을 적용하여 이러한 어려운 하위 시스템에 대한 깔끔하고 단순화된 API 인터페이스를 제공함으로써, 복잡하게 얽힌 문제를 접근하기 쉬운 작업 집합으로 변환하는 방법을 자세히 살펴보겠습니다.
핵심 개념 및 원칙
실제 적용에 앞서, 논의와 관련된 핵심 개념을 명확하게 이해해야 합니다.
레거시 시스템
레거시 시스템은 "이전 또는 오래된 컴퓨터 시스템과 관련이 있는" 오래된 방법, 기술, 컴퓨터 시스템 또는 응용 프로그램을 의미합니다. 종종 이러한 시스템은 비즈니스 운영에 중요하지만 수정이 어렵고 문서화가 부족하며 복잡하고 비표준적인 인터페이스를 가지고 있습니다.
복잡한 하위 시스템
복잡한 하위 시스템은 전체 시스템 내에서 내부 복잡성이 높은 구성 요소 또는 구성 요소 집합입니다. 이러한 복잡성은 수많은 상호 연결된 클래스, 복잡한 객체 상호 작용, 전문 용어 또는 특정 작업을 달성하기 위해 올바르게 조정되어야 하는 많은 공개 인터페이스에서 비롯될 수 있습니다.
Facade 패턴
Facade 패턴은 구조적 디자인 패턴으로, 하위 시스템의 인터페이스 집합에 대한 통합된 인터페이스를 제공합니다. 하위 시스템을 더 쉽게 사용할 수 있도록 하는 고수준 인터페이스를 정의합니다. 이는 기본 복잡성을 단순화하고 클라이언트에게 간소화된 보기를 제시하는 래퍼라고 생각할 수 있습니다. 주요 이점은 다음과 같습니다.
- 디커플링: 클라이언트와 하위 시스템의 구성 요소 간의 상호 의존성을 줄입니다.
 - 단순화: 클라이언트가 처리해야 하는 객체의 수를 줄이고 더 간단한 진입점을 제공합니다.
 - 캡슐화: 하위 시스템을 효과적으로 사용하기 위해 필요한 복잡한 상호 작용 및 작업 순서를 캡슐화합니다.
 
Facade가 복잡성을 해결하는 방법
Facade 패턴은 중개자 역할을 하여 레거시 또는 복잡한 하위 시스템에서 발생하는 문제를 해결합니다. 클라이언트가 하위 시스템에서 여러 객체를 인스턴스화하고, 구성하고, 특정 순서로 해당 메서드를 호출할 필요 없이, Facade와만 상호 작용합니다. 그러면 Facade 객체는 해당 호출을 하위 시스템 내의 적절한 객체로 위임하는 책임을 맡아 모든 복잡한 오케스트레이션을 백그라운드에서 처리합니다.
백엔드 프레임워크에 Facade 구현하기
Python (Flask 또는 Django 사용), Node.js (Express) 또는 Java (Spring Boot) 등으로 구축된 백엔드 프레임워크에서 실제 시나리오를 살펴보겠습니다. 현대 전자 상거래 플랫폼이 상호 작용해야 하는 레거시 재고 관리 시스템이 있다고 가정해 보겠습니다. 이 레거시 시스템은 수십 개의 메서드를 노출하는 SOAP 기반 API를 제공하며, 특정 인증 토큰이 필요하고, 제품 업데이트 및 재고 수준을 처리하는 독특한 방식을 가지고 있습니다.
Facade가 없으면 전자 상거래 서비스 코드는 다음과 같을 수 있습니다.
# Facade 없는 서비스 계층 import legacy_inventory_client from legacy_inventory_auth import get_auth_token class ProductService: def update_product_stock(self, product_id, quantity_change): auth_token = get_auth_token("admin", "password") # 레거시 시스템과의 복잡한 상호 작용 legacy_client = legacy_inventory_client.InventoryService( url="http://legacy-inventory/wsdl", headers={"Authorization": f"Bearer {auth_token}"} ) # 현재 재고 가져오기, 다른 SKU 순열 처리, 업데이트 적용 product_info = legacy_client.get_product_details({"productId": product_id}) # product_info에 'SKU_MAP'과 'current_stock'이 포함되어 있다고 가정 if not product_info.SKU_MAP: raise ValueError("레거시 시스템에서 제품 SKU 맵을 찾을 수 없습니다.") updated_stock = product_info.current_stock + quantity_change success = legacy_client.update_product_quantity( {"sku": product_info.SKU_MAP[product_id], "newQuantity": updated_stock} ) if not success: raise RuntimeError("레거시 재고 업데이트에 실패했습니다.") return updated_stock # 컨트롤러에서의 사용 # product_service = ProductService() # product_service.update_product_stock("PROD123", 5)
이 접근 방식에는 몇 가지 단점이 있습니다.
- 강한 결합: 
ProductService는legacy_inventory_client및legacy_inventory_auth의 특정 사항에 강하게 결합되어 있습니다. - 가독성 부족: 핵심 비즈니스 로직은 레거시 시스템과의 상호 작용 복잡성으로 인해 가려집니다.
 - 유지보수 부담: 레거시 시스템 API (예: 인증 방법, 작업 이름)에 변경이 발생하면 현대 시스템의 여러 위치에서 수정해야 합니다.
 
이제 Facade를 도입해 보겠습니다.
# inventory_facade.py import legacy_inventory_client from legacy_inventory_auth import get_auth_token class LegacyInventoryFacade: def __init__(self, service_url, username, password): self._service_url = service_url self._username = username self._password = password self._auth_token = None self._client = None def _authenticate(self): if not self._auth_token: self._auth_token = get_auth_token(self._username, self._password) if not self._client: self._client = legacy_inventory_client.InventoryService( url=self._service_url, headers={"Authorization": f"Bearer {self._auth_token}"} ) def get_product_current_stock(self, product_id): self._authenticate() legacy_product_details = self._client.get_product_details({"productId": product_id}) # 필요한 경우 레거시 데이터를 간단한 DTO로 매핑 return legacy_product_details.current_stock def update_product_quantity(self, product_id, new_quantity): self._authenticate() # 레거시 시스템에서 SKU 매핑 가져오기 (또는 캐싱) legacy_product_info = self._client.get_product_details({"productId": product_id}) if not legacy_product_info.SKU_MAP: raise ValueError(f"제품 {product_id}에 대한 SKU 맵을 찾을 수 없습니다.") sku = legacy_product_info.SKU_MAP.get(product_id) if not sku: raise ValueError(f"레거시 시스템에서 제품 ID {product_id}에 대한 SKU를 찾을 수 없습니다.") return self._client.update_product_quantity( {"sku": sku, "newQuantity": new_quantity} ) # 상위 수준 서비스 계층에서 class ProductService: def __init__(self, inventory_facade): self._inventory_facade = inventory_facade def adjust_product_stock(self, product_id, quantity_change): current_stock = self._inventory_facade.get_product_current_stock(product_id) updated_stock = current_stock + quantity_change success = self._inventory_facade.update_product_quantity(product_id, updated_stock) if not success: raise RuntimeError("인벤토리 Facade를 통한 스톡 업데이트 실패.") return updated_stock # 백엔드 애플리케이션에서 사용 (예: Flask/Django 뷰, Spring Boot 컨트롤러) # 구성 또는 종속성 주입 설정에서: # legacy_facade = LegacyInventoryFacade("http://legacy-inventory/wsdl", "admin", "password") # product_service = ProductService(legacy_facade) # 컨트롤러/라우트 핸들러에서: # @app.post("/products/<product_id>/adjust-stock") # def adjust_stock_route(product_id): # quantity_change = request.json.get("quantityChange") # new_stock = product_service.adjust_product_stock(product_id, quantity_change) # return {"message": "스톡이 성공적으로 조정되었습니다", "new_stock_level": new_stock}
이 리팩토링된 예시에서:
LegacyInventoryFacade는 인증, SKU 매핑 처리 및legacy_inventory_client의 특정 메서드 호출과 관련된 모든 복잡성을 캡슐화합니다.ProductService는 이제 단순한 고수준 인터페이스(get_product_current_stock,update_product_quantity)와 상호 작용하며, 기본 SOAP 호출, 인증 메커니즘 또는 SKU 변환에 대해 완전히 알지 못합니다.- 레거시 시스템 API가 변경되면 
LegacyInventoryFacade만 수정하면 되며, 이를 사용하는 모든 서비스가 수정될 필요는 없습니다. 이는 변경의 영향을 크게 줄입니다. 
적용 시나리오
Facade 패턴은 특히 다음과 같은 경우에 적합합니다.
- 타사 API 통합: 복잡하고 다단계 인증 또는 데이터 형식 요구 사항이 있는 외부 서비스 소비.
 - 레거시 기능 마이그레이션: 기능을 Facade로 래핑하고 그 뒤에서 점진적으로 다시 구축하여 오래된 시스템의 부분을 점진적으로 교체.
 - 하위 시스템 간 작업: 애플리케이션의 작업이 여러 별도의 하위 시스템(예: 사용자 프로필 업데이트, 알림 전송, 활동 로깅)을 조정해야 하는 경우.
 - 모놀리식 리팩토링: 모놀리식 애플리케이션을 더 관리하기 쉽고 느슨하게 결합된 모듈로 분해.
 
결론
Facade 패턴은 백엔드 프레임워크 내에서 레거시 시스템 및 복잡한 하위 시스템에 내재된 복잡성을 탐색하는 데 있어 실용적이고 효과적인 솔루션 역할을 합니다. 단순화되고 통합된 인터페이스를 제공함으로써 결합을 크게 줄이고, 가독성을 개선하며, 개발 워크플로우를 간소화합니다. 전략적 적용은 어려운 상호 작용을 간단한 API 호출로 변환하여 백엔드 시스템을 더 유지보수하기 쉽고, 적응하기 쉬우며, 궁극적으로 더 강력하게 만듭니다. Facade를 활용하면 복잡성이 명확성으로 변환되어 어려운 시스템을 개발자가 접근하고 관리할 수 있게 됩니다.