왜 클린 아키텍처는 모두를 혼란스럽게 하는가 (그리고 나는 어떻게 걱정을 멈추었는가)

발행: (2026년 2월 4일 오후 11:03 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

위 링크에 포함된 본문을 번역하려면 해당 글의 전체 텍스트가 필요합니다. 현재 제공된 내용에는 번역할 본문이 포함되어 있지 않으므로, 번역을 진행하려면 원문을 복사해서 알려주시기 바랍니다.

The Framework Problem

Spring Boot 프로젝트를 시작하면, 프레임워크가 사실상 다음과 같이 하도록 강요합니다:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false)
    private String customerEmail;

    @OneToMany(mappedBy = "order")
    private List items;
}

@Service
public class OrderService {
    @Autowired
    private OrderRepository repository;

    public Order save(Order order) {
        return repository.save(order);
    }
}

작동합니다. 작성도 빠릅니다. DevOps 팀은 빠르게 전달했기 때문에 만족합니다. 하지만 문제는 비즈니스 로직이 이제 JPA, Jackson, 그리고 아마도 수십 가지 다른 프레임워크 관점을 알게 되었다는 점입니다.

클린 아키텍처는 도메인이 이러한 것들을 알면 안 된다고 말합니다. 그런데 Spring Boot은 바로 그곳에 있어, 또 하나의 어노테이션을 추가하는 것이 너무 쉬워집니다.

패키지‑별 레이어 함정

대부분의 튜토리얼은 다음 구조를 가르칩니다:

src/main/java/
├── controller/
├── service/
├── repository/
└── model/

자연스럽게 느껴집니다. 모두가 이렇게 합니다. 하지만 이는 클린 아키텍처의 핵심을 완전히 놓칩니다.

문제는 폴더가 아니라 이 구조가 기술 레이어 관점에서 사고하도록 유도한다는 점입니다.

대신 기능별로 조직하면:

src/main/java/
├── order/
│   ├── domain/
│   ├── application/
│   └── infrastructure/
├── product/
└── customer/

흥미로운 일이 일어납니다: 시스템이 무엇을 하는지에 대해 생각하게 되고, 어떤 기술을 사용하는지는 덜 중요해집니다.

실제 시스템을 구축하며 배운 점

이전 직무에서 나는 고처리량 청구 시스템, 실시간 데이터 플랫폼, 다중 테넌트 공공 행정 시스템 등을 다뤘다. 실제로 중요한 것은 다음과 같다:

1. 비즈니스 로직은 지루해야 한다

핵심 비즈니스 규칙은 다음에 신경 쓰지 않아야 한다:

  • 데이터가 어떻게 저장되는지 (PostgreSQL? MongoDB? Firestore?)
  • 요청이 어떻게 들어오는지 (REST? GraphQL? Message queue?)
  • 어떤 프레임워크를 사용하는지

MongoDB에서 PostgreSQL로 시스템을 마이그레이션해야 했을 때, 겪은 고통이 아키텍처 품질을 그대로 보여줬다. 비즈니스 로직이 데이터베이스 어노테이션과 얽혀 있다면, 마이그레이션은 악몽이 된다.

2. 목적 없는 인터페이스는 잡음이다

누군가 “클린 아키텍처는 포트와 어댑터가 필요하다”라고 말한다 해서 인터페이스를 만들지 말고, 구현을 교체하거나 의존성을 격리해야 할 실제 필요가 있을 때 만들라.

저는 레포지토리 인터페이스가 정확히 하나의 구현만을 가지고 영원히 존재하는 코드베이스를 본 적이 있다. 그것은 아키텍처가 아니라 형식적인 절차일 뿐이다.

3. 진짜 시험은 변화이다

다음이 가능한가?

  • 비즈니스 로직을 건드리지 않고 PostgreSQL을 다른 데이터베이스로 교체할 수 있는가?
  • 서비스를 다시 작성하지 않고 REST API를 GraphQL로 바꿀 수 있는가?
  • (가정적으로) Spring Boot를 Micronaut로 교체할 수 있는가?

답이 “예”라면 꽤 괜찮은 아키텍처를 가지고 있는 것이다. 답이 “미쳤나요?”라면 폴더 구조가 무슨 의미가 있든, 이미 결합도가 높다는 뜻이다.

실용적인 중간 지점

비즈니스 규칙을 깔끔하게 유지하기

public class CancelOrderUseCase {
    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;

    public void execute(Long orderId, String reason) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFound(orderId));

        if (order.isAlreadyShipped()) {
            throw new CannotCancelShippedOrder(orderId);
        }

        if (order.isPaid()) {
            paymentGateway.refund(order.getPaymentId());
        }

        order.cancel(reason);
        orderRepository.save(order);
    }
}

주의: @Service, @Transactional, JPA 어노테이션이 없습니다. 순수 비즈니스 로직만 포함되어 있습니다.

경계에서 적응하기

@Service
@Transactional
public class OrderService {
    private final CancelOrderUseCase useCase;

    public void cancelOrder(Long orderId, String reason) {
        useCase.execute(orderId, reason);
    }
}

이제 Spring 관련 코드는 가장자리에서만 존재합니다. 비즈니스 로직은 이를 알 필요가 없습니다.

과도한 고민은 그만, 측정에 집중하자

대신 다음 질문을 스스로에게 해보세요:

  • 내 비즈니스 로직을 테스트하기가 얼마나 어려운가?
    데이터베이스를 띄우거나 15개의 의존성을 모킹해야 한다면, 뭔가 잘못된 것입니다.

  • 프레임워크 업데이트가 내 코드를 깨뜨리는 빈도는 얼마나 되나요?
    Spring Boot 4.0이 출시되었을 때, 몇 개의 파일을 수정해야 했나요?

  • 새로운 개발자가 시스템이 무엇을 하는지 이해할 수 있나요?
    하나의 기능을 이해하기 위해 컨트롤러 → 서비스 → 레포지토리 → 엔티티를 모두 읽어야 한다면, 여러분의 아키텍처가 비즈니스 로직을 가리고 있는 것입니다.

실제로 중요한 것

다양한 산업(금융 서비스부터 공공 행정까지)에서 시스템을 구축한 후, 솔직한 의견을 말씀드리자면:

클린 아키텍처는 폴더에 관한 것이 아닙니다. 비즈니스 로직을 이를 제공하는 프레임워크와 도구들로부터 독립적으로 유지하는 것입니다.

첫날부터 완벽한 헥사고날 아키텍처가 필요하지 않습니다. 요구사항이 변할 때(변하게 될 것이므로) 모든 것을 다시 작성하지 않고도 적응할 수 있도록 해야 합니다.

혼란은 우리가 잘못된 것에 집중하기 때문입니다. 도메인 모델이 @Entity 어노테이션으로 감싸져 있는 상황에서 패키지 이름을 두고 논쟁하고, 비즈니스 로직이 스프링 컨트롤러 안에 존재함에도 불구하고 복잡한 폴더 구조를 만들곤 합니다.

간단하게 시작하세요. 비즈니스 규칙을 깔끔하게 유지하세요. 세상을 모킹하지 않고 테스트하세요. 나머지는 모두 세부 사항에 불과합니다.

Back to Blog

관련 글

더 보기 »

Java 가상 스레드 — 빠른 가이드

Java Virtual Threads — 빠른 가이드 Java 21+ · Spring Boot 3.2+ · Project Loom Java Virtual Threads에 대한 간결하고 실무 중심의 가이드 — 무엇이며, 어떻게…

SPRING BOOT 예외 처리

Java & Spring Boot 예외 처리 노트 1. Exception이란? Exception = 프로그램의 정상 흐름을 방해하는 원하지 않는 상황. 예외 처리의 목표...