Software Architecture

소프트웨어 아키텍처는 구성 요소 및 그들 간의 디펜던시(dependency, 의존관계)로 엮인 고수준의 구조물
마이크로서비스 패턴, 그리스리처드슨 저, 이일웅 역

아키텍처가 중요한 이유는 소프트웨어의 품질 속성, 즉 '~성'으로 기술되는 지표가 아키텍처에 의해서 결정되기 때문이다. 즉, 애플리케이션에서 필요로 하는 기능들, 즉 계산기에서 4칙 연산을 처리한다던가, 예측 알고리즘에서 95%의 정확도로 예측을 성공해 낸다던가 하는 것들은 어떠한 아키텍처를 갖더라도 구현이 가능할 수 있다. 그러나 4칙 연산에 사인, 코사인 계산을 추가하기 위한 확장성이나, 95%의 정확도 예측을 일 1회에서 매 시간단위로 확대하는 성능확장성이라던가, 매 배포때마다 수행하는 검증을 최소화하는 테스트성이라던가, 배포 시마다 수시간 단위의 서비스 중단을 경험해야 하는 배포성 등의 문제들은 기능 자체 보다는 아키텍처에 의해서 결정되는 부분들이라고 볼 수 있다. 마이크로서비스 아키텍처는 관리성, 테스트성, 배포성 등의 '~성' 지표들이 높은 성과를 낼 수 있는 애플리케이션을 구축하기 위한 아키텍처 스타일이다. (즉, 관리성, 테스트성, 배포성 등이 중요하지 않은 애플리케이션이라면 그냥 모놀리식으로 만드는 것이 합리적일 수도 있다는 뜻이다!)

소프트웨어 아키텍처의 중요성

컴퓨팅 시스템의 소프트웨어 아키텍처는 소프트웨어 엘리먼트(element)와 그들 간의 관계, 그리고 이 둘의 속성(property)으로 구성된 시스템을 추론하는데 필요한 구조(structure)의 집합이다.
소프트웨어 아키텍처 문서화(Documenting Software Architectures), Len Bass 외

결국, 애플리케이션은 구성 요소(element)의 묶음(혹은 분해)와 구성 요소간의 관계, 그리고 그 관계를 정의하는 속성들로 되어 있다는 의미이다. 이를 통해 다음 두가지 이득을 얻을 수 있다.

  • 업무와 지식의 분리
  • 소프트웨어 구성요소(elements)간의 상호 작용 확인

The 4+1 view model describes an application’s architecture using four views, along with scenarios that show how the elements within each view collaborate to handle requests.

이러한 아키텍처를 바라보는데 사용하는 방법으로 4+1 view 모델을 사용할 수 있다. 4+1 모델은 소프트웨어 아키텍처를 바라보는 4 + 1개의 관점을 제공해 주며, 각 관점에 따른 구성 요소(elements)와 관계 정보를 나타낸다. 여기서 +1 관점인 시나리오는, 고객의 요구사항을 달성하기 위하여 시스템이 상호 작용(입력, 출력 관계 등)을 의미하는 것으로 각 뷰에 따른 구성 요소가 통합적으로 동작해야 달성 가능한 내용으로, 결국 시나리오가 나머지 4 관점을 통해서 보기 때문에 별도로 분리하여 기술한다.

이해당사자관점설명
논리 뷰(logical view)분석가, 설계자 등시스템 구조개발자가 작성한 소프트웨어 엘리먼트, 클래스 등으로, 상속(inheritance), 연관(association), 의존(depends) 등의 관계
구현 뷰(implementation view)개발자클래스, 패키지 등의 요소빌드 결과물로, 모듈과 컴포넌트(하나 이상의 모듈로 구성되어 실행/배포가 가능한 단위)로 구성된 결과물. Java의 경우 JAR 또는 WAR 형태로 패키징된 결과물이며, 각 모듈간 의존성도 포함
프로세스 뷰(process view)시스템 통합자, 운영자 등성능, 확장성 등런타임 컴포넌트로, 각 엘리먼트가 개별 프로세스로 동작하며, IPC(Inter Process Communiation)은 프로세스간의 관계
배포 뷰(deployment view)시스템 엔지니어 등형상, 배포 방식 등프로세스가 머신에 매핑되는 방법으로, 엘리먼트는 HW인프라(VM 등) 및 프로세스가 되며, 엘리먼트간의 관계는 네트워킹으로 기술. 각 프로세스가 HW에 매핑되는 관계도 포함
시나리오(scenario)최종 사용자고객 요구 기능+1 view에 해당하는 관점으로, 일반적으로 유스케이스 뷰(usecase view)라고도 말하며, 시나리오가 특정 뷰에서 각 뷰의 요소들이 동작하여 업무를 달성하는지를 나태느는 뷰, end user의 입장에서 필요한 요구사항 및 각 기능에 대한 내용 기술

아키텍처 스타일

계층화 아키텍처 스타일

N계층 아키텍처는 애플리케이션을 논리적 레이어와 물리적 계층으로 나누어 바라보는 스타일이다. 아래 그림은 MS Azure 페이지에서 가져온 그림으로 Client 가 WEB/Middle/Data tier로 연동하는 그림을 보여준다. 일반적으로 3계층 아키텍처 스타일을 많이 사용하고 있으며 필요에 따라 중간에 계층이 추가되는 형태로 주로 사용된다.

  • 표현계층(presentation layer): 사용자 인터페이스 또는 외부 API가 구현된 계층
  • 비즈니스 로직 계층(business logic layer): 비즈니스 로직이 구현된 계층
  • 영속화(데이터) 계층(persistence layer): DB등 데이터가 저장되는 계층
    이러한 3계층은 최근까지 많이 사용되고 있으나, 계층을 충분히 표시하기 어렵고, 상호간의 의존성을 표시하는데 어려움이 있는 단점이 있다.
    출처: https://learn.microsoft.com/ko-kr/azure/architecture/guide/architecture-styles/n-tier

육각형 아키텍처 스타일

육각형 아키텍처는 애플리케이션의 표현계층을 대신하여, 비즈니스 로직을 호출하는 외부 요청 처리용 인바운드 어댑터와, 영속화 계층 대신 비즈니스 로직에 의해 호출되는 외부 애플리케이션이나 DB를 처리하는 아웃바운드 어댑터로 구성되는 아키텍처 스타일이다. 이러한 육각형 아키텍처 스타일은 비즈니스 로직이 어댑터에 전혀 의존하지 않는 것이 특장점이다.

An example of a hexagonal architecture, which consists of the business logic and one or more adapters that communicate with external systems. The business logic has one or more ports. Inbound adapters, which handled requests from external systems, invoke an inbound port. An outbound adapter implements an outbound port, and invokes an external system.

비즈니스 로직에는 하나 이상의 포트가 있으며, 이 포트는 비즈니스 로직이 외부세계와 상호 작용하는 방법이 정의된 작업(operation)이다. 포트는 입력(inbound)과 출력(outbound)의 방향성을 가질 수 있으며, 입력 포트는 비즈니스 로직이 외부에 공개하는 API형태로, 외부 애플리케이션이 이 inbound 포트를 통해 애플리케이션에 요청을 하게 되는 공개(public) 메서드가 된다. 반면에 출력 포트는 비즈니스 로직이 외부에 오픈된 애플리케이션(DB 혹은 다른 애플리캐이션의 API 등)을 호출하는 역할을 수행한다.

어댑터는 비즈니스 로직을 감싸고 있으면서 포트와 유사하게 입력/출력으로 각자 동작한다. 예를 들어, 외부에서 들어오는 요청을 비즈니스 로직이 받아들이기 위한 API의 Rest endpoint, 메시지 브로커에서 메시지를 수신하기 위한 consumer나, 비즈니스로직이 DB 접근을 위한 데이터 접근 객체(Data Access Object - DAO)나, 원격 애플리케이션의 서비스를 호출하는 proxy 클래스, 혹은 메시지 브로커에 메시지를 발행하는 등의 어댑터로 구성된다.

육각형 아키텍처 스타일의 가장 큰 장점은 비즈니스 로직에 있던 표현/데이터 접근 로직이 어댑터와 분리되어 의존하지 않는다는 점으로 비즈니스 로직만 별도로 분리하여 개발, 테스트, 배포하는 등의 독립적 구현 상태를 반영하기 유리하다. 예를 든 육각형 아키텍처 스타일이나, 계층별 아키텍처 스타일 모두 3계층으로 논리뷰를 구성하고, 구성 요소를 정의했으며, 관계를 정의한 것은 비슷하지만, 육각형 아키텍처가 어댑터를 사용한 표현이 가능하기에 보다 로직과 관계를 명확하게 표시할 수 있게 된다.

아키텍처 스타일 패턴

모놀리식 아키텍처는 구현 뷰를 단일 컴포넌트(하나의 실행 파일 또는 WAR 파일)로 구성한 아키텍처 스타일을 의미한다. 육각형 아키텍처로 구성한 논리 뷰를 가질 수 있다.

패턴: 모놀리식 아키텍처
애플리캐이션을 실행/배포 가능한 단일 컴포넌트로 구성한다.

마이크로서비스 아키텍처는 구현 뷰를 다수의 컴포넌트(여러 실행 파일이나 WAR 파일)로 구성한 아키텍처 스타일을 의미힌다. 컴포넌트는 개별 서비스로 매핑할 수 있으며, 각 컴포넌트 별 잔체 논리뷰에 따른 아키텍처를 가지고 있을 수 있고, 커넥터는 각 컴포넌트 혹은 서비스간의 통신 프로토콜을 의미하며 이러한 커넥터와 local(private) 데이터를 이용하여 느슨하게 결합하여 서비스가 운영된다.

패턴: 마이크로서비스 아키텍처
애플리캐이션을 느슨하게 결합된, 독립적으로 배포 가능한 여러 서비스로 구성한다.

서비스는 어떤 기능이 구현되어 단독 배포가 가능한 소프트웨어 컴포넌트로 정의되며, 클라이언트로부터 애플리캐이션의 기능(functionalities)에 접근할 수 있도록 API를 제공하는 형태이다. Command, Query의 형태로 (HTTP POST/PUT/DELETE 또는 GET 등) 나누어서 볼 수 있으며, 서비스가 이벤트(오더가 생성되었음, 오더가 수락되었음 등)를 발행하기도 한다.
서비스 API는 기능의 내부의 상세 로직, 구현 등은 감추고 필요한 입력/출력 등만 보이도록 하는 캡슐화를 수행한다. 모놀리스 방식으로 개발이 진행되는 경우 다른 클래스를 상속하는 등의 절차로 직접 접근이 가능할 수 있었으나, 마이크로서비스 방식에서는 개별 서비스가 컴포넌트로 분리되어 있기 때문에 API를 우회하여 직접 클래스 등에 접근하는 코드는 작성이 불가능하게 된다.

A service has an API that encapsulate the implementation. The API defines operations, which are invoked by clients. There are two types of operations: commands update data, and queries retrieve data. When its data changes, a service publishes events that clients can subscribe to.

느슨하게 결합된 서비스는 API가 유지되는 상태에서 내부 구현 코드를 변경 가능하다. 따라서 느슨하게 결합된 서비스는 유지보수성, 테스트성(~성, ~lities)를 달성하게 해 준다. 더욱이 직접DB를 접근하는 일을 수행하지 않으며 클래스의 필드 같은 서비스의 영속적 데이터는 private하게 유지해야 서비스의 DB 스키마 등을 변경할때 해당 데이터를 참조하는 서비스에 독립적으로 개발이 가능하게 된다. 또한 DB를 공유하지 않기 때문에 DB Lock을 획득하기 위한 블로킹, 경쟁 등이 불필요해지는 장점이 있다. 그럼에도 불구하고, 각 서비스가 관리하는 private(local) DB의 정합성을 유지하는 것은 더 큰 복잡한 문제를 야기하게 되며, 이 내용은 SAGA 패턴 등을 사용하여 구현하게 된다.

마이크로서비스 아키텍처에서도 공유 라이브러리 사용에 대하여는 주의가 필요하다. 코드의 중복성은 막아줄 수 있으나, 의도치않은 서비스간 결합이 발생하게 될 수 있다. 따라서 매우 일반적으로 사용되는 클래스 들은 공유 라이브러리로 구성하는 것이 필요하지만, 어느 수준까지 공유 라이브러리로 구현할 것인지는 충분한 검토가 필요하다.

마이크로서비스 아키텍처 정의

애플리캐이션의 아키텍처는 다음의 3단계 절차를 따라 설계/정의 할 수 있다.

  1. 시스템 작업 식별: 시스템 작업은 애플리케이션이 처리하는 요청을 추상화 하는 것으로, 사용자의 기능 요건 사항들에 대하여 애플리캐이션이 어떤 업무를 처리해야 하는지에 대한 내용을 작성한다.
  2. 서비스 식별: 시스템 작업을 식별한 이후에는 유사한 업무와 상이한 업무를 분해하는 것으로, DDD의 하위도메인 별 분리 등이 하나의 식별 방법이다. 최종 결과는 비즈니스 적인 요건(오더를 만들수 있어야하며, 오더는 오더 도메인과, 식당 도메인, 배달 도메인에서 공통으로 처리해야 한다 등)을 정의한다.
  3. 서비스 API 및 협동 정의: 서비스가 분리되었다면 각 서비스별로 API를 정의하고 이벤트의 흐름 등도 정의한다. 따라서 각 서비스간 IPC에 대한 표현이 필요하게 된다.

A three-step process for defining an application’s microservice architecture

이처럼 서비스를 분해하는 과정을 따르다보면, 모놀리식 아키텍처에 대비하여 네트워크 지연, 비동기 통신으로 인한 가용성, 데이터 일관성 확보 및 만능 클래스(god class)에 대한 의존 등 다양한 현안들이 발생하게 된다.

시스템 작업 식별

시스템 작업은 2단계 프로세스를 기반으로 정의가 가능하다.

  1. 고수준 도메인 모델 생성: 시스템 작업을 식별, 기술하기 위해 필요한 vocabulary(핵심 클래스) 도출
  2. 도메인 모델 정의: 시스템 작업 식별 후 도메인의 동작을 기술

System operations are derived from the appliation's requirements using a two-step process. The first step is to create a high-level domain model. The second step is to define the system operations, which are defined in terms of the domain model.

도메인 모델 생성

고수준의 도메인 모델은 주로 스토리의 명사에서 도출하며, 시스템 작업은 주로 동사에서 도출된다. 하나 이상의 도메인의 객체와, 객체간의 관계로 기술하며, 도메인 모델을 생성, 수정, 삭제하거나 관계를 생성, 삭제 할 수 있는 형태이다. 각 서비스는 자체 도메인 모델을 소유하고, 동작을 기술하는데 필요한 용어가 되는 핵심 클래스를 정의하게 된다.

  • 주문하기 스토리
    • 전제
      • 소비자
      • 음식점
      • 음심점은 소비자의 주문을 받아 배달해야 한다
      • 주문 총액은 음식점이 지정한 최소 주문 금액 이상이어야 한다.
    • 조건
      • 소비자가 음식점에 주문한다.
    • 결과
      • 소비자의 신용카드를 승인한다
      • 주문 상태를 변경한다 (CREATED -> PENDING_ACCEPTANCE)
      • 주문과 소비자의 관계 생성
      • 주문과 음식점과의 관계 생성
  • 주문 접수 스토리
    • 전제
      • 현재 주문은 PENDING_ACCEPTANCE 상태
      • 배달 가능 상태
    • 조건
      • 주문을 접수한 음식점에서 음식을 만들 수 있는 시간 한정
    • 결과
      • 주문 상태는 ACCEPTED로 변경
      • 주문의 조리 완료 예상 시점을 변경(음식점에서 지정)
      • 배달원 배정

위 작업을 통해, 소비자와, 음식점, 그리고 주문, 신용카드, 배달, 배달원 등의 클래스의 필요가 도출된다. 각 스토리를 유시하게 반복해서 진행하다보면, 다음과 같은 클래스 다이어그램을 도출하게 된다.

The key classes in the FTGO domain model

시스템 작업 정의

각 클래스가 도출되었다면 애플리캐이션은 클래스가 어떤 요청을 처리할지 정의해야 한다. RESTful API를 사용한다고 하면, Command(데이터의 생성, 수정, 삭제)와 Query(데이터 조회)에 대하여 시스템 작업을 추상화해야 한다. 앞서 정의한 스토리, 시나리오에서 필요한 동사로 기술된 항목들에 대해서 분석이 필요하다.

수행자스토리Command설명
소비자주문 생성createOrder()주문을 생성한다. 생성할때 주문에 line item도 필요하다
음식점주문 접수acceptOrder()주문이 접수되엇고, 주방에서 주문에 맞는 음식을 조리한다.
음식점주문 준비완료noteOrderReadyForPickup()접수된 주문이 픽업 가능한 상태로 완료됨을 통보 -> 통보 받는 주체는 배달원이 될 수 있음
배달원위치 업데이트noteUpdateLocation()현재 위치를 업데이트
배달원배달 픽업noteDeliveryPickedUp()배달원이 주문/준비된 음식을 배달을 위해 픽업 완료
배달원주문 배달 완료noteDeliveryDelivered()배달원이 픽업한 음식을 배달 완료

서비스 정의

이러한 작업들을 통해서 FTGO 의 애플리케이션은 클래스를 정의하고 상호간 연관 관계를 정의하게 되면, 해당 겨로가를 기준으로 비즈니스에 따른 분해가 필요하다. 아래 그림은 비즈니스 능력을 기준으로 서비스로 분해한 예를 보여준다.

Mapping FTGO business capabilities to services. Capabilities at various levels of the capability hierarchy are mapped to services.

분해 지침

패턴: 하위도메인에 따라 분배
DDD 하위 도메인별로 서비스를 정의한다.

From subdomains to services: each subdomain of the FTGO application domain is mapped to a service, which has its own domain model.

DDD에 따른 분해 패턴은 그 자체로 학습이 필요한 주제이므로, 여기서는 DDD 기반으로 서비스를 분해하는데 필요한 지침들만 추가로 확인하고 넘어가자.

단일 책임 원칙

클래스는 오직 하나의 변경 사유를 가져야 한다.
로버트 C. 마틴

클래스와 마찬가지로 마이크로서비스도 하나의 책임만을 가진 작고 응집된 서비스로 정의해야 한다.

공동 폐쇄 원칙(Common Closure Principle)

패키지의 클래스들은 동일한 유형의 변경에 대해 닫혀 있어야 한다. 패키지에 영향을 주는 변경은 그 패키지에 속한 모든 클래스에 영향을 끼친다.
로버트 C. 마틴

만약 어떤 두 클래스가 동일한 사유로 인해 동시에 변경이 발생되어야 한다면, 두 클래스는 동일한 패키지에 있어야 한다는 것을 의미하는 것으로, 개발자는 가급적이면 소수의 패키지에 있는 코드만 책임지고 수정할 수 있도록 하는 원칙을 의미힌다. 클래스를 마이크로서비스 단위로 확장해보면, 동일한 사유로 변경되는 컴포넌트를 모두 같은 서비스로 묶는다 로 볼 수 있다.

서비스 분해의 장해물

Chap.1 에서 먼저 살펴보았지만, 저자는 5가지의 인력과 5가지의 척력으로 설명하였다. 여기서는 다음 5가지 내용을 분해의 장애물로 표기하고 있다.

  • 네트워크 지연
  • 동기 통신으로 인한 가용성 저하
  • 여러 서비스에 걸쳐 데이터의 일관성 유지
  • 데이터의 일관된 뷰 확보
  • 분해를 저해하는 만능 클래스

이 중 만능 클래스, 즉 God class는 애플리케이션 곳곳에서 두루 쓰이게 되어 연관관계가 너무 복잡해져서 더이상 분해가 될 수 없는 수준이 된 클래스를 의미힌다. 이런 클래스는 하나의 기능(클래스)가 유사한 업무를 추가적으로 수행하기에 적합하든, 개발 할 조직이 그 곳 뿐이 없었든, 어떤 사유로든 지속적으로 동일 기능에 고도화가 이뤄지고, 연관관계를 갖는 기능들이 늘어나다보니 더이상 손을 쓸 수 없고, 작은 변경도 애플리케이션 전반에 영향을 주게 되는 상황이 되는 클래스를 의미하게 된다. FTGO 애플리케이션의 Order 클래스는 주소, 배달원, 소비자, 식당, 결제 등 모든 영역에 걸쳐서 업무가 이뤄지는 핵심 클래스 혹은 마이크로서비스가 된다. 따라서 이러한 문제를 해결하기 위해서는 도메인 단위로 분리하고, 각 도메인에서 필요한 클래스를 구축하는 형태로 만드는 것이 필요하다. 배달 서비스는 결제나, 식당의 메뉴에는 관심이 없고 필요한 경우 오더의 키 정보를 통해서 해당 정보를 조회하는 것 만으로도 충분할 수 있다.

The Order god class is bloated with numerous responsibilities.

서비스 API 정의

시스템 작업과 서비스를 분해한 이후, 각 서비스별 API를 정의해야 한다. 서비스 API는 마이크로서비스 외부에서 호출하는 작업으로, 서비스 이벤트는 타 서비스와 협동하기 위해 발행되는 형태로 구성된다. 각각의 이벤트를 발생시키거나, 요청을 수락하는 API 들을 식별하고, 매핑하는 것이 필요하나, 본 교재가 DDD 등을 설명하는 내용이 아니다보니 해당 내용에 구체적인 내용들은 부족함이 있다. 따라서 추후 별도로 학습하는 것이 필요하다.

The services, their revised APIs, and their collaborators

profile
다시 시작해보자!

0개의 댓글