본문 바로가기

클린 아키텍처

by Recstasy 2020. 12. 28.

1  설계목표

 설계목표는 최상위 정책에 해당한다. 가령, 중고차 어플의 최상위 정책은 '유통'이며, 대중교통 정보앱의 최상위 정책은 '정보전달'에 있다. s/w를 설계하려는 목표는 최상위 정책의 기반이 되며, 최상위 정책은 유스케이스, 인터페이스, 데이터베이스, 라이브러리, MVC패턴 등 그 어떠한 계층에도 절대 의존해서는 안 된다. 아키텍트는 설계에 앞서 프레임워크, 데이터베이스, 라이브러리 등 어떠한 것도 정할 필요가 없으며, 오직 최상위 정책에 해당하는 목표와 철학만 정리한다. 

 

최상위 정책은 어플리케이션 수준의 유스케이스에 보호받고 있으며, 유스케이스는 최상위층에 의존한다. 가령, '중고책', '중고차'는 '거래'라는 정책이 같으며, 거래와 관련된 모든 객체(클래스, 모듈, 객체 등)는 최상위 정책이 반영된 컴포넌트가 된다. 또한, 이 과정에서 개발철학의 반영비율에 따라 s/w독창력이 달라진다.

카카오톡 목표 : 간편한 커뮤니케이션 도구를 제공
철학 : 통합적 콘텐츠 플랫폼를 지향
텔레그램 목표 : 간편한 커뮤니케이션 도구를 제공
철학 : 보안에 특화된 플랫폼을 지향

철학은 중요하다. 철학이 다르면 최상위 컴포넌트 및 정책 또한 달라진다. 설계목표가 같더라도 철학에 따라 다른 방향으로 성장한 카카오톡, 텔레그램과 같은 경우가 이에 해당한다.

 

 

유스케이스 제작 

 S/W제작은 빈 종이에서 그림을 그려나가는 과정이다. 화가가 그림을 그릴 때 자신만의 요구사항이 필요하다. 요구사항은 일종의 제약에 가까운데, 제약에 따라 그림의 종류(풍경화, 정물화, 인물화 등)가 달라지듯이 s/w역시 요구사항을 따라 종류가 나눠진다. 따라서 유스케이스는 마치 그림의 밑그림과 같으며, 잘 정리된 유스케이스는 이해하기 쉽다. 마치 밑그림만 보더라도 대략 어떠한 그림인지 알 수 있는 것처럼.

 

 

 일단, 유스케이스는 어플리케이션 레벨에 위치해 있고, 엔티티에 의존한다. 엔티티에 따라 유스케이스는 변경이 발생할 수 있다. 가령, '대화하기'라는 SNS매신저 앱의 유스케이스가 있다면, 해당 유스케이스의 최상위 정책은 '소통'이다. 

 

 

3  객체·메시지 맵 

 유스케이스를 작성하는 이유는 객체들을 찾기 위함이다. 객체지향 프로그램은 결국 객체들의 협력이 전부다. 설계목표를 달성하기 위해서 객체들이 각자 책임에 맞는 역할을 수행하고, 협력하는 과정이 객체지향 설계의 전부다. 그리고 객체맵은 협력하는 객체들이 모여사는 또 하나의 세계다. 마치 애니메이션 스토리를 창조하는 것처럼 스토리를 만든다면, 객체맵은 저절로 완성된다. 이때 필요한 길잡이가 바로 메시지다. 메시지는 누구나 찾을 수 있다. 아래 그림에서 물음표에 해당하는 자신만의 메시지를 생각해보자.

 

 객체·메시지맵은 유스케이스에 맞는 메시지를 떠올린 후, 해당 메시지를 수행할 수 있는 객체를 CRC카드로 정리한 아웃풋이다. CRC카드의 앞면은 아래와 같이 객체명과 역할(수행내용)을 기록한다. 그리고 뒷면에는 '무엇을 알고 있는지(책임)', '어떻게 할 수 있는지(역할·메서드)' 2가지와 협력관계에 있는 객체를 기록한다.   

 

 

메시지를 만들고, 해당 역할을 수행할 객체들을 생성하다보면 순식간에 수십장의 카드와 화살표가 만들어진다. 이때 제작된 카드의 위치와 방향은 객체간의 계층이 없는 순수한 상태다. 그리고 계층이 없는 순수한 상태는 외부의 요청에 의해 전체변경이 발생할 수 있는 취약한 상태와 같다. 따라서 레벨맵을 정리할 필요가 있다.

 

 

4  레벨맵 

 레벨맵은 컴포넌트를 제작하기 위한 과정이다. CRC카드로 제작한 객체들간의 관계를 살펴보면, 요구사항에 따라 변경이 가능하고, 자주 변경되는 객체들이 존재한다. 반면, 어떠한 요구사항에도 절대로 변경되어서는 안 되는 객체도 있는데, 불변객체가 바로 상위정책의 레벨에 속한다. 가령, 스마트폰의 경우 사용자의 어떠한 변경조작에도 '커뮤니케이션'과 관련있는 전화-메시지와 같은 기능이 삭제되지 않는 것과 같다. 

 

 

 CRC카드(객체)를 위의 그림에 맞게 배치를 해보자. 안쪽 원에 가까울수록 상위정책에 해당하며, 외부의 객체들은 내부의 객체들에 의존한다.(제어방향은 반대) 바깥쪽에 위치한 객체(컴포넌트 포함)들은 한차원 이상 건너뛰어 안쪽의 객체들에 직접적으로 연결할 수 없다.(의존성 문제) 반드시 안쪽에 인접한 객체들에 의존하며(보호), 협력관계를 형성한다. 레벨맵을 완성하고나면 객체들의 상-하위 관계를 알 수 있고, 이를 통해 컴포넌트를 생성할 수 있다.

 

 

레벨맵을 작성하는 가장 큰 이유는 컴포넌트를 생성하기 위해서다. 객체·메시지맵과 레벨맵을 완성했다면, 2가지 맵을 통해 컴포넌트 계층과 유스케이스간의 계층도를 위와 같이 생성할 수 있고, 아키텍처 경계를 설정하는 단계로 넘어갈 수 있다. 

 

 

5  아키텍처 경계 

 s/w설계과정에서 레벨맵을 작성하고, CRC카드를 작성하는 이유는 동일한 협력을 수행하는 객체들을 묶기(추상화) 위해서다. 추상화는 아키텍처의 꽃이다. 추상화를 통해 설계를 유연하게 만들 수 있으며, 의존성 방향을 제어할 수 있기 때문이다. 객체간의 협력 안에서 동일한 책임을 수행하는 객체들은 동일한 역할을 수행하기 때문에 서로 대체 가능하다. 그리고 대체 가능성은 컴포넌트를 생성하는 기초가 되며, 컴포넌트의 성질(가변성)은  아키텍처 경계의 핵심 「변하는 것 - 안정적인 것」이 된다. 아키텍트는 아키텍처의 경계를 설정함으로써 인터페이스 영역을 확보할 수 있으며, 자연스럽게 의존성을 관리할 수 있다.

 

 

『아키텍처 경계 설정』

Entities는 s/w의 최상위 정책을 포함하고 외부의 어떠한 변경에 영향을 받아서는 안 된다. 유스케이스와 데이터 인터페이스는 모두 엔티티에 의존하고 있으며(화살표 방향) 세부사항을 맡고 있는 컨트롤러나 뷰는 핵심밖에 위치해 있다. 뷰, 컨트롤러, DB에서 엔티티에 접근하기 위해서는 유스케이스나 엔티티에 의존하고 있는 인터페이스를 반드시 거쳐야 한다. (자연스럽게 아키텍처 경계 형성)

 

  

아키텍처 경계맵을 구현하기 위한 컴포넌트맵으로 변환하면 대략 아래와 같은 인터페이스 의존성을 볼 수 있다.  

『인터페이스(의존성) 설정』

의존성 관계애서 main은 모든 컴포넌트에 의존한다. 이에 반해 최상위 정책을 포함하는 Interactors는 오직 Entities에만 의존하며, Entities는 직·간접적으로 모든 컴포넌트들에게 보호받는다. 그 이유는 상위 레벨로 갈수록 어떠한 변경에도 영향을 받아서는 안 되기 때문이다. 

 

 

6  클래스

 객체지향 설계에서 클래스가 정해지는 단계는 후반부다. s/w개발을 연극으로 비유하면, 클래스는 배우다. 각본 단계에서는 배역(CRC카드)만 선정한다. 코드가 극본이라면, 배우는 구현단계(연기) 바로 전의 단계에서 정해진다. 물론, 작가가 생각한 배우가 있을 수 있지만 시나리오(설계목표)-극본(설계) 단계에서 실제 배우가 누가 될 지는 아무도 알 수 없다. 하나의 배역을 여러 배우가 연기할 수 있는 것처럼 동일한 역할을 수행하는 하나 이상의 객체들이 항상 존재할 수 있다. 즉, 객체지향 설계(협력관점)에서 동일한 역할을 수행하는 객체들은 서로 대체 가능하다는 것을 의미한다. 객체는 여러 역할을 수행할 수 있지만 특정한 협력 관계에서는 일시적으로 오직 하나의 역할만이 보여진다. 그리고 그것이 바로 클래스다.

 

아키텍처 경계 이후의 클래스 설정

 

 객체지향 설계에서 클래스는 핵심이 아니다. 상태와 행동을 갖고 있는 클래스는 객체의 일부이며, 특정한 협력과 의존관계가 정립된 이후에야 모습을 드러내는 배우다. 따라서 책임주도 설계에서 클래스를 먼저 설정해서는 안 되며, 만일 데이터 or 클래스가 먼저 정해질 경우, s/w는 마치 유명배우에 휘둘려 정체성을 잃어버린 영화나 연극처럼 된다. 

 

 

7  결합도 - 응집성 - 디자인패턴

 디자인패턴은 프로그래머의 경험과 직감을 건너뛸 수 있는 지름길이다. 능숙한 아키텍트라면 디자인패턴에 의존하지 않겠지만 초보 개발자 입장에서 디자인패턴은 고수의 경험과 직감을 빠르게 구현할 수 있는 길잡이다. 하지만 아키텍트를 지향하는 프로그래머가 항상 디자인패턴에만 의존해서는 성장할 수 없다. 메시지를 따라서 객체들의 협력관계를 구축하고, 자율적인 역할에 따라 책임을 부과하며, 상-하위 정책간의 의존성 관계를 설정하는 모든 과정이 결국 디자인패턴이다.

 

 디자인패턴을 적용하는 시간은 마지막에 있다. 만일, 협력-역할-책임-의존성이 확실하다면 굳이 디자인 패턴을 적용할 필요가 없다. 디자인패턴을 적용하는 이유는 결합도와 응집성의 최적 상관관계에 있다. 따라서 설계의 결합도-응집성 비율이 괜찮다면 억지로 디자인패턴을 적용할 필요가 없는 것이다.

 

 

8  구현

 구현은 코딩이다. 코딩은 주로 세부적인 사항과 연관되어 있다. 따라서 설계 수준에서 가장 바깥쪽에 위치해 있으며, 해당 설계에 잘 맞는 데이터베이스, 라이브러리, 컴퓨터 언어를 사용하고, 최대한 효율적인 방법을 사용하면 된다.

 

 

 

 

댓글

최신글 전체

이미지
제목
글쓴이
등록일