시스템 아키텍처 설계 패턴
🏗️ 아키텍처 패턴의 필요성
왜 아키텍처 패턴이 중요한가?
상황: "새 시스템을 만들어야 하는데... 어떻게 설계하지?"
현실:
😵 "처음부터 다 설계하기엔 너무 복잡해"
🤔 "검증된 방법이 있을 텐데..."
💡 "아, 이미 성공한 패턴들을 활용하자!"
아키텍처 패턴 = 반복되는 설계 문제에 대한 검증된 해결책
패턴 vs 안티패턴
- 패턴: 반복적으로 성공한 설계 방법론
- 안티패턴: 피해야 할 일반적인 설계 실수
- 맥락: 언제, 왜 특정 패턴을 사용해야 하는지
🎯 주요 아키텍처 패턴 분류
1. 애플리케이션 아키텍처 패턴
1.1 Layered Architecture (계층형 아키텍처)
구조:
┌─────────────────┐
│ Presentation │ ← UI, 컨트롤러
├─────────────────┤
│ Business │ ← 비즈니스 로직
├─────────────────┤
│ Persistence │ ← 데이터 접근
├─────────────────┤
│ Database │ ← 데이터 저장
└─────────────────┘
특징: 위에서 아래로만 호출 (단방향 의존성)
장점:
- 이해하기 쉬운 구조
- 역할과 책임이 명확
- 테스트하기 용이
단점:
- 성능 오버헤드 (모든 계층 통과)
- 유연성 부족 (계층 간 강한 결합)
적용 사례:
// 전형적인 Spring MVC 구조
@Controller
public class UserController {
@Autowired UserService userService; // Business Layer
}
@Service
public class UserService {
@Autowired UserRepository userRepo; // Persistence Layer
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Database Layer
}1.2 Hexagonal Architecture (육각형 아키텍처)
구조:
외부 시스템들
↕️
┌─────────────────┐
│ Adapters │ ← 외부 인터페이스
│ │
│ ┌───────────┐ │
│ │ │ │
│ │ Core/Domain │ │ ← 핵심 비즈니스 로직
│ │ │ │
│ └───────────┘ │
│ │
│ Ports │ ← 내부 인터페이스
└─────────────────┘
별칭: Ports & Adapters Architecture
핵심 개념:
- 포트(Port): 비즈니스 로직이 외부와 소통하는 인터페이스
- 어댑터(Adapter): 외부 시스템과 포트를 연결하는 구현체
- 의존성 역전: 외부가 내부에 의존 (내부는 외부를 모름)
장점:
- 비즈니스 로직이 기술에 독립적
- 테스트 용이성 (Mock 객체 활용)
- 유연한 확장성
실제 구현 예시:
// Core Domain (내부)
public class OrderService {
private OrderRepository orderRepo; // Port
private PaymentGateway paymentGateway; // Port
private NotificationService notifier; // Port
public Order processOrder(OrderRequest request) {
// 핵심 비즈니스 로직 (기술과 무관)
Order order = createOrder(request);
paymentGateway.processPayment(order.getPayment());
orderRepo.save(order);
notifier.sendConfirmation(order);
return order;
}
}
// Adapter (외부 구현체)
@Repository
public class JpaOrderRepository implements OrderRepository {
// 구체적인 DB 기술 (JPA)
}
@Component
public class StripePaymentGateway implements PaymentGateway {
// 구체적인 결제 시스템 (Stripe)
}1.3 Event-Driven Architecture (이벤트 기반 아키텍처)
구조:
Producer → Event Bus → Consumer(s)
예시:
주문생성 → [OrderCreated] → 재고감소, 결제처리, 이메일발송
핵심 개념:
- 이벤트: 시스템에서 발생한 의미있는 상태 변화
- 프로듀서: 이벤트를 발생시키는 컴포넌트
- 컨슈머: 이벤트에 반응하는 컴포넌트
- 이벤트 버스: 이벤트를 전달하는 메시징 시스템
장점:
- 높은 확장성 (느슨한 결합)
- 시스템 간 독립성
- 비동기 처리로 성능 향상
단점:
- 복잡한 디버깅 (이벤트 추적 어려움)
- 이벤트 순서 보장 문제
- 데이터 일관성 관리 복잡
실제 구현 예시:
// 이벤트 정의
const OrderEvents = {
ORDER_CREATED: 'order.created',
ORDER_PAID: 'order.paid',
ORDER_SHIPPED: 'order.shipped'
};
// 프로듀서
class OrderService {
async createOrder(orderData) {
const order = await this.orderRepo.save(orderData);
// 이벤트 발행
this.eventBus.publish(OrderEvents.ORDER_CREATED, {
orderId: order.id,
customerId: order.customerId,
items: order.items
});
return order;
}
}
// 컨슈머들
class InventoryService {
@EventHandler(OrderEvents.ORDER_CREATED)
async handleOrderCreated(event) {
await this.reduceInventory(event.items);
}
}
class NotificationService {
@EventHandler(OrderEvents.ORDER_CREATED)
async handleOrderCreated(event) {
await this.sendOrderConfirmation(event.customerId, event.orderId);
}
}2. 분산 시스템 패턴
2.1 Microservices Architecture (마이크로서비스)
구조:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ User Svc │ │Order Svc │ │Payment │
│ │ │ │ │ Svc │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────────────┼──────────────┘
│
┌───────┴────────┐
│ API Gateway │
└────────────────┘
핵심 원칙:
- 단일 책임: 한 서비스는 한 가지 비즈니스 기능만
- 독립 배포: 서비스별로 독립적 배포 가능
- 데이터 격리: 서비스마다 독립적인 데이터베이스
- 장애 격리: 한 서비스 장애가 전체에 영향 주지 않음
장점:
- 개발팀 독립성 (Conway’s Law)
- 기술 스택 자유도
- 확장성 (서비스별 개별 스케일링)
단점:
- 운영 복잡성 증가
- 네트워크 지연 및 장애
- 데이터 일관성 관리 어려움
- 분산 트랜잭션 복잡성
2.2 API Gateway Pattern
클라이언트 → API Gateway → 마이크로서비스들
기능:
- 라우팅
- 인증/인가
- 로드밸런싱
- 레이트 리미팅
- 모니터링
실제 구현 예시 (Kong Gateway):
# kong.yml
_format_version: "3.0"
services:
- name: user-service
url: http://user-svc:3000
routes:
- name: user-routes
paths:
- /api/v1/users
- name: order-service
url: http://order-svc:3000
routes:
- name: order-routes
paths:
- /api/v1/orders
plugins:
- name: rate-limiting
config:
minute: 100
hour: 1000
- name: jwt
config:
secret_is_base64: false2.3 Service Mesh Pattern
구조:
Service A ←→ Sidecar Proxy ←→ Sidecar Proxy ←→ Service B
Sidecar들이 서비스 메시 구성
- 트래픽 관리
- 보안
- 관찰 가능성
대표적인 구현: Istio, Linkerd
3. 데이터 아키텍처 패턴
3.1 CQRS (Command Query Responsibility Segregation)
구조:
Write Side (Command): Client → Command Handler → Write DB
Read Side (Query): Client → Query Handler → Read DB
↕️ (Event Sourcing)
적용 시나리오:
- 읽기와 쓰기 패턴이 매우 다른 경우
- 복잡한 비즈니스 로직 vs 단순한 조회
- 높은 읽기 성능이 필요한 경우
구현 예시:
// Command Side
public class CreateOrderCommand {
public Guid CustomerId { get; set; }
public List<OrderItem> Items { get; set; }
}
public class CreateOrderHandler {
public async Task Handle(CreateOrderCommand command) {
var order = new Order(command.CustomerId, command.Items);
await writeRepository.Save(order);
// 이벤트 발행 (Read Side 업데이트용)
await eventBus.Publish(new OrderCreatedEvent(order));
}
}
// Query Side
public class OrderSummaryQuery {
public Guid CustomerId { get; set; }
}
public class OrderSummaryHandler {
public async Task<OrderSummary> Handle(OrderSummaryQuery query) {
return await readRepository.GetOrderSummary(query.CustomerId);
}
}3.2 Database per Service
마이크로서비스마다 독립적인 데이터베이스
User Service → PostgreSQL (관계형)
Order Service → MongoDB (문서형)
Analytics Service → ClickHouse (분석용)
장점:
- 서비스 간 데이터 격리
- 기술 스택 선택의 자유
- 독립적인 스케일링
단점:
- 분산 트랜잭션 복잡성
- 데이터 중복 가능성
- 조인 쿼리 불가 (서비스 간)
3.3 Saga Pattern (분산 트랜잭션 관리)
Choreography 방식:
Order Svc → Payment Svc → Inventory Svc → Shipping Svc
↓ ↓ ↓ ↓
Order Payment Stock Shipping
Created Done Reserved Scheduled
실패 시 Compensation:
← Order ← Payment ← Stock ← Shipping
Canceled Refunded Released Canceled
두 가지 방식:
- Choreography: 각 서비스가 다음 단계 직접 호출
- Orchestration: 중앙 오케스트레이터가 전체 플로우 관리
🎯 패턴 선택 가이드
프로젝트 규모별 권장 패턴
스타트업 / MVP
권장: Layered Architecture + Monolith
이유:
- 빠른 개발과 배포
- 간단한 운영
- 팀 크기 고려
성장 단계
권장: Modular Monolith → Strangler Fig → Microservices
이유:
- 점진적 복잡성 증가
- 팀과 시스템의 동반 성장
- 학습 곡선 고려
대기업 / 복잡한 도메인
권장: Microservices + Event-Driven + CQRS
이유:
- 복잡성 관리 필요
- 팀 간 독립성 중요
- 높은 확장성 요구사항
기술팀 성숙도별 권장
Junior Team
추천:
- Layered Architecture (이해하기 쉬움)
- Hexagonal Architecture (테스트 배우기 좋음)
- 단순한 Event-Driven
피해야 할 것:
- 복잡한 Microservices
- 과도한 분산 패턴
Senior Team
활용 가능:
- 모든 패턴 적용 가능
- 상황에 맞는 최적 선택
- 커스텀 패턴 설계
주의사항:
- 과도한 엔지니어링 경계
- 비즈니스 가치 우선 고려
🔧 AI 시대의 아키텍처 패턴
AI가 바꾸는 설계 방식
기존: 경험 + 도서 + 컨퍼런스 → 설계 결정
AI 시대: AI 분석 + 추천 → 인간 검증 → 설계 결정
예시:
"Gemini야, 우리 주문 시스템에 적합한 아키텍처 패턴 추천해줘"
→ 요구사항 분석 → 패턴 추천 → 장단점 비교 → 구현 가이드
AI 활용 아키텍처 설계 프로세스
- 요구사항 분석: AI가 비즈니스 요구사항을 기술 요구사항으로 변환
- 패턴 추천: 유사한 사례 기반 최적 패턴 제안
- 트레이드오프 분석: 각 패턴의 장단점 상세 비교
- 구현 가이드: 선택된 패턴의 구체적 구현 방법 제시
- 지속적 최적화: 운영 데이터 기반 아키텍처 개선 제안
📚 실전 적용 체크리스트
아키텍처 패턴 선택 시 고려사항
□ 비즈니스 요구사항 명확화
□ 팀 역량 및 경험 수준 평가
□ 기술 제약사항 확인
□ 성능 및 확장성 요구사항 분석
□ 운영 및 유지보수 계획 수립
□ 비용 대비 효과 분석
□ 마이그레이션 전략 수립 (기존 시스템이 있는 경우)
패턴 적용 후 검증 포인트
✅ 코드 가독성과 유지보수성 향상
✅ 팀 생산성 증가
✅ 시스템 안정성 및 성능 개선
✅ 요구사항 변화에 대한 유연한 대응
✅ 새로운 팀원의 빠른 온보딩
🔮 미래의 아키텍처 패턴
새롭게 주목받는 패턴들
- Serverless Architecture: 서버 관리 없는 이벤트 기반 아키텍처
- Edge Computing: 사용자에 가까운 곳에서 처리하는 분산 아키텍처
- Mesh Architecture: 중앙 집중식에서 완전 분산형으로의 진화
- AI-Native Architecture: AI 모델이 핵심인 시스템 설계
불변의 원칙들
아키텍처 패턴은 계속 진화하지만, 핵심 원칙은 변하지 않음:
- 관심사의 분리 (Separation of Concerns)
- 단일 책임 원칙 (Single Responsibility)
- 느슨한 결합 (Loose Coupling)
- 높은 응집도 (High Cohesion)
- 의존성 역전 (Dependency Inversion)
작성일: 2025-11-08
참고자료: Martin Fowler’s Architecture Patterns, AWS Well-Architected Framework
관련 문서: 시스템_마이그레이션_패턴_완전정복