본문 바로가기
CS/설계

클린 아키텍처 방식으로 투두 만들어보기

by slowcloud_ 2026. 1. 28.

클린 아키텍처를 읽고 느낀 것과 배운 것들을 바탕으로, 클린 아키텍처 방식으로 개발을 진행한다.

 

일반적인 아키텍처는 뷰 -> 메인 로직 -> 데이터베이스 방식의 의존성을 가지나, 클린 아키텍처 방식을 사용하게 된다면 뷰 -> 메인로직 <- 데이터베이스 방식의, 메인 로직을 향한 의존성을 가지게 된다.

 

클린 아키텍처에서는 프레임워크도, 데이터베이스도 모두 세부사항이다. 초기 개발 단계에서는 오로지 메인 로직에만 집중할 수 있으며, 주변 의존성에 대해 신경쓰지 않고 테스트도 용이하게 진행할 수 있다.

 

우선 메인 로직 작성을 위해, 새로운 프로젝트를 시작한다. 작업환경은 Visual Studio Code이다.

 

Maven for Java를 활용하여 프로젝트 생성.

 

해당 리포지토리에는 어떠한 의존성도 존재하지 않는다. 이러한 스크래치 환경에서부터 개발을 진행한다.

 

우선 컴포넌트부터 설계를 진행해야 한다. 우선 기록을 위한 Todo 객체, 그리고 이를 저장하고, 조회하고, 삭제할 TodoManager가 필요하다. 이 2가지 이외에는 당장 필요한 기능이 존재하지 않는다.

 

우선 core 패키지를 생성하고, 내부에 Todo 객체를 간단히 생성한다.

 

해당 객체는 값만을 저장하므로, 특별한 추가 기능이 존재하지 않는다.

 

그리고 TodoManager를 생성한다. 상세한 내부 로직 구현에 대해 신경쓰는 대신, 인터페이스로 이를 대체할 것이다.

 

실제 서비스라면 사용자 확인, 일정 등등 더 많은 것들을 추가할 수 있을 것이다. Todo를 완료할 때마다 추가되는 포인트 시스템이 필요하다면, Point라는 새로운 로직을 생성하고 더 상위의 레이어에서 이를 관리하는 형태로 개발을 진행한다. 이러한 과정에서 인터페이스뿐만이 아니라 실제 작동하는 객체를 생성하고, 테스트를 진행할 수 있을 것이며, 그 과정에서 프레임워크, 프로젝트 의존성 등의 문제를 해결하기 위해 애 쓸 필요 없이, 메인 로직의 흐름만을 테스트할 수 있다.

 

일단 최소 필요한 기능들만이 존재하는 메인 로직이 구성되었다. 이제 해당 기능이 정상 작동하는지 테스트를 진행해보아야 한다. 현재 기능의 경우 몹시 간단한 기능만이 존재하므로 테스트를 진행할 필요는 없지만, 서비스의 로직의 복잡도가 늘어날 경우 테스트를 진행할 수 있도록 미리 연습하는 의미로 진행한다.

메이븐에 junit 의존성 추가. 글을 작성한 당시의 최신 버전을 적용했다.

 

현재 TodoManager는 구현체가 없다. 그러니 인메모리 형태로 투두를 관리해줄 InMemoryTodoManager를 개발한다.

간단한 인메모리 투두매니저.

 

외부 의존성 없이 간단한 기능을 구현했다. 이후 테스트 코드를 작성했다.

 

테스트 코드 일부.

 

이렇게 실제로 작동하는 메인 로직을 구성하게 됐다. 위와 같이 개발을 진행한다면, MySQL, SQLite와 같은 DB 서버를 구성하고 연결하는 번거로움 없이 상위 레이어들의 테스트를 진행할 수 있게 된다. 이제 뷰를 구성할 차례다.


 

뷰는 무엇이든 될 수 있다. HTML 페이지일 수도 있고, CLI에 출력된 메시지일 수도 있으며, 웹 API일 수도 있다. 어떤 형태로든 사용자와 상호작용하거나, 보여줄 수 있으면 된다. 메인 로직은 뷰의 존재를 몰라도 되며, 뷰에 대한 의존성이 없으므로 얼마든지 보여주는 방식을 변경할 수 있고, 프레임워크도 교체할 수 있다. 여기서는 스프링부트를 통해 메인 로직을 활용하는 기능을 구현할 것이며, 뷰 측에서 메인 로직을 활용하는 법을 보여줄 것이다.

 

우선 스프링부트 스타터 웹 의존성을 추가해준다.

위와 같이 추가해준다.

 

우선 Spring에서 TodoManager를 등록한다.

Configuration 방식으로 빈 등록하기.

또는 SpringTodoManager를 만들어두고 생성자를 통해 TodoManager를 주입받거나, 상속해서 빈으로 등록할 수도 있다. 위와 같은 방식을 통해, 메인 로직의 수정이나 변경 없이 스프링부트 내부에서 의존성 주입에 활용할 수 있게 된다.

 

이후 이를 출력해줄 MainController를 작성해준다.

MainController.java 내부 코드

API가 작동함을 확인하기 위해, 생성 단계에서 투두를 새로 추가하고, index에서 이를 확인할 수 있도록 작성했다.

 

이후 Main의 소스코드를 아래와 같이 작성하고, 실행해준다.

SpringApplication을 생성하고 실행하는 메인 코드.

 

그리고 localhost:8080으로 접속하면 다음과 같이 출력이 되는 모습을 볼 수 있다.

localhost:8080에서 정상적으로 투두를 조회하는 모습

패키지의 전체 모습은 다음과 같다:

메인 로직과 뷰가 분리되어 있는 모습.


 

이번엔 데이터베이스를 구현한다. Spring Data를 활용할 것이며, 그 중 JPA를 사용할 것이다. 구현 방식만을 알면 되므로, 데이터베이스는 인메모리 DB인 H2를 활용할 것이다.

 

다음 의존성을 추가해준다:

h2의 경우, scope를 test에서 runtime으로 변경해주어야 한다.

 

JPA Entity를 작성해준다. 메인 로직의 객체로의 변환이 가능해야 한다.

이하 Getter, Setter 메소드 구현.

 

그리고 레포지토리를 작성해준다.

 

이후 TodoManager 어댑터를 작성한다.

이하 TodoManager 인터페이스 구현이 존재. @Component 등록도 진행.

 

이후 기존에 등록했던 TodoManager를 지우고 실행한다.

Confiuration으로 등록했던 TodoManager 제거.

 

이후 자바를 실행한 뒤 localhost:8080으로 접속하면, h2로부터 가져온 투두를 조회할 수 있다.

패키지 전체 모습

 

TodoConfig의 6개의 경고는, 주석 처리를 하면서 불필요한 import를 지울 것을 경고하는 것이니 무시해도 된다.

패키지의 전체 의존성 방향은 core가 최종이다. (spring -> core, spring data jpa -> core)

 

spring 이외에도 cli 도구를 활용하여 메인 로직을 바탕으로 커맨드 도구를 개발할 수도 있을 것이며, GUI 또는 TUI로 화면을 구성하여 출력할 수도 있을 것이다. 저장공간 역시 로컬 이외에도 별도의 원격 서버를 구성하여 외부에 저장하는 것도 가능할 것이다.


 

클린 아키텍처의 방식으로 개발을 진행하면, 메인 로직이 종속하는 곳이 없으므로 유연한 개발이 가능해진다. 어떠한 프레임워크에도, 라이브러리에도 종속적이지 않게 된다. 다만, 앞서 보았듯이, 엔티티를 메인 로직의 형태로 별도로 변환해주는 로직의 작성이 필요해져 어느 정도의 오버헤드는 존재하게 된다.

 

클린 아키텍처를 읽으면서 느낀 점은, 해당 방법론에서의 개발은 프레임워크와 라이브러리를 조립하는 것이 아닌, 그 사이의 유연한 풀(glue)을 만들어내는 것이라고 느꼈다. 프레임워크와 라이브러리가 서로 맞닿지 않도록, 거대한 풀 덩어리에 마음껏 새로운 프레임워크와 라이브러리를 붙이고, 또 떼면서 코드를 조물거리는, 유연한 개발을 진행할 수 있는 방법론이라고 느꼈다.

 

그동안 개인적으로 개발을 진행할 때에는 프레임워크와 라이브러리를 그대로 맞닿게 붙이는 형태로 진행하다가 새로운 기능이 요구될 경우 많은 곳들을 한 번에 수정하여야 하게 됐고, 결국엔 손쓰기 힘든 형태에서 개발이 멈춰버리는 경우가 잦았다. 해당 방법론이 마냥 만능은 아닐 것이며, 항상 적용할 수 있는 것은 아니겠지만, 적어도 이런 문제가 빈번이 발생하지는 않게 해주지 않을까 생각하고 있다.

 

 

2026.01.28 14:22 일부 잘못된 사진 수정