DEV/Architecture

헥사고날 아키텍처 시리즈 10. 아키텍처에 대해 고민을 해보며 얻은 것들

행운개발자 2024. 4. 1. 23:17
728x90

아키텍처에 관심을 가지게된 이유

결론적으로는 개발하는 프로젝트의 복잡도가 증가하면서 수정하는 비용이 커졌기 때문입니다. 비즈니스 요구사항이 점점 복잡해지면서 사용자 관점의 유즈케이스를 코드만 보고 파악하기 어려워졌습니다. 기능 하나를 수정하면 영향을 받는 코드가 많아서 수정하기 어렵기도 하고 수정한 결과에 대한 심리적 안정감도 많이 떨어졌습니다.

“수정하기 쉽도록 프로젝트를 구성하려면 어떻게 해야할까?”라는 의문에 답을 찾기 위해서 클린 아키텍처를 떠올렸고, 구체적인 예제 코드를 제공한다는 이야기를 듣고 책 ‘만들면서 배우는 클린 아키텍처’를 읽어보게되었습니다.

이번 글은 책 ‘만들면서 배우는 클린 아키텍처’를 2번 정독하고, 토이프로젝트를 직접 진행해보면서 배우고 느낀 것들에 대해서 이야기합니다.

수정하기 쉬운 프로젝트에 대한 필요성을 느껴온 과정

저는 와탭랩스에서 모니터링 솔루션을 만 3년동안 개발하면서 매년 새로운 프로젝트를 진행해왔습니다.

  • 2021년 : 알림 기능 고도화
  • 2022년 : 로그 모니터링 솔루션 신규 개발
  • 2023년 : 로그 모니터링 솔루션 고도화
  • 2024년 : 애플리케이션 모니터링 솔루션 고도화

2021년에 알림시스템을 맡았을 때에는 모니터링 도메인 지식 을 학습하고 알림 기능의 동작 방식 을 파악하는데 집중했습니다. 보다 넓은 관점으로 알림 기능을 바라보기에는 시야가 많이 좁았습니다.

2022년에는 로그 모니터링 솔루션의 신규 개발을 시작했습니다. 다양한 환경에서 수집된 로그에서 인사이트를 찾는 것을 목적으로 수집된 로그의 저장, 단순조회, 검색, 통계 기능을 개발하는데 집중했습니다. 여전히 로그 모니터링이라는 하나의 제품을 벗어나서 넓게 바라보는 시야를 갖기에는 경험이 부족했습니다.

2023년에는 로그 모니터링 솔루션을 사내의 다양한 제품들과 연계하기 위한 작업들을 진행합니다. 애플리케이션, 서버, 데이터베이스에서 발생하는 로그도 수집되기 시작했습니다. 수집된 로그를 대상으로 알림을 발생시키는 기능도 추가됩니다. 그리고 로그를 수집해서 인사이트를 제공할 수 있는 기술은 쿠버네티스와 브라우저 모니터링 솔루션에서 쿠버네티스 이벤트와 브라우저 이벤트를 ‘문자열 메트릭’으로서 모니터링하는데 적용됩니다.

2024년부터는 애플리케이션 모니터링 솔루션을 담당하고 있습니다. APM 제품은 회사에서 가장 오랫동안 개발되었고 고도화된 제품 중 하나입니다. 이미 고객에게 제공하고 있는 수 많은 기능들이 존재하고, 다른 APM이 아닌 다른 제품들과의 연계기능도 존재하기 때문에 소프트웨어의 복잡도는 최고 수준으로 높습니다.

이렇게 점점 복잡한 소프트웨어를 맡게되다보니 점점 “수정하기 쉽도록 프로젝트를 구성하려면 어떻게 해야할까?”라는 의문이 들었습니다.

 

1. 의존성 방향 인식

어떤 컴포넌트에서 다른 컴포넌트를 사용한다면 의존성을 가진다고 할 수 있습니다.

그런데 전통적인 계층형 아키텍처에서는 아래와 같은 의존성 방향을 가지고 있습니다.

백엔드 개발자들은 요구사항을 풀어나갈 때 JPA와 같은 ORM 도구를 사용합니다. ORM에서 제공하는 다양한 기능들 덕분에 더 편리한 개발을 할 수 있게 되었지만, 그래서 Repository 계층에 많은 부분을 의존하게 됩니다. ORM 덕분에 객체와 데이터베이스 사이의 패러다임의 불일치는 많이 해소되었지만, 의존성의 방향 때문에 비즈니스 요구사항이 Service 계층이 아닌 Repository 계층의 Entity에 많이 의존하게 됩니다. 이러한 의존성 방향에 대해서 인지하는 것이 아키텍처에 대해서 고민해보면서 배운 첫 번째 지식입니다.

 

2. 계층형 아키텍처의 문제점 인식

프로젝트를 진행하다보면 Service와 Repository 사이에서 애매하게 존재하는 클래스들이 생기기 마련입니다. 그런데 의존성 방향이 Repository 계층을 향하고 있기 때문에 애매한 클래스들을 Service 계층에 두면 Repository 계층에서는 이러한 클래스들을 사용할 수 없게 됩니다. 그러다보니 ‘애매하면 Repository에 추가’하게 되고 영속성 계층은 점점 무거워집니다.

많은 클래스들과 기능들이 Repository 계층에 추가되면서 Repository 안에서 어떤 일들이 일어나는지 점점 파악하기 어려운 문제가 발생합니다. 하나의 패키지에서 내가 사용해야하는 클래스가 어떤 클래스인지 찾기 어려워집니다. 이러한 문제는 하나의 클래스에서 수백~수천줄의 코드가 들어있어 어떤 기능을 수행하는지 이해하기 어렵기 때문입니다. 이렇게 적절한 클래스를 찾지 못하고 신규 클래스를 추가하다보면 비슷한 역할을 하는 클래스와 메서드가 추가되고, 몇 달 뒤에 보면 히스토리를 까먹어서 정말로 곤란한 상황이 됩니다.

게다가 이렇게 적절한 클래스와 메서드를 찾기 어렵기 때문에, 너무 많은 의존성이 존재하기 때문에, 테스트코드를 추가하는데 너무 시간과 에너지가 들어갑니다.

3. 계층형 아키텍처의 문제점 극복

헥사고날 아키텍처에서는 이러한 문제점들을 의존성의 방향으로 문제를 해결하려고 했습니다. Persistence 계층으로 향하던 의존성을 의존성 역전을 사용해서 Application 계층을 향하도록 변경했습니다. 하지만 엄격하게 Persistence 계층과 Application 계층을 나누면 JPA Entity 객체는 Persistence 계층에 존재해야하기 때문에 Domain 객체와 Entity 객체를 나누어서 사용합니다. 그런데 이러한 접근 방식으로는 지연 로딩과 변경감지 등 ORM에서 제공하는 다양한 편의기능을 사용할 수가 없게 됩니다. ORM에서 제공하는 편의기능을 사용하지 못하는 것은 대단히 큰 문제이기 때문에, 의존성의 방향으로 문제를 해결하는 접근 방법은 매우 제한적이라는 생각이 들었습니다.

아래의 사항들은 계층형 아키텍처에도 당장 적용할 수 있는 실용적인 방법을 다루고 있습니다. 헥사고날 아키텍처를 실용적으로 적용할 수 있는 조건에 대해서는 뒤에서 다시 다루겠습니다.

3.1. 넓은 서비스를 좁은 서비스 여러개로 나누기

수백 수천줄의 기능을 담당하는 UserService를 CreateUserService, UpdateUserStateService와 같이 목적에 맞게 나누는 것을 의미합니다. 헥사고날 아키텍처에서는 UseCase라는 인터페이스를 두어 패키지 구조와 클래스 이름만으로도 소프트웨어가 제공하는 핵심 기능들을 파악할 수 있어야 한다고 합니다. 이러한 의도에 맞춰 너무 넓은 역할을 담당하고 있는 서비스를 여러개의 서비스로 나누어야 합니다.

3.2. 조회를 담당하는 서비스 별도로 분리하기

조회를 담당하는 서비스를 별도로 분리해야합니다.

조회를 담당하는 서비스를 헥사고날 아키텍처에서 제안하는 방법으로 분리하면 아래와 같습니다.

계층형 아키텍처 버전으로 수정하면 아래와 같습니다.

여기서 주목해야하는 부분은 사실 Query 전용 Service를 분리하는 것이 아니라 Query가 아닌 다른 Serivce에서는 Query를 위한 최소한의 정보만 반환해야한다는 것입니다.

728x90