본문 바로가기

엔지니어링(TA, AA, SA)/아키텍처

[아키텍처] LAYERED ARCHITECTURE(계층형 아키텍처)

계층형 아키텍처

[도메인 주도 설계: 소프트웨어의 복잡성을 다루는 지혜]

 

도시 목록에서 화물의 목적지를 선택하는 것과 같이 간단한 사용자 행위를 지원하는 해운 애플리케이션에도 (1)위젯을 화면에 그리고 (2)선택 가능한 모든 도시 목록을 데이터베이스에서 조회하며 (3)사용자가 입력한 내용을 해석하고 유효성을 검증하고 (4)선택된 도시를 화물과 연결하며 (5)변경내역을 데이터베이스에 반영하는 프로그램 코드가 들어있어야 한다. 이러한 모든 코드가 동일한 프로그램의 일부를 차지하더라도 그 중 일부만이 해운 업무와 관련돼 있다.

 

소프트웨어 프로그램에는 갖가지 작업을 수행하는 설계와 코드가 포함된다. 소프트웨어 프로그램은 사용자 입력을 받아들이고 업무 로직을 수행하며, 데이터베이스에 접근하고, 네트워크상으로 통신하며, 사용자에게 정보를 보여주는 등의 일을 수행한다. 따라서 각 프로그램의 기능과 관련된 코드의 양은 상당히 많을 수 있다.

 

객체지향 프로그램에서는 종종 사용자 인터페이스(UI, User Interface)와 데이터베이스, 기타 보조적인 성격의 코드를 비즈니스 객체 안에 직접 작성하기도 한다. 부가적인 업무 로직은 UI 위젯과 데이터베이스 스크립트에 들어간다. 이런 일이 발생하는 까닭은 단기적으로는 이렇게 하는 것이 뭔가를 동작하게 하는 가장 쉬운 방법이기 때문이다.

 

도메인과 관련된 코드가 상당한 양의 도메인과 관련이 없는 다른 코드를 통해 널리 확산될 경우, 도메인에 관련된 코드를 확인하고 추론하기가 굉장히 힘들어진다. UI를 표면적으로 변경하는 것이 실질적으로 업무 로직을 변경하는 것으로 이어질 수 있게된다. 업무 규직을 변경하고자 UI 코드나 데이터베이스 코드, 또는 다른 프로그램 요소를 추적해야 할지도 모른다. 응집력 있고, 모델 주도적인 객체를 구현하는 것이 비현실적인 이야기가 돼버리고 자동화 테스트가 어려워진다. 기술과 로직이 모두 각 활동에 포함돼 있다면 프로그램을 매우 단순하게 유지해야 하며, 그렇지 않으면 프로그램을 이해하기가 불가능해진다.

 

매우 복잡한 작업을 처리하는 소프트웨어를 만들 경우 관심사의 분리가 필요하며, 이로서 격리된 상태에 있는 각 설계 요소에 집중할 수 있게 되는 것이다. 동시에 시스템 내의 정교한 상호작용은 그런 분리와는 상관없이 유지돼야 한다.

 

소프트웨어 시스템을 분리하는 방법은 다양하지만, 경험과 관례에 근거해 산업계에서는 LAYRED ARCHITECTURE, 좀더 구체적으로 몇개의 일반화된 계층이 널리 받아들여지고 있다. 계층화라는 은유는 널리 활용되므로 대다수의 개발자는 이를 직관적으로 받아들인다. 계층화의 핵심 원칙은 한 계층의 모든 요소는 오직 같은 계층에 존재하는 다른 요소나 계층상 "아래"에 위치한 요소에만 의존한다는 것이다. 위로 거슬러 올라가는 의사소통은 반드시 간접적인 메커니즘을 거쳐야 한다.

 

계층화의 가치는 각 계층에서 컴퓨터 프로그램의 특정 측면만을 전문적으로 다룬다는데 있다. 이러한 전문화를 토대로 각 측면에서는 더욱 응집력있는 설계가 가능해지며, 이로써 설계를 훨씬 더 쉽게 이해할 수 있다. 가장 중요한 응집력 있는 설계 측면을 격리하는 계층을 선택하는 것도 매우 중요하다. 경험과 관례를 바탕으로 널리 받아들여지는 계층화가 어느정도 정해졌다. 이러한 계층화는 다양한 모습으로 나타나지만 대다수의 성공적인 아키텍처에서는 아래의 네가지 개념적 계층으로 나뉜다.

 

[사용자 인터페이스]

사용자에게 정보를 보여주고 사용자의 명령을 해석하는 일을 책임진다. 간혹 사람이 아닌 다른 컴퓨터 시스템이 외부 행위자가 되기도 한다.

 

[응용 계층]

소프트웨어가 수행할 작업을 정의하고 표현력 있는 도메인 객체가 문제를 해결하게 한다. 이 계층에서 책임지는 작업은 업무상 중요하거나 다른 시스템의 응용 계층과 상호작용하는데 필요한 것들이다.

이 계층은 얇게 유지된다. 여기에는 업무 규칙이나 지식이 포함되지 않으며, 오직 작업을 조정하고 아래에 위치한 계층에 포함된 도메인 객체의 협력자에게 작업을 위임한다. 응용 계층에서는 업무 상황을 반영하는 상태가 없지만 사용자나 프로그램의 작업에 대한 진행상황을 반영하는 상태를 가질 수는 있다.

 

[도메인 계층]

업무 개념과 업무 상황에 관한 정보, 업무 규칙을 표현하는 일을 책임진다. 이 계층에서는 업무 상황을 반영하는 상태를 제어하고 사용하며, 그와 같은 상태 저장과 관련된 기술적인 세부사항은 인프라스트럭처에 위임한다. 이 계층은 업무용 소프트웨어의 핵심이다.

 

[인프라스트럭처 계층]

상위 계층을 지원하는 일반화된 기술적 기능을 제공한다. 이러한 기능에는 애플리케이션에 대한 메시지 전송, 도메인 영속화, UI의 위젯을 그리는 것등이 있다. 또한 인프라스트럭처 계층은 아키텍처 프레임워크를 통해 네 가지 계층에 대한 상호작용 패턴을 지원할 수도 있다.

 

어떤 프로젝트에서는 사용자 인터페이스와 애플리케이션 계층을 명확히 구분하지 않기도 하며, 여러 개의 인프라스트럭처 계층이 존재하는 프로젝트도 있다. 하지만 MODEL-DRIVEN DESIGN을 가능케 하는 것은 결정적으로 도메인 계층을 분리하는 데 있다.

 


복잡한 프로그램을 여러 계층으로 나눠라. 응집력 있고 오직 아래에 위치한 계층에만 의존하는 각 계층에서 설계를 발전시켜라. 표준 아키텍처 패턴에 따라 상위 계층과의 결합을 느슨하게 유지하라. 도메인 모델과 관련된 코드는 모두 한 계층에 모으고 사용자 인터페이스 코드나 애플리케이션 코드, 인프라스트럭처 코드와 격리하라. 도메인 객체(표현이나 저장, 애플리케이션 작업을 관리하는 등의 책임에서 자유로운)는 도메인 모델을 표현하는 것에만 집중할 수 있다. 이로써 모델은 진화를 거듭해 본질적인 업무 지식을 포착해서 해당 업무 지식이 효과를 발휘할 수 있을 만큼 풍부하고 명확해질 것이다.

 

인프라스트럭처 계층과 사용자 인터페이스 계층에서 도메인 계층을 분리하면 각 계층을 훨씬 더 명료하게 설계할 수 있다. 격리된 계층을 유지하는데 드는 비용은 훨씬 더 적은데, 이는 격리된 계층이 각자 다른 속도로 발전해서 각기 다른 요구에 대처할 것이기 때문이다. 그뿐만 아니라 분산된 시스템에 배포할 때도 이러한 분리가 도움되는데, 통신상의 부하를 최소화하고 성능을 개선하고자 각기 다른 서버나 클라이언트에 각 계층을 유연하게 둘 수 있기때문이다.

 

 


계층 간 관계 설정

계층의 분리와 그러한 분할 방법 가운데 어떤 것이 프로그램의 각 측면, 특히 도메인 계층의 설계를 향상시키는지 집중적으로 살펴보았다. 그럼에도 당연히 각 계층은 서로 연결돼야 한다. 분리의 이점을 잃지 않으면서 각 계층을 서로 연결하는 것이야말로 각종 패턴이 존재하는 이유이다.

 

각 계층은 설계 의존성을 오직 한방향으로만 둬서 느슨하게 결합된다. 상위 계층은 하위 계층의 공개 인터페이스를 호출하고 하위 계층에 대한 참조를 가지며(최소한 임시로라도), 그리고 일반적으로 관례적인 상호작용 수단을 이용해 하위 계층의 구성요소를 직접적으로 사용하거나 조작할 수 있다. 그러나 하위 수준의 객체가 상위 수준의 객체와 소통해야 할 경우에는(직접적인 질의에 응답하는 것 이상으로) 또다른 메커니즘이 필요한데, 이 경우 콜백(callback)이나 OBSERVER(관찰자) 패턴처럼 계층 간에 관계를 맺어주는 아키텍처 패턴을 활용할 수 있다.

 

응용 계층과 도메인 계층에 UI를 연결하는 패턴은 MODEL-VIEW-CONTROLLER(MVC, 모델-뷰-컨트롤러)에서 유래한다. MVC는 과거 1970년대에 스몰토크(smalltalk) 분야에서 발견되어 MVC를 따르는 여러 UI 아키텍처에 영감을 줬다. Fowler에서는 이 주제에 관해 MVC 패턴을 비롯한 몇가지 유용한 변종에 관해 논하였다. Larman에서는 이러한 MODEL-VIEW SEPARATION PATTERN(모델-뷰 분리 패턴)에서의 관심사에 관해 연구했는데, 그가 제안한 APPLICATION COORDINATOR 패턴은 애플리케이션 계층을 연결하는 접근법 가운데 하나이다.

 

UI와 애플리케이션을 연결하는 것과 관련된 다른 유형의 접근법도 있다. 하지만 논의의 목적상 도메인 계층을 격리해서 해당 도메인 객체를 설계할 때 동시에 사용자 인터페이스도 생각할 필요가 없게 만들어준다면 어떤 접근법이라도 괜찮다.

 

보통 인프라스럭처 계층에서는 도메인 계층에서 어떤 활동이 일어나게 하지 않는다. 인프라스트럭처 계층은 도메인 계층의 "아래"에 있으므로 해당 인프라스트럭처 계층이 보조하는 도메인의 구체적인 지식을 가져서는 안된다. 사실 그와 같은 기술적인 기능은 대개 SERVICE(서비스)로 제공된다. 이를테면, 어떤 애플리케이션에서 이메일을 전송해야 한다면 메시지 전송 인터페이스가 인프라스트럭처 계층에 위치할 수 있으며, 애플리케이션 계층의 각 요소는 인프라스트럭처 계층에 메시지 전송을 요청할 수 있다. 이러한 분리는 어느 정도 융통성을 별도로 제공한다. 메시지 전송 인터페이스는 이메일 송신기나 팩스 송신기, 또는 다른 사용할 수 있는 어떤 것에도 연결될 수 있다. 그러나 분리의 주된 이점은 애플리케이션 계층이 단순해져서 애플리케이션 본연의 책임에만 집중하게 되는 것이며, 이로써 메시지를 "언제" 보내는지는 알아도 "어떻게" 보내는지는 알 필요가 없어진다.

 

응용 계층과 도메인 계층에서는 인프라스트럭처 계층에서 제공하는 SERVICE를 요청한다. SERVICE의 범위와 인터페이스를 적절히 선정하고 설계한다면 호출하는 측은 SERVICE 인터페이스에서 캡슐화하는 정교한 행위를 바탕으로 느슨하게 결합되고 단순해질 수 있다.

 

그러나 모든 인프라스트럭처가 상위 계층에서 호출할 수 있는 SERVICE의 형태로 만들어지는 것은 아니다. 어떤 기술적인 구성요소는 다른 계층의 기본적인 기능을 직접적으로 지원하도록 만들어져(이를 테면, 모든 도메인 객체에 대한 추상 기반 클래스를 제공하는 것과 같이) 그러한 계층과 관계를 맺는 메커니즘(MVC 및 그와 비슷한 패턴의 구현과 같은)을 제공하기도 한다. 그러한 "아키텍처 프레임워크"는 프로그램의 다른 요소를 설계하는데 미치는 영향이 훨씬 더 크다.

 


아키텍처 프레임워크

인프라스트럭처가 인터페이스를 통해 호출되는 SERVICE의 형태로 제공된다면 계층화의 동작방식과 각 계층이 느슨하게 결합되는 방식은 상당히 직관적이다. 하지만 일부 기술적인 문제에는 더욱 침습적인 형태의 인프라스트럭처가 필요하다. 수많은 인프라스트럭처의 요구사항을 통합하는 프레임워크는 종종 다른 계층이 매우 특수한 방식으로 구현되기를 요구하는데, 이를테면 프레임워크 클래스의 하위 클래스가 돼야 한다거나 일정한 메서드 서명을 지정해야 한다는 것이 여기에 해당한다(하위 클래스가 그 클래스의 부모 클래스보다 상위 계층에 있는 것이 직관적이지 않아 보일지도 모르지만 염두에 둘 것은 어느 클래스가 다른 클래스에 대한 지식을 더 많이 반영하고 있느냐다). 가장 바람직한 아키텍처 프레임워크라면 도메인 개발자가 모델을 표현하는 것에만 집중하게 해서 복잡한 기술적 난제를 해결한다. 하지만 프레임워크가 방해가 될 수도 있는데, 프레임워크에서 도메인 설계와 관련된 의사결정을 제약하는 가정을 너무 많이 만들어 내거나 구현을 너무 과중하게 만들어 개발을 더디게 하는 경우가 있기 때문이다.

 

일반적으로 어떤 형태로든 아키텍처 프레임워크와 같은 것은 필요하다(간혹 팀에서 고른 프레임워크가 팀에 제대로 된 도움을 주지 못하더라도). 프레임워크를 적용할 때 팀은 프레임워크의 목적에 집중해야 하는데, 그러한 프레임워크의 목적은 도메인 모델을 표현하고 해당 도메인 모델을 이용해 중요한 문제를 해결하는 구현을 만들어내는데 있다. 팀에서는 프레임워크를 이용해 그러한 결과를 만들어 내는 방법을 찾아야 하는데, 그렇다고 해서 프레임워크에서 제공하는 모든 기능을 사용해야 한다는 의미는 아니다.

 

예를 들면, 초창기의 J2EE 애플리케이션에서는 이따금 도메인 객체를 모두 "엔티티 빈"으로 구현하곤 했다. 이러한 접근법은 성능과 개발 속도 면에서 모두 좋지 않은 결과를 초래했다. 반면 오늘날 우수 실천법은 대부분의 업무 로직을 일반 자바 객체로 구현하면서 구성 단위가 큰(larger grain) 객체에 대해서는 J2EE 프레임워크를 사용하는 것이다. 한 프레임워크를 이용해 해결하기 힘든 갖가지 측면은 어려운 문제를 해결하고자 어디서든 통하는 일률적인 해법을 모색하는 것이 아니라 여러 프레임워크를 선택적으로 적용해서 극복할 수 있다. 프레임워크의 가장 유용한 기능만 분별력 있게 적용한다면 구현과 프레임워크 간의 결합이 줄어들어 차후 설계 의사결정을 더욱 유연하게 내릴 수 있을 것이다. 그리고 더 중요한 점은 현재 널리 사용되고 있는 여러 프레임워크가 사용하기에 얼마나 복잡한지 감안하면 이러한 최소주의적인 태도가 비즈니스 객체를 읽기 쉽고 표현력 있게 유지하는데 이바지한다는 것이다.

 

아키텍처 프레임워크를 비롯한 여러 도구는 계속해서 발전을 거듭할 것이다. 새로 나오는 프레임워크는 애플리케이션의 기술적인 측면을 점점 더 자동화하거나 미리 만들어 줄 것이다. 제대로만 된다면 애플리케이션 개발자들은 핵심적인 업무 관련 문제만 모델링하는데 점점 더 많은 시간을 보내게되고 생산성과 품질이 훨씬 더 향상될 것이다. 그러나 이러한 방향으로 나아가더라도 우리는 기술적인 해결책에 대한 열정만큼은 반드시 사수해야 한다. 정교한 프레임워크는 애플리케이션 개발자들을 속박할 수도 있다.