Layered에서 Onion으로

중고 개발자
중고 개발자
Published in
8 min readApr 1, 2024

2024년 3월 사내 본부의 스터디 모임에서 팀 내에 클린 아키텍처를 도입한 과정을 발표했다. 발표한 내용 중에서 사내 정보를 제외하고 게시한다.

제목대로 “레이어드에서 어니언으로” 클린 아키텍처를 도입한 과정에 대해 스토리텔링 방식으로 접근해봤다. 아키텍처에 대한 구체적인 정의나 설명은 생략한다. 도입 전에 어떤 문제가 있었고, 어떻게 해결하려고 했으며, 결과는 어땠는지에 대한 과정이 주제다.

사내 규정으로 인해 담당 프로덕트에 대한 정보를 공개할 수는 없지만, 사용중인 자원량이나 요청량을 봤을 때 꽤 대규모 시스템이라는 점을 알리며 시작하고 싶다.

도입 전

1. 요건 분석

기획팀으로부터 특정 기능을 개선하는 안건이 들어왔다. 기존의 기능은 A-B-C의 순으로 작업을 수행한다. A 작업은 자체적으로 처리하고 있던 작업인데, 이 작업을 외부 시스템에 의존하도록 변경하는 것이 주요 요건이다. 외부에 의존하는 작업이 A’ 작업이다. 따라서 새로운 기능은 A 작업을 A’ 작업으로 대체하여, A’-B-C의 순으로 작업을 수행하는 흐름이 된다.

2. 설계

요건만 보면 이상적인 설계로는 A 작업을 A’ 작업으로 수정하면 될 것 같지만 실제로는 그렇지 않았다.

  • 첫번째 이유는, 기존 어플리케이션이 불안정했기 때문이다. 기존 어플리케이션을 고스펙의 서버에서 운영해도 장애가 빈번하게 발생하여 불안정했다. 여기에 새로운 A’ 작업을 추가하면 영향을 그대로 받게 된다. 이 영향을 피하고 싶었다. (A 작업은 기존 어플리케이션에 그대로 두고 점진적으로 A’ 작업으로 이행해야 했다)
  • 두번째 이유는, 기술 부채 때문이다. 기존 어플리케이션의 코드 베이스가 레거시이다보니, 해당 코드 베이스에 A’ 작업을 추가하면 기존 레거시의 관성을 따르게 된다. 레거시로 인해 기술 부채가 지속되는 것을 피하고 싶었다.

첫번째 이유는 운영 상의 이슈, 두번째 이유는 설계 상의 이슈이다. 우선은 신규 어플리케이션에 A’ 작업과 B 작업을 구현하도록 설계했다. C 작업은 기존 어플리케이션을 그대로 이용하도록 했다. (C 작업까지 신규 어플리케이션에 구현하면 공수가 배가 되어 원하는 일정에 발매를 못할 수도 있었다)

3. 개발

설계대로 열심히 개발했다.

4. Code Review

우선 A’ 작업을 개발한 PR을 오픈하고 팀 내 코드 리뷰를 받았다. 앞서 말했듯이, 기술 부채가 계속되는 것이 우려가 되었기 때문에 새로운 아키텍처를 도전해봤다. 새로운 아키텍처라지만, 기존의 불분명한 레이어드 아키텍처에서 도메인 모델을 의식하는 명확한 레이어드 아키텍처였다.

하지만 패키지 구조부터 시작해 세세한 부분이 많이 바뀌었기 때문에 코드 리뷰의 단계가 아니라 그 전에 팀 내에서 인식을 맞출 필요가 있다고 피드백을 받았다. 이 타이밍이 팀 내에서 새로운 아키텍처에 대해 논의하는 기회가 되었다.

문제점

현재 프로덕트의 문제점은?

지금까지는 클린 아키텍처를 도입하기 전의 얘기였다. 도입 과정의 얘기를 시작하기 전에 배경으로서 담당 프로덕트의 문제점에 대해 짚고 넘어가려고 한다. (여우가 범의 허리를 끊듯이)

자료에는 복잡함이라고 적었는데, 복잡함이란 무엇인가 하면 이해하기 어려운 사양과 변경이 어려운 시스템을 만드는 것을 말한다. 그렇다면 우리 프로덕트의 문제점이 복잡함이라고 한다면 그것뿐만은 아니었다.

프로덕트가 대규모라는 점을 밝히면서 글을 시작한 이유가 여기에 있다. 복잡한 시스템이면서 시스템의 규모가 크면 커질수록 문제도 커진다. 극단적으로 다음의 이미지와 같이 된다.

예를 들어 복잡한 냉장고가 1대 있으면 문제가 되지 않지만, 수십대 있으면 문제가 된다. 냉장고의 안에 무엇이 들어있는지 알 수가 없고, 같은 물건이 중복될 가능성이 높다.

나는 이것이 우리 프로덕트와 같은 상황이라고 보았다. 복잡하면서 규모가 크다는 점이 문제였다. (이 문제로 인해 담당자도 사양을 모르게 되고, 담당자는 이 프로덕트에서 일하는 것이 싫어지는 가장 큰 문제가 발생한다)

해결책은?

복잡함을 줄이기 위한 해결책으로서, 코드를 이해하기 쉽고 변경이 쉽게 하는 룰이 필요했다. 그것이 아키텍처다. 하지만 해결책이 단지 아키텍처라고 한다면 그것뿐만은 아니었다. 새로운 어플리케이션을 구축하는 멤버에 의해서 이미 아키텍처가 부여되고 있었기 때문이다.

우리 프로덕트는 이미 레이어드 아키텍처가 주로 적용되고 있지만, 이 아키텍처도 멤버에 의해 조금씩 달랐다. 아키텍처가 없을 때도 있다. 우리 팀은 기존에 개발을 가이드하는 통일된 명확한 아키텍처가 없었기 때문에 아키텍처를 선정할 필요가 있었다.

도입 과정

5. Architecture 선정 회의

지금부터는 아키텍처를 도입한 과정에 대한 이야기다.

아키텍처 후보는 레이어드 아키텍처와 어니언 아키텍처가 있었다. 두 가지의 공통점은 도메인 모델에 의존하고 있는 것이다. 차이점은 외부와 내부에 대한 경계의 유무다. 우리 팀은 어니언이 더 적절하다고 판단하여 이 아키텍처를 선정했다.

다만, 여기에 있는 아키텍처만이 정답은 아니다. 팀의 상황과 프로덕트에 맞는 적절한 아키텍처가 있다고 생각한다.

6. Package 구조

가장 많이 신경을 쓴 부분은 패키지 구조다. 멤버 뿐만 아니라 팀의 새로운 참가자도 이 패키지 구조를 봐도 아키텍처가 바로 이해되는 것을 목표로 했다.

  • 어니언 아키텍처의 가장 바깥 레이어 중, 외부에서 내부로 요청이 들어오는 요소는 consumer, controller와 같은 폴더로 나누어 엔트리 포인트를 알 수 있게 했다. 반대로 내부에서 외부로 요청을 보내는 요소는 infrastructure 폴더로 나누었다. 이 폴더 하에 cache, database, web 등의 외부 컴포넌트들을 구성했다.
  • 중간의 레이어는 유즈 케이스 레이어로, usecase 폴더에 두었다.
  • 가장 안쪽 레이어는 핵심 비즈니스 로직을 처리하는 도메인 레이어로, domain 폴더에 두었다.

이 역시 팀 내 회의를 해서 모든 멤버가 가장 이해하기 쉬운 구조를 선택했다.

7. Layer 책무

우리 팀은 외부 레이어를 제외하고 크게 3개의 레이어를 구성했다. 유즈 케이스 레이어, 도메인 서비스 레이어, 도메인 모델 레이어다. 각 레이어의 책무도 팀 내 회의를 거쳐 결정했는데, 가장 중요한 규칙은 각 레이어는 내부 레이어에만 의존하고 외부 레이어에는 의존하지 않는 것이다.

8. 재개발

위에서 결정한 방침대로 리팩토링 및 재개발을 진행했다.

9. Code Review

결정한 방침을 코드에 반영해서 코드 리뷰를 받았다. 회의와 상담을 통해 논리적인 룰은 정했지만, 코드베이스에 적용하려면 컨벤션과 같은 상세한 룰이 필요했다. 리뷰를 통해 팀 멤버의 의견을 들으면서 코드 레벨의 룰을 구체화했다.

도입 후

결과

드디어 새로운 어플리케이션에 새로운 아키텍처를 도입했다. 앞으로 새로운 어플리케이션을 구축할 때도 이 아키텍처를 적용하기로 했다.

아키텍처에 도전하면서 개인적인 성장에 도움이 되었지만, 기술적으로 토론하고 합의를 얻는 활발한 커뮤니케이션을 통해 팀의 성장에도 도움이 되었다고 생각한다.

도입한 결과는 다음과 같다.

  1. 팀 내 개발을 가이드하는 아키텍처를 결정했다.
  2. 기술적인 논의를 하고 합의를 도출하는 과정이 팀의 성장에 영향을 주었다.

기대 효과

Design Stamina Hypothesis 는 소프트웨어 설계에 노력을 기울이면 프로젝트의 스태미나가 향상되어 빠르게 작업할 수 있다는 가설이다.

이번에 클린 아키텍처를 도입해서 생산성이 좋아졌다고 측정할 수 있는 것은 아니다. 대규모의 레거시가 아직 남아 있고, 여기에 클린 아키텍처를 도입하는 것도 현실적으로 어렵다. 단기간에 개선되는 것은 힘들다. 다만, 이번의 도전이 앞으로 생산성을 개선해 나가는 기반이 되기를 기대하고 있다.

소감

개인적인 소감으로, 아키텍처는 결과가 아닌 과정이라는 것을 깨달았다. 완성된 형태로 성과를 내는 것이 아니라, 지속적으로 개선하는 여정에 가깝다. 이 여정에서 문제점과 해결책, 목표와 배경이 명확해야 방향을 잃지 않는다. 이 때 팀 내의 커뮤니케이션이 방향을 잘 잡아주는 역할을 한다.

--

--