처음 Swift 개발을 시작했을 때는 문법을 배우고 앱을 개발하는 프로세스에 집중을 하게 된다.
지금까지 나는 이러한 상황 속에 있었고, 주변에 있는 경험많은 개발자들이 아키텍처의 필요성에 대한 이야기를 할 때 아직 내가 도달하기엔 어려운 이야기라고 생각했다.
그리고 드디어 이제는 아키텍처를 도입해도 될 때라는 생각이 들어 마지막 프로젝트에서 클린아키텍처를 적용해보는 과정을 기록해보려고 한다.
1. 시스템 아키텍처란?
시스템 아키텍처는 시스템의 구조를 정의하고 구성 요소 간의 관계를 설계하는 방법을 말합니다.
아키텍처라는 용어를 여기저기서 참 많이 들어보는 것 같은데 대략적인 느낌만 있고, 그 실체를 설명하라고 하면 항상 어렵다고 느끼는 경우가 있을 것이다. 위에서 설명하는 정의가 어렵게 느껴지지만 아키텍처는 무언가를 만들기 위한 설계도와 같은 것을 생각하면 된다. 건물을 짓는다면 건물의 건축도면이 예시가 되는 것처럼 시스템을 만들기 위한 시스템도면이 아키텍처이다.
시스템 아키텍처에는 여러 아키텍처 스타일(예: 마이크로서비스, 계층형, 클라이언트-서버 등)이 있다. 우리가 어떤 것들을 구현해야 하는지, 어떤 것에 중점을 둘지에 따라서 여러 아키텍처중에서 조합을 통해 적용해 나가는 것이다.
[시스템 아키텍처와 건물의 설계]
- 이해를 돕기 위해서 건물의 설계와 아키텍처를 비교해보았어요.
1. 시스템 아키텍처는 건물의 기본 설계도와 같다
- 건축에서는 건물이 어떤 구조로 지어질지를 결정하기 위해 먼저 설계도를 만들고, 설계도는 층의 구조, 방의 위치, 벽과 창문의 위치를 나타내주게 된다. 시스템 아키텍처도 이와 마찬가지로 소프트웨어 시스템이 어떤 구조로 구성될지를 정한다.
2. 레이어드 아키텍처 = 건물의 층을 계획하는 것
- 층의 구조를 나타내는걸 예시로 든다면 건물의 1층은 로비 역할, 2~5층은 상가 역할, 6~11층은 거주 역할로 나누는 것처럼 시스템에서는 데이터베이스 계층, 비즈니스 로직 계층, 사용자 인터페이스 계층 기능별로 층을 나눠서 정의할 수 있다.
3. 마이크로서비스 아키텍처 = 건물 단지를 분리하는 것
- 대학 캠퍼스가 여러 건물로 나뉘어 도서관, 강의동, 연구실, 기숙사 등이 따로 존재하고 각 건물의 역할이 명확하게 정해져 있는 것처럼, 마이크로서비스에서는 사용자 관리, 결제 처리, 주문 관리 등 각 서비스가 독립적으로 운영된다.
4. 이벤트 드리븐 아키텍처 = 건물 내 비상 경보 시스템
- 화재 경보기가 작동하면 비상 벨 울림, 엘리베이터 정지, 비상구로 안내 표시등 켜짐과 같은 행위가 실행된다. 이는 특정 이벤트가 발생할 때마다 이에 반응하는 시스템이 작동하는 구조로 시스템 아키텍처에서도 사용자 동작이나 외부 이벤트에 따라 관련 서비스들이 작동되도록 구현하는 것을 의미한다.
2. 클린 아키텍처란 무엇인가?
클린 아키텍처는 시스템 내부의 코드 구조와 모듈화를 체계적으로 설계하는 데 중점을 둔 아키텍처 패턴입니다.
주요 목표는 의존성 규칙에 따라 비즈니스 로직과 외부 요소(예: 데이터베이스, UI)를 분리하여, 코드의 변경과 확장이 용이하도록 만드는 것입니다. 이는 시스템 아키텍처의 설계 패턴 중 하나로 사용될 수 있습니다.
클린 아키텍처는 시스템 아키텍처 내에서 사용될 수 있는 설계 원칙으로, 모듈 간의 의존성을 관리하고 비즈니스 로직을 외부 요소로부터 분리하는 데 초점을 둡니다.
모듈이란? 자신의 역할에 맞게 분리된 하나의 기능만을 담당하는 코드파일 혹은 그룹이라고 생각한다.
[클린 아키텍처와 건물의 설계]
- 클린 아키텍처는 건축물의 내부 기능 배치와 구조 설계에 가까운 개념이라고 이해하면 된다. 각 기능이 독립적이면서도 필요한 데이터나 흐름을 원활하게 공유할 수 있게 하며, 변화나 확장에 유연하게 대응할 수 있는 구조를 만들도록 돕는 역할을 한다.
1. 기능 분리 및 모듈화: 내부 공간의 역할 구분
- 클린 아키텍처는 시스템의 각 기능을 독립적으로 분리하고, 핵심 비즈니스 로직과 구체적인 구현(예: 데이터베이스, UI) 간의 의존성을 분리한다. 건물 내부의 각 방이나 구역이 서로 독립적인 역할을 수행하도록 설계하는 것과 비슷하다고 할 수 있다.
- 병원 건물에서는 진료실, 수술실, 응급실이 각기 다른 기능을 수행하지만 서로 연계되어 각 역할을 올바르게 수행했을 때 환자가 필요로하는 진료, 수술 등을 거쳐 치료의 길로 나갈 수 있다. 진료실과 수술실은 맡은 역할이 명백하게 다른 기능을 수행하지만, 필요할 때 서로의 데이터를 공유할 수 있는 구조가 있어야 원활한 프로세스가 되는 것처럼 클린 아키텍처에서도 각 모듈이 독립적으로 기능하되 필요한 정보는 교환할 수 있게 설계해야 함은 동일한 것이다.
2. 의존성 역전: 주요 기능이 구체적인 부분에 의존하지 않도록 설계
- 클린 아키텍처의 핵심 원칙인 의존성 역전 원칙은, 중요한 비즈니스 로직이 구체적인 외부 구현에 의존하지 않도록 설계하는 것을 목표로 하고 있다.
- 위에서 병원 건물을 예시로 들었으니 계속 해보자면 건물에서 진료실의 전기 배선의 일부분을 변경해야 한다고 했을 때, 수술실 및 중환자실을 포함한 병원 전체의 전기를 내리고 진행을 해야한다고 하면 뭐라고 할 것인가? 당연히 그렇게 진행되는 것은 허용할 수 없습니다!라고 할 것이다.
- 이처럼 중요한 공간의 경우 전기 배선 등의 요소들이 변경되더라도 전체적인 기능에 영향을 받지 않도록 구조를 만들고 배치하는 것처럼 클린 아키텍처에서도 핵심적인 로직이 주변의 변경될 가능성이 많은 요소에 종속되지 않도록 하는 것이 중요하다.
4. 유연성과 확장성: 공간 변경이 쉽게 이루어질 수 있도록 설계
- 클린 아키텍처는 코드의 유연성과 확장성을 중요하게 여기며, 이 때문에 특정 부분을 수정하거나 확장할 때 다른 부분에 영향을 최소화하는 설계를 하는 것을 목적으로 한다. 이는 건축물에서 공간의 용도나 구조를 쉽게 변경할 수 있도록 설계하는 것과 유사하다.
- 설계를 아무리 완벽하게 했다고 생각해보 구현을 하다보면 수정사항이 단 한건도 없기는 쉽지 않다. 따라서 이런 변경에 대한 니즈가 존재한다는 가정하에서 변경 시 들어가는 리소스를 최소화 해야 함은 틀림 없이 중요한 사항이다.
- 병원의 진료실을 확장할 필요가 생기면 다른 공간의 영향을 최소화하면서 쉽게 확장할 수 있도록 초기 설계에서 여유 공간을 두거나, 벽을 이동 가능한 파티션으로 설계하는 것과 같다.
2-1. 클린 아키텍처의 주요 원칙
1) 경계(Boundary)의 분리
- 시스템을 여러 영역으로 나누고, 각 영역 사이의 인터페이스를 정의하여 각 영역의 독립성을 보장한다.
- 가장 핵심적인 비지니스 로직이 안쪽 계층에 존재하고, 경계를 명확히 분리하여 안쪽 계층이 외부 요소의 변경에 영향을 받지 않고 시스템의 핵심을 유지할 수 있도록 해야 한다.
2) 의존성 역전 원칙(Dependency Inversion Principle)
- 고수준 모듈은 저수준 모듈에 의존해서는 안되며, 양쪽 모듈 모두 추상화에 의존해야 한다.
"의존한다"의 정의: A가 B에 의존한다라는 말은 B를 변경하면 A도 B의 변경사항에 맞춰서 변경되어야 함을 의미한다.
- 클린 아키텍처에서는 의존성은 밖에서 안으로 흘러야 한다고 이야기 하고 있다. 즉 원의 내부 계층에서 외부 계층을 의존하면 안된다는 의미이다.
- 핵심 비즈니스 로직과 유즈케이스는 외부 요소(UI, 데이터베이스, 네트워크 등)에 의존하지 않고, 오히려 외부 요소가 내부 계층의 인터페이스를 구현하도록 하여 의존성을 반대로 설정하도록 해야한다.
3) 인터페이스 분리 원칙(Interface Segregation Principle)
- 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않아야 한다. 즉, 인터페이스는 클라이언트의 요구에 딱 맞는 형태로 분리되어야 한다.
2-2. 내가 생각하는 클린 아키텍처
클린 아키텍처의 지향점
1) 시스템의 유지보수성 2) 변경 유연성 3) 테스트 용이성
- 내가 생각하는 클린 아키텍처가 지향하는 점은 크게 3가지라고 생각한다. 그 중에서도 시스템의 유지보수성을 고려한 부분이 돋보인다고 생각한다.
- 이유 1: 유지보수를 원활하게 하기 위해서는 서로 얽혀있는 것보다 각각이 분리되어 있는 것이 중요하다. 이를 위해서 Boundary 분리를 통해서 계층, 각 계층 내에서도 기능별로 분리하도록 설계가 필요했다고 생각이 들었다.
- 이유 2: 이렇게 분리를 해놨지만 서로 필요한 상황이 존재하고 유기적으로 활용되어야 한다. 이렇게 상호작용이 필요한 상황에서 의존에 의한 연쇄적 변경을 최소화화기 위해 인터페이스 즉 추상화라는 매개체를 의존함을 통해 자체적 의존성을 줄이는 방식을 고안한 것이라고 생각한다.
- 이유 3: 시스템의 부분을 변경한 후에는 시스템이 기능별 명세에 맞게 올바르게 작동하는지 확인하는 테스트 작업이 필요하다. 시스템이 분리되어있는 경우 이런 테스트 작업에 있어서 시스템 전체가 아닌 부분적으로 테스트가 가능하고, 이 것은 궁극적으로 최종 테스트를 포함한 유지보수적인 측면에서 리소스를 줄일 수 있는 방법이라고 생각한다.
따라서 지속적이고 장기적인 유지보수적 측면에서 효율성을 고려한 아키텍쳐라는 생각을 하게 되었다.
3. 클린 아키텍처 구현기
1) 서비스 소개
- 국악으로 음악에 입문해서 서양음악 체계에 익숙하지 않은 국악 연주가가 혼자서 기본기 연습을 할 때 사용하는 국악 음악체계(장단별 특징, 용어)를 반영한 메트로놈 앱
2) MVP Feature List
- 우리팀에서 MVP로 구현하고자 하는 기능을 아래와 같이 정의하였다.
3-1. 한배 서비스에 클린 아키텍처를 적용하게 된 이유
- 우리 팀은 국악 서비스를 제공하지만 팀원 내 국악기를 연주하는 사람이 1명뿐이고, 국악기 자체보다 사물놀이의 쇠(꽹가리) 연주자에 가깝다보니 우리의 솔루션이 정말로 타겟한테 필요한 솔루션인지를 검증하는 작업을 지속적으로 필요로했다.
- 따라서 UT를 통해서 UX 사용성, 기능의 필요성을 검토하고 수정하는 작업 또한 지속적으로 이뤄질 것으로 예상하였기에 유지보수적인 측면에서 효율적인 구조를 가져가야 한다고 생각했다.
1) UseCase 정의
- Feature List를 통해서 사용자가 우리 서비스를 통해서 어떤 행위를 수행할지에 대해 정의해보았다.
위에 정의된 Domain Layer의 내용을 바탕으로 우리 앱의 구조가 결정되었다. 각 과정에서 클린 아키텍처가 지향하는 바를 구현하였다.
3-2. 한배 서비스의 클린 아키텍처 적용의 첫번째 스프린트를 마치고,
1) 결론적으로 구현된 클린 아키텍처
- 인터페이스 사용을 통한 UseCase간의 의존성 분리
- 시스템 경계의 분리
2) 아직 적용되지 않은 것( = 앞으로 고민해봐야 할 것)
- 현재 우리앱은 시작 시 MainView에서 장단 리스트를 보여주는 형식으로 구현되어 있다. 이 경우 ForEach에서 각 jangdan에 해당하는 view를 호출해주고 있고, View의 initializer에서 viewModel을 생성하고 있기 때문에 ViewModel이 장단의 개수대로 생성되고 있다.
- 현재 viewModel 내부적으로도 다수의 UseCase를 의존성 주입 없이 사용하고 있기 때문에 jangdan * viewModel 내부 UseCase 객체의 init을 생각하면 의존성 주입을 하는 것이 좋을 것 같다는 생각이 들었다.
- 현재 Repository는 template에 대한 데이터를 보관하고 있는 상황에서 Repository의 의미를 조금 다르게 사용하고 있다고 생각한다.
- SwiftTest를 사용해서 UseCase와 하위기능별 Testing을 진행중에 있다. Test를 진행하다보니 구조적으로 의존성이 분리되어 있지 않거나 개선점이 보이는 코드가 있어서 수정이 필요할 것 같다.
3) 1차 스프린트를 통해 느낀점
- 처음에 우리 서비스에서 필요하다고 생각한 기능을 정의하고 기능과 구현 프로세스를 생각하면서 아키텍처를 설계했다고 생각했다. 구현을 했으니 클린 아키텍처만의 장점을 누려봐야 하지 않겠는가? 각 기능별, 즉 UseCase별로 개발 전에 주어진 기능명세에 따른 테스트를 진행해보고자 하였다!
(원래는 TDD를 해보고 싶었지만, 아직 테스트코드의 필요성이나 방법을 몰라서 우선 기능을 두고 테스트를 도전해보았어요!)
3-3. 첫번째 변화 : 데이터 관리 주체의 변경
1) 장단DataUseCase에서 사용중인 데이터의 관리 책임분리
- 앞선 내용처럼 우리가 처음 아키텍처를 정의했던 구조에 의한다면 "장단데이터UC"가 하는일은 다음과 같다
1. 장단Repository로 부터 불러온 데이터를 객체로 저장해서 앱의 생명주기 내에서 사용되는 데이터의 위치가 됨
2. ViewModel에서 강세변경UC, 속도변경UC 변경 요청이 들어오면 실제 데이터를 변경하고 Combine으로 데이터를 배포하는 역할을 함 - 그렇다면 현재의 장단데이터Repository는 단순히 장단데이터UC에서 사용할 데이터 원본을 가지고 있는 역할을 하고 있다. 그렇다면 Repository하기보다 DB의 역할에 가까운 것 같다. 추후에 예정된 기능 추가 시 CRUD를 통한 데이터저장이 구현될 가능성이 높은 상황이지만 어떠한 DB(아마 SwiftData가 될 것 같지만...)를 사용하게 될지 모르기 때문에 Read만 하고있는 장단데이터 원본을 따로 분리해주기로 했다.
2) 이제야 알게된 사실...
- 기존에 UseCase, Repository를 구현체라고 생각했고, 구현체의 이름으로 사용하고 있었다. 물론 클린 아키텍처에서는 인터페이스, 구현체 모두에 해당한다고 하지만 각 기능을 정의하고 분리된 기능에서 필요로하는 인터페이스를 채택하는 과정에서 각 구성요소는
4. 클린 아키텍처 필요성을 느꼈는가?
- 굳이....? 소규모 프로젝트에서 필요한가? 앱의 규모가 작았기 때문의 이슈라고 생각한다.
- 아직은 정말 대규모로 기능별로 나눌만한 개발을 해보지 않아서 View, ViewModel, Model, Repository만 분리하고 의존성을 크게 고려하지 않았던 경우에도 변경사항에 어려움이 없었던 것 같다.
- 앱 하나만을 기반으로 유지보수 작업을 진행하면서 유용성, 필요성에 대해서 크게 와닿지 않음이 있다. 하지만 클린 아키텍처가 적용되지 않은 프로젝트와 적용된 프로젝트를 같은 시기에 유지보수하고 배포를 해보니 확실히 클린 아키텍처가 적용된 프로젝트가 변경사항을 반영하는데 있어서 비교적 수월함을 느꼈던 것 같다.
4-1. 취준, 초년생 개발자로서 클린 아키텍처를 알아야하는 이유
- 웹개발자로 첫 인턴을 하면서 칭찬을 받았던 적이 있다. 내가 개발하고자 하는 서비스를 운영하는 서버비용에 대한 계산을 같이 보고했고, 회사가 필요로 하는 정보가 무엇인지 고려해본 자세에 대한 칭찬이였다. 클린아키텍처도 그런 관점에서 개발자를 하려고 하는 사람이 가지고 있으면 좋은 개념이라고 생각한다.
- 회사는 소규모의 단기적인 프로젝트보다 지속적으로 운영할 수 있는 서비스를 필요하다고 생각한다. 기존에 운영되는 서비스와 유지보수를 통해 신규로 필요한 기능을 추가 및 삭제를 진행하면서 안정적으로 운영을 하기는 원할 것이다. 단기적인 관점에서는 서비스를 개발만 하고 사용자가 사용할 수 있도록 배포하는 것이 중요하지만, 장기적인 관점을 생각한다면 언제든지 누구나 쉽게 수정할 수 있도록 기능을 분리해놓은 설계도 기반 구현이 중요할 수 밖에 없는 것 같다.
- 따라서 회사의 입장에서는 이런 설계를 이해하고 맞춰서 유지보수를 해줄 능력이 있는 개발자를 고려하게 될 것이기 때문에 알아두면 분명히 도움이 될 것이라고 생각한다.
기타 참고
'2024 Apple Developer Academy 3기' 카테고리의 다른 글
[MC3] 내 기준 가장 이상적인 팀플레이 (0) | 2024.07.10 |
---|---|
[Bridge3] 나의 1순위 내재적 동기를 찾아서 (0) | 2024.06.24 |
[NC2] 애플 생태계에서 수영해보기 (0) | 2024.06.18 |
[MC2] 유저 파악하기 실패의 기록 (0) | 2024.05.08 |
[Bridge2] 조금 늦은 정리와 계획세우기 (2) | 2024.05.01 |