← all posts
DEV 2026.05.02 · 12 min read Intermediate

아키텍처 패턴의 공통 언어 — 관심사 분리란 무엇인가

Layered Architecture부터 Microservices까지, 8개 아키텍처 패턴이 공유하는 하나의 원칙을 추적한다. 각 패턴은 왜 같은 문제를 다르게 풀었는가.


Layered Architecture, MVC, Repository, Hexagonal, MVVM, Event-Driven, Microservices, MVP. 이 여덟 가지 패턴은 전혀 다른 문제를 풀기 위해 만들어진 것처럼 보인다. 그런데 각 챕터의 “문제 상황” 섹션을 나란히 놓으면 동일한 코드가 반복된다. God Object, 계층 간 강한 결합, 테스트 불가능한 구조. 이 패턴들은 같은 적을 상대로 서로 다른 무기를 들었다. 그 적의 이름은 관심사의 혼재다. 왜 이 하나의 개념이 아키텍처 패턴 전체를 관통하는가?

문제는 항상 같았다

모든 패턴의 출발점에는 공통 코드가 있다.

public class UserService {
    public void registerUser(String name, String email, String password) {
        // UI 로직
        System.out.println("처리 중...");
        // 비즈니스 로직
        String hashed = BCrypt.hashpw(password, BCrypt.gensalt());
        // DB 접근
        Connection conn = DriverManager.getConnection(DB_URL);
        PreparedStatement stmt = conn.prepareStatement(
            "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"
        );
        stmt.executeUpdate();
        // 외부 서비스
        sendWelcomeEmail(email);
    }
}

이 클래스는 UI와 비즈니스 로직과 DB 접근과 외부 서비스를 동시에 안다. 변경이 일어날 이유가 네 가지 이상이다. Robert C. Martin의 표현을 빌리면 “변경의 이유가 하나 이상이면 그것은 단일 책임 원칙 위반”이다. 각 패턴은 이 문제를 서로 다른 축을 따라 분리한다.

분리의 축이 다르다

Layered Architecture는 수평으로 자른다. Presentation → Application → Domain → Infrastructure. 각 계층은 바로 아래 계층만 의존한다. MVC는 같은 아이디어를 더 좁은 범위에 적용한다. 단일 화면 안에서 Model, View, Controller를 분리한다.

Repository 패턴은 분리의 위치를 더 구체적으로 지정한다. “도메인 로직과 데이터 접근 로직 사이에 경계를 그어라.” 인터페이스는 Domain Layer에, 구현체는 Infrastructure Layer에.

// Domain Layer에 인터페이스만
public interface OrderRepository {
    Order save(Order order);
    Optional<Order> findById(Long id);
}

// Infrastructure Layer에 구현체
public class JpaOrderRepository implements OrderRepository {
    // JPA 세부사항은 여기에만
}

이 구조의 핵심은 의존성의 방향이다. 도메인은 JPA를 모른다. JPA가 도메인을 안다. 이것이 Dependency Inversion Principle이다.

Hexagonal Architecture는 이 원칙을 극단으로 밀어붙인다. “도메인은 외부 세계를 전혀 몰라야 한다.” Port(인터페이스)를 경계로 두고, 모든 외부 접점은 Adapter가 담당한다. JPA가 바뀌면 Adapter만 교체하면 된다. 도메인 코드는 건드리지 않는다.

트레이드오프

관심사를 분리할수록 코드량은 늘어난다. Layered Architecture는 계층마다 DTO 변환 비용이 생긴다. Hexagonal은 Domain ↔ JPA Entity 간 변환 코드가 따라붙는다. Microservices는 서비스 간 네트워크 지연이 발생한다. 분리의 편익은 장기적으로 발생하고, 비용은 즉시 발생한다. “이 시스템이 얼마나 오래, 얼마나 크게 유지될 것인가”가 선택의 기준이 된다.

동기화 문제를 해결하는 두 가지 방법

MVC에서 Model이 변경되면 View에 알려야 한다. 전통적인 방법은 Observer 패턴이다. Model이 notifyObservers()를 호출하면 View가 update()를 받는다.

MVP는 이 구조를 뒤집는다. View를 완전히 수동적으로 만들고(Passive View), Presenter가 모든 것을 제어한다. View는 showUserName(String name)처럼 단순 명령만 받는다. 덕분에 Presenter는 View Interface만 있으면 테스트가 가능하다. Mockito로 View를 Mock 처리하면 UI 없이도 모든 로직을 검증할 수 있다.

MVVM은 반대 방향으로 간다. 수동적으로 만드는 대신 Observable 프로퍼티를 이용해 자동 동기화한다.

// ViewModel
private final StringProperty name = new SimpleStringProperty();

// View에서 바인딩 한 줄
label.textProperty().bind(viewModel.nameProperty());

이제 name이 바뀌면 label도 자동으로 바뀐다. 수동 update() 호출이 사라진다. MVP가 Presenter에 통제권을 집중시켰다면, MVVM은 바인딩 선언을 통해 통제권을 분산시켰다.

결합을 시간의 축으로 분리하다

Event-Driven Architecture는 다른 차원의 분리를 제안한다. 공간적 분리(계층, 포트)가 아니라 시간적 분리다.

// Before: 직접 호출 — 동기, 강한 결합
public void createOrder(Order order) {
    orderRepository.save(order);
    emailService.send(order);      // 2초 대기
    smsService.send(order);        // 1초 대기
    analyticsService.track(order); // 0.5초 대기
}

// After: 이벤트 발행 — 비동기, 느슨한 결합
public void createOrder(Order order) {
    orderRepository.save(order);
    eventBus.publish(new OrderCreatedEvent(order));
    // 이 줄이 끝나는 즉시 반환
}

OrderService는 이제 누가 이 이벤트를 받는지 모른다. EmailListener가 추가되거나 삭제되어도 OrderService 코드는 변경되지 않는다. 새 기능을 추가하는 것이 기존 코드를 수정하지 않는 일이 된다. Open-Closed Principle이 자연스럽게 달성된다.

분리를 프로세스 경계까지 확장하다

Microservices는 관심사 분리를 극한까지 밀어붙인다. 코드 수준의 분리(패키지, 계층)를 넘어, 프로세스와 네트워크 경계로 분리한다.

각 서비스는 독립적인 DB를 소유한다. OrderServiceuserId만 알고 User 객체는 모른다. 서로 다른 기술 스택을 쓸 수 있다. User Service는 PostgreSQL, Product Service는 MongoDB. 확장도 독립적이다. Order Service에 트래픽이 몰리면 Order Service 인스턴스만 늘리면 된다.

대신 분산 시스템의 복잡도가 따라온다. 서비스 간 통신이 실패할 수 있다. 트랜잭션이 두 서비스에 걸쳐 있으면 Saga 패턴이 필요하다. 모니터링도 각 서비스마다 따로 해야 한다.

정리

  • 모든 아키텍처 패턴은 “변경의 이유를 하나로 줄여라”라는 단일 원칙의 구체적 표현이다.
  • 분리의 축은 다르다. 수평(Layered), 경계(Hexagonal), 화면 내부(MVC/MVP/MVVM), 시간(Event-Driven), 프로세스(Microservices).
  • 분리 비용은 즉시 발생하고, 분리 편익은 나중에 발생한다. 시스템의 수명과 규모가 선택 기준이다.
  • 패턴을 섞어 쓰는 것이 정답일 때가 많다. Hexagonal Architecture 내부에서 Event-Driven을 쓰고, Microservices 안에서 MVC를 쓴다.

다음 글에서는 이 시리즈의 각 패턴이 Java 코드 수준에서 어떻게 구현되는지, Layered Architecture의 계층 간 DTO 변환과 트랜잭션 경계를 구체적으로 추적한다.