컴포넌트 원칙

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일, 송준이 역

S.O.L.I.D 법칙

Clearn Architecture[1]에서는 좋은 소프트웨어 시스템은 깔끔한 코드(Clean Code)로 부터 시작한다고 이야기 한다. 이러한 코드들이 모여서 요소(Element)가 되는데 이를 건물에 보면 벽돌로 볼 수 있다. 좋은 벽돌로 좋은 아키텍처를 정의하는 원칙이 필요한데 그게 바로 SOLID라고 이야기 한다. 즉, SOLID 원칙은 함수와 데이터구조를 클래스로 배치하는 방법, 그리고 이들 클래스를 서로 결합하는 방법을 설명한다. 사실 SOLID 원칙은 "Design Pattern 첫 번째: Object Oriented Principles"[2]에서 Object Oriented Programming의 설계 원칙 중 하나로 다루었지만 여기서는 조금 더 다른 관점에서 다룬다. 

 

원칙의 목적은 "중간 수준"의 소프트웨어 구조가 아래와 같도록 만드는데 있다.

  • 변경에 유연하다.
  • 이해하기 쉽다.
  • 많은 소프트웨어 시스템에 사용될 수 있는 컴포넌트의 기반이 된다.

 

S.O.L.I.D는 아래 다섯가지 원칙의 약자를 모은 것이다.

  • Single Responsibility Principle
  • Open Close Principle
  • Liskov Substitution Principle
  • Interface Seggregation Principle
  • Dependency Inversion Principle

책에서는 2004년 무렵, 레거시 코드 활용 전략의 저자 마이클 페더스(Michael Feathers)가 기존에 있던 원칙을 재배열하여 각 원칙의 첫 번째 글자들로 SOLID라는 단어를 만들었다고 한다. 각 원칙을 하나씩 살펴 보자.

 

단일 책임 원칙 (Single Responsibility Principle)

객체 지향 프로그래밍(Object Oriented Programming)에서 하나의 객체는 하나의 책임을 가진다는 원칙이다.

 

하나의 책임이라는 부분은 사실 모호한다. 어떤 클래스가 가지는 책임이 하나라는 것은 한가지 일만 한다고 하기에는 1차원 적인 것이 있다. 이 책에서는 최종 버전으로 "하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다."라고 이야기 한다. 모듈이 하나의 객체 혹은 소스 파일로 볼 수 있다. 여기서 액터는 개발자/팀이라고 볼 수 있다. 그렇다면, 하나의 소스 파일은 개발자/팀이 책임 져야 한다는 것이다. 분리된 팀이 하나의 소스 파일을 건드린다면 팀을 합치거나 혹은 파일을 분리해야 한다는 이야기로 이해할 수 있다.

 

책[1]의 예를 살펴 보자. 

 

Employee Class [1]

 

  • calculatePay() 매서드는 회계팀에서 기능을 정의하며, CFO 보고를 위해 사용한다.
  • reportHours() 매서드는 인사팀에서 기능을 정의하고 사용하며, COO 보고를 위해 사용된다.
  • save 매서드는 데이터베이스 관리자(DBA)가 기능을 정의하고, CTO 보고를 위해 사용된다.

즉, 서로 다른 액터가 의존하는 경우는 많이 발생한다. SRP에 따르자면, 이 코드는 분리하라고 하는 것이다. 어떻게 분리할 수 있을까? 최종적으로는 2가지가 가능해 보인다.

 

이 해결책은 개발자가 세가지클래스를 인스턴스화하고 추적해야 한다는게 단점이다.  이럴 때, 흔히 쓰는 기법으로 파사드(Facade) 패턴이 있다. 아래 그림과 같이, EmployeeFacade에 코드는 거의 없다. 이 클래스는 세 클래스의 객체를생성하고, 요청된 메서드를 가지고 객체로 위임하는 일을 책임진다. 이렇기 때문에,  Facade는 Architectural Composition이라고도 한다.

Facade  Pattern[1]

Employee Data를 CFO에서 담당한다고 하면 Employee Facade Class로 가져올 수 있다면 구조는 조금 더 단순화 될 수 있다. 아래 구조가 최종 구조라고 하면, CFO는 Employee Class를 담당하고, COO는 HourReporter Class를 담당하며, CTO는 EmployeeSaver Class를 각각 담당하여 개발을 진행하면 SRP가 잘 유지되며 개발될 수 있다고 볼 수 있다. 이처럼 조직의 구조도 Software의 구조에 영향을 미친다.

 

Facade Pattern 두 번째[2]

 

개방-폐쇄 원칙 (Open Close Principle OCP)

개방-폐쇄 원칙(OCP)이라는 용어는 1988년 버트란드 마이어(Bertrand Meyer)가 만들었는데, 소프트웨어 개체(Artifact)는 확장에는 열려 있어야 하고(Open to Extension) 변경에는 닫혀 있어야 한다(Close to Modification)라는 원칙이다. 즉, 객체의 행위는 확장할 수 있어야 하지만, 이 때 개체를 변경해서는 안된다. 어찌 보면 모순 같은 이야기는 매우 중요한 원칙이기도 하다.

 

책에서는 여러 번 이야기 하지만, 변경이 되지 많아야 할 곳과 변경이 되는 것을 분리하고 변경이 되는 곳이 변경되지 않는 것에 종속되도록(Dependent)하도록 해야 한다는 것이다. 그리고, 이 변경 되는 쪽을 확장하는 쪽으로 쓰고 변경되지 않도록 하는 쪽은 한 곳으로 모아 두어야 한다는 원칙이다.

 

어떻게 이를 획득할 수 있을까? 책에서의 제무제표 웹 시스템에서는 그러한 예를 많이 보유하고 있다.

재무 제표 웹 시스템[1]

 

 

일반적인 방향성 제어의 방법에서는 위의 예에서 FinancialDataGateway 인터페이스는 FinacialReportGenerator와 FinancialDataMapper 사이에 위치하는데, 이는 의존성을 역전시키기 위해서이다. 만일 이러한 DataGateway가 없다면 FinancailReportGenerator Class는 Database Component에 의존하게 된다. 즉, Database가 SQL이었다고 다른 Database로 변경된다면 영향을 받고 변경될 가능성이 생기게 된다. 하지만 Database들이 거꾸로 FinancialDataGateway가 존재함으로서 Database들은 여기에 의존하게 되면서 의존성이 역전되게 된다. FianancialReportPresenter 인터페이스와 2개의 View 인터페이스도같은 목적을 가진다.


정보은닉 차원에서 OCP도 위의 예에서 살펴 볼 수 있다. FainacialReportRequester 인터페이스는 방향성 제어와는 달리, FinancialReportController가 Ineractor 내부에 대해 너무 많이 알지 못하도록 막기 위해서 존재한다. 즉, 의존 관계를 바꾸지는 않지만 의존하는 Class의 의존도를 제한하고 변경이 있을 때에도 인터페이스에 한정하여 의존성을 가지도록 하여 OCP를 유지할 수 있다. 즉, 여러 모듈의 의존도가 있는 모듈인 경우 각 Interafce를 분리하여 정보은닉을 하면서 OCP 의 효과를 획득하는 것이라 할 수 있다.

리스코프 치환 원칙(Liskov Substituion Priniciple)

1988년 바바라 리스코프(Barbara Liskov)는 하위 타입(Subtype)을 아래와 같이 정의했다.

 

S 타입의 객체 o1, T 타입의 객체 o2일 경우, T타입을 이용해서 정의한 모든 프로그램 P에서 o2의 자리에 o2을 치환하더라도 P의 행위가 변하지 않는다면, S는 T의 하위 타입이다.

LSP 예제

이 원칙은 일반적으로 OOP에서 상속을 사용하도록 가이드 한다. 위의 예가 바로 그것인데, Billing 애플리케이션의 행위가 License 하위 타입 중 무엇을 사용하는지에 전혀 의존하지 않기 때문이다. 이들 하위 타입은 모두 License 타입을 치환할 수 있다. 이를 Liskov Substitution Principle LSP라고 한다.

 

정사각형/직사각형 문제는 LSP에서 발생될 수 있는문제를 설명한다.  Square는 Rectangle의 하위 타입으로는 적합하지 않은데, Rectangle의 높이와 너비는 서로 독립적 변경이다. 하지만, Squarue이 높이와 너비는 반드시 함께 변경되기 때문이다. 이런 형태의 LSP 위반을 막기 위한 유일한 방법은 (if문 등을 이용해서) Rectangle이 실제로는 Square인지 검사하는 매커니즘을 User에 추가하는 것이다. 하지만 이렇게 하면 User의 행위가 사용하는 타입에 의존하게 되므로 결국 타입을 서로 치환할 수 없게 된다. 다시 말하자면,  개념적으로는 LSP라 생각하기 쉽지만 이렇게 LSP를 적용하는데는 적당하지 않다.

 

Square LSP 위반 사례[1]


객체 지향이 혁명처럼 등장한 초창기에는 앞서 본 것 처럼 LSP는 상속을 사용하도록 가이드하는 방법 정도로 간주 되었다. 하지만, LSP는 인터페이스와 구현체에도 적용되는 더 광범위한 소프트웨어 설계 원칙으로 변모하였다.

 

인터페이스 분리 원칙(Interface Segregation Principle, ISP)

아래 왼쪽 그림 기술된 상황에서,다수의 사용자가 OPS 클래스의 오퍼레이션을 사용한다. User1은 오직 op1을 User2는 op2만을 User3는 op3만을 사용한다고 가정해 보자. 이러한 문제는 아래 그림 오른쪽에서 보는 것처럼 오프레이션을 인터페이스 단위로 분리하여 해결할 수 있다. 

Interface Segregation Principle [1]

 

정적타입 언어는 소스코드에 '포함된 included'선언문으로 인해 소스 코드 의존성이 발생한다. 루비나 파이썬과 같은 동적 타입 언어서는 소스코드에 이러한 선언문이 존재하지 않는다. 대신 런타임에 추론이 발생한다. 동적 타입 언어를 사용하면 유연하며 결합도가 낮은 시스템을 만들 수 있는 이유는 바로 이때문이다. 이러한 사실로 인해 ISP를 아키텍처가 아니라, 언어와 관련된 문제라고 결론내릴 여지가 있다. 

일반적으로 필요 이상으로 많은 걸 포함하는 모듈에 의존하는 것은 해로운 일이다. 고수준인 아키텍처 수준에서도 마찬가지 상황이다. 

의존성 역전 원칙 (Dependency Inversion Principle)

의존성 역전 원칙(DIP)에서 말하는 '유연성이 극대화된 시스템'이란 소스 코드 의존성이 추상(Abstraction)에 의존하며 구체(Concretion)에는 의존하지 않는 시스템이다. use, import, include 구문은 오직 인터페이스나 추상 클래스 같은 추상적인 선언만 참조해야 한다는 뜻이다. 구체적인 대상에는 절대로 의존해서는 안 된다.


우리가 의존하지 않도록 피하고자 하는 것은 바로 변동성이 큰(Volatile) 구체적인 요소다. 그리고 이 구체적인 요소는 우리가 열심히 개발하는 중이라 자주 변경될 수 밖에 없는 모듈들이다.

 

실제로 뛰어난 소프트웨어 설계자와 아키텍트라면 인터페이스의 변동성을 낮추기 위해 애쓴다. 인터페이스를 변경하지 않고도 구현체에 기능을 추가할 수 있는 방법을 찾기 위해 노력한다. 다음과 같은 매우 구체적인 코딩 실천법이 있다. 사실 이것이 DIP를 다시 쓴것과 같다.

  • 변동성이 큰 구체 클래스(Concrete Class)를 참조하지 말라. (Refer)
  • 변동성이 큰 구체 클래스(Concrete Class)로 부터 파생하지 말라 (Derive)
  • 구체함수를 오버라이드하지 말라. 대체로 구체 함수는 소스 코드 의존성을 필요로 한다. 따라서 구체 함수를 오버라이드하면 이러한 의존성을 제거할 수 없게 되며, 실제로는 그 의존성을 상속하게 된다.
  • 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라 

 

아래 그림에서 곡선은 아키텍처 경계를 뜻한다. 이 곡선은 구체적인 것들로부터 추상적인 것들을 분리한다. 소스 코드 의존성은 해당 곡선과 교차할 때 모두 한 방향, 즉, 추상적인 쪽으로 향한다. 제어흐름은 소스 코드 의존성과는 정반대 방향으로 곡선을 가로지른다는 점에 주목하자. 다시 말해 소스 코드 의존성은 제어흐름과는 반대 방향으로 역전된다. 이러한 이류로 이 원칙을 의존성 역전(Dependency Inersion)이라고 부른다. 

Dependency Inversion Principle[1]

위 그림의 구체 컴포넌트에는 구체적인 의존성이 하나 있고, 따라서 DIP에 위배된다. 이는 일반적인 일이다. DIP 위배를 모두 없앨 수는 없다. 하지만 DIP를 위배하는 클래스들은 적은 수의 구체 컴포넌트 내부로 모을 수 있고, 이를 통해 시스템의 나머지 부분과는 분리할 수 있다.

 

결론

S.O.L.I.D는 Single Responsibility Principle, Open Close Principle, Liskov Substitution Principle, Interface Seggregation Principle, Dependency Inversion Principle의 앞글자를 딴 것이다. 원칙 하나씩 따져 보았지만, 서로 서로 관련되어 있는 것을 살펴 볼 수가 있다. 이러한 원칙은 컴포넌트 혹은 아키텍처로 확장될 수 있다. 또한, 이러한 원칙은 Design Pattern의 각 패턴의 기본 원칙으로 각 패턴을 상세히 설명하는데도 사용된다.

 

참고 도서

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

[2] Design Pattern 첫 번째: Object Oriented Principles, https://technical-leader.tistory.com/7

 

소개

이 글[1]은 2015년도 글이기는 해도, 최근에 발견했다. 애자일 과정과 기존의 Software Architecture 접근법에 대한 고찰이 포함되어 있어 참고할만한 글이다.

 

Towards an Agile Software Architecture

소프트웨어 아키텍처는 시스템의 골격(Skeleton)이다. 다른 기능적 요구 사항(Functionall Requirements)과 비기능적 요구 사항(Non-functional requirements)으로 시스템이 작동하는 방식을 정의한다. 한편으로 전통적인 워터폴 방식은 프로젝트 개발의 모든 단계에 강한 제약을 가하기 때문에 엄격하다. 반면에 애자일 접근법은 개발 단계 후반에도 변경 사항을 환영한다. 우리는 견고한 개발에서보다 유연한 개발로 전환하고 있지만, 소프트웨어 아키텍처는 골격(Skeleton)이라는 태생상 기본 특성으로 인해 변화에 민감하다. 따라서 애자일 접근법을 따라갈 때 핵심 요소는 지속 가능한 소프트웨어 아키텍처의 개념을 수용하는 것이다. 이는 프로젝트의 복잡성 증가와 함께 시스템 확장이 점진적이고, 쉽고, 유지 보수 가능한 방식으로 이루어질 수 있도록한다.

이 글에서는 기존의 워터폴 소프트웨어 아키텍처와 애자일 아키텍처 모두에서 작업 할 때의 경험을 다룬다. 나는 세 가지 영역에 중점을 둔 이들의 유사점과 차이점을 묘사한다.

 

  • 소프트웨어 아키텍트 역할의 세부 사항
  • 소프트웨어 아키텍처의 타임스팬(Timespan)
  • 소프트웨어 아키텍처의 결과물

소프트웨어 아키텍처란 무엇인가?

소프트웨어 아키텍처에 대한 정의는 수백 가지가 있다 (실제로 새로운 본인의 정의를 직접 추가 할 수도 있음). 그 이유는 모든 사람이 자신의 상황에 따라 정의하기 때문이다. 나는 특히 IEEE의 정의를 좋아하는데, 기본 개념을 설명하고 머리에서 쉽게 그려 볼 수 있기 때문이다. 또한 소프트웨어 아키텍처의 생성 방식이 아니라 소프트웨어 아키텍처의 본질을 묘사하기 때문에 워터폴 및 애자일 프로세스에 모두 적용된다. 이 글의 나머지 부분에서 나는 이 정의를 언급 할 것이다.

 

구성 요소(Components) 서로와 그리고 환경(Environment)과의 관계(Relationship), 설계 및 진화(Evolution)를 안내하는 원칙으로 구현되는 시스템(System)의 기본 구조(Fundamental Organization)

 

워터폴 소프트웨어 아키텍처

기존의 워터폴 개발은 시작 날짜와 종료 날짜가 명확한 일련의 단계로 구성되며, 각 단계에는 특정 활동 세트가 포함된다. 모든 단계는 서로 연결되어 있으며 각각의 단계는 이전 단계에서 생성된 결과물에 크게 의존한다. 그림 1은 폭포 개발과 관련된 일반적인 단계를 보여준다.

 

 

전통적 워터폴 개발방식

소프트웨어 아키텍처에 대한 작업은 일반적으로 소프트웨어 요구 사항이 정의 된 후에 시작된다. 이 시점에서 시스템은 수행해야 할 작업에 대해 잘 정의되어 있다고 가정한다. 다음 단계는 소프트웨어 아키텍처를 정의하는 담당자와 이 단계의 실제 결과를 조사하는 것이다.

 

전통적인 소프트웨어 아키텍트

소프트웨어 아키텍처는 실제로 소프트웨어 아키텍트가 수행한다. 기술 지식과 경험이 풍부한 사람이다. 일반적으로 회사의 특정 수준에 도달한 후 승진 한 개발자이다. 이 사람은 소프트웨어 요구 사항을 분석하고 이러한 요구 사항을 기반으로 미래 시스템에 대한 특정 기술 결정을 담당한다. 많은 회사에는 일반적으로 프로젝트 당 하나의 소프트웨어 아키텍트가 있지만 일부 대기업에는 소프트웨어 설계자 팀이 함께 작업 할 수 있다. 이는 일반적으로 프로젝트 도메인이 복잡하거나 프로젝트의 기간이 더 큰 경우 (예 : 2-3 년 이상)이다. 모든 회사에 지정된 소프트웨어 아키텍트 역할이있는 것은 아니다. 많은 회사에서 이러한 책임을 경험 많은 개발자(Senior Developer)에게 위임한다. 이 글의 나머지 부분에서는 명시적으로 언급하지 않는 한 소프트웨어 아키텍트는 다른 누군가가 하지 않고 소프트웨어 아키텍트가 수행하는 특정 활동을 언급한다.


전통적인 소프트웨어 아키텍트는 다음과 같은 네 가지 주요 기능을 담당한다.

  • 큰 그림에 초점을 맞춘다. - 소프트웨어 아키텍트는 시스템이 향후 몇 달 (때로는 몇 년)처럼 어떻게 보일지 고려해야한다. 또한 관련된 다른 모든 시스템 (예 : 타사 시스템 및 데이터 저장소)과 시스템 간의 통신이 어떻게 진행 될지 고려해야 한다.
  • 규정 준수 지향 - 소프트웨어 아키텍트는 가능한 규정 준수 문제를 고려해야 한다. 이것은 입법 규범, 라이센스, 표준 또는 기타를 포함하여 미래의 시스템이 이러한 중요한 기준을 충족하도록 해야한다.
  • 청사진을 생성한다. - 중요한 산출물은 다양한 관점에서 아키텍처를 설명하는 문서 및 다이어그램 모음이다. 개발 팀은 이러한 결과물을 사용하여 시스템 구축을 시작한다.
  • 실무 경험이 많지 않음 - 소프트웨어 아키텍트의 목표는 개발 팀을위한 최종 문서를 작성하는 것이다. 소프트웨어 아키텍트는 일반적으로 개발자로서의 경험이 있지만 개발 프로세스에는 거의 관여하지 않으며 개발자가 설계 시스템을 구축하도록 유도한다. 어느 시점에서 그는 개발자는 남겨 두고, 다른 프로젝트로 이동할 수 있다. 
실제 사례의 고통 

나는 세계에서 가장 큰 맥주 회사 중 하나를 위한 소프트웨어 프로젝트를 진행하고 있다. 이 프로젝트를 완료하는 데 2-3 년이 걸렸으며 일반적인 워터폴 방식으로 수행 되었기 때문에 소프트웨어 아키텍트, 개발자, 테스터 등 여러 단계를 담당하는 사람들이있었다. 나는 소규모 팀의 개발자였으며 ​​소프트웨어 아키텍트로부터 시스템 개발 수행 방법에 대한 지시와 가이드라인을 받았다. 처음에는 소프트웨어 아키텍트가 우리를 지원하기 위해 팀에 있었지만 나중에 그 프로젝트의 작업량이 줄어들면서 다른 프로젝트로 옮겼다. 제안 된 소프트웨어 아키텍처는 정의된 경계에 맞지 않는 새로운 종속성이 등장함에 따라 어느 시점에서 따르기가 어려워졌다. 수석 개발자가 아키텍처 비전을 인수하려고 했지만이 프로젝트는 소위 스파게티 코드로 성장했다.이 프로젝트는 다른 곳에서 문제를 일으킬 수 있으므로 모든 사람이 변경을 두려워 했다. 불행히도 프로젝트를 정상 괘도에 올릴 수 있는 과감한 변화를하기에는 너무 늦었었다. 우리는 프로젝트를 출시했지만 나중에 결국 종료되었다.


애자일 접근법

기존의 워터폴 구조는 시작 날짜와 종료 날짜가 명확한 일회성 활동이지만, 애자일 소프트웨어 아키텍처는 진행중인 프로세스이므로 결코 끝나지 않을 수 있다. 이를 통해 필요한 경우 아키텍처 변경 사항을 정기적으로 적용 할 수 있다. 프로젝트의 변화를 환영하는 메커니즘 중 하나는 반복적이고 점진적인 개발을 적용하는 것이다. 스크럼에서는 이러한 반복을 스프린트라고하며 하나의 스프린트는 일반적으로 그림 2에 표시된대로 2-4 주 사이에 지속된다. 이러한 작은 간격을 사용하면 발생하는 모든 변경 사항에 대해 바로 바로 논의 할 것이다. 또한, 팀 협업에 중점을 두고 있으며 오해와 의사 소통이 원활치 않은 것을 방지하며 팀원 간의 문제를 즉시 해결한다.


스크럼의 일반적인 스프린트


애자일 접근법을 통해 프로젝트의 변화를 환영할 수 있지만 얼마나 빠르게 대응해야하는지 알려주지는 않는다. 소프트웨어 아키텍처는 시스템의 백본이므로 변경에 따라 움직인다. 예를 들어, 프로젝트 중간에 사용하는 플랫폼이나 언어를 변경할 수 있다고 생각하는가? 드물기는하지만 이러한 변경에는 많은 추가적인 이터레이션을 필요로 한다. 심지어 프로젝트의 시작 부분으로 돌아갈 수도 있다. 소프트웨어 아키텍처와 관련하여 일부 유형의 변경은 고통스럽고 구현하는 데 시간이 걸린다.

 

애자일 소프트웨어 아키텍트

스크럼은 세 가지 유형의 역할을 정의한다.

  • 제품 소유자(Product Owner) - 특정 비즈니스 도메인에 대한 정보를 제공한다.
  • 스크럼 마스터 - 팀의 의사 소통 및 협업 촉진 한다.
  • 개발팀 - 사용자 스토리을 구현하고 작동하는 소프트웨어를 제작한다.

전통적인 소프트웨어 아키텍트 역할을 애자일 개발에 맞는 역할로 변환하기 위해 가능한 변형을 살펴 봐야한다. 스크럼 팀을 구성하는 한 가지 방법은 개발자 팀과 별도의 소프트웨어 아키텍트 팀이 긴밀하게 협력하는 것이다. 그림 3은 이 시나리오를 여러 스크럼 팀에 적용한 것을 보여준다.

 

열 개발팀과 함께 작업하는 별도의 소프트웨어 아키텍트 팀

이것이 팀을 구성하는 올바른 방법이지만 두 가지 문제가 있다.

  • 소프트웨어 아키텍트 팀은 수요자(Demander)가 될 수 있지만 개발자 팀은 일반적으로 워터폴 접근 방식의 경우 생산자(Producer)입니다. 소프트웨어 아키텍트는 비전을 정의하고 개발자는 비전을 구현해야한다. 이는 개발자를 관여시키지 않고 정중하게 다루지 않으면 개발자 간의 동기를 감소시킬 수 있다.
  • 위의 결과로 일부 개발자는 다른 개발자가해야 할 일을 선택할 수 있도록 소프트웨어 설계자 팀의 일원이되기를 원할 수 있다. Trustpilot은 핵심 팀과 개발 팀을 보유한 사례를 제시했지만, 이는 작업장과 공동 작업에 대한 도덕을 감소 시켰기 때문에 핵심 팀의 각 구성원을 하나의 개발 팀으로 옮겼다. 이는 긍정적인 결과를 낳았다.

또 다른 방법은 소프트웨어 아키텍트를 그림 4와 같이 개발 팀에 직접 배치하는 것이다.

각 개발 팀에는 하나의 소프트웨어 아키텍트가 있다.

이 경우 애자일 소프트웨어 아키텍트는 약간 다른 책임을 갖는다.

  • 큰 그림과 현재의 균형 – 민첩한 소프트웨어 아키텍트는 전체 시스템의 큰 그림에 맞춰 개발하는 동안 발생하는 상황을 고려해야 한다.
  • 실무 경험 – 애자일 소프트웨어 아키텍트는 개발자이며 시스템 구현 작업을 한다. 이것은 선택된 구조적 결정(architectural decision)에 대한 직접적인 피드백을 제공한다.
  • 결정 사항을 설명할 수 있도록 프로토 타입을 제작 - 중요한 기술적 결정을 내려야 할 경우, 빠른 프로토타입을 통해이 결정이 가능한지 여부와 기존 시스템에 어떤 영향을 미치는지 알 수 있다. 또한 전체 개발 팀과의 커뮤니케이션은 개인 개별 활동이 아닌 공동 노력해야 하는 것으로 필수적이다.
  • 지속 가능성에 초점 – 구조적 결정(architectural decision)이 지속 가능한 소프트웨어 아키텍처를 이끌어내는 것이 매우 중요하다. 이는 장기적으로 프로젝트를 지원할 것이다. 이것의 필수 부분은 개인적인 책임과 공감이다. 애자일 소프트웨어 아키텍트는 개발 팀의 일원이므로 위에서 설명한 의사 결정을 통해 직접적인 피드백을 받는다. 이는 워터폴에서 접근하는 방식과 달리 개인의 책임 수준을 높이며, 결정은 개발 팀에 전달되어 구현된다.

다른 개발 팀 (및 프로젝트)간에 구조를 공유하려는 경우 별도의 소프트웨어 설계자 팀과 함께 구조를 선택할 수 있다. 또한 많은 관점을 고려해야하는 복잡한 도메인에서 작업하는 경우, 이 구조를 적용 할 수도 있다. 그러나 이 경우 소프트웨어 아키텍트가 개발자와 긴밀하게 협력하고 지원하도록 해야한다. 애자일 방법론은 협업에 중점을 두므로 소프트웨어 아키텍트를 개발자와 분리하면 협업이 어려워진다. 결과적으로 개발 프로세스는 워터폴 모델에 가까워진다. 나는 개인적으로 팀 구성원 간의 의사 소통이 향상되기 때문에 각 개발 팀에 한 명의 소프트웨어 설계자가 존재하는 두 번째 변형을 선호한다. 선임 아키텍트(High level architects)는 여전히 서로 협력 할 수 있다 (꼭 그렇게 해야 한다).

 

애자일 소프트웨어 아키텍처의 타임스팬

애자일 소프트웨어 아키텍처의 중요한 측면은 언제 시작하는가 이다. 우리가 잘 정의 된 단계를 가지고있는 워터폴 모델과는 달리, 애자일 세계에서는 사람들이 시작 단계라 정의 하는 특정 항목이 없다. 일반적인 접근 방식 중 하나는 스프린트 #0을 진행하는 것이다. 개발 환경을 구성하고 프로그래밍 언어, 플랫폼, 데이터베이스 등과 같은 몇 가지 기본 결정을 내리는 특수 스프린트이다.

그러나 이러한 접근 방식의 일반적인 함정은 사람들이 항상 “거의 준비되었다”고하면서 이 기간을 연장하는 경향이 있다는 것이다. "일주일 더 있으면 정규 스프린트를 시작할 수 있다"라는 말이 종종 들린다. 많은 경우에, "사전 도움말 기능을 미리 구현하는 것이 정말 멋지므로"라며서 유저 스토리없이 시스템에 이미 작업하고있는 것을 알 수 있다. 이러한 상황에서는 스프린트 #0의 종료 날짜에 미리 동의해야 한다. 이는 스프린트 지속 시간 또는 그와 유사한 기간이 될 수 있다.

정규 스프린트가 시작될 때 아키텍처가 준비되지 않은 경우 어떻게 될지 궁금 할 것이다. 글쎄, 그래도 괜찮다. 실제로 준비되지 않았을 수 있다. 그리고 그것 또한 괜찮다. 소프트웨어 아키텍처는 지속적으로 진행되는 프로세스이다. 이것은 시스템의 골격이므로 항상 되돌아와 수정해야한다. 당신의 아키텍처가 필요한 것을 충분히 지원하지 않는다면 시스템을 개발할 여유가 없다. 사이먼 브라운(Simon Brown)의 말을 인용해 보자 :

 

기민한 팀(Agile Team)이 반드시 기민한 소프트웨어 아키텍처(Agile Software Architecture)를 만들지는 않는다.
그러나 좋은 아키텍처는 기민성(Agility)을 가능하게한다.

 

관리 원칙

우리가 사는 세상은 복잡하며 모든 비즈니스 영역도 마찬가지이다. 소프트웨어 아키텍처를 구축 할 때는 처음부터 작업을 너무 복잡하게하면 결과적으로 오류가 발생하기 쉽다. 의사 결정시 두 가지 원칙이 사실상의 표준이되었다.

 

  • 바보야, 단순하게 해 (Keep It Simple, Stupid. KISS)
  • 넌 그것을 필요로하지 않을 거야 (Your Aren't Gonna Need It. YAGNI)

이 두 가지 원칙이 지키려고 하는 것이 바로 그 순간에 특정 기능이나 결정이 필요한지 생각하게 하는 것이다. 나중에 결정을 연기 할 수 있다면 아키텍처를 단순하게 유지하여 더 오랜 시간 동안 쉽게 관리 할 수 ​​있다. 소프트웨어 아키텍처가 복잡 해지는 일반적인 방법 중 하나는 추상화를 도입하는 것이다. 이 형식은 데이터를 한 형식으로 복사하여 다른 형식으로 변환하는 새로운 고급 레이어 일 수도 있고, 코드 100 % 테스트 가능하게 하기 위해 많은 클래스, 인터페이스, 팩토리 등을 생성하는 것일 수도 있다. .

 

그러나, 이 두 가지 원칙을 적용 할 때 한 가지 함정은 우리가 마지막 순간까지 모든 것을 지연시키는 경향이 있다는 것이다. 그때에는 필요한 변경을 구현하기가 이미 너무 어려워 졌을 수 있다. 대신 우리의 임무는 훨씬 더 어렵다. 왜냐하면:

 

우리는 마지막 순간이 아닌 가장 적정한 시간(Most Responsible Time)에 결정을 내려야 하기 때문이다 

 

구조적 결정을 내리려고 할 때, 결정의 확실성이 높은 경우, 일반적으로하는 일이 시간이 많이 걸리지 않는 작은 준비를하는 것이다. 나는 또한 동료들과 상의하고 함께 토론한다.

 

실제 상황의 고통

온라인 페리 티켓 판매에 관한 프로젝트를 진행하고 있었다. 다른 4 개의 타사 시스템과 통신해야했기 때문에 복잡한 시스템이었다. 처음에는 주로 사용자 스토리를 기반으로 기능을 구현하는 데 중점을 두었다. 우리는 정교한 캐싱 메커니즘이 필요하다는 것을 알고 있었지만 그 순간에 필요하지 않았기 때문에 이를 지연시켰다. 당시 사용자 스토리에 집중해야 했다. 그러나 나중에 다른 타사 시스템과의 광범위한 통신으로 인해 시스템 속도가 느려졌다. 우리는 다른 선택의 여지 없이 사용자 스토리에 대한 작업을 중단하고 캐싱 메커니즘에 초점을 맞추어야 했다. 그 때에는 이미 구현하기가 어려웠다.

문서를 작성하는 방법

워터폴 접근 방식은 단계 간에 정보를 전송하는 데 문서가 사용되므로 각 단계에 관련된 사람들이 광범위하게 문서를 작성해야한다. 이 작업은 시간이 많이 걸리는 프로세스 일뿐만 아니라 잘못 설명된 기능으로 인해 오해로 이어질 수 있다. 또한 개발 속도가 빨라지면, 문서가 더 이상 이를 반영하지 않기 때문에 문서를 최신 상태로 유지하기가 어렵다. 코드 작성하기 위해 반복(Iteration)을 하는 애자일 접근법을 사용하면, 문서 작성도 유사하게 수행할 수 있다. 우리는 시스템의 중요한 측면만을 설명하고 시작한 다음 필요할 때 지속적으로 더 많은 정보를 추가한다.

 

문서화 내용(What to Document)

동일한 형식을 여러 형식으로 여러 번 문서화하지 말자. 예를 들어 코드에서 다이어그램을 생성하는 데 도움이되는 도구가 있다. 이 도구는 사용되지 않는 경향이 있으므로 별도의 다이어그램을 만드는 대신 매우 편리 할 수 ​​있다. 또한 시스템의 특정 측면을 설명하기 위해 시각적 산출물을 작성한 경우 단어로 동일한 내용을 설명하는 광범위한 텍스트 문서를 작성할 필요가 없다 (세부 사항을 추가하지 않으면 시각적으로 표현할 수 없음). 여기서 중요한 점은 당신과 당신의  팀은 사용 된 도면 표기법에 대한 일반적인 이해가 필요하다는 것이다.

 

문서화하는 방법

소프트웨어 아키텍처를 문서화 할 때 UML이 최선의 방법이라고 생각할 수 있다. UML은 표준이며 모든 사람이 알고 있으며 대학에서 이를 가르치므로 조직의 모든 사람을 통합할 수있는 도구 중 하나다. 내 경험에 따르면 실제로 UML을 사용하는 사람은 거의 없다. 그 이유 중 하나는 다른 관점에서 시스템을 설명하는 많은 방법이 있기 때문에 정기적으로 사용하지 않으면 좌절 할 수 있습니다.

애자일 세계에서 소프트웨어 아키텍처를 문서화하기위한 특정 도구는 없다. 화이트 보드 그림, Post-It 메모, 텍스트 문서, 위키 등을 사용할 수 있다 (아래 그림 참조). 실제로 2-3 개의 다른 산출물 형식만 사용하는 것이 더 안전하다. 그렇지 않으면 해당 형식을 저장하고 정보를 검색하기가 어려워 질 수 있습니다. 예를 들어, 그림을 그린 후에 화이트 보드 사진을 찍어야 디지털 버전의 다이어그램을 만들 수 있다. 그러나 나중에 편집해야하는 경우 디지털 방식으로 편집 할 것인지 화이트 보드에 다시 그릴 것인지 새 사진을 찍을 것인지 선택해야한다.

화이트 보드 도면 및 포스트잇 형태의 문서

 

결론

소프트웨어 아키텍처는 미래 시스템의 골격(Skeleton)을 정의한다. 이것은 선과 점이있는 다이어그램이 아니라 코드 자체를 포함하여 시스템 개발을 관리하는 완전한 결정 세트(Decision Set)이다. 모든 결정은 타협이므로 신중하게 고려해야 한다. 애자일 사고 방식은 요구 사항이 다소 안정적 일 것으로 예상되는 기존 워터폴 모델과 달리 프로젝트 후반에도 변화에 개방적이어야 한다. 그러나 시스템 골격의 변경이 항상 구현하기 쉽지는 않으며 개발 프로세스를 이전으로 되돌릴 수도 있다. 따라서, 마지막 순간까지 결정을 지연시키지 말고 그 시점에 필요하지 않은 것을 구현할 위험이 적더라도 가장 적정한 결정(Most Responsible Decision)을 내리는 것이 중요하다. 또한 애자일 소프트웨어 아키텍처는 모든 사람이 구축 할 수있는 지속 가능한 플랫폼을 만들기 위해 큰 그림과 "지금"을 복합적으로 혼합해야 한다.

 

번역의 결론

이 글을 읽으면서 가장 기억에 남은 문구는 가장 적정한 결정(Most Responsible Decision)이라 번역한 표현이다. 외국에 있을 때, 음주 관련 공익 광고 중에 "Drink Responsibly"라는 광고 카피가 기억에 남는다. 이는 자기가 감당할 정도로만 마셔서, 자신의 생활과 관계를 잘 유지할 수 있도록 하자는 뜻이라고 한다. 나는 이와 일맥 상통한다고 생각했다. 즉, 구조적 결정을 하는 시간과 범위를 가능한 미루는 것이 아니고 알맞고 발라야 한다는 것이다.

 

또한, 토론에 대한 언급도 좋았다. 나는 애자일 접근법이 소통으로 이루어 지는 부분이 매우 훌륭하다고 생각한다. 특히, 애자일 12원칙 중 "최고의 아키텍처, 요구사항, 설계는 자기 조직적인 팀에서 창발한다."라는 구절을 매우 좋아 한다. 창발은 팀의 원할한 소통에서 나올 수 있다. 그러므로, 소통이 없는 팀에서는 좋은 아키텍쳐가 나오기 어렵다.

 

아쉬운 부분은 절차에 대한 이야기가 있지만, 어떻게 구조적 결정을 해야 하는지에 대한 기술적인 방법(Technical Method)에 대해서는 설명이 없다는 것이다. 이러한 부분은 아직 Design Pattern, Architectural Style, Aspect Driven Design(ADD) 혹은 AD를 활용해야 하는 것인지도 모르겠다. 이 부분은 숙제로 남겨두고 고민해도 좋겠다.

 

참고 문헌

[1] Boyan Mihaylov, "Towards an Agile Software Architecture," https://www.infoq.com/articles/towards-agile-software-architecture/?p13nId=6817&p13nType=followTopic#

 

Software Architecture 평가 방법에 대해서 [1]에서 CBAM 및 ATAM을 포함하여 설명하고 있다. 특히, ATAM 관련해서는 [2]에서 잘 설명하고 있다. ATAM의 예로서는 [3]에서 살펴 볼 수 있다.

 

참고 문헌

[1] 소프트웨어 구조 평가 모델 비교, http://blog.skby.net/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8F%89%EA%B0%80-%EB%AA%A8%EB%8D%B8-atam-cbam/

 

[2] 소프트웨어 아키텍처 평가, https://www.slideshare.net/ssuserff7918/atam-19297511

[3] Rick Kazman, et al, "ATAM: Method for Architecture Evaluation," https://www.win.tue.nl/~wstomv/edu/2ii45/year-0910/00tr004.pdf

 

개요

여기서는 SEI의 Architecture 설계 [1]에서 설명하는 Quality Attribute에 대해서 설명한다. 4.4절에서 설명하는 바와 같이 Quality Attribute가 Quality Scenario로 구체화 된다. 이를 그림으로 표현하면 아래와 같다. ISO 25010의 경우는 이름이나 분류가 다른 경우도 있으니, 참고 바란다.

 

 

 

 

이 후에는 [1]에서 설명하는 Quality Attribute들 중요서 주요한 몇가지를 Quality Scenario 형태로 설명한다.

 

성능(Performance)

성능은 Software System이 가지는 중요한 Quality 중의 하나이다. 이는 [1]의 8장에서 설명한다. 아래는 설명하는 Quality Attribute의 상세히 설명하는 Quality Scenario가 가져야 하는 내용의 일반적인 셜명이다.

 

 

 

 시나리오 항목

입력 가능 값 

 주체(Source)

 . Event 발생 요인(사용자 요청, 시스템 내부/외부) 

 자극 (Stimulus)

 . Event 도착 패턴 (Event의 주기적/Periodic 도착, 산발적/sporadic 도착, 확률적/Stochastic 도착)

 대상(Artifact)

 . 시스템의 서비스 또는 시스템의 하나 혹은 그 이상의 콤포넌트

 환경 (Environment)

 . 다양한 동작 모드(Normal, Emergency, Peak Load, Overload)

 반응 (Response)

 . 도착한 이벤트의 처리하며 다음 내용을 결정한다.

 . 자극을 처리한다.

 . 서비스 수준을 변경한다.

 측정 (Response Measure)

 . 도착 이벤트의 처리시간/양(Latency, deadline, throughput, jitter, miss rate)

 

 

변경 용이성 (Modifiability)

변경 용이성은 개발 측면에서 추가적인 요구 사항에 대해서 변경하기 효율성을 의미한다. 

 

 시나리오 항목

 입력 가능 값

 주체 (Source)

 End user, developer, system administrator

 자극 (Stimulus)

 기능 추가/삭제/변경하라는 지시 혹은 품질 속성, 용량 혹은 기술의 변경

 대상(Artifact)

 코드, 데이터, 인터페이스, 콤포넌트, 자원, 설정(Configuration)

 환경 (Environment)

 실행시간, 컴파일 시간, 빌드 시간, 시동 시간, 설계 시간

 반응 (Response)

 다음 중 하나 혹은 여러가지:

 . 변경을 수행한다.

 . 변경을 테스트한다.

 . 변경을 배포한다.

 측정 (Response Measure)

  다음 중 비용:

 . 영향받은 대상의 숫자, 크기, 복잡도

 . 노력

 . 필요한 시간

 . 비용 (직접 비용 혹은 기회 비용)

 . 이러한 변경을 통해 영향 받는 기능이나 품질 속성

 . 새롭게 추가되는 결함

 

가용성(Availability)

가용성은 Software System이 사용자에게 가용한 시근으로 보통 확률로 많이 표시된다. [1]에서도 MTBF / (MTBF + MTTR)로 설명하고 있다. 여기서 MTBF는 Mean Time Between Failure이고, MTTR은 Mean Time To Repair로 하고 있다. 즉, 시스템을 고치는 시간을 제외한 시스템의 사용 가능 시간(MTBF)의 비율을 나타낸다. 여기서 99%의 경우는 Downtime이 연간 3일 15.6시간이고 Cloud에서 동작하는 시스템은 99.9%를 이야기 하는 경우가 많은데 연 8시간 45초로 매우 낮다. 아래는 가용성에 대한 일반적인 Quality Scenario이다.
 

 시나리오 항목

 입력 가능 값

 주체 (Source)

 . 결함/실패의 원인 [시스템 내부/시스템 외부][사람, HW, SW, 장비, 환경]

 자극 (Stimulus)

 . 발생 결함 [Omission, Crash, Incorrect Timing, Incorrect Response]

 대상(Artifact)

 . 가용성을 위해 요구되는 자원[프로세서/통신채널/자료저장소/프로세스]

 환경 (Environment)

. 결함 또는 실패 발생의 시스템 상태

  [Normal Operation, Start up, Shutdown, Repair mode, Degraded operation]

 반응 (Response)

 실패 방지. 

 실패 인지: 

 . 실패를 기록한다. 

 . 사용자 및 다른 시스템을 포함하는 적절한 당사제에게 알린다.

 실패 복구: 

 . 정의된 규칙에 따로 오류, 실패를 유발한 이벤트 소스를 비활성화 시킨다.

 . 시스템 중요도에 따라 결정되는 지정된 간격에서 사용할 수 없게 한다.

 . 정상 모드 또는 성능 저하 모드에서 계속 작동한다.

 측정 (Response Measure)

 . 시스템이 사용 가능할 때까지의 시간 간격

 . 가용성 시간 (99.999%)

 . 보수 시간

 . 비정상 모드에서 동작시간 간격

 

기타 Quality Attributes

상호 운영성(Interoperability), 보안성(Security), 시험 용이성(Testability), 사용성(Usability) 뿐만 아니라, 다른 속성도 많다. 이는 설계하려는 Software System의 Domain에 따라 달라지기도 한다. 이야기 한데로, System을 분석하다 보면, 위의 품질 뿐 아니라, 다른 품질 속성이 도입되기도 한다. 여기서는 상세히 다루지 않는다. 필요하면, [1] 혹은 [2]를 참조하기 바란다.

 

마무리 하며

여기서는 SEI에서 이야기하는 주요 Quality Attribute[1]들에대해서 살펴 보았다. 성능(Performance), 변경 용이성(Modifiability) 그리고 가용성(Availability)를 살펴 보았다. 이외에도 Software System에 따라 다른 Quality Attribute들이 있을 수 있다. 각 Quality Attribute는 Quality Scenario로 표시 될 수 있다. 또한 Quality Attribute들은 때에 따라 Functional Requirement가 될 수 있다.

 

Quality Attribute를 만족하기 위한 Tactics나 Architecture Style들은 따로 논의 한다. Tactics는 Quality Attribute별로 접근하는 방식을 정리해놓은 것으로 [1]에도 설명이 되어 있다. Architecture Style은 Architecture Pattern이라고도 불리는데, 이는 POSA[3]의 책 시리즈에 잘 설명이 되어 있고, Design Pattern 처럼 종류가 다양하다.

 

Reference

[1] Len Bass, et. al., "Software Architecture in Practice, Third Edition," Addison Wisely, 2012

[2] ISO/IEC 25010, https://iso25000.com/index.php/en/iso-25000-standards/iso-25010

[3] Frank Buschmann, et. al., "Pattern-Oriented Software Architecture Volume 1: A System of Patterns Volume 1 Edition", Wiley

 

개요

Software System을 구현할 경우에 돈을 지불하는 고객(Customer)과 혹은 실재 사용하는 사용자(User)와 같은 요구 사항을 정의 하는 이해 관계자(Stakeholder)가 원하는 것(요구사항 Requirement)을 파악하는 것은 쉽지 않다. 이를 잘 설명해 주는 유명한 그림[1]이 있다. 아래 그림에서 실제 Software의 모습은 고객/사용자가 실재로 필요로 하는 것으로 하단 오른쪽에서 3번째 모습이다. 하지만, 이해 관계자가 이해하는 모습도 다르다는 것도 알수 있다. 특히, 상단의 가장 왼쪽 그림은 고객이 요구 사항을 잘 설명하지 못하는 것을 보여 주는 그림이다. 특히 이는 고객이 Software System에 대해서 잘 모르거나, System이 세상에 존재하지 않는 경우도 이에 해당할 수 있다.
 

 

 

 

이와 같은 비유처럼 고객의 요구 사항을 잘 이해하기 위해서 요구 사항 분석(Requirement Analysis)가 Software Engineering의 주요 부분을 차지하고 있다. Architecture Design에서도 이에 필요한 요구 사항 분석을 이야기 한다.

 

요구 사항 분석(Requirement Analysis)

 

이전 글에서도 살펴보았듯이, Architecture Design에서는 기능 요구 사항(Functional Requirement), 품질 요구 사항(Quality Attribute)와 제약 조건(Constraint)를 요구 사항으로 나눌 수 있다[2]. 여기서는 Functional Requirement에 대해서 자세히 살펴 본다.

 

 

Use Case Diagram

Functional Requirement는 Software System이 제공해야 하는 요구 사항으로 크게는 Use Case Diagram으로 표시 될 수 있다. 아래 그림은 [3]에서 예로 보여주는 Use Case Diagram으로 Software System의 경계(Boundary)와 개별 Use Case를 보여 준다.

 

 

 

 

 

 

Structured Language Specification

일반적인 언어로는 요구 사항을 설명하면, 일반적인 언의의 특징 상 모호성이 증가한다. 그렇기 때문에 이를 줄이기 위해서 Structured Format을 많이 사용한다. 대표적인 예로는 IEEE 830 SRS가 있다. 여기에는 아래와 같은 표[4]로서 요구 사항에 정리해야 하는 항목들을 가지고 있어 이에 따로 요구사항의 모호성을 줄일 수 있다.

 

 Function

 요구 사항이 수행하는 기능. Use Case의 이름이 될 수 있다.

 Description 

 요구 사항/Use Case에 대한 설명 

 Inputs

 요구 사항의 입력 

 Source

 입력을 주는 소스 

 Outputs

 결과물 

 Destination

 출력물의 수신처

 Requires

 요구 사항이 가지는 Dependency를 표시

 Pre-condition  선행 조건을 설명

 Post-condition

 후행 조건을 설명
 Side effects  부수적인 효과

 

상기 내용들이 항상 필요한 것은 아니지만, 위와 같이 기술해야 하는 항목을 요구 사항별로 설명할 수 있다. 다른 형태로는 [5] 아래와 같은 표도 가능하다.

 

 Number-Title 

 요구사항/Use Case 이름 

 Description

 설명 

 Inputs

 입력 

 Processing

 동작 

 Outputs 

 결과 

 Pre-condition 

 선행 조건 

 Post-condition

 후행 조건

 

 

위와 같이 구조화된 양식에 요구 사항을 기술하는 방식으로 자연어(Natural Language)로만 기술하는 것 보다는 모호성을 줄일 수 있는 효과가 있다.

 

결론

여기서는 기능적 요구 사항에 대해서 분석하였다. 이는 Use Case로 표시 되기도 하고 상세 내용은 Structured Language Specification으로 기술 될 수 있다. 이렇게 하는 이유는 고객 혹은 다른 Stakeholder들이 가지고 있는 요구 사항이 모호하기 때문에 이를 좀 더 명확하게 하기 위한 목적이 있다. 

 

요구 사항에는 이 뿐 아니라, 품질에 대한 요구 사항(Quality Attribute)들도 중요하다. Quality Attribute들은 Stake Holder들에게서 주어질 수도 있고 Business Goal과 연계될 수도 있다. 이는 Architecture Design을 수행하면서 혹은 추가 혹은 우선 순위가 바뀔 수 있다. 이와 같은 요구 사항들을 가지고 Architecture Design이 수행 될 수 있다는 것은 다른 글로 설명하였다. 

 

References

[1] The Project Cartoon, http://www.projectcartoon.com/
[2] Len Bass, et. al., "Software Architecture in Practice, Third Edition," Addison Wisely, 2012
[3] Rick Kazman, et. al., "Designing Software Architectures: A Practical Approach," Addison Wisely, May 2016
[5] Requirements Engineering — Requirements Specification (Part 3), https://medium.com/omarelgabrys-blog/requirements-engineering-elicitation-analysis-part-5-2dd9cffafae8

 

 

개요

여기서 설명하려는내용은 [1]에서 설명하는 Architecture 설계 방법이다. Software System의 요구사항(Requirement)를 만족하기 위해서 이를 분류하고, 이중에서 Quality Attribute, 특히 Architecture에 중요한 요구 사항(Architecture Significant Requirement, ASR)를 만족시키는 구조를 설계하는 방법을 설명한다. 

Architecture와 요구 사항(Requirement)

Software System의 요구 사항(Requirement)은 텍스트 요구 사항(Textual Requirement), 모형(Mockup), 기존 시스템(Existing System), 유즈 케이스(Use Case), 유저 스토리(User Story) 등 다양한 형태로 제공된다. 요구 사항이 어디서 왔든 상관없이 모두 다음과 같이 구분된다. [1]
1. 기능 요구 사항(Functional Requirement FR): 이러한 요구 사항은 시스템이 수행해야하는 작업과 런타임 자극에 어떻게 반응하거나 반응해야 하는지를 설명합니다.
2. 품질 속성 요구 사항(Quality Attribute Requirement, QA): 이러한 요구 사항은 기능 요구 사항(FR) 또는 전체 제품에 대한 자격 확인(Qualification)이다. 기능 요구 사항의 자격은 기능이 얼마나 빨리 수행되어야하는지, 오류 입력에 대해 얼마나 탄력적이어야하는지 등의 항목입니다. 전체 제품의 자격은 제품을 배포하는 데 걸리는 시간 또는 운영 비용의 제한과 같은 항목입니다.
3. 제약 조건(Constraint): 이는 자유도가 0 인 설계 결정이다. 즉, 이미 결정이 나 있는 디자인 결정이다. 예를 들어, 특정 프로그래밍 언어를 사용하거나 특정 기존 모듈을 재사용하거나 시스템 서비스가 기반으로 해야 하는 경영층의 지시를 포함한다. 이러한 선택은 아마도 건축가의 관점에서 볼 수 있지만, 새로운 언어로 직원을 양성 할 수 없거나, 소프트웨어 공급 업체와 비즈니스 계약을 맺었거나, 서비스 상호 운용성이라는 비즈니스 목표를 추진할 수 밖에 없는 등의 광범위한 요인으로 인해 이러한 설계 결과를 강제하게 된다.
 
[2]에서는 이러한 Requirement들을 Architectural Driver로 이야기 하고 있다. 그리고 이런 것들이 결국 Architecture에 영향을 미친다고 볼 수 있다.
 

 

 

 
이러한 종류의 요구 사항 각각에 대한 아키텍처의 "대응"은 무엇인가?
1. 기능 요구 사항(Functional Requirement)은 설계 전반에 걸쳐 적절한 책임(Responsibility)을 순차적으로 부여함으로써 만족된다. 즉, 아키텍처 요소에 책임을 할당하는 것은 아키텍처 설계의 기본적인 결정이다.
2. 품질 속성(Quality Attribute) 요구 사항은 아키텍처에 설계된 다양한 구조와 해당 구조를 채우는 요소의 동작 및 상호 작용에 의해 충족됩니다. 
3. 설계 결정(Design Decision)을 수락하고 영향을받는 다른 설계 결정(Design Decision)과 조화를 이루면 제약 조건(Constraint)이 충족됩니다.

 

Architectural Significant Requirement

어떤 요구 사항은 다른 것보다 Architecture에 훨씬 더 큰 영향을 미친다. 이러한 구조적 중요 요구 사항 (Architecturally Significant Requirement, ASR)은 이와 같이 아키텍처에 큰 영향을 주는 요구 사항이다. ASR을 모른다면 성공적인 아키텍처를 설계 할 수는 없다. ASR은 아키텍처가 시스템에 제공해야하는 품질 속성(Quality Attribute) 요구 사항의 형태를 취한다. Architecture에 사용할 패턴(Architecture Pattern, Architecture Style) 또는 전술(Tactics)을 선택할 때마다 품질 속성(Quality Attribute) 요구 사항을 충족해야하므로 Architecture가 변경됩니다. 그러므로 QA 요구 사항이 더 어렵고 중요할수록 아키텍처에 큰 영향을 미쳐 ASR이 될 가능성이 커진다. 다음은 ASR을 추출하는 방법들이다[2]. 필요한 경우, 이후에 상세히 살펴 보도록 한다.

 

  • Requirement 문서에서 ASR을 추출
  • Stakeholder 인터뷰를 통해 ASR을 추출
  • 비즈니스 목표(Business Goals) 이해를 통한 ASR 수집
  • Utility Tree를 통한 ASR의 추출
  • 여러 방법의 연결
 

Architecture와 Quality Attribute 

선택된 ASR인 Quality Attribute들과 다른 요구 사항들을 기반으로 Architecture 설계하는 방법을 [2]의 17장에서 설명한다. 이는 아키텍처를 설계하기 위한 전략(Design Strategy)와 이러한 아이디어를 하나로 패키징하는 방법, 즉 속성 중심 설계(Attribute Driven Design, ADD)을 설명한다.

 

 

디자인 전략은 아래와 같은 절차를 포함한다

  • Decomposition: 충분히 시스템 내의 모듈을 분해 해야 한다. 
  • ASR에 대한 설계: 도리어 다른 Requirement들에 영향이 없는지 확인해야 한다.
  • 가설(Hypothesis) 생성과 테스트를 통한 확인

 

 

속성 중심 설계, ADD는 5 단계 방법이다.

  1. 디자인 할 시스템 요소를 선택한다.
  2. 선택된 요소에 대한 ASR을 식별한다.
  3. 선택한 요소에 대한 설계 솔루션을 생성한다. : 여기서 여러 솔루션들에 대해서 비교가 필요하다.
  4. 나머지 요구 사항을 재고하고 다음 반복을위한 입력을 선택한다.
  5. 모든 ASR이 충족 될 때까지 1-4 단계를 반복한다.

 

위의 방법, 특히 ADD는 실재 예를 보면서 이해하는 것이 필요하다. 특히, 위의 절차는 지속적으로 반복하여 요구 되는 요구 사항들과 Quality Attribute들을 만족할 때까지 반복하여 최종 Architecture가 설계 되도록 해야 한다.

 

마무리 하며...

구조 설계에는 명확한 답이 있어 보이지는 않는다. 하지만, 새로운 방법들이 제안되고 있다. 이런 것들은 추후 검토하고 추가하기로 한다. 예를 들자면, 

ADD 3.0 및 예제 문서들[3]이 그 예이다. 위에 예들을 이해하기 위해서는 예를 많이 살펴 보는 것이 좋고, 그 후에 실재로 설계를 진행해 보는 것이 좋다.

References

[1] Len Bass, et. al., "Software Architecture in Practice, Third Edition," Addison Wisely, 2012
[2] Mohamad Kassab, "Software Architectural Patterns in Practice: An Empirical Study," https://slideplayer.com/slide/13920131/
[3] Rick Kazman, et. al., "Designing Software Architectures: A Practical Approach," Addison Wisely, May 2016
 
 
 
 

 

개요

여기서는 Software Architecture의 정의에 대해서 살펴 보고, Architecture가 무엇으로 어떻게 표현되는지 살펴 본다. 이 후 어떻게 Architecture Design을 수행하는지, 이와 관련된 Architecture Style, Tactics 등에 대해서는 따로 정리해 보기로 한다. 최종 설계된 Architecture가 어떻게 문서화 될 수 있는지도 이후 살펴 보도록 한다.
 

소프트웨어 아키텍처(Software Architecture)의 정의

Wikipedia[1]에서도 [2]의 정의를 가장 앞에 두고 채용하고 있다. 이 책은[2]는 Carnegie Mellon 대학의 Software Engineering Institute (SEI)의 Software Engineering Book Series[3] 중의 하나이기도 하다.

 

"The software architecture of a system is the set of structures needed to reason about the system, which comprise software elements, and properties of both"

 

"시스템의 소프트웨어 아키텍처는 시스템에 대해 추론하는 데 필요한 구조의 집합으로, 소프트웨어 구성 요소(Element)와 속성(Properties)로 구성된다."

 

여기서 구조(Structure)와 뷰(View)에 대해서 잘 이해애야 한다. [2]에서의 설명을 보면 다음과 같다.

 

  • A view is a representation of a structure. It consists of a representation of a set of elements and the relations among them.
  • A structure is the set of elements itself, as they exist in software or hardware.

즉, Structure가 Architecture를 구성하는 Element의 집합이라는 것이고 Architecture의 일부라는 것을 알 수 있다. 그렇다면, View를 이 Structure를 보여주는 (Represent) 것이다. Architecture를 설계하는 Architect는 결국 Structure를 설계하는 것이다. 그리고 설계한 Structure를 View를 통해서 보여주고(Represent)하고 문서화 한다(Document). 이 View에 대해서는 따로 상세히 이야기 하기로 한다.

 

Software Architecture에 대한 다른 정의를 Martin Fowler의 논문[4]에서 찾아 볼 수 있다. 

 

"Architecture is the set of design decisions that must be made early in a project." 

 

여기서 design decision을 Architectural Design Decision이라고 부른다. 이는 Software System의 요구 사항(Requirement)에 대해서 다른 결과를 제공하는 Architecture Design들의 차이점을 비교하여 내리는 결정들이라고 할 수 있다. 

 

이 내용을 요약하면, Software Architecture는 어떻게 Software System의 Structure를 보여주는 View들을 통해서 Stakeholder들에게 보여 주는가? 그리고, 이러한 Structure들을 선택하여 결정한 Architectural Design Decision을 Stakeholder들에게 설명하는가가 중요하다고 할 수 있다. 이는 최종 결과물에 대한 것이고 이러한 최종 결과물을 찾아 가는 과정은 또한 다른 이야기 이다. 이 최종 결과물은 여러가지 형태가 가능하겠지만, 여기서는 문서 (Architecture Document)로 보도록 한다.

 

 

뷰(Views)

View는 Software Architecture를 다양한 측면에서 바라 보는 것이라고 할 수 있다. 여기서는 CMU SEI의 Three View와 RUP의 4+1 View를 기준으로 설명하고 둘 간의 겹치는 부분에 대해서 살펴 본다.
 

CMU SEI Three Views

[5]에서는CMU의 SEI에 이야기 하는 Software Architecture의 3가지 View에 대해서 설명한다. 

첫 번째는 Module View이다. Module은 소프트웨어의 구현단위이다. 그렇기 때문에, Module View는 소스 코드 작성의 청사진(Blueprint)으로 사용될 수 있다. 그리고, Module은  소스코드나디렉터리같은 물리적구조와 대응된다.

 

 

 

두 번째는 Component-and-Connector (C&C) View이다. 이는 런타임(Runtime)에 나타나는 요소들(Component)로 구성된다. 실재로는 선(Connector)과 도형(Component) 위주의 다이어그램으로 표현된다. 즉, 실행시간에 나타나는 시스템의 전체 윤곽을 보여준다. 여기서, Component는 프로세스, 객체, 클라이언트, 서버, 데이터저장공간을 나타내고, Connector는 통신연결, 통신규약, 정보흐름, 공유저장소를 나타내는데, 2개 이상의 대상을 연결할 수도 있다.

 

일반적으로 C&C View는 실행 시간의 수행 Component이고 Module View에서는 구현 단위 이다. 아래 그림은 [5]에 있는 예이다. 여기서 볼 수 있 듯이, Module View는 구현 입장에 있는 Element들과 이와 관련된 구조를 보여 준다. 이에 대한 실재 실행이 된다면, 왼쪽과 같은 모습을 가질 수 있게 되는 것이다.

 
 
 

 

세 번째 View는 Allocation View이다. 이는 Software Architecture와 환경을 매핑시키는 역할을 한다. 우선 Deployment Style와 Work Assignment Style을 살펴 보자

 

Deployment Style은 Software Element가 어떤 물리적 장치에 탑재되는지 보여 준다. 시스템 동작 시, Deployment 내용이 변경 될 수 있으므로 할당은 동적일 수 있다.

 
Work Assignment Style은 Module View의 내용 중 구현 책임이 있는 개인이나 그룹에 할당하는 것이다. 즉, 구조와 작업하는 개발 그룹간의 관계를 보여 주는 모습이라고 할 수 있다.
 

RUP Views: 4+1 Views

아래 4+1 View[6]는 Rational에서 일하던 Philippe Kruchten에 의해서 제안되었다. Scenario(Use Case)를 기반으로 4개의 View가 있다.
 
 

 

 
  • Development View : Development View는 프로그래머 관점에서 시스템을 보여 주며 소프트웨어 관리와 관련됩니다. 이 뷰는 Implementation View라고도합니다. UML Component Diagram을 사용하여 시스템 구성 요소를 설명합니다. Development View를 표현하는 데 사용되는 UML Diagram에는 Package Diagram이 포함됩니다.
  • Logical View : Logical View는 시스템이 최종 사용자에게 제공하는 기능과 관련됩니다. Logical View을 나타내는 데 사용되는 UML Diagram은 Class Diagram 및 State Diagram을 포함한다.
  • Physical View: Physical View는 시스템 엔지니어의 관점에서 시스템을 묘사합니다. 이는 물리적 레이어의 소프트웨어 구성 요소와 이러한 구성 요소 간의 물리적 연결의 토폴로지와 관련이 있습니다. 이 View는 Deployment View라고도 합니다. Physical View를 나타내는 데 사용되는 UML Diagram에는 Deployment Diagram이 포함됩니다. 
  • Process View : Process View는 시스템의 동적 측면을 다루고, 시스템 프로세스와 이들이 통신하는 방법을 설명하고, 시스템의 런타임 동작에 초점을 맞 춥니다. Process View는 Concurrency, Distribution, Integrators, Performance및 Scalability 등을 처리합니다. Process View를 나타내는 UML Diagram에는 Activity Diagram이 포함됩니다.
  • Scenario: 아키텍처에 대한 설명은 작은 Use Case의 집합 또는 Scenario를 사용하여 설명됩니다.이 시나리오는 다섯 번째 View가 됩니다. Scenario는 Object간 및 Process 간 상호 작용을 설명합니다. 이들은 아키텍처 요소를 식별하고 아키텍처 설계를 설명하고 검증하는 데 사용됩니다. 또한 아키텍처 프로토 타입 테스트의 출발점 역할을합니다. 이 View는 Use Case View라고도 합니다.
 
  • Scenarios: The description of an architecture is illustrated using a small set of use cases, or scenarios, which become a fifth view. The scenarios describe sequences of interactions between objects and between processes. They are used to identify architectural elements and to illustrate and validate the architecture design. They also serve as a starting point for tests of an architecture prototype. This view is also known as the use case view.
 

결국은 어떤 View를 선택해야 할까?

SEI와 RUP의 View에서는아래 내용들이 유사하게 보여 진다.
 
Allocation View-Work Assignment Style Development View
Module View  Logical View
C&C View Process View 
Allocation View-Deployment Style  Physical View(Deployment View) 

 

다른 View들도 있지만, 결국 이 View들이 기본적인 View라고 볼 수 있다. 

Architectural Decisions

[2]의 Chapter 4.3을 보면 아래와 같이 7가지 Design Decision을 설명하고 있다. 물론 이것 말고도 다른 것도 있을 수 있지만, 이를 통해서 결국 Architecture를 설명할 수 있다는 입장에서 이해하면 된다.
 
  1. Allocation of responsibilities
  2. Coordination model
  3. Data model
  4. Management of resources
  5. Mapping among architectural elements
  6. Binding time decisions
  7. Choice of technology
 

책임 할당 (Allocation of responsibilities)

  • 이 Architectural Decision은 중요한 책임(Responsibility)를 구분하는 것이다. 이 책임(Responsibility)에는 Basic System Function, Architectural Infrastructure, 품질 속성(Quality Attribute)에 대한 충족을 포함한다.
  • 이 Responsibility를 결정하는 것은 (Module, Component 혹은 Connector)라고 불리는 Non-runtime/Runtime Element에 할당하는 것을 의미한다.
 

모델 코디네이션 (Coordination model)

  • 반드시 Coordinate해야 하거나 Coordinate하면 안되는 시스템의 Element를 구분해내야 한다. 
  • Coordination의 속성(Properties)를 결정해야 한다. 이에는 시간관련 사항(Timeliness), 흐름(Currency), 완료성(Completeness), 정확성(Correctness) 그리고 일관성(Consistency)가 포함된다.
  • 이 속성들을 실현하는 통신 메커니즘(Communication Mechanism)을 선택해야 한다.
 

데이터 모델 (Data model)

  • 주요 데이터 추상화(Major Data Abstraction)과 관련된 동작(Operation)과 속성(Properties)을 선택해야 한다.
  • 데이터의 일관된 해석을 위해 필요한 Metadata를 만들어야 한다(Compile).
  • 데이터를 오거나이징(Organizing)해야 한다.
 

자원 관리 (Management of resources)

  • 관리해야 할 자원(Resource)를 구분하고 각각의 제약(Limit)을 결정해야 한다.
  • 어떤 System Element(s)가 각 자원(Resource)를 관리할지 결정한다.
  • 어떻게 자원이 공유되고 충돌(Contention)이 있을 때 어떠한 중재 정책(Arbitration Strategy)을 채용할지 결정한다.
  • 다른 자원들의 포화되는 경우의 영향을 결정한다.
 

Architectural Element 매핑 (Mapping among architectural elements)

  • Module과 Runtime Element를 서로 매핑합니다. 즉, 각 Module에서 생성된 Runtime Element입니다. 즉, Module은 각각의 Runtime Element에 대한 코드를 포함하는 것이라 할 수 있습니다.
  • Runtime Element를 Processor(실재 실행되는 컴퓨팅 리소스)에 할당합니다.
  • Data Model의 항목을 데이터 저장소(Data Store)에 할당합니다.
  • Module과 Runtime Element를 전달 단위(Units of delivery)로 매핑합니다.
 

바인딩 시간 결정 (Binding time decisions)

  • 책임(Responsibility) 할당을 위해 매개 변수화 된 makefile을 통해 모듈(Module)을 빌드 타임으로 선택합니다.
  • 프로토콜의 런타임 협상(Negotiation)의 설계를 위해 코디네이션 모델을 선택합니다.
  • 리소스 관리를 위해 런타임에 플러그인 된 새로운 주변 장치를 수용하도록 시스템을 설계 한 다음 시스템이 이를 인식하고 올바른 드라이버를 자동으로 다운로드 및 설치합니다.
  • 기술 선택을 위해 스마트 폰용 앱 스토어를 구축하여 앱을 구매하는 고객의 전화에 적합한 앱 버전을 자동으로 다운로드합니다.
 

기술 선정 (Choice of technology)

  • 다른 카테고리의 Architectural Decision을 실현하기 위해 사용할 수있는 기술을 결정합니다.
  • 이 기술 선택을 지원하는 데 사용할 수있는 도구 (IDE, 시뮬레이터, 테스트 도구 등)가 개발 진행에 적합한지 판단합니다.
  • 기술의 외부 지원의 정도 (예 : 강좌, 자습서, 사례 및 위기 상황에서 전문성을 제공 할 수있는 계약자의 가용성)뿐만 내부의 친밀도를 판별합니다. 또한, 이의 도입을 진행하기에 적합한 지를 결정합니다.
  • 요구되는 조정 모델 또는 제한된 자원 관리 기회와 같은 기술 선택의 부작용 판별합니다.
  • 새로운 기술이 기존 기술 스택과 호환되는지 여부를 판별합니다.
 

마치며...

여기서는 Software Architecture의 정의를 살펴 보고, 결국 Architecture가 View의 집합 혹은 Architectural Decision의 집합으로 설명될 수 있는 것을 알 수 있다. 특히 View의 경우는4개의 View로 설명이 가능하고, Architectural Decision은 7가지의 종류로 나뉘어 지는 것을 알 수 있었다. 그렇다면, 어떻게 Architectural Decision들이 이루어 지고, 최종적으로 Architecture가 결정되는지 살펴 볼 필요가 있다. 그리고, 최종 결정된 Architecture를 4가지 View로 그려보는 것이 필요하다. 그리고, 결정된 Architecture를 어떻게 문서로 전달할지도 알아 볼 필요가 있다.

References

[2] Len Bass, et. al., "Software Architecture in Practice, Third Edition," Addison Wisely, 2012
[3] SEI Book Series in Software Engineering, https://resources.sei.cmu.edu/library/asset-view.cfm?assetid=465870
[4] Martin Fowler, "Who Needs Architect," IEEE Software Volume: 20 , Issue: 5 , Sept.-Oct. 2003
[5] Paul Clements et. al., "Documenting Software Architectures - Views and Beyond 2nd Ed," Addison-Wesley 2011
[6] Philippe Kruchten, "Architectural Blueprints - The “4+1” View Model of Software Architecture," IEEE Software 12 (6), 1995, November
 

 

왜 여기까지 왔는가?

궁금증이 있었다. 우선 회사에서 가르치는 아키텍트(Architect) 관련 과정이 무엇을 가르치는 것일까? 그리고, 거기서 말하는 소프트웨어 아키텍트(Software Architecture)라는 것은 어떤 것일까? 그 과정을 들었떤 지인들에게 이야기 들었던 것들은 나에게 속시원한 답을 주지는 않았다. 그래서, 우선은 과정에 참여하였고, 일단락을 맺었다. 하지만, 이제 시작이라는 느낌이다. 그래서, 이글을 시작으로 또 공부하게되는 내용들을 정리하려고 한다.

 

교육 과정을 통해서 알게 된 소프트웨어 아키텍처 설계(Software Architecture Design)은 소프트웨어 공학(Software Engineering) 관점에서 최적 설계에 대한 것이었다. 단계별로 보면, 요구사항 분석(Requirement Analysis)에서 시작하여 시스템의 범위를 정하고, 기능적 요구 사항(Functional Requirement)를 도출한다. 그리고, 이를 분석하여 품질 시나리오(Quality Scenario)를 도출한다. 여기서, 비기능적 요구 사항(Non-Functional Requirement)와 품질 속성(Quality Attribute)를 선정한다. 이 때, 비지니스 동인(Business Driver)를 고려하여 우선순위 선정이 필요하다. 그리고, 이를 기반으로 시스템 범위 내에 중요한 내용을 기반으로 하는 후보 구조(Candidate Architecture)들을 도출하고 이를 비교 분석하며 아키텍처 디시젼(Architectural Decision)을 해가면서 최종적 아키텍처를 완성해 간다. 이 내용들은 시스템의 이해관계자(Stakeholder)과 공유 되어야 하면서 장점과 단점이 지속적으로 언급이 되면서, 의사결정이 되는 과정인 것이다.

 

아주 오래전에 소프트웨어 공학(Software Engineering) 교수님께서 설명하신 객체 지향 패러다임(Object Oriented Paradigm)관련한 강의에서 들은 내용이 있다. 소프트웨어 공학(Software Engineering)이 무엇인지에 대한 것인데, 이는 소프트웨어 개발자가 가져야 하는 상식(Common Knowledge)이다라는 것이 있다. 나도 소프트웨어를 개발하는 사람이 가져야 하는 기본적 소양이라는 관점에 지금까지도 동의 하는 부분이다. 하지만, 많은 소프트웨어 공학 책에서 강조하는 정식 프로세스(Formal Process) 관점에 대해서는 나는 수긍하지 못하고 있다. 많은 회사들이 이를 채용하고 있다고는 하지만, 직간접적으로 경헌한 나의 경우는 아직 보지 못했기 때문이다. 물론 많은 회사들이 제품을 만들기 때문에 프로세스가 있을 것이다. 

 

가장 큰 이유 중에 하나는 사람이다. 프로세스 중에 사람이 없다. 내 경험으로 여기에 가장 가까운 것은 애자일 소프트웨어 개발(Agile Software Development)이다. 나는 이것도 결국은 소프트웨어 공학의 한 분야로 이해하고 있다. 사람이 하는 일은 사람을 떠나서 생각하면 공허해 진다.

 

문서화에 대한 거부감

내가 SW 개발 실무를 처음 시작할 때에는 통신 프로토콜 스택의 설계 및 구현에 참여 하였다. 요구 사항(Requirement)이 사양서(Specification)이었다. 나의 상사는 소프트웨어 공학을 했던 분으로 내가 받았던 업무 지시는 요구 사항 분석을 통해서 상세 설계를 하는 것이었다. 코딩은 중요하지 않다(Trivial하다는 의미로 이해된다)고 이야기를 계속 들었다. 이러한 작업은 내가 구현해야 하는 분야의 도메인 지식(Domain Knowledge)을 높여 주는데는 성공했지만, 최종적으로 코딩을 하는데는 실패하게 되었다. 이유는 나의 기술적인 미숙, 하드웨어 제약에 대한 이해 부족, 목표한 테스트의 실패, 충분하지 못한 일정 그리고는 지속적은 목표 변경 등 여러 가지가 복합적이었다.

 

이후 내가 경험했던 여러 성공한 프로젝트들은 문서화보다는 실질적으로 동작하는 제품에 맞춰졌다. 내부 혹은 외부의 고객이 원하는 제품 혹은 우리가 만들 수 있는 제품을 고객에게 보여주면서 정제(Refine)해가는 절차를 통해서였다. 즉, 동작하는 솔루션을 만들어 놓고, 그를 바탕으로 확장해 나아가는 방식이다. 여기에서의 우리가 하는 문서화는 점점 줄어 들었다. 이는 읽지 않는 문서는 만들지 않는다는 중요한 원칙이 있었던 것이다.

 

성공적이라 할 수 있는 프로젝트는 많았지만, 늘 지적 받던 것은 소프트웨어의 품질이었다. 과제가 끝났지만, 여전히 소프트웨어에는 문제점(Defect)이 많고, 변경이 용이하지 않으며, 성능이 좋지 않은 경우가 많았다. 여러 가지 개선 방법들을 논의 하였지만, 우선적으로 소개되며 고민되었던 방법은 프러덕트 기반의 애자일 개발 접근 방법이었다. 여기서도 문서는 그렇게 중요한 것은 아니었다. 그 보다는 진행 사항을 가시화(Visualize)하고 제품을 점진적으로 개발하여 실재 제품에 적용가능한지를 평가하면서 진행하는 방식으로 프로젝트가 변경되었다.

 

이렇듯, 소프트웨어 공학에서 이야기 하는 설계서와 같은 문서화에 대해서는 여러 거부감이 있다. 하지만, 아이젠하워의 말대로 "계획대로 승리한 전투는 없지만, 계획없이 승리한 전투도 없다. No battle was ever won according to plan, but no battle was ever won without one" 인 것이다. 결국은 읽힐 문서, 다이어그램(Diagram)을 만들어야 하는 것이다.

 

소프트웨어 공학에 대한 비판

많은 아키텍처 책이나 글 그리고 프로세스는 소프트웨어 공학을 가정한 내용이다. 아래 글은 Wikipedia의 소프트웨어 공학(Software Engineering)[1]에 있는 내용이다. 여러가지 관점에서 살펴 보기 위해서 이러한 의견도 고려해 보려 한다.

 

소프트웨어 공학에서는 개발자를 전문가(Practitioner)로 본다. 즉, 문제 해결에 대해 잘 정의된 엔지니어링 방식을 따르는 개인, 즉 전문가로 본다. 이는 마치 의사, 변호사와 마찬가지로 보는 것이다. 이러한 접근법은 다양한 소프트웨어 공학 서적 및 연구 논문에 명시되어 있으며 항상 예측 가능성, 정확성, 리스크의 완화 및 전문성을 함축한다. 이러한 관점은 엔지니어링 지식을 전파하고 현장을 성숙시키는 메커니즘으로서 라이센스, 인증 및 체계화 된 지식 체계를 요구하게 되었다.

 

하지만, 소프트웨어 공학에 대한 핵심 쟁점 중 하나는 일반적으로 실제 접근 방식의 검증이 없거나 매우 제한적이어서 접근 방식이 충분히 경험적이지 않다는 것이다. 그래서, 소프트웨어 엔지니어링이 종종 "이론적 환경"에서만 가능한 것으로 오인된다.  

 

에츠허르 데이크스트라(Edsger Dijkstra)는 오늘날 소프트웨어 개발에서 사용 된 여러 개념을 만든 사람으로서 2002 년 사망 할 때까지 소프트웨어 엔지니어링에 대해 컴퓨터 과학의 "급진적 참신성(Radical Novelty)"라고 반박했다.[2]

 

"이러한 현상은 소프트웨어 공학이라는 이름으로 포장되었다. 경제학이 비참한 과학(The Miserable Science)으로 알려짐에 따라 소프트웨어 공학은 종말에 이른 체계(Doomed Discipline)으로 알려 져야한다. 목표가 자기 모순적이어서 목표에 다가 갈 수 없기 때문이다. 물론 소프트웨어 공학은 또 다른 가치있는 이유를 제시하지만, 눈속임이다. 글을 주의 깊게 읽고 그것이 실제로 무엇을 하려고 헌신하는지 분석하면 프로그래밍을 할줄 모르면 어떻게 하는가(How to program if you can not)가 소프트웨어 공학의 진정한 근본 원리(Charter)로 받아 들여 졌음을 알게 될 것이다."

 

그럼에도 불고하고, 소프트웨어 공학은 이러한 비판에 대해서 닫혀 있지 않았다. 여러 경험적이고 공통적으로 받아 지는 것들을 쌓아 왔다. 특히, 애자일 소프트웨어 개발(Agile Software Development)의 성공적인 부분을 받아 들였따. 특히, 반복적 접근 방법(Iteratvive Development)를 받아 들이고 있다.

 

정리해 보면...

받아 들이기 어려운 극단적인 비판적 입장일 필요도 없다. 간단하고 명료하게, 우리는 실용적(Practical)일 필요가 있다. 즉, 결과를 낼 수 있고, 논리적으로 명백하다면 받아 들이기는 더욱 쉬울 것이다. 단지, 늘 비판에 대해서 열려 있어야 한다. 하지만, 목표는 분명하고 단단해야 할 것이다. 즉, 소프트웨어는 완성되어야 한다. 그리고, 소프트웨어 개발이 어려 사람들과 함께 만들어 내야 하는 것일 것이다.

 

이러한 실용적이라는 관점에서는 목표를 이야기 하고 싶다. 우리는 최상의 결과물을 만들어 내야 한다. 그런 입장에서 결과물을 처음 부터 그려 가는 것이 필요하다. 처음에 그렸던 최종 결과물이 마지막에도 꼭 그런 모습을 가지지 않을 수 있다. 아이젠하워의 말처럼 "Plan is nothing, but planning is indispensable"이 생각난다. 결국, 전쟁에서는 승리가 최종 목표 이듯이, 설계서에 함몰하지 말고 최종 결과물을 목표로 하는 설계에 집중해야 한다고 생각한다.

 

그래서, 나는 소프트웨어 공학 관점에서의 아키텍처에 관해서 고민을 시작하여 여러 관점에서의 실용적인 소프트웨어 아키텍처의 설계에 대해서 살펴 보고자 한다.

 

참고 문서

[1] Software Engineering, https://en.wikipedia.org/wiki/Software_engineering 

[2] On the cruelty of really teaching computing science, https://www.cs.utexas.edu/~EWD/transcriptions/EWD10xx/EWD1036.html

 

 

+ Recent posts