모놀리스에서 마이크로서비스로
무엇을 분리하고 서비스 경계를 어떻게 정할지 결정합니다.
모놀리스에서 마이크로서비스로은(는) CoddyKit의 무료 PHP Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 PHP Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
모놀리스에서 마이크로서비스로
마이크로서비스는 기본 선택이 아니라 조직과 운영 측면에서 감수하는 선택입니다. PHP 모놀리스를 분할하면 프로세스 내부의 단순성을 네트워크 호출, 분산 데이터, 배포 복잡성과 맞바꾸게 됩니다. 잘못된 이유로 진행하면 모든 것이 느려집니다.
이 강의에서는 가장 어려운 부분인 서비스 경계 그리기를 다룹니다. 경계를 올바르게 정하면 나머지는 배관 작업에 불과하지만, 잘못 정하면 모든 비용은 부담하고 이점은 하나도 없는 분산형 모놀리스를 만들게 됩니다.
분할해야 하는 이유와 하지 말아야 하는 이유
분할해야 하는 좋은 이유:
- 독립적인 배포 가능성과 팀별 소유권.
- 부하가 높은 하위 시스템의 독립적인 확장.
- 기술/런타임 격리 및 장애 격리.
나쁜 이유는 "코드가 엉망이다"(먼저 모놀리스를 리팩터링하십시오) 또는 유행을 좇는 것입니다. 두 서비스가 항상 함께 배포되어야 하거나 데이터베이스 테이블을 공유해야 한다면, 사실상 한 서비스가 두 역할을 맡은 것입니다.
바운디드 컨텍스트
도메인 주도 설계는 경계를 정하는 가장 명확한 도구인 바운디드 컨텍스트를 제공합니다. 하나의 컨텍스트 안에서는 용어가 하나의 정확한 의미를 가집니다. 청구에서의 "고객"은 청구서 수신 대상인 반면, 지원에서의 "고객"은 티켓 작성자입니다.
각 바운디드 컨텍스트는 서비스의 유력한 후보입니다. 경계는 비즈니스 언어와 조직이 실제로 소통하는 방식에 따라 정해집니다. 이는 콘웨이의 법칙이 실제로 적용되는 사례입니다.
높은 응집도, 낮은 결합도
좋은 서비스 경계는 함께 변경되는 것들을 내부에 두고, 독립적으로 변경되는 것들은 외부로 밀어냅니다. 다음 기준으로 제안된 분할을 평가하십시오:
- 몇 개의 기능이 동시에 두 서비스를 건드려야 합니까? (적어야 합니다.)
- 두 서비스 사이의 호출 패턴이 얼마나 잦게 호출하는 형태입니까? (큰 단위여야 합니다.)
하나의 기능을 구현할 때 계속 경계를 넘나든다면 경계를 잘못 설정한 것입니다.
<?php
// Chatty boundary smell: N network calls to render one view
foreach ($order->lineItems as $item) {
$product = $catalogApi->get($item->productId); // one call PER item!
$names[] = $product['name'];
}
// Coarse-grained: one batch call across the boundary
$ids = array_map(fn($i) => $i->productId, $order->lineItems);
$names = $catalogApi->getMany($ids); // single round-trip
echo count($names) . " products in one call\n";서비스별 데이터베이스
타협할 수 없는 규칙은 다음과 같습니다. 각 서비스가 자신의 데이터를 소유하고, 다른 서비스는 그 테이블에 접근하지 않아야 합니다. 공유 데이터베이스는 벗어나기 위해 분리했던 결합을 다시 만듭니다. 스키마 변경 하나가 관련 없는 서비스들을 고장 낼 수 있습니다.
따라서 모놀리스에서 SQL JOIN으로 처리하던 서비스 간 쿼리는 API 호출이나 복제된 읽기 모델이 됩니다. 이것이 치러야 할 대가이며, 바로 그 점이 목적입니다.
<?php
// In the monolith: one JOIN across domains
$sql = 'SELECT o.id, c.email
FROM orders o JOIN customers c ON c.id = o.customer_id';
// After split: Orders service holds only the foreign id;
// it asks the Customers service for the rest (or keeps a local read model).
$order = $orderRepo->find($id); // local
$customer = $customersApi->get($order->customerId); // network call
echo $order->id . ' / ' . $customer->email . "\n";스트랭글러 피그 패턴
모놀리스를 한 번에 전부 다시 작성하지 마십시오. 스트랭글러 피그 패턴은 점진적으로 추출합니다. 퍼사드/프록시를 통해 트래픽 일부를 새 서비스로 라우팅하고, 새 서비스의 범위를 확장한 다음, 새 구현이 기존 경로를 완전히 대체했을 때만 이전 코드 경로를 폐기합니다.
역방향 프록시(또는 API 게이트웨이)가 앞단에 위치하여 경로별로 레거시 모놀리스와 새 서비스 중 어디에 요청할지 결정합니다. 마이그레이션은 한 번에 하나의 기능씩 진행되며, 각 단계에서 항상 배포할 수 있습니다.
<?php
// Facade routing: peel off one capability at a time
function route(string $path): string {
$migrated = ['/invoices', '/invoices/pdf']; // moved to billing-svc
foreach ($migrated as $prefix) {
if (str_starts_with($path, $prefix)) {
return 'http://billing-svc' . $path;
}
}
return 'http://legacy-monolith' . $path; // everything else, for now
}
echo route('/invoices/pdf'), "\n";
echo route('/users/42'), "\n";모듈 추출
실용적인 추출 순서는 다음과 같습니다:
- 들어오는 의존성이 적고 데이터 소유권이 명확한 모듈을 찾습니다.
- 먼저 모놀리스 내부에서 해당 모듈에 대한 프로세스 내부 호출을 인터페이스 뒤로 감쌉니다.
- 모듈이 소유한 데이터를 자체 스키마/DB로 옮깁니다.
- 인터페이스 구현을 네트워크 클라이언트로 교체합니다.
- 퍼사드를 통해 트래픽을 전환하고 이전 코드를 삭제합니다.
먼저 모놀리스 내부에서 인터페이스로 감싸면 네트워크 단계의 위험을 줄일 수 있습니다.
<?php
// Step 2: hide the implementation behind a port the monolith calls
interface InvoiceService {
public function generate(string $orderId): string; // returns invoice id
}
// Today: local class. Tomorrow: HTTP client to billing-svc.
// The monolith's calling code never changes.
final class LocalInvoiceService implements InvoiceService {
public function generate(string $orderId): string { return 'inv-1'; }
}분산 데이터 일관성
데이터를 분리하면 서비스 간 ACID 트랜잭션을 사용할 수 없게 됩니다. 최종적 일관성을 받아들이십시오. 각 서비스는 자신의 데이터에 관한 이벤트를 발행하고, 다른 서비스는 해당 이벤트로부터 로컬 읽기 모델을 구축합니다.
주문 서비스는 모든 요청마다 고객 서비스를 조회하지 않습니다. 대신 CustomerUpdated 이벤트로 갱신되는 작은 프로젝션(필요한 필드만 포함)을 유지합니다. 이렇게 하면 실행 시 의존성과 지연 시간 단계를 제거할 수 있습니다.
<?php
// Orders service keeps a tiny local projection of customer data
function onCustomerUpdated(PDO $db, array $evt): void {
$db->prepare(
'INSERT INTO customer_read_model (id, email)
VALUES (:id, :email)
ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email'
)->execute(['id' => $evt['id'], 'email' => $evt['email']]);
}운영 현실
마이크로서비스는 복잡성을 코드에서 운영으로 옮깁니다. 분할하기 전에 다음이 필요합니다:
- 중앙 집중식 로그 수집과 분산 추적(호출 단계마다 전달되는 상관관계 식별자).
- 서비스별 CI/CD와 버전을 독립적으로 관리하는 배포.
- 모든 호출에 대한 상태 확인, 시간 제한, 재시도, 회로 차단기.
- 제공자가 소비자를 조용히 고장 내지 못하도록 하는 계약 테스트.
팀이 서비스 하나도 제대로 운영하지 못한다면, 열 개를 운영할 때는 열 배나 더 나빠집니다.
서비스 규모 조정
"마이크로"라는 말은 오해를 부릅니다. 서비스 규모는 코드 줄 수가 아니라 비즈니스 역량과 팀 소유권을 기준으로 정하십시오. 너무 세분화하면(나노서비스) 하나의 기능이 수많은 네트워크 호출로 퍼지고, 너무 크게 잡으면 다시 모놀리스로 돌아가게 됩니다.
건강한 판단 기준은 다음과 같습니다. 하나의 팀이 서비스를 책임질 수 있고, 독립적으로 배포할 수 있으며, 여러 동료 서비스를 잇는 동기 호출 사슬 없이 핵심 사용 사례를 처리할 수 있어야 합니다.
모듈형 모놀리스라는 대안
분산 구조로 가기 전에 모듈형 모놀리스를 고려하십시오. 배포 단위는 하나지만 내부적으로는 명시적인 경계와 자체 스키마를 가진 모듈로 나뉘며, 모듈 간에는 공개된 인터페이스로만 통신하고 다른 모듈의 테이블에는 접근하지 않습니다.
분산 시스템의 비용을 부담하지 않고도 깔끔한 경계와 손쉬운 리팩터링을 얻을 수 있습니다. 모듈에 독립적인 확장이나 소유권이 정말 필요해지면, 이미 스트랭글러 피그 패턴으로 분리해낼 수 있는 형태가 갖춰져 있습니다. 대부분의 팀에는 이것이 올바른 첫 단계입니다.
<?php
// Modules talk only through interfaces, never each other's tables.
namespace App\Billing; // owns billing_* tables
interface BillingFacade {
public function invoiceForOrder(string $orderId): string;
}
namespace App\Sales; // owns sales_* tables
final class Checkout {
public function __construct(private \App\Billing\BillingFacade $billing) {}
// Sales never SELECTs from billing_* directly - only via the facade.
}빠른 확인
잘못된 분할을 식별하는 방법입니다.
복습
서비스 경계를 올바르게 그리려면 다음을 따르십시오:
- 코드가 엉망이어서가 아니라 배포 가능성, 확장, 소유권을 위해 분할합니다.
- 경계를 바운디드 컨텍스트에 맞추고, 높은 응집도와 낮은 결합도를 목표로 합니다.
- 서비스별 데이터베이스를 사용합니다. 공유 테이블을 두지 않고 JOIN을 API나 읽기 모델로 대체합니다.
- 스트랭글러 피그 패턴으로 점진적으로 마이그레이션합니다.
- 최종적 일관성을 받아들이고 확장하기 전에 운영에 투자합니다.
다음: 이러한 서비스가 실제로 통신하는 방법 — REST 및 gRPC.
자주 묻는 질문
“모놀리스에서 마이크로서비스로” 강의는 무료인가요?
네 — “모놀리스에서 마이크로서비스로” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 PHP Academy 강의 전체를 잠금 해제할 수 있습니다. PHP Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“모놀리스에서 마이크로서비스로”에서 뭘 배우나요?
무엇을 분리하고 서비스 경계를 어떻게 정할지 결정합니다. 브라우저에서 직접 실행하는 실습 코드로 PHP Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
PHP Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 PHP Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“모놀리스에서 마이크로서비스로” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 PHP Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 PHP Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 모놀리스에서 마이크로서비스로
- 서비스 통신: REST와 gRPC
- API 게이트웨이와 서비스 검색
- 복원력: 회로 차단기와 재시도