title: "서드파티 API가 코드를 망가뜨리는 일을 막는 법" title_original: "How to Stop Third-Party APIs From Ruining Your Code" channel: "ArjanCodes" source: "YouTube" video_id: "vskwNNdnqMc" url: "https://www.youtube.com/watch?v=vskwNNdnqMc" date: "2026-09-05" duration_seconds: 667 tags: [software-design, facade-pattern, architecture, third-party-api, python]
📌 핵심 질문 / 서드파티 API를 어디까지 애플리케이션에 들여올 것인가
==외부 시스템의 타입·오류·용어·구현 세부사항이 비즈니스 로직으로 새어 나오지 않게, 파사드(Facade)를 단순 래퍼가 아닌 경계(boundary)로 설계해야 한다.==
- Stripe의 고객(customer), 결제 의도(payment intent), Stripe 오류를 결제와 무관한 애플리케이션 코드가 알 필요가 없도록 만든다.
- 외부 API를 감싸는 메서드 이름과 반환 객체를 우리 도메인의 언어와 데이터 구조로 정의한다.
- 테스트에서는 외부 구현 대신 같은 프로토콜을 구현하는 fake/mock을 주입해 비즈니스 로직을 빠르게 검증한다.
서드파티 API는 처음에는 한 함수 안에 넣어도 작동하지만, 호출 코드가 늘어날수록 외부 제공자의 설계가 애플리케이션 아키텍처를 결정하게 만든다. 파사드는 복잡한 하위 시스템에 접근하는 표면을 줄이는 동시에, 무엇이 애플리케이션 안으로 들어오고 나가는지 결정해야 한다. 핵심은 코드를 짧게 만드는 데서 끝나지 않고, 외부 시스템의 설계 결정을 경계 밖에 가두는 데 있다.
1. 직접적인 Stripe 통합이 만드는 결합
Stripe 결제 제공자를 예로 들면 외부 API를 호출하는 코드 자체는 정상적으로 실행될 수 있지만, 시스템 전체의 결합도와 변경 비용이 빠르게 커진다.
1.1. AI가 작성한 것처럼 보이는 결제 흐름
-
pay_for_order가 외부 객체를 직접 받는다
- 함수는 Stripe 참조, 주문(order), 결제 수단(payment method)을 인자로 받는다.
- 결제 제공자 객체가 비즈니스 함수 안에 직접 들어오므로 함수의 표면부터 Stripe에 종속된다.
-
고객과 결제 의도를 함수 안에서 조합한다
- 기존 고객을 찾고, 고객이 없으면 새 고객을 만든다.
- Stripe의 결제 흐름 객체인 PaymentIntent를 생성한다.
- 발생한 오류의 종류에 따라 서로 다른 처리를 수행한다.
-
작동하는 코드와 유지 가능한 코드는 다르다
- 한 곳에서 실행하면 코드 자체는 “fine”하게 작동한다.
- 결제가 필요한 모든 위치에 같은 Stripe 특화 로직을 복사하면 코드베이스가 엉킨다.
- 여러 샘플 주문을 만들고 이 함수를 몇 차례 호출하는 간단한 main 스크립트로도 결과는 확인할 수 있지만, 실행 성공이 좋은 경계를 보장하지는 않는다.
1.2. 결제 API가 애플리케이션 설계를 지배하는 순간
-
교체와 업그레이드가 대규모 리팩터링이 된다
- 언젠가 Stripe를 다른 제공자로 교체하거나 새 버전으로 올리려면 Stripe 호출이 들어간 모든 위치를 찾아 바꿔야 한다.
- 외부 API 버전 변경이 결제 모듈 한 곳의 작업이 아니라 애플리케이션 전체의 작업으로 번진다.
-
도메인 코드가 Stripe의 내부 개념을 알아야 한다
- 결제를 다루는 각 시스템 구성요소가 고객 ID, PaymentIntent ID, 메타데이터 규칙, 확인(confirmation) 호출, 오류 클래스 같은 세부사항을 알게 된다.
- 각 호출자는 PaymentIntent의 필드 값을 검사해야 하고, Stripe가 돌려주는 카드 오류도 처리해야 한다.
- 결제와 직접 관련 없는 비즈니스 로직까지 고객과 PaymentIntent가 어떤 모양인지 알아야 하므로 결제 제공자의 어휘가 도메인 모델로 스며든다.
-
결합은 데이터와 어휘를 통해서도 발생한다
- 코드를 한 서비스 클래스로 묶었다고 해서 외부 시스템과의 결합이 사라지는 것은 아니다.
- 메서드가 Stripe 객체를 반환하고 호출자가 Stripe 오류를 받는다면, 실제 경계는 여전히 존재하지 않는다.
- 애플리케이션의 자체 설계가 Stripe의 설계 방식에 맞춰지는 것이 문제다.
2. 파사드의 첫 번째 시도와 흔한 실패
파사드는 복잡한 하위 시스템 위에 단순한 인터페이스를 제공한다. 그러나 단순히 메서드를 한 클래스에 모으는 것만으로는 외부 의존성을 격리할 수 없다.
2.1. 래퍼로 복잡성을 한곳에 모으기
-
Payment Service 파사드를 만든다
- Stripe 객체를 여전히 import하지만, PaymentService가 Stripe의 checkout workflow를 감싼다.
- 서비스는 주문과 결제 수단을 받아 고객 생성, PaymentIntent 생성 등 결제 조합 로직을 내부에서 수행한다.
- 기존 pay_for_order는 Stripe 객체 대신 이 서비스를 인자로 받아 호출한다.
-
비즈니스 로직의 반복을 줄인다
- 도메인 로직은 고객 조회나 PaymentIntent 생성 과정을 반복하지 않고 Payment Service에 요청한다.
- Stripe 구현 세부사항을 한 곳에 모으므로 코드가 눈에 띄게 단순해진다.
- 이 단계만 보면 파사드를 구현했으니 끝났다고 생각하기 쉽다.
2.2. 단순화했지만 경계를 만들지 못한 구조
-
반환 타입이 여전히 Stripe 객체다
- pay 메서드의 시그니처가 PaymentIntent 객체를 반환하면 모든 호출자는 Stripe 전용 객체를 받는다.
- 호출자는 PaymentIntent 내부 값을 읽고 처리해야 하므로 Stripe의 구현 세부사항을 계속 배워야 한다.
-
오류도 그대로 외부에서 온다
- Stripe가 반환한 오류가 상위 로직까지 올라오면 상위 로직은 Stripe 카드 오류를 알아야 한다.
- 파사드를 거쳤는데도 호출자는 Stripe-specific error를 처리하고, 결제 수단의 인증 요구 같은 외부 개념을 직접 판단한다.
-
단순 래퍼는 의존성을 숨길 뿐 제거하지 않는다
- 행동을 한 메서드로 묶어도 외부 타입·오류·명명법이 경계를 넘으면 결합은 그대로다.
- 진짜 파사드는 접근을 줄이는 동시에 경계를 정의해야 한다.
3. 파사드를 외부 의존성의 경계로 설계하기
파사드의 가장 중요한 역할은 복잡한 코드를 짧게 만드는 것이 아니라 애플리케이션에 어떤 정보가 들어올지 결정하는 것이다.
3.1. 도메인 프로토콜과 결과 객체 만들기
-
애플리케이션 언어로 결제 프로토콜을 정의한다
- PaymentsProtocol 같은 클래스 또는 프로토콜에 pay(order, payment_method)를 정의한다.
- 이 메서드는 외부 제공자의 API 이름이 아니라 도메인에서 이해할 수 있는 결제 작업을 표현한다.
- 반환값은 Stripe PaymentIntent가 아니라 결제 정보를 담은 자체 PaymentsResult 객체다.
-
자체 결과 객체는 외부와 독립적이다
- PaymentsResult는 결제 성공 여부, 필요한 상태, 도메인에서 사용할 식별 정보처럼 애플리케이션에 필요한 정보만 담는다.
- 객체 자체에는 Stripe와 관련된 필드나 타입이 없어야 한다.
- Stripe를 다른 결제 제공자로 바꿔도 도메인 로직이 같은 결과 객체를 사용할 수 있다.
-
도메인 로직은 구현을 몰라도 된다
- 주문 처리 함수는 결제 프로토콜의 객체를 받아 주문과 결제 수단을 넘기고 결과를 사용한다.
- 정확한 구현이 무엇인지, 고객을 어떤 API 호출로 만들었는지, PaymentIntent가 어떻게 생겼는지 알지 못한다.
- 주문 처리 흐름 안에는 Stripe-specific한 코드가 남지 않는다.
3.2. Stripe Gateway에 외부 지식을 가둔다
-
구체 구현은 별도 Gateway에 둔다
- StripeGateway가 PaymentsProtocol을 구현한다.
- 이 클래스 안에서만 Stripe 고객을 찾거나 생성하고, PaymentIntent를 만들고, Stripe의 확인 호출을 수행한다.
- Stripe 오류를 잡아 도메인에서 이해할 수 있는 PaymentsResult로 변환한다.
-
경계를 넘는 것은 선별된 정보뿐이다
- Gateway는 주문과 결제 수단을 Stripe 요청 형식으로 변환한다.
- Stripe 응답에서 애플리케이션에 필요한 값만 추출해 자체 결과 객체에 넣는다.
- Stripe 객체·예외·메타데이터 규칙은 Gateway 내부에 머문다.
-
Stripe는 완전히 숨겨진다
- 애플리케이션은 Stripe Gateway를 결제 서비스로 사용하지만 Stripe를 직접 알 필요가 없다.
- 이렇게 하면 결제 구현과 도메인 로직 사이에 명확한 경계가 생긴다.
- 파사드는 단순한 접근점이면서 정보·동작·어휘의 필터로 작동한다.
3.3. 경계를 판단하는 세 가지 질문
-
어떤 연산이 통과하는가
- 도메인에서 필요한 pay나 refund_order 같은 연산만 노출한다.
- create_payment_intent, confirm_payment_intent, cancel_payment_intent처럼 Stripe 내부 작업을 그대로 공개하지 않는다.
-
어떤 데이터 구조가 통과하는가
- 주문, 결제 수단, 자체 결제 결과처럼 도메인이 소유한 구조를 사용한다.
- 외부 시스템의 응답 객체를 애플리케이션 전역으로 흘려보내지 않는다.
-
어떤 오류와 어휘가 통과하는가
- 도메인이 처리해야 하는 결제 실패나 인증 필요 상태는 자체 개념으로 표현한다.
- Stripe 카드 오류 클래스, PaymentIntent라는 명칭, 고객 생성 규칙은 경계 안에 남긴다.
- 연산·데이터·오류뿐 아니라 vocabulary까지 통제해야 비즈니스 로직이 깨끗하게 유지된다.
4. 다른 외부 시스템에도 같은 원칙 적용하기
Stripe는 하나의 사례일 뿐이다. 외부 응답과 타입이 코드베이스 전체에 퍼지는 순간마다 동일한 문제가 생긴다.
4.1. 외부 데이터와 도메인 모델 분리
-
Weather API 응답을 자체 객체로 변환한다
- 날씨 API 응답을 여러 계층에 전달하는 대신 필요한 정보를 담은 애플리케이션 소유 객체를 만든다.
- 공급자를 바꾸거나 응답 스키마가 바뀌어도 날씨를 사용하는 도메인 로직은 자체 객체를 계속 사용한다.
-
그래픽 엔진 타입을 도메인 모델에 넣지 않는다
- Pygame을 사용하더라도 Pygame vector나 엔진 전용 rectangle이 도메인 모델의 일부가 되게 하지 않는다.
- 엔진의 좌표·도형 타입이 게임의 비즈니스 규칙 전역에 퍼지면 그래픽 엔진이 아키텍처를 결정한다.
-
ORM 모델을 비즈니스 계층 전체에 전달하지 않는다
- SQLAlchemy 모델을 모든 비즈니스 코드에 넘기면 데이터베이스 접근 도구의 구조가 도메인 로직을 형성한다.
- 필요한 값을 자체 도메인 객체로 추출해 계층 사이를 이동시킨다.
-
AI 제공자의 응답 객체를 유출하지 않는다
- OpenAI나 Anthropic의 response object를 애플리케이션 나머지 부분에 그대로 노출하지 않는다.
- 한 지점에서 필요한 데이터만 추출한 뒤 도메인이 편하게 다룰 수 있는 객체로 변환한다.
4.2. 외부 객체가 아키텍처를 바꾸는 방식
-
객체가 퍼질수록 제공자의 설계가 기준이 된다
- 다른 시스템의 객체가 코드베이스 곳곳으로 퍼질 때 그 시스템이 애플리케이션의 구조를 형성하기 시작한다.
- 외부 시스템의 타입이 함수 시그니처, 오류 처리, 데이터 흐름, 명명법에 반복해서 등장하면 결합도가 올라간다.
-
파사드는 오류와 응답까지 감싸야 한다
- 행동만 단순화하고 응답 객체와 오류를 그대로 넘기면 외부 의존성은 경계 밖에 남는다.
- 오류 매핑과 결과 변환까지 설계한 파사드가 테스트 가능하고 교체 가능한 구조를 만든다.
5. 경계를 이용한 테스트 설계
외부 시스템을 경계 뒤에 두면 통합 테스트와 비즈니스 로직 테스트를 분리할 수 있다.
5.1. Fake 구현으로 호출자를 검증하기
-
프로토콜을 구현하는 fake를 만든다
- 테스트 파일에 FakeStripe처럼 같은 계약을 구현하는 가짜 클래스를 만든다.
- 실제 Stripe SDK를 호출하지 않고, 프로토콜이 요구하는 결제 결과를 반환하게 만든다.
-
파사드 전체를 테스트 대역으로 교체한다
- 테스트 대상 클래스에 실제 Gateway 대신 fake 객체를 주입한다.
- 호출자 코드는 Stripe의 네트워크·고객 생성·PaymentIntent 생성을 실행하지 않고도 결제 흐름을 검증한다.
- 외부 구현의 세부사항이 아니라 도메인 로직의 계약과 결과에 집중할 수 있다.
-
설정 비용이 작고 자동화하기 쉽다
- 몇 개의 테스트 클래스가 fake를 사용하도록 구성하면 된다.
- pytest를 실행했을 때 테스트가 통과하고, 실제 외부 계정이나 결제 API 상태에 의존하지 않는다.
- AI에게 이런 반복적인 테스트 코드 생성을 맡길 수 있지만, 어떤 프로토콜과 경계를 설계할지는 사람이 결정해야 한다.
6. 파사드를 구현할 때 피해야 할 세 가지 실수
파사드는 단순한 패턴인 만큼 이름만 붙이고 핵심 경계를 놓치기 쉽다.
6.1. 실수 1: Pass-through Wrapper 만들기
-
Stripe 메서드를 그대로 복제하지 않는다
- create_payment_intent, confirm_payment_intent, cancel_payment_intent 같은 메서드를 파사드에 그대로 만들면 Stripe API의 복사본이 된다.
- 이런 메서드는 매우 Stripe-specific하고, 호출자에게 외부 시스템의 작업 순서를 학습시킨다.
-
도메인에 맞는 작업을 노출한다
- 결제 파사드는 pay나 refund_order처럼 애플리케이션에서 의미 있는 작업을 제공한다.
- 메서드가 실제 도메인에서 사용하는 동작을 표현해야 복잡성이 줄어든다.
6.2. 실수 2: God Facade 만들기
-
모든 외부 기능을 한 객체에 몰아넣지 않는다
- 결제, 청구서(invoices), 구독(subscriptions), 고객(customers), 쿠폰(coupons)을 모두 처리하는 거대한 Stripe Facade는 God Object가 된다.
- “무엇이든 처리하는” 객체는 파사드의 크기와 책임을 계속 키운다.
-
응집도 있는 서비스로 나눈다
- 서로 관련된 기능을 별도의 서비스나 클래스로 분리한다.
- 결제와 구독처럼 변경 이유가 다른 기능을 분리하면 각 경계가 더 작고 이해하기 쉬워진다.
6.3. 실수 3: 중요한 동작 숨기기
-
도메인의 현실을 거짓말로 바꾸지 않는다
- 결제가 항상 동기(synchronous)로 끝나는 것처럼 가장하지 않는다.
- 외부 결제 흐름에 비동기 상태, 재시도, 확인 단계가 있다면 자체 결과와 인터페이스에 그 사실을 반영한다.
-
인증 필요 상태를 노출할 때는 도메인 용어를 쓴다
- 호출자가 고객 인증이 필요하다는 사실을 알아야 한다면 그 개념을 숨기지 않는다.
- StripeAuthenticationRequired 같은 제공자 이름을 그대로 퍼뜨리지 말고 도메인이 이해할 수 있는 인증 필요 상태로 표현한다.
- 파사드는 복잡성을 감추되, 비즈니스 의사결정에 필요한 중요한 행동과 상태까지 감춰서는 안 된다.
7. 파사드와 어댑터의 차이
두 패턴 모두 무언가를 감싸지만 해결하려는 문제가 다르다.
7.1. 어댑터는 인터페이스를 바꾼다
-
호환 가능한 형태로 변환한다
- 어댑터(Adapter)는 한 인터페이스를 다른 인터페이스로 바꾼다.
- 다른 애플리케이션 구성요소가 기대하는 형태로 맞추고, 필요하면 구현을 다른 것으로 쉽게 교체할 수 있게 한다.
-
핵심 목표는 호환성이다
- 기존 코드가 원하는 계약과 실제 객체의 계약이 다를 때 둘을 연결한다.
- 어댑터가 복잡한 하위 시스템 전체를 단순하게 만드는 것이 주된 목표는 아니다.
7.2. 파사드는 복잡한 하위 시스템의 접근을 단순화한다
-
핵심 목표는 복잡성의 격리다
- 파사드는 복잡한 하위 시스템에 접근하는 방법을 단순화한다.
- 애플리케이션의 나머지 부분이 하위 시스템의 복잡성을 반복해서 처리하지 않도록 한다.
-
둘은 함께 쓰일 수도 있다
- Stripe Gateway가 프로토콜과 외부 SDK 사이를 맞추는 어댑터 역할을 하면서, Payment Service가 전체 checkout 작업을 단순화하는 파사드 역할을 할 수 있다.
- 이름보다 중요한 것은 어떤 문제를 해결하려고 경계를 만들었는지 명확히 하는 것이다.
주요 발언 모음
“A facade needs to work like a boundary.”
“Every time another system's objects spread through your code base, you're basically letting that system shape your architecture.”
“A good facade protects your application from somebody else's design decisions.”
“They both wrap something, but they solve different problems.”
“Don't let Pygame vectors or engine-specific rectangles become part of your domain model.”
핵심 데이터 & 수치
- 영상 길이: 667초(약 11분 7초)로 Stripe 결제 예제에서 시작해 파사드·테스트·어댑터 구분까지 이어진다.
- 초기 결제 흐름: 고객 조회 또는 생성 → PaymentIntent 생성 → 오류별 처리 순서로 구성된다.
- 샘플 실행: 간단한 main 함수가 샘플 주문을 만들고 pay_for_order를 몇 차례 호출한다.
- 테스트 구성: 실제 Stripe 대신 같은 계약을 구현하는 fake를 주입하고 pytest로 호출자 로직을 검증한다.
- 외부 시스템 사례: Stripe, Weather API, Pygame, SQLAlchemy, OpenAI, Anthropic이 경계 설계의 사례로 제시된다.
- 구분되는 패턴 수: 파사드와 어댑터라는 두 설계 패턴은 모두 래핑을 사용하지만 목표가 서로 다르다.
결론 및 시사점
- 서드파티 SDK를 빠르게 호출하는 것보다 외부 타입과 오류가 도메인에 침투하지 않을 경계를 먼저 정한다.
- 파사드의 반환 객체·오류·메서드 이름은 외부 제공자가 아니라 우리 도메인의 언어로 만든다.
- 외부 객체를 한 지점에서 자체 객체로 변환하면 API 교체와 버전 업그레이드의 영향 범위를 줄일 수 있다.
- 날씨 응답, 그래픽 엔진 도형, ORM 모델, AI 응답 객체도 같은 방식으로 경계 밖에 둔다.
- 행동만 감싼 pass-through wrapper, 책임을 다 떠안는 god facade, 중요한 비동기·인증 상태를 숨기는 facade를 피한다.
- 어댑터는 인터페이스 변환, 파사드는 복잡한 하위 시스템 접근 단순화라는 차이를 기준으로 선택한다.
- 외부 시스템의 연산·데이터·오류·어휘 중 무엇을 경계 너머로 보낼지 결정하는 일이 파사드 설계의 핵심이다.
- 좋은 경계는 비즈니스 로직을 깨끗하게 유지하고, 테스트에서 외부 구현을 fake로 바꾸며, 다른 제공자로의 교체 가능성을 높인다.
핵심 요약 (20줄)
- 서드파티 API의 타입과 오류가 애플리케이션 전역으로 퍼지면 외부 제공자의 설계가 우리 아키텍처를 지배한다.
- Stripe를 직접 호출하는 pay_for_order는 고객 조회·생성, PaymentIntent 생성, 오류 처리를 한곳에서 수행한다.
- 직접 통합은 처음에는 작동해도 결제가 필요한 모든 위치에 Stripe 특화 코드가 반복되는 문제를 만든다.
- Stripe 교체나 API 버전 업그레이드는 호출부 전체를 고쳐야 하는 대규모 리팩터링이 된다.
- PaymentIntent와 Stripe 카드 오류를 상위 로직에 노출하면 파사드를 만들어도 결합이 사라지지 않는다.
- 파사드는 복잡한 하위 시스템의 접근을 단순화하면서 애플리케이션과 외부 시스템 사이의 경계를 정해야 한다.
- PaymentService가 Stripe checkout 흐름을 감싸면 고객 생성과 결제 의도 생성의 반복을 줄일 수 있다.
- Stripe 응답 객체를 그대로 반환하는 래퍼는 외부 구현 세부사항을 호출자에게 다시 전달하는 pass-through wrapper다.
- PaymentsProtocol의 pay처럼 도메인 작업을 표현하는 인터페이스를 외부 SDK와 분리해야 한다.
- PaymentsResult는 Stripe PaymentIntent가 아니라 애플리케이션이 필요한 결제 정보만 담는 자체 객체여야 한다.
- StripeGateway는 고객·PaymentIntent·확인 호출·Stripe 오류 처리를 내부에 가둔 프로토콜 구현체다.
- 도메인 로직은 Stripe의 정확한 구현이나 고객 생성 방식 대신 결제 프로토콜과 자체 결과만 사용한다.
- Weather API 응답도 필요한 정보만 자체 객체로 변환해 공급자 스키마가 도메인에 새지 않게 해야 한다.
- Pygame vector와 엔진 전용 rectangle을 도메인 모델에 넣으면 그래픽 엔진이 애플리케이션 구조를 결정한다.
- SQLAlchemy 모델과 OpenAI·Anthropic 응답 객체도 필요한 데이터만 추출해 별도 도메인 객체로 바꿔야 한다.
- 경계는 연산뿐 아니라 데이터 구조, 오류 종류, 외부 시스템의 어휘가 통과할지까지 결정한다.
- 같은 프로토콜을 구현하는 fake를 주입하면 실제 Stripe 없이도 비즈니스 로직을 pytest로 검증할 수 있다.
- 결제·청구서·구독·고객·쿠폰을 한 객체에 넣는 god facade 대신 응집도 있는 서비스와 클래스로 분리해야 한다.
- 결제가 비동기이거나 고객 인증을 요구한다면 중요한 상태를 숨기지 말고 도메인 개념으로 노출해야 한다.
- 어댑터는 한 인터페이스를 다른 형태로 변환하고 파사드는 복잡한 하위 시스템 접근을 단순화한다.
📁 /Users/flowkater/Obsidian/flowkater/flowkater/Study/YouTube다이제스트/2026-09-05-ArjanCodes-third-party-api-문제-방지.md
