본문 바로가기
STORAGE

좋은 설계를 향한 시도: 클린 아키텍처

2025. 5. 21. 08:18

컴퓨터프로그래밍

 

더 나은 설계에 대한 고민

소프트웨어에서 아키텍처란, 시스템을 어떤 구조로 구성할지 미리 설계하는 것을 말합니다. 집을 짓기 전에 설계도를 그리는 것처럼, 시스템도 아키텍처를 기반으로 만들어집니다. 체계적인 설계는 초기에 들어가는 노력과 비용이 많이 들지만, 장기적으로는 시스템의 확장성과 유지보수 측면에서 훨씬 효율적이고 안정적인 결과를 가져옵니다.

 

컴퓨터프로그래밍

아키텍처 품질에 따른 효율성 비교

(출처: martinfowler.com)

 

그렇기 때문에 프로젝트를 진행하면서 자연스럽게 더 나은 설계에 대한 고민이 들기 시작했습니다. 당시 우리 팀의 백엔드 개발 환경은 Spring Boot 기반에 멀티 모듈 구조를 가진 형태로 운영되고 있었습니다. 기본적으로는 흔히 말하는 계층형 아키텍처(Controller, Service, Repository)로 레이어를 나누었고, 각 계층 간 담당하는 역할을 나누며 어느 정도의 독립성을 지키고 있었습니다. 오래전부터 많이 사용된 아키텍처였기에 개발팀 모두에게 익숙했고, 자연스럽게 녹아들며 개발할 수 있었습니다. 하지만 "정말로 이게 최선일까?" 하는 생각이 계속 들었습니다. 멀티 모듈 구조로 물리적 분리는 이루어졌지만, 계층 간의 역할이 강제되지 않다 보니, 시간이 지나면서 점점 계층의 역할이 모호해져 갔습니다. 결국 계층 간 의존도는 다시 높아지고, 구조도 복잡해지면서 기술적인 부담 역시 늘어나는 모습을 보게 되었습니다.

 

 

 

계층 간 역할을 더 명확하게

특히 Service 계층과 Repository 계층을 바라보면서, "이 둘이 단순히 기술적으로 연결된 게 아니라, 정말 역할이 분리되어야 하지 않을까?" 하는 생각이 들었습니다. 그래서 우리 팀은 Service 계층과 Repository 계층을 각각 두 계층으로 나누어 Interface 계층과 구현체 계층으로 나누어 관리하기 시작했습니다.

 

컴퓨터프로그래밍

아키텍처 변화 과정

(출처: ChatGPT 생성)

 

  • Service 계층은 프로세스의 과정 및 흐름만 책임지되, 세부적인 내용은 Service 구현 계층 안으로 숨겼습니다. 
  • Repository 계층은 DB 접근을 책임지되, 세부 내용은 Repository 구현 계층 안으로 숨겼습니다.

이 작업을 통해 핵심적인 프로세스에 대한 정의와 구현을 물리적으로 분리했습니다. 내용을 숨기고 Interface를 제공함으로써 계층 간 독립성을 기대했습니다.

 

 

 

그래도 남아있던 의존성

하지만 금방 이 구조에 대해 몇 가지 문제를 느꼈습니다. 여전히 Controller 계층은 Service 계층을 직접 의존하고 있다는 점이었습니다. Controller가 Service를 호출하는 것이 너무나 자연스러웠지만, Controller는 외부 요청을 받아 내부 기능을 호출하는 역할만 해야 한다고 생각했습니다. 그런데 시간이 지나면서, Controller가 특정 Service 구현에 강하게 의존하게 되고, 결국 기술적으로 계층 간 연결이 다시 생겨버리는 상황이 있었습니다. 결과적으로, 의존성 관점에서는 Service 계층과 Repository 계층을 추상 / 구현 계층으로 나눈 의미가 없었습니다.

가령, 다음과 같은 예시 코드가 있습니다.

 

  • Service 구현 계층에서, Interface 없이 직접 구현된 Service가 존재합니다.
아래 코드는 분석 과정에서 객체의 상태를 일괄 변경하고 로그를 출력하는 기능을 담당합니다.

Interface 없이 직접 구현된 Service

(출처: 코드를 통해 직접 생성)

 

  • 그런 Service를 Controller 계층에서 직접 사용하게 되면서, Controller가 Service의 구체적인 내부 프로세스까지 알게 되는 상황이 생겼습니다.
아래 코드는 사용자로부터 분석 실행 준비 요청을 받으면, 필요한 Service를 호출 후 응답을 전달하고 로그를 출력하는 기능을 담당합니다.

직접 구현된 Service를 사용하는 Controller

(출처: 코드를 통해 직접 생성)

 

그러다 보니 
"좀 더 역할의 구분을 강제할 수 있는 방법이 없을까?" 
"꼭 계층적으로만 의존해야 하는 걸까?" 
"모든 계층이 Interface를 중심으로 느슨하게 연결되면, 계층 간 분리가 더 확실해지지 않을까?" 
하는 생각이 들게 되었고, 계층형 아키텍처의 형태에서 조금 벗어날 필요를 느꼈습니다. 그렇게 더 강한 역할의 분리와 의존성 관리 방법에 대해 고민하면서, 이후 우리는 클린 아키텍처라는 개념을 접하게 되었습니다.

 

 

 

 

클린 아키텍처

 

컴퓨터프로그래밍

클린 아키텍처 구조

(출처: MyTaskPanel Consulting)

 

클린 아키텍처는 우리가 고민하던 문제를 이미 체계적으로 해결하고 있었습니다.

 

  • 프로세스의 기능과 과정이 시스템의 중심입니다. 
  • 기술적인 세부 사항은 바깥으로 밀어내는 구조입니다. 
  • 모든 계층은 직접적으로 서로 의존하지 않고, 오직 추상화(Interface)를 통해 서로 연결되도록 강제합니다.

즉, 우리가 직관적으로 느끼던 문제에 대한 개선 방향을 이미 정교한 형태로 설계한 구조였습니다.

 

 

 

클린 아키텍처 적용

아키텍처를 점진적으로 리팩터링 하는 과정은 다음과 같았습니다.

 

  • 기존에 따로 분리했던 ServiceRepository의 추상 계층을 Domain 계층으로 통합했습니다. 
    • Domain 계층: 프로세스 규칙과 핵심 로직을 담당하는 계층
  • 모든 계층은 Domain 계층을 의존하도록 변경하고, Interface를 통해서만 통신하도록 했습니다.
  • 그 후, Root 계층을 만들고 Controller (api), Service (application), Repository (infrastructure) 계층을 이 계층에 의존하도록 설계했습니다.

 

클린 아키텍처를 반영한 실무 프로젝트

(출처: IDE를 통해 직접 생성)

 

각 계층 간 역할을 분리하는 과정에서 DTO (Data Transfer Object) 등의 코드가 조금 더 복잡해지고, 구조 설계에도 많은 고민이 필요했습니다. 하지만 프로세스 자체를 보호하고, 기술적 변화에 유연하게 대처할 수 있는 기반을 마련한다는 점에서, 큰 기대를 가지고 진행했습니다.

 

이 구조로 변경한 결과, 각 계층 간 독립성이 더 강하게 보장되었고 시스템이 변경에 매우 유연하게 대응할 수 있게 되었습니다.

 

  • 계층형 아키텍처 형태를 벗어나면서, Security 역시 별도의 계층으로 분리했습니다. 
  • Domain 계층에 정의된 프로세스 규칙 및 기능을 바탕으로, 필요에 따라 JWT token 방식이나 Session 방식으로 인증을 유연하게 구현할 수 있게 설계했습니다. 
  • Repository 계층 역시 JPA 나 Mybatis로 유연하게 구현하되, 세부 구현 내용은 외부에 노출되지 않도록 설계했습니다.

 

Domain을 통한 유연한 계층 구현

(출처: 코드를 통해 직접 생성)

 

또한, 단위 / 통합 테스트 작성이 훨씬 수월해졌습니다.

 

  • ControllerServiceMocking을 통해 단위 테스트를 진행했습니다. 
    • Mocking: 테스트를 위해 실제 객체 대신 사용하는 가짜 객체 생성 기법
  • Repository는 TestContainer를 통해 통합 테스트를 독립적인 환경에서 진행할 수 있었습니다.
    • TestContainer: 실제 데이터베이스나 외부 시스템을 가상 환경(컨테이너)에서 구동하여, 실제와 유사한 조건에서 통합 테스트를 수행할 수 있도록 지원하는 도구 

 

역할이 명확하여 분리된 덕분에 개발 과정이 일관성이 생겼고, 팀원 간 협업 만족도 역시 크게 높아졌습니다.

아래 코드는 Repository 계층의 통합 테스트를 진행하는 기능을 담당합니다.

Repository 통합 테스트

(출처: 코드를 통해 직접 생성)

 

다만, 여러모로 좋은 효과를 체감하는 동시에 다시 고민하게 되는 부분도 있었습니다. 초기 설계에 투입되는 비용이 많이 들었고, "이 정도까지 과한 설계가 필요할까?" 하는 의문이 들기도 했습니다. 개인적으로는 체계적인 설계를 통해 시스템의 품질을 높이는 것이 좋다고 생각되지만, 규모에 비해 너무 과하다면 오히려 역효과가 될 수 있겠다는 팀원의 얘기에 공감했습니다.

 

 

 

마무리

클린 아키텍처는 2012년에 나온 개념이지만, 실제로 이를 적용하기 위해서는 많은 개발 지식이 필요했습니다. 이 과정에서 많은 시간이 소요되었고, 구조를 설계하는 과정에서도 여러 난관이 있었습니다. 신경 써야 하는 부분과 작성해야 하는 코드의 양은 많아졌지만, 기대 이상으로 유연하고 테스트하기 좋은 개발 환경을 구축할 수 있었습니다. 한편으로는, 개발팀 모두가 계층 간 역할의 중요성을 이해하고 이를 지켜낸다면, 기존의 계층형 아키텍처 역시 동일한 효과를 가지면서 개발 속도 측면에서는 오히려 더 효율적일 수 있겠다는 점도 이번 비교를 통해 체감했습니다.

계층형 아키텍처에서 클린 아키텍처를 도입하는 과정은 단순히 새롭고 멋진 기술을 배우는 것이 아니라 "어떻게 하면 시스템을 더 건강하게 만들 수 있을까"를 끊임없이 고민하는 과정이었습니다. 우리 개발팀은 앞으로도 시도하고, 고민하고, 적용하며 나아가겠습니다.

 

 

 

참고자료


 

EDITOR

배현태

Daejeon Branch · Developer

댓글