의존성 역전 실전
의존성 역전으로 어댑터를 핵심에 연결합니다.
의존성 역전 실전은(는) CoddyKit의 무료 PHP Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 PHP Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
역전과 주입
두 가지 서로 다른 개념을 혼동하는 경우가 많습니다. 의존성 주입은 협력 객체를 직접 생성하지 않고 전달받는 기법입니다. 의존성 역전(SOLID의 D)은 고수준 정책과 저수준 세부 사항이 모두 추상화에 의존하고, 그 추상화를 고수준 모듈이 소유해야 한다는 원칙입니다. 이 수업에서는 두 번째 원칙을 실제로 구현하는 방법, 즉 인터페이스를 정의하는 핵심부에 구체적인 어댑터를 연결하는 방법을 다룹니다.
원칙을 정확히 이해하기
DIP는 다음과 같이 말합니다.
- 고수준 모듈은 저수준 모듈에 의존하지 않아야 합니다. 둘 다 추상화에 의존해야 합니다.
- 추상화는 세부 사항에 의존하지 않아야 합니다. 세부 사항이 추상화에 의존해야 합니다.
미묘한 부분은 인터페이스가 구현자가 아니라 사용자에게 속한다는 점입니다. 도메인이 PaymentGateway를 선언하고 Stripe 어댑터가 이에 맞춰야 하며, 그 반대가 아닙니다.
핵심부에서 추상화 정의하기
인터페이스를 이를 필요로 하는 코드 옆에 두고 도메인 용어로 표현하십시오. Stripe의 형식이 새어 들어오면 안 됩니다.
<?php
// src/Billing/Application/Port/PaymentGateway.php
interface PaymentGateway
{
public function charge(Money $amount, CardToken $token): ChargeId;
}
final class Money {
public function __construct(public readonly int $cents, public readonly string $currency) {}
}
final class CardToken { public function __construct(public readonly string $value) {} }
final class ChargeId { public function __construct(public readonly string $value) {} }어댑터를 추상화에 맞추기
인프라 어댑터는 핵심부의 인터페이스를 구현하고 공급업체 SDK로 변환합니다. 의존성 화살표가 어댑터(세부 사항)에서 추상화(정책)로 향하므로 역전이 이루어진 것입니다.
<?php
final class StripePaymentGateway implements PaymentGateway
{
public function __construct(private \Stripe\StripeClient $stripe) {}
public function charge(Money $amount, CardToken $token): ChargeId {
$intent = $this->stripe->paymentIntents->create([
'amount' => $amount->cents,
'currency' => strtolower($amount->currency),
'payment_method' => $token->value,
'confirm' => true,
]);
return new ChargeId($intent->id);
}
}생성자 주입을 기본으로 사용하기
생성자를 통해 추상화를 주입하고 형식을 지정하십시오. 의존성이 명시적이고 변경할 수 없게 되며 빠뜨릴 수도 없습니다. 필수 협력 객체에는 세터 주입과 속성 주입을 사용하지 마십시오. 이러한 방식은 객체가 불완전하게 생성되는 것을 허용합니다.
<?php
final class CheckoutService
{
public function __construct(
private PaymentGateway $payments, // abstraction, not StripeClient
private OrderRepository $orders,
) {}
public function pay(OrderId $id, CardToken $token): ChargeId {
$order = $this->orders->get($id);
$charge = $this->payments->charge($order->total(), $token);
$order->markPaid($charge);
$this->orders->save($order);
return $charge;
}
}구성 루트
모든 구체적인 연결은 가능한 한 main()/진입점에 가까운 단 하나의 장소인 구성 루트에서 이루어져야 합니다. 다른 곳에서는 인프라를 직접 생성하지 않습니다. Stripe가 존재한다는 사실을 아는 곳도 여기뿐입니다.
<?php
// public/index.php — composition root
$stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET'));
$gateway = new StripePaymentGateway($stripe);
$orders = new PdoOrderRepository(new PDO(getenv('DB_DSN')));
$checkout = new CheckoutService($gateway, $orders);
// Everything below depends only on abstractions
$controller = new CheckoutController($checkout);DI 컨테이너로 연결하기
복잡한 애플리케이션에서는 컨테이너(PHP-DI, Symfony)가 연결 작업을 자동화합니다. 핵심은 인터페이스를 구현에 연결하는 것입니다. 자동 연결은 형식을 기준으로 생성자를 해결하므로 인터페이스→클래스 매핑만 선언하면 됩니다.
<?php
use function DI\autowire;
use function DI\get;
return [
PaymentGateway::class => autowire(StripePaymentGateway::class),
OrderRepository::class => autowire(PdoOrderRepository::class),
\Stripe\StripeClient::class => fn() => new \Stripe\StripeClient(getenv('STRIPE_SECRET')),
PDO::class => fn() => new PDO(getenv('DB_DSN')),
];어댑터 교체로 원리 입증하기
핵심부는 PaymentGateway에만 의존하므로 공급자를 바꾸거나 오프라인으로 테스트하는 작업이 연결 설정 한 줄을 바꾸는 일로 끝납니다. 다음은 단위 테스트에서 사용하는 가짜 구현입니다. CheckoutService는 변경되지 않으며 Stripe를 가져오지도 않습니다.
<?php
final class FakeGateway implements PaymentGateway {
public array $charges = [];
public function charge(Money $a, CardToken $t): ChargeId {
$this->charges[] = $a;
return new ChargeId('ch_test_' . count($this->charges));
}
}
$fake = new FakeGateway();
$id = $fake->charge(new Money(2500, 'EUR'), new CardToken('tok_visa'));
echo $id->value, ' charges=', count($fake->charges), PHP_EOL; // ch_test_1 charges=1서비스 로케이터 함정 피하기
컨테이너 자체를 주입하고 메서드 안에서 의존성을 꺼내는 것은 서비스 로케이터 반패턴입니다. 의존성을 숨기고, 형식 검사를 무력화하며, 코드를 컨테이너와 다시 결합시킵니다. 필요한 항목을 명시적으로 주입하십시오.
<?php
// ANTI-PATTERN: hidden dependencies, container leaks everywhere
final class BadCheckout {
public function __construct(private ContainerInterface $c) {}
public function pay($id, $token) {
$gateway = $this->c->get(PaymentGateway::class); // hidden!
// ...
}
}컨테이너가 나타나도 되는 곳
컨테이너는 정확히 한 계층에서만 사용해도 됩니다. 바로 구성 루트와 그 주변의 프레임워크 연결 코드(예: 컨트롤러 팩토리)입니다. 도메인과 애플리케이션 클래스는 컨테이너에 독립적으로 유지해야 합니다. 생성자를 통해 일반 객체를 받고 직접 생성할 수도 있어야 합니다. 좋은 테스트는 다음과 같습니다. 컨테이너 없이 하나의 PHP 파일에서 전체 애플리케이션을 연결할 수 있나요? 그렇다면 의존성이 정직하게 드러난 것입니다.
서비스 로케이션 없이 지연 연결하기
의존성을 생성하는 데 비용이 많이 들거나 특정 조건에서만 필요한 경우가 있습니다. 컨테이너를 주입하지 말고 대신 팩토리 클로저를 주입하십시오. 의존성은 명시적이고 형식이 지정된 상태로 유지하면서 실제로 사용할 때까지 생성을 미룰 수 있습니다.
<?php
final class ReportService
{
/** @param Closure():PaymentGateway $gatewayFactory */
public function __construct(private Closure $gatewayFactory) {}
public function refundIfNeeded(bool $needed): void {
if (!$needed) return;
$gateway = ($this->gatewayFactory)(); // built only when required
// $gateway->charge(...) etc.
}
}
// Composition root supplies the factory, not the container
$svc = new ReportService(fn() => new StripePaymentGateway($stripe ?? null));
echo 'lazy dependency wired', PHP_EOL;빠른 확인
PaymentGateway 인터페이스는 누가 소유해야 하나요?
복습
어댑터를 핵심부에 올바른 방식으로 연결했습니다.
- 역전은 주입과 다릅니다. 주입은 메커니즘이고, 역전은 사용자가 추상화를 소유하는 것입니다.
- 핵심부가 인터페이스를 정의하고 인프라 어댑터가 이에 맞춥니다.
- 추상화를 생성자로 주입하고, 모든 구체적인 연결은 하나의 구성 루트에서 수행합니다(또는 인터페이스→클래스를 연결하는 컨테이너 설정에서 수행합니다).
- 어댑터를 교체하거나 테스트에서 가짜 구현을 사용하는 일이 한 줄 변경으로 가능해집니다.
- 서비스 로케이터 반패턴을 피하고 컨테이너는 가장자리에서만 사용하십시오.
자주 묻는 질문
“의존성 역전 실전” 강의는 무료인가요?
네 — “의존성 역전 실전” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 PHP Academy 강의 전체를 잠금 해제할 수 있습니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“의존성 역전 실전”에서 뭘 배우나요?
의존성 역전으로 어댑터를 핵심에 연결합니다. 브라우저에서 직접 실행하는 실습 코드로 PHP Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
PHP Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 PHP Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“의존성 역전 실전” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 PHP Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 PHP Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.