Skip to main content

Command Palette

Search for a command to run...

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

Published
4 min readView as Markdown

반응형 아키텍처 ReactiveArchitecture

  • n x m 문제: 변경에 대한 원인과 효과가 강력하게 결합되어 나타나는 문제

  • 핵심 원칙: 이벤트에 대한 반응으로 일어날 일을 지정하는 것, "이벤트 핸들러"

  • 동작

    • 하려고 하는 것만 처리

    • 모든 것은 어떤 일이 일어나면 그에 대한 응답으로 처리

      • X를 하고 Y를 하는 대신, X가 일어나면 언제나 Y를 함

      • X가 일어나면 Y -> Z -> A..

  • 언제 사용할 수 있을까?

    • 만능 열쇠는 없음, 언제, 어떻게 사용할지는 스스로 판단해야 함

    • 원인과 효과가 결합한 것을 분리해야 할 때

      • 어떤 경우는 분리하면 코드가 더 읽기 어려워짐. 다만 코드가 더 유연하고 하려고 하는 것을 정확하게 표현할 수 있음
    • 여러 단계를 파이프라인으로 처리할 때

    • 순서를 표현하는 방법을 뒤집어, 타임라인을 유연하게 만들 때

상태 모델

  • 변경 가능한 값을 일급 함수로 만들기

    • 반응형 아키텍처로 만든 스프레드시트

    • watcher: observer, listener, event-handler.. 등 유사한 개념

  • ValueCell

      function ValueCell (initialValue) {
          let currentValue = initialValue;
          const watchers = [];
    
          return {
              val() {
                  return currentValue;
              },
    
              /** 교체 패턴 */
              update(f) {
                  const oldValue = currentValue;
                  const newValue = f(oldValue);
    
                  if ( oldValue !== newValue ){
                      currentValue = newValue;
                      watchers.forEach(watcher => watcher(newValue));
                  }
              },
    
              addWatcher (f) {
                  watchers.push(f);
              }
          }
      }
    
    • 장바구니 예제에서 shoppingCart

    • Redux store, Recoil atom

    • Clojure Atom, Elixir Agent, Haskell TVar

    • update에는 계산(순수함수)만 전달

    • 다른 타임라인에서 읽거나 쓰는 순서를 보장하지 않지만,

    • 어떤 값이 저장되어도 그 값이 항상 올바른 값이라는 것은 보장

  • FormulaCell

    • ValueCell에서 파생해 만드는 셀

    • ValueCell의 값이 변경되면 값을 다시 계산

    function FormulaCell(upstreamCell, f) {
        const myCell = ValueCell(f(upstreamCell.val()));
        upstreamCell.addWatcher(newUpstreamValue => {
            myCell.update(currentValue  => f(newUpstreamValue))
        });

        return {
            val: myCell.val,
            addWatcher: myCell.addWatcher
        }
    }
  • 장바구니 예제에서 cartTotal
  • 기존 코드 vs 반응형 아키텍처 비교

    • 기존 코드
    const shoppingCart = {};
    function addItemToCart (name,price) {
        const item = makeCartItem(name, price);
        shoppingCart = addItem(shoppingCart, item);

        const total = calcTotal(shoppingCart);
        setCartTotalDom(total);
        updateShippingIcons(shoppingCart);
        updateTaxDom(total);
    }
  • 반응형 아키텍처
    const shoppingCart = ValueCell({});
    const cartTotal = FormulaCell(shoppingCart, calcTotal);

    function addItemToCart(name, price) {
        const item = makeCartItem(name, price);
        shoppingCart.update(cart => addItem(cart, item));
    }

    shoppingCart.addWatcher(updateShippingIcons);
    cartTotal.addWatcher(setCartTotalDom);
    cartTotal.addWatcher(updateTaxDom);

반응형 아키텍처가 바꾼 시스템

원인-효과가 결합된 것을 분리

  • 원인과 효과의 중심이 무엇인가를 파악 => 장바구니

  • m x n 문제 해결

    • 일반적인 아키텍처: 장바구니 바꾸는 방법 5가지 X 장바구니가 바뀔 때 해야할 액션 4가지 => 20가지

    • 반응형 아키텍처: 장바구니 바꾸는 방법 5가지 + 장바구니가 바뀔 때 해야할 액션 4가지 => 9가지

  • 장바구니처럼 원인과 효과의 중심이 없다면 분리하지 마라

    • 문제가 없는데 이 방법으로 분리하는 것은 좋지 않다

    • 코드에 액션을 순서대로 표현하는 것이 더 명확할 수 있다

여러 단계를 파이프라인으로 처리

  • 파이프라인은 작은 액션과 계산들을 조합한 하나의 액션

    • 데이터가 파이프라인으로 들어가 각 단계에서 처리
  • 어떤 일이 발생하는 여러 단계가 있다면 파이프라인으로 처리하는 것이 좋음

    • 각 단계에서 생성된 데이터는 다음 단계의 입력값

    • 데이터를 전달하지 않는다면 이 패턴을 사용하지 않는 것이 좋음

  • 구현 방식

    • 언어에 맞는 기본형을 사용하는 것이 좋다

    • 반응형 프레임워크, Promise

    • 이벤트 스트림이 필요하다면 ReactivX 라이브러리 활용; RxJS

    • 외부 스트림 서비스가 필요하다면 Kafka, RabbitMQ 등

유연한 타임라인

  • 공유하는 자원이 없으면 타임라인이 많아져도 문제가 없음

  • ValueCell, FormulaCell 은 watcher 호출할 때 현재 값을 넘겨주기 때문에 watcher가 직접 Cell의 값을 읽을 필요가 없음


어니언 아키텍처 OnionArchitecture

  • 서비스 전체를 구성하는데 사용, 반응형 아키텍처보다 더 넓은 범위에 사용

  • 계층

    • 인터랙션 계층 - 바깥 세상에 영향을 주고 받는 액션

    • 도메인 계층 - 비즈니스 규칙을 정의하는 계산

    • 언어 계층 - 언어 유틸리티와 라이브러리

전통적인 계층형 아키텍처 ( #LayeredArchitecture )와의 비교

  • 전통적인 계층형 아키텍처

    • 웹 인터페이스 계층

    • 도메인 계층

    • 데이터베이스 계층

  • 함수형 아키텍처

    • 데이터베이스는 변경 가능하고 접근하는 모든 것을 액션으로 만듦

    • 함수형 아키텍처는 액션과 계산을 명확하게 구분하고, 도메인 로직은 모두 계산으로 만들어야 함

    • 데이터베이스를 도메인과 분리해야 함

변경과 재사용이 쉬운 아키텍처

  • 어떤 것이 바꾸기 쉬운가?

    • 소프트웨어 아키텍처는 변화를 다루는 일

    • 이 질문에 답할 수 있다면 아키텍처의 반은 결정한 것

  • 인터랙션 계층

    • 변경하기 쉬운 것

    • 데이터베이스, 서비스 프로토콜

    • API handler

  • 도메인 계층

    • 재사용하기 쉬운 것

    • cartTotal

도메인 규칙 domain rule

  • 프로그램의 핵심 로직, 비즈니스 규칙 business rule

  • 도메인 규칙은 도메인 용어를 사용

    • 전자 상거래 도메인 용어에 AJAX 요청은 없다 => 인터랙션 계층에 속하는 코드

도메인의 가독성, 균형

  • 특정 패러다임의 장점이 항상 좋은 것은 아님

  • 도메인 역시 항상 계산으로 만드는 것이 권장되지만, 문맥에 따라 액션이 더 읽기 좋은 경우가 있음

  • 현실 세계 문제와 이상적 다이어그램 사이 균형을 유지하기 위해 고려할 것

    • 코드 가독성

      • 가독성을 결정하는 몇 가지 요소

        • 프로그래밍 언어

        • 라이브러리

        • 레거시 코드와 코드 스타일

        • 개발자들의 습관

      • 함수형 코드가 아닌 코드가 얼마나 더 명확해지는지를 봐야

    • 개발 속도

      • 비즈니스 이유로 빠르게 출시해야하는 경우

      • 많은 것을 타협해야 하지만, 이런 경우에도 나중에 아키텍처에 맞춰 코드를 정리할 준비를 하는 것이 좋다

    • 시스템 성능

      • 변경 가능한 데이터 구조는 불변 데이터 구조보다 빠름

      • 성능 개선과 도메인을 계산으로 만드는 것은 별개의 문제

        • 인터랙션 계층 => 최적화

        • 도메인 계층 => 재사용 가능한 계산 만들기

More from this blog

Kimi Agentic Slides + Nano Banana로 4살 아들 맞춤 동화 만들기

Kimi Agentic Slides 지난주 금요일 (11. 28.) Kimi에서 Agentic Slide를 런칭하면서, 48시간 무제한 슬라이드 생성 이벤트를 진행했다. Nano Banana Pro가 워낙 강렬해서 이건 또 뭐가 다른지 써보고 있는 와중에, 아들이 옆방에서 징징대는 소리가 들렸고, 이걸로 동화책을 만들어봐도 되겠다는 생각이 뇌리를 스쳤다. 아들이 워낙 자동차를 좋아하고, 요즘 육아 이슈가 사회성을 길러주는 거라, 처음엔 사회성...

Dec 1, 20252 min read29
Kimi Agentic Slides + Nano Banana로 4살 아들 맞춤 동화 만들기

[발번역] Deno에 package.json 지원을 추가한 이유

원문: Why We Added package.json Support to Deno 내맘대로 세줄 요약 Node.js package.json 구린거 맞음. 이거에 대한 생각 여전함 근데 HTTP URL 쓰다보니 더 복잡하고 지저분해진 것도 맞음 이것저것 해보다가 뭐 어쩔수없이 이번에 package.json 지원하긴 했는데, 앞으로 deno: URL 지원할 거임. 기대해보셈 Deno 최신 버전 (v1.31, https://deno.com...

Mar 21, 20235 min read199

Wii-World

28 posts