이 책은 에디슨 위슬리 반 버논 시리즈의 여러 책 중의 한 권이다. 특히, 아키텍처 설계와 관련해서 관심을 가지다가 알게 되었고 번역 제안을 받아 진행하게 되었다. 쉽지 않은 과정이었지만, 몇 가지 알게 된 것, 느낀 것 그리고 번역에 대한 나의 짧은 생각을 적어 보려 한다. 

 

반 버논은 도메인 주도 설계(Domain Driven Design, DDD)가 도입되는데 큰 기여를 하고 있다. 특히, 도메인 주도 설계 구현(Implementing DDD)으로 유명하다. 나는 DDD가 요구사항과 구현 사이의 간극을 드라마틱하게 줄여주는 실천법이라고 생각한다. 이를 통해 확보한 요구 사항의 명확한 이해를 기반으로 효과적인 소프트웨어 설계를 수행할 수 있다. 흥미롭게도 비지니스 전문가와 개발자들이 함께 일하는 애자일 원칙과도 맞닿아 있다. 또한 이 책은 반 버논 시리즈의 두 번째 역서이다. 하나는 API 설계 원칙이다. 반 버논의 추천사를 보아도 두 책의 저자들이 상호 보완적으로 글을 썼다고 한다. 이 책에서는 웹 API 설계 원칙의 정렬-정의-설계-구체(Align-Define-Design-Refine) 단계 개념을 패턴에 적용하기도 했다.

 

반버논의 추천사에서 보면 유기적 구체(organic refinement)는 소프트웨어와 관련된 모든 개념이 무생물이 생물체의 측면을 '특성화'한다는 아이디어를 언급한다. 소프트웨어는 구체적인 시나리오를 통해 모델을 논의하거나 아키텍처를 설계하고 단위 테스트와 도메인 모델을 작성함으로써 살아 움직이기 시작한다. 마찬가지로, 건축가이자 패턴 개념을 만든 크리스토퍼 알렉산더는 "Nature of Order"에서 인간 배아(Embryo)의 예를 들며, 분화(Differentiation)에 대해 이야기한다. 이는 유기적 구체와 매우 유사하며 유기적이며 발전하고 성장하는 프로세스를 강조한다. 이러한 관점에서 소프트웨어의 진화는 생물의 발전에 대한 이해와 유사한 맥락에서 이해할 수 있다. 생명체가 환경과 상호작용하면서 성장하고 변화하는 것처럼, 소프트웨어도 사용자와 시스템 사이의 상호작용 속에서 점진적으로 발전하는 것이라 이해할 수 있다.

 

이 책에서 언급하는 패턴의 경우도, 이러한 유기적 구체 혹은 분화 과정에서 발견되는 반복적인 내용들을 잡아 내어 패턴의 커뮤니티에서 키워내고 리뷰하여 만들어 내는 과정을 거친다. 그러므로, 패턴, 유기적 구체, 분화와 같은 것을 이해하고 소프트웨어 구조를 설계할 때 분화의 관점 그리고 소프트웨어가 발전 성장하는 과정에서 패턴을 적용해가는 것이 필요하다는 관점에서 이 책에 접근하는 것을 권한다.

 

개별 언어는 독특한 여러 특징을 가진다. 이로 인해, 각 언어에 내재한 작은 뉘앙스 차이를 다른 언어로 옮기는 일은 매우 어렵다고 생각한다. 이 책에서는 "force"라는 어찌 보면 ""이라고 단순하게 번역할 수 있는 단어의 번역에 고민을 오래 하였다. 소프트웨어 설계에서 "force"는 설계 결정에 영향을 미치는 다양한 요인을 의미한다. 특히 이 책에서는 패턴의 문제-해결 짝과 이후에 상세 설명에서 패턴과 관련된 여러 "중요한 요구 사항"을 가리키는 용어로 등장한다. 이는 서로 상충되는 경우가 많다. 다른 아키텍처 책에서도 이 "force"를 언급하지만 이 책에서 더 자주 언급되어 시간을 들여 고민을 하였다.

 

이 책에서는 한글과 원어를 여러 번 병기하였다. 그 이유는 소프트웨어 개발이나 소프트웨어 개발과 관련된 도메인의 중심이 여전히 미국에 있기 때문이다. 따라서 영어로 쓴 원서나 관련 논문, 인터넷 글을 읽을 기회가 많으므로 원어 용어에 익숙해져야 한다. 따라서 이 책에서도 병기를 충분히 했다. 여러분도 책을 보면서 소프트웨어 아키텍처와 소프트웨어 개발에 관련된 원어 용어에 익숙해졌으면 한다.

 

번역 중 저자들과의 몇 번의 소통이 있었다. 올라프 짐머만과의 몇차례 이메일을 통해서 사소한 오탈자 뿐 아니라 그들이 생각하는 용어 정의와 같은 부분에 대해서도 의견을 나눌 수 있었다. 이러한 과정은 나에게 새로운 경험이었고, 책에 담긴 내용을 조금 더 깊게 이해하는데 도움이 되었다.

 

책의 번역을 마무리하면서 소프트웨어 개발, 설계에 대해 다시금 돌아보며 좋은 학습의 기회였고, 저자와 연락하며 짧지만 전문가들과 이야기 나눌 수 있어서 좋았다. 번역이라는 작업에 대해서도 내 생각을 정리하는 것은 나름 의미가 있었다

애자일 소프트웨어 개발 중에서 처음 제안이 되기도 했고, 가장 극단적인 방법이 Extreme Programming(XP)이라고 볼 수 있다. 프로세스 적인면에서 특히 그렇다. 또한 짝 프로그래밍(Pair programming), 지속적인 통합(Contiunous Integration), 테스트 주도 개발(Test-drinven development)와 같은 많은 실천법이 다른 개발에서도 채용되기도 했다.

 

여기서는 마틴 파울러의 2004년도 글[1]을 요약한 것이다. 원문을 읽어 보면 알게되지만, 마틴 파울러는 켄트 벡과 같은 좀 더 극단적인 XP 추종자들 보다는 더 실용적고 다른 접근 방법에 대해서도 열려 있는 것으로 보인다. 이 부분은 아키텍처 설계에 대한 관점에서 부터 크게 차이가 난다.

 

계획 설계(Planned Design)과 진화적 설계(Evolutionary Design)

마틴은 프로젝트 초반에 충분한 시간을 들여 소프트웨어 설계하고 진행하는 기존의 방식을 계획 설계라고 하고 XP와 같이 설계하는 단계 없이 점진적인 개발에서 나오는 소프트웨어도 아키텍처 혹은 구조가 있다고 하고 이를 진화적 설계로 불렀다.

   

애자일 개발에서 진화적 설계에 대해서 일반적인 비판은 임시 방편의 모음이라는 것이다. 마틴은 글에서 "우선 코딩하고 문제는 나중에 고친다(code and fix)"라고 설명하고 있다. 그리고, 시간이 지남에 따라 점점 더 코드에 기능을 추가하기 힘들어 지고 버그 수정 비용도 기하급수적으로 증가한다고 지적한다.

 

그렇다면, 반대로 계획 설계에는 문제가 없는가? 계획 설계에서는 설계자와 구현 담당자가 분리된 경우 종종 있다. 이러한 환경은 구현을 하는 동안에 해결해야 하는 문제를 설계자가 생각할 수 없어 문제를 만들기도 한다. 또한, 많은 개발자가 경험하지만, 요구 사항은 지속적으로 변하게 되어 있다. 이러한 문제들도 계획된 디자인을 유연하게 만들지 못하고 결국에는 "우선 코딩하고 문제는 나중에 고친다"의 접근법을 사용하게 된다고 마틴은 주장한다.

XP에서의 진화적 설계가 가능하게 하는 실천법

마틴은 이러한 문제의 해결법이 바로 XP에서 사용하는 실천법이라고 한다. 즉, 지속적인 테스팅(TDD)과 지속적인 통합(Continous Integration)이다. XP에서는 구현하기 전에 요구 사항을 테스트하는 테스트 코드를 만들고 이를 구현하고, 구현된 테스트 케이스들이 기존 코드의 동작을 보장하는 테스트 주도 개발(Test Driven Development)를 사용한다. 이를 통해서 다른 팀이 개발하는 기능의 통합도 매시간 혹은 매일하게 된다 (Continous Integration).

 

그 유명한 리팩토링[2]을 쓴 마틴은 리팩토링의 효과도 강조한다. 주먹구구의 느슨한 구조의 재구성과 비교해 XP에서 이야기하는 리팩토링은 아주 강력한 효과를 제공한다고 이야기 하고, 마틴으 켄트 벡으로 부터 이를 배우고 난 후에 자신이 책까지 썼다고 이야기 한다. XP에서 이야기하는 리팩터링은 TDD를 위한 테스트 코드가 존재해야 하고, 기능을 추가하지 않은 상태에서 구조를 변경하는 것이다. 리펙터링의 단순한 예제들을 따라해 봐도 이러한 것이 주는 효과는 분명하다.

 

디자인 패턴에 대한 마틴의 의견은?

나도 애자일 선언문[3]의 12가지 원칙 중 가장 좋아하는 부분이 "단순성이 -- 안 하는 일의 양을 최대화하는 기술이 -- 필수적이다(Simplicity--the art of maximizing the amount of work not done--is essential)"이다. 마틴도 이 단순성을 매우 강조한다. 즉, 현재 단계에서 필요한 것만 구현하는 것이다.

 

디자민 패턴에 대해서도 마틴은 다음과 같은 조언을 한다. XP 추종자들에게 하는 조언이기는 하지만, 애자일리스트에게는 누구에게나 적용될 수 있다.

  • 패턴에 대해 학습하는 시간을 미리 해두자.
  • 패턴을 언제 적용할 것인지 시기에 집중한다. 너무 일찍 적용하지 않도록 하자.
  • 먼저 가장 단순한 형태로 패턴을 구현하는 방법에 집중하고 복잡한 것은 나중에 추가하자.
  • 패턴을 넣었지만 충분한 효과가 없다고 생각되면 과감히 제거하라.

 

아키텍처 키우기

이 글[1]에서 마틴 파울러의 유명한 소프트웨어 아키텍처에 대한 정의가 나오기도 한다. 그는 소프트웨어 아키텍처를 시스템의 변경하기 어려운 부분인 시스템의 핵심 요소(core element)의 개념(notion)이라고 정의 한다.

 

물론 마틴은 자신을 켄트 벡과 같은 극단적인 XP 추종자가 아니라고 인정하면서 초기 소프트웨어 아키텍처가 필요하다고 이야기 한다. 하지만, 이 설계가 확정되어서 바뀌지 않는다고 생각하지 않는다. 도리어 초기 결정에 문제가 있을 수 있다는 것을 인정하고 과감하게 변경할 수 있어야 한다고 이야기 한다.

 

예를 들어 Enterprise Java Bean(EJB)와 같은 개발 언어는 초기에 결정해야 하는 중요한 결정일 수 있다. 그렇다면 EJB를 쓸지 않을지 결정은 프로젝트의 초기에 하는 것이 좋을까 가능한 늦게 하는 것이 좋을까? 당연히 가능한 초기에 결정하는 것이 좋을 것이다. 그렇다면, 과제를 시작하면서 바로 결정하는 것이 가장 좋을 것이다. 다시 생각해 보자. 그렇다면, 우리 과제에 EJB가 필요한지 아닌지 어떻게 알 수 있을까? 이를 확인하기 위한 진행이 가장 먼저 선행되어야 할 수 있다. 그 결과 EJB를 프로젝트에서 쓸지 아니면 쓰지 않을지 아키텍처적으로 결정하고 진행할 수 있을 것이다. 이 것이 마틴이 주장하는 소프트웨어 아키텍처를 진화적으로 활용하는 요체라고 할 수 있다.

 

전통적인 XP 추종자들은 아마도 데이터베이스가 필요할 때까지 데이터베이스를 적용하는 것을 가능한 미룰 것이다. 하지만, 마틴은 많은 사용자가 사용하는 시스템에서 다량의 데이터가 있다면 첫날 부터 데이터 베이스를 고려하고, 복잡한 비지니스 로직이 보이면 도메인 모델을 아키텍처에 넣으라고 조언한다.

 

문서화(UML 사용하기)

문서는 애자일리스트에게서는 적과 같다. 그렇기 때문에 UML에 사용도 그렇게 치부되어 왔다. 하지만, 많은 애자일을 따르는 개발자들이 단순화 된 UML을 사용하는데 이 또한 마틴에게 영향 받은 것을 보인다.

 

마틴은 다이어그램을 잘 사용하기 위한 조언도 제공한다.

  1. 다이어그램의 일차적 가치는 소통이므로 이를 읽을 독자를 먼저 두어야 한다. 즉, 중요한 것은 넣고 덜 중요한 것은 빼는 것이 중요하다고 강조한다.
  2. 어려운 작업일 수록 모여서 빠르게 설계 세션을 가지는 것이 중요하다고 이야기 한다. 하지만, 이 세션은 짧고, 중요한 사항만 다르며, 결과물은 최종 디자인이 아닌 스케치로 간주한다.
  3. 이러한 선행 설계는 잘못된 부분이 반드시 있을 수 있다는 것을 인정하고, 구현하면서 발경하면 디자인을 변경해야 한다는 것이다. 
  4. 디자인을 변경한다고 해서 다이어그램을 변경할 필요가 없다. 디자인을 이해하는데 도움이 되었다면 충분하고 그 다이어그램은 버려도 된다.
  5. 다이어그램이 지식 전달(Knowledge Transfer)할 때에도 유용고 중요한 문제를 요약하고 강조하는 역할을 한다. 하지만, 코드가 가장 자세한 정보의 저장소임을 잊지 말자.

경력이 쌓이면 아키텍트가 되길 원하나요?

결론 부터 이야기 하면, 마틴은 테크니컬 리드가 되라고 이야기 한다. 아키텍트라고 하면 개발 업무에서 멀어지고 코딩을 하지 않는 높은 사람처럼 보인다. XP에서 하듯이 경험많은 개발자는 자신의 기술을 강화하고 팀의 여기 저기를 다니며 코칭과 가르침을 주는 것이 더 의미있다고 주장한다.

 

여기서 고민을 해보자. 이글은 2004년도의 글이다. 2022년 내가 알고 있는 테크니컬 리드의 상반되는 양쪽 극단은 어떤 것일까? 아마도 자신의 도메인에서 경험이 아주 많은 아키텍트가 있을 것이고, 다른 한쪽에는 실무 능력을 아주 무섭게 키운 소프트웨어 장인(Software Craft)가 있을 수 있겠다. 이 두 사람 모두 자신과 연결된 사람들에게 긍정적인 영향을 끼치며 프로젝트가 좋은 방향으로 흘러가게 기여하고 있다고 가정해 보자.

 

아키텍트는 코딩이나 심지어 코드리뷰도 전혀 하지 않을 것이다. 그가 하는 일은 도메인의 지식을 넓히기 위해 트렌드를 익히고, 매일 다이어그램을 그리고 이를 기반으로 몇몇의 모듈 개발 팀의 일부 인력들(리더 혹은 그 팀의 아키텍트일 수 있고 개발자 일 수도 있다)과 소통하며 방향을 잡을 것이다. 또한, 이 인력들에게 아키텍처 설계에 대해 코칭하고 가르치고 있을 것이다. 소프트웨어 장인은 늘 자신의 개발 역량을 강화하기 위해 반복 연습(카타, 태권도의 품새 같은 연습 루틴)을 하고 새로운 기술을 익힐 것이다. 자신의 담당 모듈의 완성도를 높이며, 자신이 속해 있는 동료들과 협력하며 그들을 코칭할 수 있는 것을 생각해 볼 수 있다. 

 

이 둘은 서로 다르지만, 또한 비슷한다. 자신의 역량을 강화하고, 프로젝트를 위해 기여를 하며, 주변에 있는 동료들을 코칭하며 가르칠 것이다. 우리는 이 두 사람 사이에 어디엔가 있을 것이다. 그렇다면, 올바른 길을 가고 있는 것이라고 이야기 하고 싶다.

 

가역성(Reversibility)

마틴은 글에서 애자일 방법론에서 가역성의 중요합에 대해서 이야기 한다. 이 부분이 아키텍처에서도 중요하다는 것이다. 즉, 진화적 설계의 측면에서 되돌리기 쉬운 것은 덜 중요한 것이고 되돌릴 수 없는 결정이 중요한 결정이라는 것이다. 이러한 평가를 위해서 미래의 변경이 얼마나 어려울지 실험해 보는 것도 의미가 있다고 이야기 한다.

 

진화적 설계가 일어나고 있는가?

마틴은 진화적 설계가 일어나는지 파악하는 것이 어려운 일이라고 한다. 최근에는 프로그램 구조에 대해서 정량적/객관적 측정방법도 제안이 되고 있다. 하지만, 마틴은 이 부분은 주관적인 사실 정성적/주관적 측정이 되어야 하는 부분이라고 주장한다. 개발자가 아닌 이해당사자들은 이 부분에 대해서 이해하기 어려울 것이다. 고객의 경우를 생각한다면 자신들이 지불하고 있는 소프트웨어가 이 후에 기능을 추가하려면 더 많은 비용이 들 것인지 아닌지 알 수 없다는 것이다.

 

마틴은 구체적인 방법 대신 몇가지 제안을 한다.

  • 개발자의 이야기를 들어 보자. 코드베이스가 변경하기 어렵다고 불평한다면, 이를 심각하게 받아들이고 문제를 해결할 시간을 주어야 한다.
  • 얼마나 많은 코드가 버려지고 있는지 살펴 보자. 건강한 리팩토링을 하고 있다면 꾸준히 나쁜 코드는 삭제되고 있다. 버려지는 코드가 없다면 리팩토링이 충분하지 않다는 것이 거의 확실하다.

이러한 제안에도 불구하고 이 지표 또한 남용될 수 있다. 하지만, 우수한 개발자들의 의견은 매우 가치가 있다고 마틴은 주장한다.

 

참고 문헌

[1] Martin Folwer, "Is Design Dead?", https://martinfowler.com/articles/designDead.html

[2] 마틴 파울러, "리팩터링 2판 (리팩토링 개정판) - 코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기", 한빛미디어, 2020, 역자 남기혁

다음 글[1]에 있는 내용을 요약한 것이다.

 

얼마나 아키텍처링을 해야 하는가?

여기서도 애자일에서도 [2]에서와 같이 소프트웨어 아키텍처가 필요하다고 이야기 하고 있다. 애자일 측면에서는 창발(emegence)한다고 이야기 하고, [2]에서는 진화적 설계라는 이름이로 이야기 하고 있다. 특히, 요사이는 DevOps등도 채용되고 있기 때문에 계획된 설계 보다는 진화적 설계가 더 널리 받아들여 지고 있다.

 

[1]에서 아키텍처링이 얼마나 진행되어야 하는가에 대해서는 [2]에서와 마찬가지로 "적정하게(Just Enough)"라고 이야기 한다. 기본적으로 구출할 프로젝트나 제품에 따라 다르다고 하는 부분은 일치한다. 또한, 어느 시점에 하느냐의 경우도, 스프린트 제로 혹은 일부 스프린트(이터레이션, 반복)에서 진행해야 하는 것도 필요하다고 이야기 한다. 하지만, 어떻게 하느냐의 부분은 달라진다.

 

진화적 설계의 입장에서 위에서 언급한 것 같이 여러 번의 스프린트에서 재설계(소위 리팩터링)으로 아키텍처가 확정된다면, 초기에 충분한 선행 설계(Upfront Design)가 있는 것이 나을 수도 있었다. 저자는 이러한 것 때문에 이런 결정이 섯부르게 처리한 것이고 지연과 막대한 기술 부채를 발생시킨다고 이야기 하고, 이것이 도리어 애자일 철학에 모순된다고 주장한다. 

 

큰 기업에서의 소프트웨어 아키텍처 및 애자일 접근법

이 세셕이 [1]의 가장 중요한 부분이라고 할 수 있다.

 

처음에는 작은 스타트업으로 시작했던 기업이 크게 성장하는 경우에도 여전히 애자일 개발 방법론이 적용된다. 이는 자기조직화(Self-organized)되어 있고 작은 팀들이 책임지고 있는 기능들은 애자일하게 개발이 된다. 예측하기 힘든 여러 상황에서 팀은 이를 해결하며 적응적으로 개발을 진행한다. 특히, 이 애자일리스트들은 이러한 과정 속에서 자신들의 소프트웨어 아키텍처가 창발(emgerge)한다고 믿는다.

 

그렇지만, 앞에서 이야기 한데로 이러한 여러 팀이 있는 경우라면 회사의 전체 시스템을 이해하기 어렵고 기업에 가지는 전체의 맥락을 이해하기 어려우며 팀이 여러 중복 솔루션이나 호환성을 떨어 뜨릴 수도 있다. 이를 위해서 이러한 문제를 다룰 수 있는 전담인력들이 필요할 수 있다. 즉, 애자일과는 반대 방향일 수 있으나 중앙 집중적인 방법으로 관리되어 참조 아키텍처(Reference Architecture)으로 관리되어야 한다.

 

두 가지 측면이 반대이고 호환되지 않는 것 처럼 보일 수 있다. 하지만, [1]에서는 SAFe의 Agile Architecture를 설명하면서 이 두가지가 호환되며 상호보완적이라고 주장한다. 기존과 가장 다른 점은 자기조직화팀의 새로운 디자인을 선행 디자인으로 만들어지는 전체 아키텍처에 통합하면서 진행하는 것이다. 저자는 이를 리트로-피딩(retro-feeding, retrospective, 회고)이라고 한다.

 

의도적 아키텍처와 창발적 설계의 리트로-피딩(Retro-feeding)[1]

이러한 접근법에서 두 가지 장점을 강조한다.

  1. 의도적 아키텍처를 각 팀이 참조하여 필요로 하는 솔루션을 구축할 수 있다.
  2. 그러면서도, 팀이 어느 정도의 혁신할 수 있는 여력을 가지고, 이를 의도적 아키텍처에 포함하게 개선하고, 다른 팀이 이를 활용할 수 있게 한다.

 

이렇게 하기 위한 구조로는 Spotify의 챕터를 [1]에서는 이야기 하지만, 일반적으로 시스템 아키텍트와 어플리케이션 아키텍트 처럼 역할을 나누어 수행할 수도 있을 것으로 보인다.

 

참고 자료

[1] Miguel Arlandy, "Software Architecture and Agile. Are they both really compatible?" , Feb 19, 2019

https://medium.com/quick-code/software-architecture-and-agile-are-they-both-really-compatible-c1eef0afcbb1

[2] George Fairbanks, "Just Enough Software Architecture: A Risk-Driven Approach," 2010 https://technical-leader.tistory.com/89

 

이 부분은 소프트웨어 아키텍처 101 (Fundamentals of Software Architecture)[1]의 2장에 나온 내용을 정리한 것이다.

 

아키텍처와 설계

저자는 아키텍처와 설계를 비교 설명한다. 이를 수행하는 아키텍트와 개발자의 역할이 다음과 같다고 이야기 한다.

 

아키텍트는 아래 사항을 수행한다

  1. "비즈니스 요구사항"을 분석하여 "아키텍처 특성(Architecture Characteristics, ~bilities ~성)"을 도출/정의하고
  2. 어떤 "아키텍처 패턴"과 "아키텍처 스타일"이 해당 문제 영역에 적절한지 선택하여
  3. 각종 "컴포넌트(시스템 구성 요소)"를 만든다.

 

개발자는 다음 역살을 수행한다.

  1. 아키텍트의 수행 결과인 "아티팩트(artifact 산출물)"이 전달되면, 컴포넌트의 "클래스 다이어그램(상세 설계)"를 그린 뒤
  2. "UI 화면"을 만들고
  3. 소스 코드를 개발/테스트 한다.

이 부분에 대해서는 이견이 있을 수 있다. 상세 설계에 해당한다고 보이는 부분도 아키텍처에서 중요하다고 할 수 있는 품질 속성(Quality Attribute, 이 책에서는 아키텍처 특성이라고 하고 있다.)과 관련될 수 있고 이 부분이 아키텍처와 관련된 산출물에 포함되어 설명될 수 있기 때문이다. 이 책에서는 이러한 내용은 명시되어 있지는 않지만, 아키텍트와 개발자의 소통의 중요성을 이야기 하면서 언급되고 있다.

 

기술 폭(Technical Breadth)

아키텍트이든 개발자 이든 자신이 알고 있는 기술 세부의 범위는 한정적일 수 밖에 없다. 그렇다면, 아키텍트의 이 범위가 어떻게 될까? 책의 저자는 "내가 알고 있는 것", 내가 모른다는 사실을 아는 것" 그리고 "내가 모른다는 사실조차 모르는 것"으로 지식을 설명한다.

 

지식의 분류[1]

저자는 여기에서 지식의 깊이(Depth)와 폭(Breadth)를 구분하면서 개발자는 이 기술의 폭을 넓혀야 한다고 주장한다. 이 지식의 폭을 넓혀야 하는 이유 중에 하나가 다양한 분야에 전문성을 유지하려고 하면, 어느 하나에도 전문성을 충분히 확보하지 못할 수 있기 때문이다. 두 번째는 김빠진 전문성(stale expertise)이 나타나 자신은 첨단이라고 이야기 하지만, 실상은 낡은 정보만 가지고 있는 경우가 생긴다는 것이다.

 

정도의 차이는 있을 수 있으나 자신의 전문성을 가지고 다른 분야에도 얕지만 넓은 지식을 가지는 T자형 인재 혹은 제너럴라이징 스페셜리스트(Generalizing Specialist)[2]가 좋은 인재상으로 이야기 된다. 여기서도 깊이와 폭으로 이해될 수 있다. 마치 스펙트럼으로 살펴 보면, 한 분야의 깊이만 아주 깊은 학교의 교수, 전문가를 한쪽 끝으로 생각할 수도 있고 넓이만 아주 넓은 강연자, 이야기꾼을 반대쪽 한쪽 끝으로 생각할 수 있다. 경력이 높은 개발자와 경험이 많은 아키텍트는 모두 이 사이에 있을 것이고 개발자는 스페셜리스트에 이키텍트는 제너럴리스트에 가까울 수 있다. 하지만, 이키텍트들도 획일적으로 한 포인트에 머무르는 것이 아니고, 이 깊이와 폭이 가지각색인 것을 쉽게 예상할 수 있다.  

기술 지식의 깊이와 폭[1]

 

트레이드오프 분석

저자들은 아키텍처 사고(Architectural Thinking)의 요체를 다양한 솔루션과 기술 간의 트레이드오프를 이해하고, 분석하고, 조율하는 것이라고 한다. 책에서는 간단하지만 좋은 비교를 통해서 이 부분이 담고 있는 내용이 어떤 것인지 설명한다. 대표적인 소프트웨어 아키텍처 설계 방법인 속성 주도 설계(ADD, Attribute Driven Design)[3]에서는 품질 속성을 요구사항으로 도출해 내고 여기에 맞는 설계 전략 혹은 아키텍처 패턴/스타일을 적용[4]하는데, 여기에서 다루게 되는 것이 결국은 트레이드오프가 된다고 볼 수 있다.

 

책[1]에서는 경매 시스템을 구현할 때 컴포넌트 간의 연결을 Queue 혹은 Topic을 사용하였을 때, 트레이드오프에 대해서 논의 한다. 실재 설계할 때에는 접근 방법이 다르지만, 여기서는 우선 두 기술의 장점/단점을 비교하여 트레이드오프가 두드러지게 하였다.

 

토픽의 장점/큐의 단점 토픽의 단점/큐의 장점
아키텍처 확장성(extensibility, 책에서는 신장성)
서비스 디커플링
보안
서로 다른 계약 혼용
모니터링과 프로그래밍 방식의 규모확장성(Scalability)

Queue/Topic의 장단점 비교

 

실재로는 어떻게 될까? 여러 요인이 있겠지만 시스템의 품질 속성과 같은 여러 요소들에 따라 더 적절한 기술을 선택해 가는 것이 아키텍처 설계가 된다.

 

아키텍처와 코딩 실무 간의 균형 맞추기

이 책의 저자 뿐 아니라 다른 책의 저자들도 이야기 하지만, 아키텍트도 개발자로서 코딩 실무를 놓지 말아야 한다는 이야기를 많이 한다. 저자는 특히 이러한 균형을 맞추는 여러가지 방법을 제안하고 있고, 아주 실용적인 예들이기도 하다.

  1. 개념 증명(Proof of Concept, PoC)를 주주 해보자
  2. 개발팀이 아주 중요한 유저 스토리 작업을 할 수 있도록 기술 부채(Technical Debt) 스토리나 이키텍처 스토리에 전념한다.
  3. 개발 이터레이션을 하는 도중에 버그를 잡는다.
  4. 개발팀 일상 업무를 간소화 하거나 자동화 하는 간단한 커맨드라인 도구나 분석기를 만든다. 아키텍처 컴플라이언스 보장을 자동화 하는 피트니스 함수 측정기로 쓸 수 있는 아치유닛(ArchUnit)을 이용할 수 있다.
  5. 자주 코드 리뷰를 한다.

4번 자주 하고 있는 것 중에 하나이다. 또한, 5번은 코드의 흐름을 놓치지 않을 수 있는 좋은 접근 방법이기도 하다.

 

참고 문서

[1] 마크 리처즈, 닐 포드, "소프트웨어 아키텍처 101 (Fundamentals of Software Architecture)", 한빛 미디어, 이일웅 옮김, 2021

[2] 위르헌 아펄로, "매니지먼트 3.0" 에이콘, 조승빈 옮김. 2019

[3] 렌 베스, 폴 클레멘츠, 릭 캐즈먼, "소프트웨어 아키텍처 이론과 실제, 개정 3판" 에이콘, 전병선 옮김, 2015 

[3] "Software Architecture: 어떻게 설계 할 것인가?," https://technical-leader.tistory.com/35

조지 페어뱅크스의 "Just Enough Software Architecture: A Risk-Driven Approach"[1]은 성공적으로 소프트웨어를 만드는 것이 "가능한 실패를 예상하고 실패할 수 있는 설계를 피하는 것을 의미"한다고 이야기 한다. 그렇기 때문에 실패 리스크를 찾고 이것에 매핑하는 소프트웨어 설계 테크닉을 적용하는 것이라고 한다.

 

책에서 말하는 리스크 주도 모델의 경우는 다음과 같은 단계를 거치면서 반복하는 과정이라고 말한다.

  1. 리스크 식별/우선 순위 지정
  2. 적용할 테크닉 선택/적용
  3. 리스크 감소의 평가

소프트웨어 개발은 설계만으로 끝나는 것이 아니고 구현을 해야 한다. 그러므로 다음과 같은 예의 논리를 바탕으로 진행하는 것을 제안한다.

 

"A, B 그리고 C를 리스크로 식별하고, B는 매우 중요하다. 이를 위한 테크닉 X와 Y를 통해 B의 리스크를 줄일 수 있다고 판단했다. 결과물인 설계를 평가했을 때, B의 리스크가 충분히 완화되어 구현을 계속 했다."

 

리스크란 무엇인가?

책에서 이야기 하는 리스크의 정의는 "인지된 실패 확률*인지된 영향"이라고 정의 한다. 즉, 인지되지 않은 실패 혹률을 리스크로 판단할 수 없고, 실패로 인한 소프트웨어 프로젝트로의 영향도 파악할 수 없다면 리스크는 확인할 수 없는 것이다.

 

리스크의 구분은 크게 엔지니어링 및 프로젝트 관리 리스크로 나눈다. 일반적으로 엔지니어링 리스크는 제품의 분석, 설계 및 구현과 관련된 사항이고, 프로젝트 관리 리스크는 일정, 작업 순서, 팀 규모 등과 관련된 것들이다. 여기서 말하는 리스크는 엔지니어링 리스크와 관련되고 이를 해결하기 위한 엔지니러링 테크닉을 적용하는 것이 일반적이다.

 

리스크의 식별의 경우는 요구 사항에서 하는 것이 일반적이다. [2]에서 설명한 것과 같이 소프트웨어의 요구 사항에서 아키텍처적으로 중요한 요구 사항(Acrhitectural Significant Requirement, ASR)을 추출과 연결 시켜 보면, 추출된 요구 사항(기능적 그리고 비기능적 요구사항/품질 속성)에서 중요한 것을 찾는 것이 기존 방법과 다르게 리스크라는 관점에 중점에 두고 선정하는 것이 차이일 수 있다. 여기서 가장 달성하기 어려운 것을 찾는 것이 방법이라고 할 수있다.

 

개발하는 소프트웨어의 도메인에 따라 주로 발생하는 원형적 리스크(Prototypical Risk)도 있다. 예로는 아래와 같은 것들이 있다.

프로젝트 도메인 원형적인 리스크
정보 기술(IT) 복잡하고 잘 이해되지 않은 문제
실제 문제를 해결하고 있는지 확실하지 않음
잘못된 상용 기성품(COTS) 소프트웨어를 선택
잘 이해되지 않은 기존 소프트웨어와 통합
사람들에게 흩어져 있는 도메인 지식
수정가능성(modifiability)
시스템 성능, 신뢰성, 크기, 보안
동시성(concurrency)
구성(composition)
보안
애플리케이션 규모확장성(scalability)
개발자 생산성/표현성(expressability)

 

적용 테크닉

리스크를 인식하고 나면 리스크를 줄일 수 있을 것으로 예상 되는 테크닉(Techique)을 적용할 수 있다. 조지 페어뱅크스는 기존에 방식에서 지혜를 얻고 있다. 소프트웨어 아키텍처 설계에서 사용되는 전술(Tactics)[3] 혹은 패턴(Pattern)을 예로 들고 있다.

 

이와 같은 테크닉은 수 없이 많을 수 있다. 하지만, 시간과 돈을 낭비하지 않으려면 우선 순위가 지정된 리스크 목록을 가장 잘 줄이는 테크닉을 선택해야 한다. 즉, 최적화 문제로 생각하고 접근해야 한다. 이러하듯, 엔지니어링 리스크를 완전히 제거할 수 없는 이유는 주로 프로젝트 관리 리스크인 비엔지니러링 리스크와 균형르 이루어야 하기 때문이라고 책[1]에서는 이야기한다.

 

이와 같은 제약 뿐 아니라 적용할 테크닉의 선택을 위해서 문제의 종류(답을 찾는 문제/증명 문제), 모델의 종류(분석 모델 및 유추 모델)에 따라 다르다고 한다. 그리고, 설계를 바라 보는 관점이라고 할 수 있는 뷰타입(Viewtype)의 경우는 리스크와 테크닉의 쌍(Pair)에 효과를 다르게 하기 때문에 어떤 것이 적절한지 매칭(matching)을 고려해야 한다고 이야기 한다.

언제 멈추는가?

소프트웨어 아키텍처를 설계에는 중요한 두가지 질문은 "어떤 테크닉을 사용하는가?" 그리고 "얼마나 설계와 아키텍처를 수행하는가?"라고 할 수 있다. 리스크 주도 설계에서는 아주 단순히 답한다면 "충분히 리스크가 줄어 들 때까지 설계한다"가 답일 수 있다.

 

이를 위해서는 설계에 들이는 노력은 "리스크에 상응해야 한다"라고 한다. 저자는 아버지의 집앞에 우편함을 만드는 이야기를 하면서 단순하게 땅을 파고 우편함을 묻기만 하면 될 일을 위해서 측량을 한다거나와 같은 높은 건물 건축에서 사용되복잡한 설계나 리스크를 측정하는 테크닉을 적용할 필요가 없다는 것이다.

 

불완전해 보일 수 있지만, 리스크 모델에서는 리스크가 인식되는 영역만 설계한다는 부분과 리스크 평가가 주관적인 평가라는 부분 또한 언급한다. 이 부분은 단점일 수 있지만, 여전히 장점이기도 하다. 

계획된 설계와 진화적 설계

책[1]에서는 소프트웨어 개발에 사용되는 설계 접근 방식을 계획된 설계(Planned design)과 진화적 설계(Evolutionary design)으로 나눈다. 리스크 주도 설계는 이 두가지 설계 방식에 모두 적용 가능하다고 책에서는 주장한다.

 

진화적 설계의 경우, 주로 애자일 소프트웨어 개발 방식에서 사용하는 방식이다. 시스템이 구현됨에 따라 설계가 확장되기 때문에 지협적이고 파편화된 설계 결정이 여러 문제를 발생 시킨다. 이러한 단점을 해결하는 방법으로 리펙터링(Refactoring), 테스트 주도 설계(Test drivn design), 지속적인 통합(Continous integration)이 제안되어 채영되고 있다. 여전히 아키텍처적 입장에서는 좋은 지침에 제공되고 있지 않다고 지적하고 있다.

 

계획된 설계는 진화적 설계의 반대쪽 끝에 있다고 설명하며, 실재 구현 이전에 설계를 자세하게 작성하는 것이다. 하지만, 이는 지나친 선행 설계(Big Design Up Front BDUF)라고 비판을 받는다. 앞에서도 이야기 한 저자의 아버지 우편함에 대한 예가 주요 내용이라고 볼 수 있다.

 

위의 두 가지는 양쪽 극단이다. 마치 스펙트럼 같은 것일 수 있다. 이 중간에는 최소 계획된 설계(Minimal planned design) 또는 작은 선행 설계(Little Design Up Front LDUF)가 있다. 

 

이러한 접근 방법들은 서로 다른 지지자들이 있다. 또한 책에서는 만병통치약이 없듯이 다른 시스템에서는 다른 방법이 적절하다고 이야기 한다. 또한 둘은 다르고, 장점 또한 다르다. 이케텍처 설계를 한다는 것은 장기적인 관점을 가지고 시스템 전체의 속성을 보장하는 장점을 가진다. 진화적 설계는 앞에서 살펴 보았듯이 리펙터링, TDD 그리고 지속적 통합(CI)의 장점을 활용할 수 있다.

프로세스에 적용하기

리스크 주도 소프트웨어 설계를 여러 프로세스에 적용하기 위해서 몇가지 프로세스의 특징을 우선 아래와 같이 정리하고 있다.

 

프로세스 선행 설계 설계 본질 작업 우선순위 반복 길이
폭포수 분석 및 설계 단계 계획된 설계, 재설계 없음 개방적 개방적
반복적 선택적 계획 또는 진화, 재설계 허용 개방적이고 종종 기능 중심 개방, 보통 1-8
나선 없음 계획 또는 진화 가장 위험한 작업부터 개방적
UP/RUP 선택적, 사전 설계 전반부 배치 계획 또는 진화 가장 위험한 작업 후, 가장 높은 가치 보통 2~6
XP 없음, 일부 반복 제로(Iteration zero) 진화적 설계 가장 높은 고객 가치 우선 보통 2~6

우선 애자일 개발 프로세서인 eXtream Programming(XP)에서는 일반적으로 설계 작업이 없지만 반복 제로(Iteration zero)를 두고 설계 작업을 할 수 있다. 이렇게 반복적인 작업에서 리스크 주도 기반 접근법은 각 단계에서 필요한 만큼 설계 작업을 진행할 수 있다.

 

반복적 프로세스에서 단계별 설계의 양이 다른 예

대표적인 애자일 프로세스인 스크럼은 기능 중심적 접근 방법으로 백로그에 구현할 기능을 관리한다. 여기에도 리스크 주도 접근법을 적용할 수 있다. 일반적인 기능 백로그는 제품 책임자(Product Owner)가 담당한다. 재품 책임자는 엔지니러링 리스크를 파악하기 어렵다. 대신 소프트웨어 팀이 리스크를 파악하고 이를 시스템의 테스트 가능한 기능으로 만들어 백로그에 넣을 수 있다. 이는 각 반복이 끝날 때 회고(Reflection/Retrospective)의 결과로 도출될 수 있다. 제품 책임자는 백로그 중에서 우선 순위에 따라 각 단계에 수행할 내용을 결정할 수 있다.

기능 및 리스크 백로그 활용방안

 

정리해보면...

리스크 주도 설계 접근 방식의 주요 내용은 설계에 많은 비용이 들기 때문에 리스크를 찾아 내고 이에 적절한 테크닉을 필요한 만큼 반복적으로 적용한다는 것이다. 이는 현재 소프트웨어 개발에서 적용되고 있는 양극단의 소프트웨어 개발 방법에서 모두 적용할 수 있다고 책에서는 주장하고 있다.

 

특히, 애자일 혹은 반복적 개발에서 알 수 있는 주요한 내용 중 하나가 설계가 항상 개발 프로세스의 앞부분에서만 진행되어야 하는 것은 아니라는 점이다. 후반부에 설계를 하게 되면설계를 변경하기 어려운 부분도 있을 수 있으나 구현하려고 하는 소프트웨어를 초기에는 잘 알지 못하는 문제점도 있다는 것을 책에서는 지적하고 있다. 소프트웨어 개발이 지식을 얻어 가는 과정이라면, 이를 이를 소프트웨어 설계 및 아키텍처링에 적용하는 방법으로서 리스크 주도 접근 방식의 장점과 적용하는 방법에 대해서도 흥미로운 재안을 하고있다.

참고 도서

[1] George Fairbanks, "Just Enough Software Architecture: A Risk-Driven Approach," 2010

[2] "Software Architecture: 어떻게 설계 할 것인가?," https://technical-leader.tistory.com/35

[3] 렌 베스 , 폴 클레멘츠 , 릭 캐즈먼, "소프트웨어 아키텍처 이론과 실제, 개정판 3판" 2015년, 에이콘출판

[4] Design Pattern 배우기, https://technical-leader.tistory.com/7?category=1015396 

 

 

개요

클린 아키텍처[1]의 5부 아키텍처에서는 원칙이라고 분류하여 설명하지는 않는다. 물론 YAGNI(You Aren't Going to Need It), KISS(Keep It Simple Stupid), DRY(Don't Repeat Yourself)에 대해서 언급하기는 하지만 이 원칙은 아키텍처 설계와 관련되지만 객체 지향 설계에서도 채용되는 개념으로 클린 아키텍처에서 말하는 아키텍처 설계의 핵심 원칙이라고 하기는 어렵다. 여기서는 클린 아키텍처 책의 4부에서는 이야기 하는 주요 개념들을 정리해 본다. 즉, 우선은 15장 부터 22장까지의 내용을 정리하고자 한다. 다른 장들은 전체적인 개념을 바탕으로 상세화 하여 설명한다고 볼 수 있어 여기서는 다루지 않겠다. 

 

언급하는 주요 항목은 프로덕트 생명 주기 (개발, 배포 운영)를 고려하되, 소프트웨어 시스템을 지금까지 논의 한 컴포넌트로 분리하는 것인데, 이를 경계(Boundary)라고 하고 아키텍처 설계는 결국은 어떻게 경계를 나누는가에 대한 것이다. 소프트웨어를 시스템에서 정책(Policy)을 가장 핵심적인 요소로 식별하고 동시에 세부 사항(Detail)은 정책에 무관하게 만들어 이를 결정하는 일은 연기할 수 있게 한다. 더 구체적으로 보면, 정책, 레벨, 그리고 각각의 업무 원칙들을 어떻게 구획을 나누어 컴포넌트로 분리하는지에 대한 결합 분리(Decoupling, 디커플링)에대해 설명한다. 책에서 결국 주장하고자 하는 클린 아키텍처는 핵심 업무 규칙에 가장 엔터프라이즈 업무 규칙을 엔터티로 구분하고 이를 사용하여 구현하는 어플레케이션 업무 규칙을 유스 케이스 계층으로 본다. 이를 외부에서 사용하기 편하게 변환할 인터페이스 어댑터가 그 다음 계층이며 세부 사항인 외부 인터페이스를 프레임워크와 드라이버로 구분한다.

 

책에서는 좋은 아키텍처는 시스템이 모노리틱 구조(Monolithic Structure)로 태어나서 단일 파일로 배포되더라도, 이후에는 독립적으로 배포 가능한 단위들의 집합으로 성장하고 또 독립적인 서비스나 마이크로서비스 수준까지 성장할 수 있도록 만들어져야 한다고 이야기 한다. 각각의 상세 사항을 살펴 보자.

 

15장 아키텍처란

이책의 영어 원제는 Clean Architecture: a craftsman's guide to software structure and design 이다. 즉, 소프트웨어 크래트맨쉽을 강조하는 저자의 경험으로 소프트웨어 구조와 설계에 대한 가이드로서 아키텍처를 논하는 것이라 볼수 있다. 그래서, 저자는 아키텍트가 개발을 계속해야 한다고 한다. 이 부분에 대해서는 이견이 있을 수 있다.

저자는 소프트웨어 시스템의 아키텍처란 시스템을 구축했던 사람들이 만들어낸 시스템의 형태라고 한다. 이 형태는 개발, 배포, 운영, 유지보수되도록 만들어진다고 한다. 이러한 일을 용이하게 만들기 위해서는 가능한 한 많은 선택지를, 가능한 한 오래 남겨두는 전략을 따라야 한다고 이야기 한다.

  • 개발: 팀 구조가 다르다면 아키텍처 관련 결정에서도 차이가 난다. 이를 콘웨이의 법칙으로 연결하여 설명하였다.
  • 배포: 배포 비용이 높을수록 시스템의 유용성은 떨어진다. 따라서 소프트웨어 아키텍처는 시스템을 단 한 번에 쉽게 배포할 수 있도록 만드는 데 그 목표를 두어야 한다.
  • 운영: 아키텍처가 시스템 운영에 미치는 영향은 개발, 배포, 유지보수에 미치는 영향보다는 덜 극적이다. 운영에서 겪는 대다수의 어려움은 단순히 하드웨어를 더 투입해서 해결할 수 있다. 이는 인터넷 서비스를 기준으로 하는 이야기로 보이며, 실재로 휴대폰과 같은 제품을 목표라면 제품의 비용이 적게 해야 할 수 있다. 하드웨어는 값싸고 인력은 비싸다는 말이 뜻하는 바는 운영을 방해하는 아키텍쳐가 개발, 배포, 유지보수를 방해하는 아키텍쳐보다는 비용이 덜 든다는 것이다. 즉, 인력/하드웨어 보다는 비용 측면에서 고려해야 하지 않을까? 상황에 따라 사람이 비쌀 때도 혹은 프러덕트의 다른 요소의 비용이 높을 수도 있다고 생각한다.
  • 유지보수: 유지보수는 모든 측면에서 봤을 때 소프트웨어 시스템에서 비용이 가장 많이 든다. 새로운 기능은 끝도 없이 행진하듯 발생하고 뒤따라서 발생하는 결함은 피할 수 없으며, 결함을 수정하는 데도 엄청난 인적 자원이 소모된다.

모든 소프트웨어 시스템은 주요한 두 가지 구성요소로 분해할 수 있다. 바로 정책(Polciy)세부사항(Detail)이다. 정책 요소는 모든 업무 규칙(Business Rules)과 업무 절차(Procuedures)를 구체화 한다. 정책이란 시스템의 진정한 가치가 살아 있는 곳이다. 세부사항은 사람, 외부 시스템, 플그래머가 정책과 소통할 대 필요한 요소지만, 정책이 가진 행위에는 조금도 영향을 미치지 않는다. 이러한 세부 사항에는 입출력 장치, 데이터 베이스, 웹 시스템, 서버, 프레임워크, 통신 프로토콜 등이 있다.

 

아키텍트의 목표는 시스템에서 정책을 가장 핵심적인 요소로 식별하고 동시에 세부사항은 정책에 무관하게 만들 수 있는 형태의 시스템을 구축하는 데 있다. 세부사항을 결정하는 일은 미루거나 연기할 수 있게 된다. 요점을 파악했을 것이다. 세부사항에 몰두하지 않은 채 고수준의 정책을 만들 수 있다면, 이러한 세부사항에 대한 결정을 오랫동안 미루거나 연기할 수 있다. 이러한 결정을 더 오래 참을 수 있다면, 더 많은 정보를 얻을 수 있고, 이를 기초로 제대로 된 결정을 내릴 수 있다. 선택 사항을 더 오랫동안 열어 둘 수 있다면 더 많은 실험을 해볼 수 있고 더 많은 것을 시도할 수 있다. 그리고 결정을 더 이상 연기할 수 없는 순간이 닥쳤을 대 이러한 실험과 시도 덕분에 더 많은 정보를 획득한 상태일 것이다. 좋은 아키텍트는 결정되지 않은 사항의 수를 최대화 한다.

 

16장 독립성.

좋은 아키텍처는 결국은 요구 사항을 지원해야 한다. 클린 아키텍처에서도 이 요구 사항을 시스템의 유스케이스를 지원해야 한다고 이야기 한다.  여기에 추가로 아키텍처는 이전에 이야기 한 프러덕트의 라이프 싸이클 중에서 유지 보수를 제외한 운영, 개발, 배포 관점에서도 지원해야 한다고 이야기 한다.

어쩌 보면, 아키텍처는 시스템의 행위에 그다지 큰 영향을 주지 않는다. 다른 말로 행위와 관련하여 아키텍처가 열어 둘 수 있는 선택사항은 거의 없다. 좋은 아키텍처가 행위를 지원하기 위해 할 수 있는 일 중 에서 가장 중요한 사항은 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아 볼 수 있게 만드는 것이다.
이 부분은 뒤에도 나오지만, "소리치는 아키텍처"로 다르게 표현하기도 한다.

아키텍처를 구조적인 분리로 보면, 계층(Layer)와 유스 케이스(Use Case) 차원에서 분리로 볼 수 있다. 계층 디커플링/결합 분리 (Decoupling Layers) 측면에서 보면, 사용자 인터페이스(User Interface, UI)가 변경되는 이유는 업무 규칙(Business Rule)과는 아무런 관련이 없다. 만약 유스케이스가 두 가지 요소를 모두 포함한다면, 뛰어난 아키텍트는 유스케이스에서 UI부분과 업무 규칙 부분을 서로 분리하고자 할 것이다.


업무 규칙은 그 자체가 애플리케이션과 밀접한 관련이 있거나, 혹은 더 범용적일 수도 있다. 예를 들어 입력 필드 유효성 검사는 애플리케이션 자체와 밀접하게 관련된 업무 규칙이다. 계좌의 이자 계산이나 재고품 집계는 업무 도메인에 더 밀접하게 연관된 업무 규칙이다. 이들 서로 다른 두 유형의 규칙은 각자 다른 속도로 그리고 다른 이유로 변경될 것이다. 이들 규칙은 서로 분리하고, 독립적으로 변경할 수 있도록 만들어야 한다. 즉, 분리/디커플링 해야 한다.

서로 다른 이유로 변경되는 것에 유스케이스 그 자체가 포함된다. 이를 유스 케이스 디커플링 (Decoupling Use Cases)이라고 한다. 주문 입력 시스템에서 주문을 추가하는 유스케이스는 주문을 삭제하는 유스케이스와는 틀림없이 다른 속도로 그리고 다른 이유로 변경된다. 유스케이스는 시스템을 분할하는 매우 자연스러운 방법이라고 설명한다.

 

결합 분리/디커플링(Decoupling)[1]



이렇게 분리된 컴포넌트를 서로 다른 서버에서 실행해야 하는 상황이라면, 이들 컴포넌트가 단일 프로세서의 동일한 주소 공간에 함께 상주하는 형태로 만들어져서는 안 된다. 분리된 컴포넌트는 반드시 독립된 서비스가 되어야 하고 일종의 네트워크를 통해 서로 통신해야 한다. 많은 아키텍트가 이러한 컴포넌트를 '서비스(Service)' 또는 '마이크로서비스(Microservice)라고 하는데, 그 구분 기준은 모호한 면이 있다.  실제로 서비스에 기반한 아키텍처를 흔히들 서비스 지향 아키텍처(Service-oriented architecture SOA)라고 부른다.

17장 경계: 선 긋기

소프트웨어 아키텍처는 선을 긋는 기술이며, 저자는 이 선을 경계(boundary)라고 부른다. 경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막는다. 아키텍트가 내리는 경계의 결정이 핵심적인 업무 로직을 오염시키지 못하게 만들려는 목적으로 쓰인다. 그러면서, 이케텍트의 목표는 필요한 시스템을 만들고 유지하는 데 드는 인적 자원을 최소화하는 것이라는 사실을 상기하자. 로버트 마틴은 인적 자원이 가장 비싼 자원으로 보고 있다.


어떤 종류의 결정이 이른 결정일까? 바로 시스템의 업무 요구사항, 즉 유스케이스와 아무런 관련이 없는 결정이다. 프레임워크, 데이터베이스, 웹 서버, 유틸리티 라이브러리, 의존성 주입에 대한 결정 등이 여기 포함된다.

어떤게 선을 그을까? 그리고 언제 그을까? 이와 관련된 답으로는 관련이 있는 것과 없는 것 사이에 선을 긋는 것이 기본 규칙이라 할 수 있다.  GUI는 업무 규칙과는 관련 없기 때문에, 이 둘 사이에는 반드시 선이 있어야 한다. 데이터베이스는 GUI와는 관련이 없으므로, 이 둘 사이에도 반드시 선이 있어야 한다. 데이터베이스는 업무 규칙과 관련이 없으므로, 이 둘 사이에도 선이 있어야 한다.

 

업무 규칙(Business Rules)에 플러그인으로 연결하기[1]


두 컴포넌트 사이에 이러한 경계선을 그리고 화살표의 방향이 Business Rules를 향하도록 만들었으므로, Busines Reules에서는 어떤 종류의 데이터 베이스도 사용할 수 있음을 알 수 있다. Database 컴포넌트는 다양한 구현체로 교체될 수 있으며, Business Rules는 조금도 개의치 않는다. GUI는 다른 종류의 인터페이스로 얼마든지 교체할 수 있으며 BusinessRules는 전혀 개의치 않는다는 사실을 알 수 있다. 데이터베이스와 GUI에 대해 내린 두 가지 결정을 하나로 합쳐서 보면 컴포넌트 추가와 관련한 일종의 패턴, 즉, 플러그인 아키텍처가 만들어진다.

18장 경계 해부학(Boundary Anatomy)

위와 같이 분리된 일련의 소프트웨어 컴포넌트와 그 컴포넌트들을 분리하는 경계를 통해서 시스템 아키텍처가 정의 된다. 런타임에 경계를 횡단한다함은 그저 경계 한쪽에 있는 기능에서 반대편 기능을 호출하여 데이터를 전달하는 일에 불과하다. 적절한 위치에서 경계를 횡단하게 하는 비결은 소스 코드 의존성 관리에 있다.

단일체로 변역하는 모노리틱(Monolithic) 구조는 쉽게 이야기 하면, 단일 실행 파일에 지나지 않는다. 이 파일은 정적으로 링크된 C/C++ 프로젝트이거나, 실행 가능한 jar 파일로 묶인 일련의 자바 클래스 파일이거나, 단일 .EXE파일로 묶인 일련의 .NET 바이너리등일 것이다.

배포형 컴포넌트(library, jar, DLL), 스레드, 로컬 프로세스(OS상의 Process), 서비스(물리적으로도 분리된 컴퓨팅 리소스에서 동작) 등으로 컴포넌트들을 분리하는 방법들이 있을 수 있다.

19장 정책과 수준

소프트웨어 시스템이란 정책(Policy)을 기술한 것이다. 실제로 컴퓨터 프로그램의 핵심부는 이게 전부다. 컴포터 프로그램은 각 입력을 출력으로 변환하는 정책을 상세하게 기술한 설명서다. 대다수의 주요 시스템에서 하나의 정책은 이 정책을 서술하는 여러 개의 조그만 정책들로 쪼갤 수 있다. 예를 들어 집계와 관련된 업무 규칙을 처리하는 방식을 서술하는 조그만 정책이 있을 수 있다. 그리고 특정 보고서를 어떤 포맷으로 만들지 서술하는 또 다른 정책이 있을 수 있다. 또한 입력 데이터를 어떻게 검증할지를 서술하는 정책이 있을 수 있다.

수준(Level)을 엄밀하게 정의하자면 '입력과 출력까지의 거리'다. 시스템의 입력과 출력 모두로 부터 멀리 위치할 수록 정책의 수준은 높아진다. 입력과 출력을 다르는 정책이라면 시스템에서 최하위 수준에 위치한다.

정책을 컴포넌트로 묶는 기준은 정책이 변경되는 방식에 달려 있다는 사실을 상기하자. 단일 책임 원칙(SRP)와 공통 폐쇄 원칙(CCP)에 따르면 동일한 이유로 동일한 시점에 변경되는 정책은 함께 묶인다. 고수준 정책, 즉 입력과 출력에 서부터 멀리 떨어진 정책은 저수준 정책에 비해 덜 빈번하게 변경되고, 보다 중요한 이유러 변경되는 경향이 있다.저수준 정책, 즉 입력과 출력에 가까이 위치한 정책은 더 빈번하게 변경되며, 보다 긴급성을 요하며, 덜 중요한 이유로 변경되는 경향이 있다.


이처럼 모든 소스 코드 의존성의 방향이 고수준 정책을 향할 수 있도록 정책을 분리했다면 변경의 영향도를 줄일 수 있다. 시스템의 최저 수준에서 중요하지 않지만 긴급한 변경이 발생하더라도, 보다 높은 위치의중요한 수준에 미치는영향은 거의 없게 된다.

20장 업무 규칙

업무 규칙(Business Rules)은 사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차다. 더 엄밀하게 말하면 컴퓨터 상으로 구현했는지와 상관없이, 업무 규칙은 사업적으로 수익을 얻거나 비용을 줄일 수 있어야 한다. 심지어 사람이 수동으로 직접 수행하더라도 마찬가지다. 이러한 규칙을 핵심 업무 규칙(Criticial Business Rule)이라고 부를 것이다. 왜나하면 이들 규칙은 사업 자체에 핵심적이며, 규칙을 자동화하는 시스템이 업서라도 업무 규칙은 그대로 존재하기 때문이다. 핵심 업무 규칙은 데이터를 요구하는데, 책에서는 이를 핵심 업무 데이터(Critical Business Data)라고 부르고 있다. 


핵심 규칙과 핵심 데이터는 본질적으로 결합되어 있기 때문에 객체로 만들 좋은 후보가 된다. 우리는 이러한 유형의 객체를 엔티티(Entity)라고 부른다.

유스케이스
자동화된 시스템이 동작하는 방법을 정의하고 제약함으로써수익을 더거나 비용을 줄이는 업무 규칙도 존재한다. 바로 이것이 유스케이스(Use case)다. 유스케이스는 애플리케이션에 특화된 업무 규칙(Application-specific Business Rules)을 설명한다.

21장: 소리치는 아키텍처

여러분의 애플리케이션 아키텍처는 뭐라고 소리치는가? 상위 수준의 디렉터리 구조, 최상위 패키지에 담긴 소스 파일을 볼 때, 시스템의 본질을 이야기 하는가? 즉,  "핼스 케어 시스템이야" 또는 "재고 관리 스스템이야"라고 소리치는가? 아니면 사용하고 있는 디테일인 프레임워크에 대해서 이야기 하고 있는가? 예를 들자면, "레일스(Rails)야", "스프링(Spring)/하이버네이트(Hibernate)야", 아니면 "ASP야"라고 소리치는가?

책에서는 좋은 아키텍처는 유스케이스를 그 중심에 두기 때문에, 프레임워크나 도구 환경에 전혀 구애받지 않고 유스케이스를 지원하는 구조를 아무런 문제 없이 기술 할 수 있다고 이야기 한다. 좋은 소프트웨어 아키텍처는 프레임워크, 데이터베이스, 웹 서버, 그리고 여타 개발 환경 문제나 도구에 대해서는 결정을 미룰 수 있도록 만든다.

 

프레임워크는 열어 둬야 할 선택사항이다. 좋은 아키텍처는 프로젝트의 훨씬 후반까지 레일스, 스프링, 하이버네이트, 톰갯(Tomcat), MySQL에 대한 결정을 하지 않아도 되도록 해준다. 프레임워크, 데이터베이스, 웹 서버, 여타 개발환경 문제나 도구에 대한 결정을 미룰 수 있어야 한다. 프레임워크는 매우 강력하고 상당히 유용할 수 있다. 하지만, 프레임워크는 도구일 뿐, 삶의 방식은 아니다.

22장: 클린 아키텍처

책에서는 다른 아키텍처 접근 방법에 대해서 설명한다. 이 접근 법들도 목표가 같은데, 바로 관심사의 분리(Seperation of concerns)다. 모두 소프트웨어를 계층으로 분리함으로써 이 관심사의 분리라는 목표를 달성한다고 한다.

이들 아키텍처는 모두 시스템이 다음과 같은 특징을 지니도록 만든다.
. 프레임워크 독립성
. 테스트 용이성
. UI 독립성
. 데이터베이스 독립성
. 모든 외부 에이전시에 대한 독립성

아래 다이어그램은 이들 아키텍처 전부를 실행 가능한 하나의 아이디어로 통합하려는 시도라고 이야기 한다. 즉, 핵심 개념도로 볼 수 있으며 지금까지 이야기 한 부분에 대한 큰 그림으로 이해할 수 있다.

 

클린 아키텍처[1]

 

의존성 규칙

클린 아키텍어 다이어 그램에서 안으로 들어갈 수록 고수준의 소프트웨어가 된다. 바깥쪽 원은 메커니즘이고 안쪽 원은 정책이다.  여기서 가장 중요한 규칙은 의존성 규칙(Dependency Rule)이다. 즉, 소스코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다. 내부의 원에 속한 요소는 외부의 원에 속한 어떤 것도 알지 못한다. 함수, 클래스, 변수, 그리고 소프트웨어 엔티티로 명명되는 모든 것이 포함된다. 같은 이유로, 외부의 원에서 선언된 데이터 형식도 (Data Type)도 내부의 원에서 절대로 사용해서는 안 된다.

엔티티

엔티티는 전사적인 핵심 업무 규칙을 캡슐화한다. 엔티티는 메서드를 가지는 객체이거나 일련의 데이터 구조와 함수의 집합이다. 운영 관점에서 특정 애플리케이션에 무언가 변경이 필요하더라도 엔티티 계층에는 절대로 영향을 주어서는 안 된다.

인터페이스 어댑터

인터페이스 어댑터(Interface Adapter) 계층은 일련의 어댑터들로 구성된다. 어댑터는 데이터를 유스케이스와 인터티에게 가장 편리한 형식에서 데이터베이스나 웹 같은 외부 에이전시에게 가장 편리한 형식으로 변환한다. GUI의 MVC 아키텍처를 모두 포괄한다. 프레젠터(Presenter), 뷰(View), 컨트롤러(Controller)는 이 계층에 속한다. 모델은 그저 이 계층에서 사용되는 데이터 구조로 보면 된다.

프레임워크와 드라이버

클린 아키텍처 그림에서 가장 바깥쪽 계층은 일반적으로 데이터베이스나 웹 프레임워크 같은 프레임워크나 도구들로 구성 된다. 이는 안쪽 원과 통신하기 위한 접합 코드로 볼 수 있다.

원은 네 개여야만 하나?

이 다이어 그램은 개념을 설명하기 위한 하나의 예시로, 실재 시스템에서는 더 많은 원이 필요할 수도 있다. 하지만, 어떤 경우에도 의존성 규칙은 적용된다. 소스 코드 의존성은 항상 안쪽으로 향한다. 안쪽으로 이동할 수록 추상화와 정책의 수준은 높아진다. 가장 바깥쪽 원은 저수준의 구체적인 세부사항으로 구성된다.

경계 횡단하기

그림의 우측 하단 다이어그램에 원의 경계를 횡단하는 방법을 보여 주는 예시가 있다. 컨트롤러와 프레젠터가 다음 계층에 속한 유스케이스와 통신하는 모습을 확인할 수 있다. 우선 제어흐름에 주목해 보자. 제어흐름은 컨트롤러에서 시작해서, 유스케이스를 지난후, 프레젠터에서 실행되면서 마무리 된다. 이와 상반되게 의존성은 반대이다.이와 같은 경우, 대체로 의존성 역전 원칙을 사용하여 해결한다.


경계를 가로지르는 데이터는 흔히 간단한 데이터 구조로 이루어져 있다. 기본적인 구조체나 간단한 데이터 전송 객체(Data Transfer Object)등 원하는 대로 고를 수 있다. 또는 함수를 호출할 때 간단한 인자를 사용해서 데이터로 전달할 수도 있다. 그게 아니라면 데이터를 해시맵으로 묶거나 객체로 구성할 수도 있다.

전형적인 시나리오

이 클린 아키텍처의 예로서 전형 적인 웹 서비스 아키텍처를 아래 그림에서 살펴 볼 수 있다. 웹 서버는 사용자로 부터 입력 데이터를 모아서 좌측 상단의 Controller로 전달한다. Controller는 데이터를 평범한 자바객체(POJO)로 묶은 후, InputBoundary 이터페이스를 통해 UseCaseInsteractor로 전달한다. UseCaseInteractor는 이 데이터를 해석해서 Entities가 어떻게 춤출지를 제어하는데 사용한다.

 

 

전형적 웹 서비스 구조[1]

 

결론

클린 아키텍처에서 주장하는 가장 핵심 적인 것을 보통 클린 아키텍처 다이어 그램에서 이야기 하는 4가지 영역의 분리라고 볼 수 있다. 서론에서도 이야기 했듯이 아키텍처의 구조적인 측면에서 경계(Boundary)를 만드는 것이 가장 중요한 일일 수 있다. 다른 말로 관심사의 분리(Separation of Concern)이라고도 했다.

 

이 분리의 주된 목적은 변경이 필요할 때, 영향을 받지 않는 부분을 분리하자는 것이다. 이렇게 하기 위해 가장 먼저 분리 한 것은 정책(Policy)와 세부 사항(Detail)이다. 그림에서는 프레임워크/드라이버와 나머지가 분리되었다고 볼 수 있다. 정책에서는 업무 규칙 중에서도 유스 케이스에서 더 핵심적인 것을 분리해서 엔터프라이즈 업무 규칙과 유스 케이스 업무 규칙이 분리되고 이 것들이 외부의 세부 사항과 연결하기 위한 영역으로 인터페이스 어댑터를 둔 것으로 볼 수 있다. 

 

이 구조에서 가장 중요한 원칙이 의존성 규칙이라고 했다. 즉, 내부 영역이 외부 영역에 의존하면 안되고 몰라야 한다는 것이다. 혹시 제어 흐름이 반대인 것은 의존성 역전 원칙(DIP)를 적용하는 것도 언급하였다. 이렇게 하면서, 세부 사항들은 늦게 결정하도록 하면서 여러 시험을 통해 더 좋은 선택을 할 수 있게 한다고 이야기 하고 이 것이 좋은 아키텍처, 클린 아키테처라고 주장한다.

 

애자일과 소프트웨어 크래프트맵쉽을 중시하는 저자의 철학과 연결되는 것이 제품/프러덕트가 모노리틱 구조(Monolithic Structure)로 태어나고, 성장하고, 분화 한다고 이야기로 보인다. 이렇게 분화한 것이  독립적인 서비스(Service Oriented Architecture, SOA)나 마이크로서비스(Microservice Architecture)가 된다고 한다. 

 

참고 문헌

[1] 로버트 C. 마틴 저, "클린 아키텍처 소프트웨어 구조와 설계의 원칙" 인사이트(insight) 2019년 08월 20일, 송준이 역

 

컴포넌트 원칙

Cleann Architecture[1]에서 컴포넌트는 배포 단위를 나타낸다. 즉, "시스템의 구성 요소"로 배포할 수 있는 "가장 작은 단위"로서, 자바의 경우 jar 파일이 루비에서는 gem파일 그리고, 닷넷에서는 DLL이 그것이라고 할 수 있다. 컴포넌트가 마지막에 어떤 형태로 배포되든, 잘 설계된 컴포넌트라면 반드시 독립적으로 배포 가능한, 따라서 독립적으로 개발 가능한 능력을 갖춰야 한다. 즉, 팀이 개발하는 독립된 단위가 컴포넌트여야 한다.

 

이전에 이야기한 SOLID원칙이 벽과 방에 벽돌을 배치하는 방법을 알려 준다면, 여기서 이야기 하는 컴포넌트 원칙은 빌딩에 방을 배치하는 방법을 설명해 주는 것이라 생각하면 된다고 한다. 

 

주로 응집도(Cohesion)과 결합도 혹은 의존도(Coupling)에 대해서 논의 한다. 응집도는 관련된 것들이 모여 있도록 하는 법칙이다. 반대로 의존도는 관련 없는 것들을 분리하는 것이라고 볼 수 있다. 컴포넌트들이 관련 없을 수 없기 때문에 효과적으로 의존하도록 만든는 것이라 볼 수 있다.

컴포넌트 응집도 관련한 원칙

 

재사용/릴리즈 등가 원칙(Reuse/Relese Equivalence Principle, REP)

REP를 한마디로 말하자면, "재사용 단위는 릴리스 단위와 같다"라는 것이다.

 
재사용/릴리즈 등가 원칙은 너무 당연해 보인다. 소프트웨어 컴포넌트가 릴리스 절차를 통해 추척 관리되지 않거나 릴리스 번호가 부여되지 않는다면 해당 컴포넌트를 재사용하고 싶어도 할 수도 없고, 하지도 않을 것이다.


이 원칙을 소프트웨어 설계와 아키텍처 관점에서 보면 단일 컴포넌트는 응집성 높은 클래스와 모듈들로 구성되어야 함을 뜻한다. 단순히 뒤죽박죽 임의로 선택된 클래스와 모듈로 구성되어서는 안 된다. 컴포넌트를 구성하는 모든 모듈은 서로 공유하는 중요한 테마나 목적이 있어야 한다.


이 조언만으로는 클래스와 모듈을 단일 컴포넌트로 묶는 방법을 제대로 설명하기 힘들기에 이 조언이 약하다고 하는 것이다. 그렇더라도 이 원칙 자체는 중요하다. 이 원칙을 어기면 쉽게 발견할 수 있기 때문이다.

 

공통 폐쇄 원칙 (Common Closure Principle, CCP)

CCP는 "동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어라. 서로 다른 시점에 다른  이유로 변경되는 클래스는 다른 컴포넌트로 분리하라."라는 원칙이다.


이 원칙은 단일 책임 원칙(SRP)을 컴포넌트 관점에서 다시 쓴 것이다. 공동 폐쇄 원칙(CCP)에서도 마찬가지로 단일 컴포넌트(Component)는 변경이 이유가 여러 개 있어서는 안 된다고 말한다.

 

대다수의 애플리케이션에서 유지보수성(maintainability)은 재사용성보다 훨씬 중요하다. 애플리케이션에서 코드가 반드시 변경되어야 한다면, 이러한 변경이 여러 컴포넌트 도처에 분산되어 발생하기보다는, 차라리 변경 모두가 단일 컴포넌트에서 발생하는 편이 낫다. CCP는 같은 이유로 변경될 가능성이 있는 클래스는 모두 한곳으로 묶을 것을 권한다.

 

이 원칙은 개방 폐쇄 원칙(OCP)과도 밀접하게 관련되어 있다. 실제로 CCP에서 말하는 '폐쇄closure'는 OCP에서 말하는 '폐쇄closure'와 그 뜻이 같다. OCP에서는 클래스아 변경에는 닿혀 있고 확장에는 열려 있어야 한다고 말한다. 100%완전한 폐쇄란 불가능하므로 전략적으로 폐쇄해야 한다.

 

공통 재사용 원칙(Common Reuse Principle, CRP)

CRP는 "컴포넌트 사용자들은 필요하지 않은 것에 의존하게 강요하지 말라"라는 원칙이다.

 
공통 재사용 원칙(CRP)도 클래스와 모듈을 어느 컴포넌트에 위치시킬지 결정할 때 도움되는 원칙이다. CRP에서는 같이 재사용되는 경향이 있는 클래스와 모듈들은 같은 컴포넌트에 포함해야 한다고 말한다.

 

간단한 사례로 컨테이너 (Container) 클래스와 해당 클래스의 이터레이터(Iterator) 클래스를 들 수 있다. 이들 클래스는 서로 강하게 결합되어 있기 때문에 함께 재사용 된다. 따라서 이들 클래스는 반드시 동일한 컴포넌트에 위치해야 한다. 따라서 CRP는 어떤 클래스를 한데 묶어도 되는지 보다는, 어떤 크래스를 한데 묶어서는 안되는지에 대해서 훨씬 더 많은 것을 이야기 한다. CRP는 강하게 겨합되지 않은 클래스들을 동일한 컴포넌트에 위치시켜서는 안 된다고 말한다.

 

CRP는 SOLID의 인터페이스 분리 원칙(ISP)의 포괄적인 버전이라 할 수 있다. ISP는 사용하지 않은 메서드가 있는 클래스에 의존하지 말라고 조언한다. CRP는 사용하지 않는 클래스를 가진 컴포넌트에 의존하지 말라고 조언한다.

컴포넌트 응집도에 대한 균형 다이어그램

아마도 응집도에 관한 세원칙이 서로 상충된다. REP와 CCP는 포함(Inclusive)원칙이다. 즉, 두 원칙은 컴포넌트를 더욱 크게 만든다. CRP는 배제(exclusive)원칙이며, 컴포넌트를 더욱 작게 만든다.

 

컴포넌트응집도(Component Cohesion)[1]


원칙에 반대 위치에 있는 Edge에는 원칙을 따르지 않을 때 감수해야 하는 비용을 나타낸다. 즉, 위에 그림에서 오로지 REP와 CRP에만  중점을 두면, 사소한 변경이 생겼을 때 너무 많은 컴포넌트에 영향을 미친다. 반대로 CCP와 REP에만 과도하게집중하면 불필요한 릴리즈가 너무 빈번해 진다.

 

뛰어난 아키텍트라면 이 균형 삼각형애서 개발팀이 현재 관심을 기울이는 부분을 충족시키는 위치를 찾아야 하며 또한 시간이 흐르면서 개발팀이 주의를 기울이는 부분 역시 변한다는 사실도 이해하고 있어야 한다. 일반적으로 프로젝트는 삼각형의 오른쪽에서 시작하는 편이며, 이대는 오직 재사용성만 희생하면 된다. 프로젝트가 성숙하고,, 그 프로젝트로 부터 파생된 또 다른 프로젝트가 시작되면 프로젝트는 삼각혀엥서 점차 왼쪽으로 이동해 간다.

 

 

컴포넌트 결합

 

의존성 비순환 원칙(Acyclic Dependencies Principles, ADP)

ADP는 "컴포넌트 의존성 그래프에 순환(cycle)이 있어서는 안 된다"는 원칙이다.

 

숙취 증후군 the morning after syndrome'이라고 부른다. 퇴근 후, 이튿날 다른 사람의 작업으로 전혀 돌아가지 않는 경험을 말하는 것으로 많은 개발자가 동일한 소스 파일을 수정하는 환경에서 발생한다. 소수의 개발자로 구성된 상대적으로 작은 프로젝트에서는 이 증후군이 그다지 큰 문제가 되지 않는다.

 

이 문제의 해결책은 개발 환경을 릴리스 가능한 컴포넌트 단위로 분리하는 것이다. 컴포넌트가 새로 릴리스되어 사용할 수 있게 되면 다른 팀에서는 새 릴리스를 당장 적용할지를 결정해야 한다. 적용하지 않기로 했다면 그냥 과거 버전의 릴리스를 계속 사용한다. 이렇게 될 수 있다면, 어떤 팀도 다른 팀에 의해 좌우 되지 않는다. 특정 컴포넌트가 변경되더라도 다른 팀에 즉각 영향을 주지 않는다. 각 팀은 특정 컴포넌트가 새롭게 릴리스되면 자신의 컴포넌트를 해당 컴포넌트에 맞게 수정할 시기를 스스로 결정할 수 있다. 뿐만 아니라 통합은 작고 점진적으로 이뤄진다. 특정 시점에 모든 개발자가 한데 모여서 진행 중인 작업을 모두 통합하는 일은 사라진다.

 

이 절차가 성공적으로 동작하려면 컴포넌트 사이의 의존성 구조를 반드시 관리해야 한다. 의존성 구조에 순환이 있어서는 안 된다. 구조가 방향 그래프(Directed graph)이라고 하면, 컴포넌트는 정점(Vertex)에 해당하고, 의존성 관계는 방향이 있는 간선 (Directed edge)에 해당한다 볼 수 있다. 이렇게 비순환 방향 그래프(Directed Acyclic Graph, DAG)로 구조를 만들면 문제가 해결 된다.  이렇게 할 수 있는 두 가지 방법은 아래와 같다.

 

  1. 의존성 역전 원칙(DIP)을 적용한다.
  2. Entities와 Authorizer가모두 의존하는 새로운 컴포넌트를 만든다.

 

안정된 의존성 원칙 (Stable Dependencies Principles, SDP)

SDP는 "안정성의 방향으로(더 안정된 쪽에) 의존하라"라는 원칙이다.

하나의 모듈에 누군가가 의존성을 매달아 버리면 이 모듈은 변경하기 어려워진다. 이 모듈에서는 단 한 줄의 코드도 변경되지 않았지만, 어느 순간 갑자기 해당 모듈을 변경하는 일이 상당히 도전적인 일이 되어 버리는 것이다. 

 

안전성(Stability)이란 무엇인가? '쉽게 움직이지 않는'이라고 정의할 수 있다. 아래 그림5의 X는 안정된 컴포넌트다. 세 컴포넌트가 X에 의존하며, 따라서 X 컴포넌트는 변경하지 말아야 할 이유가 세 가지나 되기 때문이다. 이 경우 X는 세 컴포넌트를 책임진다(Responsible)라고 말한다. 반대로 X는 어디에도 의존하지 않으므로 X가 변경되도록 만들 수 있는 외적인 영향이 전혀 없다. 이 경우 X는 독립적이다(Independent)라고 말한다.

 

아래 그림의 Y는 상당히 불안정한 컴포넌트다. 어떤 컴포넌트도 Y에 의존하지 않으므로 Y는 책임성이 없다고 말할 수 있다. 또한 Y는 세 개의 컴포넌트에 의존하므로 변경이 발생할 수 있는 외부 요인이 세 가지다. 이 경우는 Y는 의존적이라고 말한다.

 

안정성(Stability)[1]

 

이와 같은 정의에서 안정성 지표는 아래와 같이 정의 될 수 있다.

  • Fan-in 안으로 들어오는 의존성
  • Fan-out 바깥으로 나가는 의존성
  • I(불안정성):I = Fan-out / (Fan-in + Fan-out) 이 지표는 [0,1] 범위의 값을 갖는다. 1이면 최고로 불안정한 상태이고, 0이면 최고로 안정된 상태

안정된 추상화 원칙(Stable Abstractions Principle, SAP)

SAP는 "컴포넌트 안정된 정도만큼만 추상화되어야 한다"라는 원칙이다.

안정된 추상화 원칙(Stable Abstractions Principle, SAP)은 안정성(Stability)과 추상화 정도(Abstractness) 사이의 관계가 정의 한다. 이 원칙은 한편으로는 안정된 컴포넌트는 추상 컴포넌트여야 하며, 이를 통해 안정성이 컴포넌트를 확장하는 일을 방해해서는 안 된다고 말한다.

 

추상화 정도 측정하기 위한 A지표는 컴포넌트의 추상화 정도를 측정한 값으로 다음과 같이 정의 될 수 있다.

  • Nc: 컴포넌트의 클래스 개수
  • Na: 컴포넌트의 추상 클래스와 인터페이스의 개수
  • A: 추상화 정도. A=Na/Nc" A가 0이면 컴포넌트에는 추상 클래스가 하나도 없다는 뜻이다. A가 1이면 컴포넌트는 오로지 추상 클래스만을 포함한다는 뜻이다.

 

주계열(the main sequence)

이제 안정성(I)과 추상화 정도(A) 사이의 관계를 정의해야 할 때가 왔다. 우선 고통의 구역과 쓸모 없는 구역을 살펴 보고, 주 계열(The main sequence)에 대해서 살펴 보자.

고통의  구역(Zone of Pain), (0,0) 주변 구역에 위치한 컴포넌트를 살펴보자. 이 컴포넌트는 매우 안정적이며 구체적이다. 일부 소프트웨어 엔티티는 고통의 구역에 위치하곤 한다. 데이터베이스 스키마가 한 예다. 데이터베이스 스키마는 변동성이 높기로 악명이 높으며, 극단적으로 구체적이며, 많은 컴포넌트가 여기에 의존한다. 유틸리티 라이브러리로 실제로는 변동성이 거의 없다. 변동성이 없는 컴포넌트는 (0,0)구역에 위치했더라도 해롭지 않다.

 

쓸모없는 구역(Zone of Uselessness), (1, 1) 주변의 컴포넌트를 생각해 보자. 이 영역도 바람직하지 않은데, 여기위치한 컴포넌트는 최고로 추상적이지만, 누구도 그 컴포넌트에 의존하지 않기 때문이다. 이러한 컴포넌트는 아무도 쓰지 않으므로 쓸모가 없다.

 

일반적으로 변동성이 큰 컴포넌트 대부분은 두 배제 구역으로부터 가능한 한 멀리 떨어뜨려야 한다. 각 배제 구역으로부터 최대한 멀리 떨어진 점의 궤적은 (1,0)과 (0,1)을 잇는 선분이다. 나는 이 선분을 주계열(Main Sequence)이라고 부른다.

 

여기서, 주계열과의 거리, 세 번째 지표가도출된다. 컴포넌트가 주계열 바로 위에 또는 가까이 있는  것이 바람직하다면 이 같은 이상적인 상태로 부터 컴포넌트가 얼마나 떨어져 있는지 특정하는 지표를 만들어 볼 수 있다.

  • D: 거리. = |A+I-1| 유효범위는 [0,1]

 

주 계열(Main Sequence)와 컴포넌트 산점도[1]

 

결론

Clearn Architecture에서 설명하는 응집도(Cohesion)와 관련된 REP, CRP, CCP원칙에 대해서 살펴 보았고, 응집도 균현 다이어그램을 살펴 보았다. 그리고, 결합도와 관련된 ADP에서는 어떻게 순환을 끊을 수 있는지 살펴 보았고, SDP와 SAP 원칙에서 분안정성(Instability)와 추상화 정도(Abstractness)의 관계를 통해서 주 계열(Main sequence)를 살펴 보았다.

 

참고문헌

[1] 로버트 C. 마틴 저, "클린 아키텍처 소프트웨어 구조와 설계의 원칙" 인사이트(insight) 2019년 08월 20일, 송준이 역

+ Recent posts