관리 메뉴

흰둥씨의 개발장

[함수형 코딩#18] 반응형 구조 & 어니언 구조 본문

함수형 프로그래밍/쏙쏙 들어오는 함수형 코딩

[함수형 코딩#18] 반응형 구조 & 어니언 구조

돈워리비해삐 2023. 7. 16. 04:03

반응형 아키텍쳐 

  - 순차적 액션 단계에 사용
    (여러 원인 + 여러가지 효과 있는 경우 사용하기 좋음 / 하나의 원인으로 a-b-c단계로 효과가 일어난다면 비추)
  - "이벤트에 대해 일어날 일을 지정하는 것 (X가 일어나면 Y가 일어난다)"
  - 원인과 효과를 분리해서 표기
  - 파이프라인 사용(여러가지 함수를 조합해서 쓸수있음, 그래서 여러단계일때 유용)
  - 타임라인이 유연해짐(순차적인 액션의 순서를 뒤집어서, 효과효과의 원인을 분리함)

//초기 값을 받아서 => 업데이트 할수있는 기능을 가진 함수 만들기
function ValueCell(initialValue){
    let curr = initialValue;
    return{
    	val: ()=>{
            return curr;
            },
        update: (f)=>{
            let oldValue = curr;
            let newValue = f(oldValue); //콜백으로 받은 함수에 초기값넣어서
            curr = newValue;   //업데이트 된 값적용해서 교체함
            }
          };
}
//위 함수에 감시자 개념을 추가해보자 
function ValueCell(initialValue){
    let curr = initialValue;
    let watchers = [];  //감시자 목록
    return {
    	val : ()=>{
            return curr;
            },
        update: (f)=>{
            let oldValue = curr;
            let newValue = f(oldValue);
            if(oldValue !== newValue){   //값이 바뀔때 모든 감시자 실행됨
            	curr = newValue; 
                forEach(watchers, (watcher)=>{
                	watcher(newValue);
                    });
                }
             },
         addWatcher: (f)=>{  //새로운 감시자를 추가하는 메서드
         	watchers.push(f)
            },
         };
}

//ValueCell()을 일관되게 유지하기 위한 방법 
//1. 올바른 값으로 초기화하기
//2. update()에는 계산을 전달해야한다(액션전달하지 않기)
//3. 계산은 in이 올바르면 out도 항상 올바르게 리턴해야한다.
//감시자 기능 들어간 ValueCell 사용해보기 
//감시자는 상태가 바뀔 때마다 실행되는 기능

let shopingCart = ValueCell({});            //초기값으로 {}빈객체 할당

function addItemtoCart(name, price){
	let item = make_cart_item(name, price);
    shopingCart.update(()=>{                //update()메소드 호출해서 add_item실행
    	return add_item(cart, item);
        });
    let total = calc_total(shopingCart.val());//val()메소드로 초기값 할당
    set_cart_total_dom(total);
    
    update_tax_dom(total);
 }
 
 shopingCart.addWatcher(update_shipping_icons); //장바구니가 바뀔때마다 실행됨(update_shipping_icons를 감시함)
 
function update_shipping_icons(cart){
    let buy_buttons = get_buy_buttons_dom();
    for(let i = 0; i <buttons.length; i++){	
    	let button = buttons[i];
        let item = button.item;
        let newCart = add_item(cart, item);
        if(gets_free_shipping(newCart)){
        	button.show_free_shipping_icon();
            } else {
            	button.hide_free_shipping_icon();
            	}
         }
 }

 

어떤 값이 바뀌면 따라서 파생된 값을 바꿔주는 기본형 FormulaCell()만들어보기 

function FormulaCell(upstreamCell, f) {
   let myCell = ValueCell(f(upstreamCell.val()));
   upstreamCell.addWatcher((newUpstreamValue)=>{
      myCell.update((curr)=>{
         return f(newUpstreamValue);
      });
   });
  return {
    val: myCell.val,
    addWatcher: myCell.addWatcher
  }
}

함수평 프로그래밍뿐 아니라 소프트웨어는 "변경 가능한 상태"를 잘 관리해야한다. 
(모든 데이터가 불변성을 유지할 수는 없기 때문에 , 상태 흐름을 잘 추적할 수 있도록 작업하기 ...어렵다...)

 

반응형 아키텍쳐가 바꾼 시스템의 결과

  1. 원인과 효과가 결합된 것을 분리함 

//일반 아키텍처  (3번의 원인으로 6개의 작업만들기 필요)
  제품추가버튼클릭        제품삭제클릭        장바구니비우기클릭
      |                  |                 |
      v                  v                 v
전역장바구니에제품추가   전역장바구니에제품삭제    전역장바구니비우기
배송아이콘 업데이트     배송아이콘 업데이트      배송아이콘 업데이트
//반응형 아키텍쳐 (3개의 원인으로 4개의 작업만들기하면됨)
  제품추가버튼클릭        제품삭제클릭        장바구니비우기클릭
      |                  |                 |
      v                  v                 v
전역장바구니에제품추가   전역장바구니에제품삭제    전역장바구니비우기
      |                  |                 |
      ㄴ -------------------------------------------> 전역 장바구니 변경
                                                           |
                                                     배송 아이콘 업데이트

  2. 여러 단계를 파이프 라인으로 처리함(파이프라인 : 작은 액션과 계산을 조합한 하나의 액션)
  3. 타임라인이 유연해짐 (원인과 결과가 명확해짐)

//일반적인 타임라인(원인과 효과가 불명확)
전역 장바구니에 제품 추가 -> 합계 DOM업데이트 -> 배송아이콘 업데이트 -> 세금 DOM업데이트

//분리된 타임라인 (원인으로 인한 결과가 명확)
                        합계DOM업데이트 (결과1)
                      /
전역장바구니에 제품추가(원인) - 배송아이콘업데이트 (결과2)
                      \ 
                        세금 DOM업데이트 (결과3)



 

어니언아키텍쳐 

  - 현실세계와 상호작용 하기 위한 서비스구조를 만드는 방법(3개의 계층으로 나눔)
  - 인터랙션 계층 : 바깥세상에 영향을 주거나 받는 액션 (현실세계와 상호작용함)
  - 도메인 계층 : 비즈니스 규칙을 정의하는 계산 
  - 언어 계층 : 언어유틸리티와 라이브러리
  - 각각의 계층은 독립적이고, 계층에서 호출하는 방향은 중심방향임

계층형 아키텍쳐(전통적 아키텍처)와 함수형 아키텍처의 차이 

전통적 아키텍처 함수형 아키텍처
웹인터페이스 계층
(웹서버, 웹요청을 도메인 동작으로 변환함)
인터렉션계층
(웹서버 + 데이터베이스 + 웹핸들러) 
도메인 계층
(웹핸들러 + 데이터 베이스 기반으로 동작)
도메인계층
(도메인 동작들) 
데이터 베이스계층
(시간에 따라 바뀌는 정보 저장)
언어계층
(라이브러리, 자바스크립트...)

ㄴ> 함수형 아키텍처의 인터렉션 계층 : 변경하기 쉬움 (데이터베이스나 서비스 프로토콜 쉽게 변경가능) => 액션 몰빵?
ㄴ> 함수형 아키텍처의 도메인 계층 : 전통적 아키텍처와 다르게 데이터 베이스와 웹요청에 의존하지 않음(소프트웨어 동작으로 구성됨)
                                                        => 계산으로 바꾸기 쉬움(재사용쉬워짐, but 가독성좋다면 액션이 나은 경우도 있음)
ㄴ> 함수형 아키텍처의 언어계층 : 소프트웨어를 만들 수 있는 언어기능과 라이브러리로 구성

//인터렉션 계층에 해당하는 코드 예시 
let image = newImageDB.getImage('123');  //데이터베이스를 옮겨오는 과정이라 인터렉션계층에 속함
if(image === undefined) {
  image = oldImageDB.getImage('123');
}

function getWithRetries(url, retriesLeft, success, error) { //웹요청 실패시 여러번 재시도하는 로직(외부와 인터렉션)
  if(retriesLeft <= 0) {
    error('No more retries');
  } else {
    ajaxGet(url, success, function(e) {
      getWithRetries(url, retriesLeft - 1, success, error);
    })
  }
}

 

 

내정리:

반응형 아키텍처는 재사용성을 높이는 액션, 계산들로 파이프라인 만들어서 한방에 처리 용이하고, 원인과 효과 분석에 탁월한듯 함

함수형 아키텍처는 인터렉션계층에서 데이터요청이나, 웹요청 등을 모두 처리한 후 , 도메인에서는 동작만 신경쓰면되고,

전통적 아키텍처는 도메인계층에서 데이터를 받아오고 반영시키는 동작과 로직에 필요한 동작을 함께 수행하는 것같음