# Chapter18. 반응형 아키텍처와 어니언 아키텍처


# 1. 반응형 아키텍처와 어니언 아키텍처 개요

아키텍처 내용
반응형 아키텍처
  • 순차적 액션 단계에 사용하고, 코드에 나타난 순차적 액션의 순서를 뒤집는다.
  • 효과와 그 효과에 대한 원인을 분리해서 코드에 복잡하게 꼬인 부분을 풀 수 있다.
  • 반응형 아키텍처는 원인과 효과의 중심을 관리한다.
어니언 아키텍처
  • 서비스의 모든 단계에서 사용하며 웹 서비스나 온도 조절 장치 같은 현실 세계와 상호작용하기 위한 서비스 구조를 만든다.
  • 함수형 사고를 적용한다면 자연스럽게 쓸 수 있는 아키텍처이다.

# 2. 반응형 아키텍처

  • 애플리케이션을 구조화하는 방법으로 핵심 원칙은 이벤트에 대한 반응으로 일어날 일을 지정하는 것이다.
  • 웹 서비스와 UI에 잘 어울리는데 웹 서비스는 웹 요청 응답에 일어날 일을 지정하고, UI는 버튼 클릭과 같은 이벤트 응답에 일어날 일을 지정하면 된다.
    • 이런 것을 일반적으로 이벤트 핸들러라고 한다.
  • 반응형 아키텍처는 코드에 나타난 순차적 액션의 순서를 뒤집는다.
    • X를 하고 Y 하는 대신, X가 일어나면 언제나 Y를 한다.
    • 이렇게 하면 코드를 읽기 쉽고 유지보수하기도 좋다.
  • 반응형 아키텍처가 코드에 주는 중요한 영향
    • 원인과 효과가 결합되 것을 분리한다.
    • 여러 단계를 파이프라인으로 처리한다.
    • 타임라인이 유연해진다.

# 3. 반응형 아키텍처 예시

# (1) ValueCell -> 액션!

  • ValueCellupdate() 메서드 사용시 현재 값을 항상 올바르게 유지할 수 있는 이유
    • update()를 사용할 때 계산을 넘기기 때문이다.
    • 계산은 현재 값을 받아 새로운 값을 리턴한다.
    • 현재 값이 도메인 상에서 올바른 값이 계산이 항상 올바른 값을 리턴한다면 이 메서드는 올바른 값으로 새로운 값을 계산하기 때문에 항상 올바른 값을 유지할 것이다.
  • ValueCell다른 타임라인에서 읽거나 쓰는 순서를 보장하지 않는다.
    • 하지만 어떤 값이 저장되어도 그 값이 항상 올바른 값이라는 것은 보장한다.
  • ValueCell을 일관되게 유지하기 위한 전제조건
    • 올바른 값으로 초기화한다.
    • update() 메서드에는 계산을 전달한다. (절대로 액션을 전달하지 말 것)
    • 계산은 올바른 값이 주어졌다면 올바른 값을 리턴해야한다.
function ValueCell(initialValue) {
  var currentValue = initialValue; // 변경 불가능한 값(컬렉션이 될 수 있다)을 하나 담아 둔다.
  var watchers = []; // 감시자 목록을 저장
  return {
    val: function() { // 현재의 값을 가져온다.
      return currentValue;
    },
    update: function(f) { // 현재 값에 함수를 적용해 값을 바꾼다.(교체 패턴)
      var oldValue = currentValue;
      var newValue = f(oldValue);
      if (oldValue !== newValue) { // 값이 바뀔 때
        currentValue = newValue;
        forEach(watchers, function(watcher) { // 모든 감시자를 실행
          watcher(newValue);
        });
      }
    },
    addWatcher: function(f) { // 새로운 감시자를 추가
      watchers.push(f);
    }
  }
}

# (2) FormulaCell -> 액션!

  • FormulaCell로 이미 있는 셀에서 파생한 셀을 만들 수 있다.
  • 다른 셀의 변화가 감지되면 값을 다시 계산한다.
function FormulaCell(upstreamCell, f) {
  var myCell = ValueCell(f(upstreamCell.val())); // ValueCell을 재사용
  upstreamCell.addWatcher(function(newUpstreamValue) { // 셀 값을 다시 계산하기 위해서 감시자를 추가
    myCell.update(function(currentValue) {
      return f(newUpstreamValue);
    });
  });
  return { // val()과 addWatcher()를 myCell에 위임
    val: myCell.val,
    addWatcher: myCdell.addWatcher, // FormulaCell 값은 직접 바꿀 수 없다.
  };
}

var shipping_cart = ValueCell({});
var cart_total = FormulaCell(shopping_cart, calc_total); // shopping_cart가 바뀔 때 cart_total도 바뀐다.
  • FormulaCell은 값을 직접 바꿀 수 없다.
    • 감시하던 상위(upstream) 셀 값이 바뀌면 FormulaCell 값이 바뀐다.
    • 상위 셀이 바뀌면 상위 값을 가지고 셀 값을 다시 계산한다.
  • FormulaCell에는 값을 바꾸는 기능은 없지만 FormulaCell을 감시할 수 있다.

# 4. 반응형 아키텍처가 주는 영향

# (1) 원인과 효과가 결합한 것을 분리한다.

  • 결합의 분리는 원인과 효과의 중심을 관리한다.
  • 원인이나 효과가 늘어나면 관리해야 할 것도 늘어난다.
    • 원인과 효과의 중심을 잘 관리해서 관리해야 할 것이 빠르게 늘어나지 않도록 해야 한다.
  • 관리해야 할 것이 늘어나는 문제를 곱셈에서 덧셈으로 바꿀 수 있다.
    • 5개의 원인과 4개의 결과가 있기 때문에 5 X 4 대신 5 + 4로 만들 수 있다.
  • 효과를 추가해도 원인을 고치지 않아도 되고, 원인을 추가해도 효과를 고치지 않아도 되기 때문에 관리해야 할 것은 하나만 늘어난다.
  • 비슷한 상황으로 DOM을 갱신하는 곳에서는 DOM을 갱신하는 것만 신경 쓰면 된다.
  • 주의할 점은 문제가 없는데 이 방법으로 분리하는 것은 좋지 않다.
    • 코드에 액션을 순서대로 표현하는 것이 더 명확한 경우도 있다.
    • 장바구니처럼 원인과 효과의 중심이 없다면 분리하지 않는 것이 좋다.

# (2) 여러 단계를 파이프라인으로 처리한다.

  • 반응형 아키텍처도 간단한 액션과 계산을 조합해 복잡한 동작을 만들 수 있다.
    • 조합된 액션은 파이프라인과 같다.
    • 데이터가 파이프라인으로 들어가 각 단계에서 처리된다.
    • 파이프라인은 작은 액션과 계산을 조합한 하나의 액션이라고 볼 수 있다.
  • 어떤 일이 발생하는 여러 단계가 있다면 파이프라인으로 처리하는 것이 좋다.
    • 각 단계에서 생성된 데이터는 다음 단계의 입력값으로 사용될 것이다.
  • 자바스크립트의 Promise 패턴, RxJS 등 여러 가지 구현체가 있다.
  • 만약 여러 단계가 있지만 데이터를 전달하지 않는다면 이 패턴을 사용하지 않는 것이 좋다.
    • 데이터를 전달하지 않으면 파이프라인이라고 볼 수 없다.
    • 따라서 올바른 반응형 아키텍처가 될 수 없다.

# (3) 타임라인이 유연해진다

  • 순서를 정의하는 방법을 뒤집기 때문에 자연스럽게 타임라인이 작은 부분으로 분리된다.
  • 기존에 짧은 타임라인이 좋은 것이라고 했지만 타임라인이 많아지는 것도 좋지 않다.
    • 하지만 타임라인이 많아도 문제가 없는 경우가 있다.
    • 공유하는 자원이 없으면 타임라인이 많아져도 문제가 없다.

# 5. 어니언 아키텍처

  • 어니언 아키텍처는 현실 세계와 상호작용하기 위한 서비스 구조를 만드는 방법이다.
    • 이름에서 알 수 있듯이 둥글게 겹겹이 쌓인 양파 모양을 하고 있다.
  • 어니언 아키텍처 계층 구분
계층 설명
인터랙션 계층 바깥세상에서 영향을 주거나 받는 액션으로 도메인 계층과 액션을 사용하는 것을 조율한다.
도메인 계층 도메인 로직과 비즈니스 규칙을 주로 정의하고 대부분 계산으로 구성된다.
언어 계층 소프트웨어를 만들 수 있는 언어 유틸리티와 라이브러리로 되어 있다.
  • 어니언 아키텍처는 특정 계층이 꼭 필요하다고 강제하지 않는다.
    • 하지만 많은 경우에 위와 같이 3가지 큰 분류로 나눌 수 있다.
  • 어니언 아키텍처의 중요 규칙
    • 현실 세계와 상호작용은 인터랙션 계층에서 해야 한다.
    • 계층에서 호출하는 방향은 중심 방향이다.
    • 계층은 외부에 어떤 계층이 있는지 모른다.


# ✔️ 전통적인 계층형 아키텍처

  • 전통적인 아키텍처로 웹 API를 만들 때 계층(layer)이라고 하는 개념을 사용한다.
  • 하지만 어니언 아키텍처의 계층과는 다르다.
계층 설명
웹 인터페이스 계층 웹 요청을 도메인으로 바꾸고 도메인을 웹 응답으로 바꾼다.
도메인 계층 애플리케이션 핵심 로직으로 도메인 개념에 DB 쿼리나 명령이 들어간다.
데이터베이스 계층 시간에 따라 바뀌는 정보를 저장한다.
  • 전통적인 계층형 아키텍처는 데이터베이스를 기반으로 한다.
    • 도메인 계층은 데이터베이스 동작으로 만든다.
    • 그리고 웹 인터페이스는 웹 요청을 도메인 동작으로 변환한다.
  • 데이터베이스 계층이 가장 아래 있다면 그 위에 있는 모든 것이 액션이 되기 때문에 함수형 스타일이 아니다.
    • 모든 것이 계층에 쌓여있고 계산은 따로 관리되지 않고 우연히 사용된다.
    • 함수형 아키텍처는 계산과 액션에 대한 명확한 규칙이 있어야 한다.


# 6. 어니언 아키텍처 이모저모

# (1) 변경과 재사용이 쉬워야 한다

  • 어니언 아키텍처는 데이터베이스나 API 호출과 같은 외부 서비스(인터랙션 계층)를 바꾸기 쉽다.
    • 가장 높은 계층에서 사용하기 때문이다.
  • 도메인 계층은 외부 서비스에 의존하지 않아서 테스트하기 좋고 재사용하기 좋다.
  • 어니언 아키텍처는 좋은 인프라보다 좋은 도메인을 강조한다.
  • 전형적인 아키텍처에서 도메인 규칙은 데이터베이스를 부르지만 어니언 아키텍처에서는 그렇게 하면 안 된다.

# (2) 도메인 규칙은 도메인 용어를 사용한다

  • 도메인 규칙은 도메인 용어를 사용한다.
    • 도메인 규칙에 속하는지 인터랙션 계층에 속하는지 판단하려면 코드에서 사용하는 용어를 보면 된다.
  • 도메인 규칙에는 제품, 이미지, 가격, 할인과 같은 용어를 사용한다.
    • 데이터베이스, AJAX 요청들은 도메인을 나타내는 용어가 아니다.

# (3) 가독성을 따져 봐야 한다

  • 가독성을 결정하는 요소
    • 사용하는 언어
    • 사용하는 라이브러리
    • 레거시 코드와 코드 스타일
    • 개발자들의 습관
  • 코드의 가독성
    • 일반적으로 함수형 코드는 읽기 좋다.
    • 하지만 함수형 코드가 아닌 코드가 더 명확한 경우도 있다.
    • 이때 얼마나 더 명확해지는지 봐야 한다.
    • 도메인 계층을 계산으로 만들어 인터랙션 계층과 분리하면서 읽기 좋은 코드를 만들려고 노력해야 한다.
  • 개발 속도
    • 비즈니스 이유로 기능을 빨리 출시해야 하는 경우도 있다.
    • 일반적으로 업무가 밀려오는 것은 좋지 않지만 이런 경우 많은 것을 타협해야 하기 때문이다.
    • 이런 경우에도 나중에 아키텍처에 맞춰 코드를 정리할 준비를 하는 것이 좋다.
  • 시스템 성능
    • 변경 가능한 데이터 구조는 불변 데이터 구조보다 빠르다.
    • 성능 개선과 도메인을 계산으로 만드는 것은 따로 생각하는 것이 좋다.
    • 최적화는 인터랙션 계층에서 하고 도메인 계층은 재사용 가능한 계산으로 만드는 것이다.