본문으로 바로가기
728x90

아키텍처란

시스템이 잘 동작함은 물론 쉽게 이해, 개발, 유지보수 그리고 배포할 수 있게 해주며 시스템 수명주기 비용은 최소화하고 프로그래머 생산성을 최대화할 수 있도록 해야합니다.

소규모 팀에게 지나친 아키텍처가, 규모 있는 팀에서는 잘 설계된 컴포넌트 단위로 분리하지 않는다면 개발에 있어 장애물이 됩니다.

배포도 초기에 고려해야 하는데 자주 놓치는 부분입니다. 초기에 MSA로 결정했지만 배포 당시에 서비스간 연결과 작동 순서를 결정하는 단계에 대해서 고민해보지 않았다면 문제가 됩니다. 물론 유지보수 측면에서 MSA와 같이 컴포넌트를 분리하고 안정화된 인터페이스를 두어 격리하는건 탐사와 위험 부담을 낮추는데 비용을 줄일 수 있는 장점이 있겠지만 상황에 맞는 아키텍처를 하는것이 중요합니다.

따라서 아키텍처는 상황마다 다르고 빠르게 변화하기 때문에 최대한 유연하게 설계해야 합니다. 이를 이해하기 위해 정책과 세부사항을 구분해보면, 다음과 같습니다.

  • 정책 : 모든 업무 규칙과 업무 절차를 구체화
  • 세부사항 : 사람, 외부 시스템, 입출력 장치, 데이터베이스, 웹 시스템, 서버, 프레임워크, 통신 프로토콜

아키텍트의 목표는 시스템에서 정책을 가장 핵심적인 요소로 식별하고, 동시에 세부사항은 정책에 무관할 수 있는 형태의 시스템을 구축하는 데 있기에 세부사항에 관한 결정을 나중으로 미뤄 선택을 넓힐 수 있도록 해야합니다.

 

독립성

좋은 아키텍처는 다음을 지원해야한다.

  • 시스템의 유스케이스 - 행위를 명확히 하고 외부로 드러내며 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있도록 만드는 것이 중요하다.
  • 시스템의 운영 - 각 컴포넌트를 적절히 격리하여 유지하고 컴포넌트 간 통신 방식을 특정 형태로 제한하지 않는다면 시간이 지나 운영에 필요한 요구사항이 바뀌더라도 스레드, 프로세스, 서비스로 구성된 기술 스펙트럼 사이를 전환(모노리틱↔다중 프로세스↔다중 스레드↔마이크로 서비스)하는 일이 쉽도록 해야합니다.
  • 시스템의 개발 - 각 팀이 독립적으로 행동하기 편한 아키텍처를 확보하여 개발하는 동안 팀들이 서로 방해받지 않도록 해야 합니다.
  • 시스템의 배포 - 시스템이 빌드 된후 즉각 배포될 수 있도록 지원해야 합니다.

이를 위해 계층을 독립 시키고 결합을 시키는 과정이 나타나는데 독립에 신경 써야할 부분은, 첫째로 UI, 어플리케이션에 특화된 업무 규칙, 어플리케이션과는 독립적인 업무 규칙, 데이터베이스 등은 서로 다른 측면이고 다른 이유와 속도로 변경되기에 독립적으로 변경할 수 있도록 만들어야만 하는 수평적 계층, 그리고 주문 추가와 주문 삭제는 다른 속도와 이유로 변경되는 수직적 계층인 독립적인 유스케이스입니다.

계층간 결합에 있어 ‘중복’이라고 생각하고 코드를 통합하고 싶은 욕구가 생기는 경우 주의 해야하는데, 진짜 중복인지 다시 한 번 생각하는 태도를 가져야 한다. 시간이 지나며 다른 방향으로 분기될 가능성이 높은 경우 그리고 데이터베이스의 레코드를 뷰모델 없이 UI에 그대로 사용하고 싶다는 유혹과 같은 경우는 주의해야 합니다.

항상 결합과 분리를 쉽게 할 수 있도록 아키텍처를 구성해야하며 상황에 맞게 모노리틱 구조로 시작하다가 마이크로서비스로 가고 다시 모노리틱으로 갈 수 있도록 만들어야 한다.

우아한모노리스에서도 modular-monoliths 내용에 지금 읽고 있는 로버트 C. 마틴(Robert C. Martin)의 저서 클린 아키텍처 언급이 있다.

https://github.com/arawn/building-modular-monoliths-using-spring

 

GitHub - arawn/building-modular-monoliths-using-spring: 스프링을 기반으로 모듈형 모노리스를 만들기 위한 방

스프링을 기반으로 모듈형 모노리스를 만들기 위한 방안을 공유합니다. Contribute to arawn/building-modular-monoliths-using-spring development by creating an account on GitHub.

github.com

[우아한테크세미나] 200123 우아한모노리스 by 박용권님

 

경계: 선긋기

소프트웨어 아키텍처는 선을 긋는 기술이다. 초기에 선을 그어 경계를 만들어 핵심 업무 규칙들을 오염시키지 못하도록 막고 프레임워크, 데이터베이스, 웹 서버, 라이브러리, DI, GUI와 같은 결정들을 뒤로 미룰수 있게 해야한다.

데이터베이스 같은 경우 비지니스 규칙과 꽤나 가깝다고 생각하지만 인터페이스를 두어 결정을 미루면 Business Rule, Database Interface ↔ Database Access, Database 를 경계로 나누어 언제든지 데이터베이스를 파일시스템, MySQL, MongoDB 를 나중에 필요에 맞게 선택할 수 있다.

 

경계해부학

아키텍처에서는 크게 볼 수 있는 첫째는 물리적으로 엄격하게 분리되지 않은 데이터가 단일 프로세서에서 같은 주소 공간을 공유하는 모노리스 구조이다. 객체지향의 다형성을 이용하여 저수준에서 고수준으로 함수를 호출하여 구조를 분리할 수 있다. 컴포넌트 간 통신에 있어서 단순히 함수 호출이라 매우 값싸고 빠르다.

둘째는 아키텍처 경계가 드러나는 배포형 컴포넌트(라이브러리)이다. 이 또한 배포 과정과 런타임 로딩으로 인한 최초 함수 호출 시간에 차이가 날 뿐 모노리스와 동일하게 단순한 함수 호출이라 값싸다.

셋째로 물리적인 형태의 경계로 로컬 프로세스가 있다. 로컬 프로세스는 하나 또는 멀티 코어 프로세스에서 실행되지만 각 독립된 주소에서 실행된다. 이 또한 고수준 컴포넌트로 향하도록 해야하는데 로컬 프로세스를 최상위 컴포넌트로 생각하고 프로세스의 이름, 물리 주소, 레지스트리 조회 키 등을 포함하지 않도록 해야한다. 운영체제 호출, 데이터 마샬링, 프로세스간 문맥 교환의 통신은 엄청 비싸니 신중하게 제한해야 한다.

넷째로 서비스인데 물리적 형태중 가장 강력한 경계이자 물리적 위치에 구애 받지 않고 네트워크를 통해 이뤄져서 함수에 비해 매우 느리다.

 

정책과 수준

동일한 이유로 동일한 시점에 변경되는 정책은 동일한 수준에 위치하고 동일한 컴포넌트에 속해야 한다. 수준은 ‘입력과 출력까지의 거리’ 라고 정의했는데 시스템의 입력과 출력으로 부터 멀리 위치할 수록 높은 수준, 입력과 출력을 다룰수록 낮은 수준이라고 정의한다.

초기에 고수준, 저수준에 관해 이야기가 나왔는데 고수준 정책은 저수준 정책에 비해 덜 빈번하게 변경되고 더 중요한 이유로 변경되어 의존성 방향을 고수준으로 향하게 설계해야 변경의 영향도를 줄일 수 있다는 내용이었다.

예시가 이해에 도움이 잘 됐는데,

function encrypt() {
  while(true)
  writeChar(translate(readChar()))
}

다음과 같은 코드를 작성한적이 있을텐데, 이는 고수준인 encrypt가 저수준인(입력/출력과 가까운) readChar와 writeChar에 의존하는 잘못된 아키텍처이다.