binary01.me

  • about
Copyright 2026 Jinsoo Lee
쏙쏙 들어오는 함수형 코딩 Chapter 13~18

쏙쏙 들어오는 함수형 코딩 Chapter 13~18

Chapter 13 : 함수형 도구 체이닝

#

앞장에서 함수형 도구로 반복문에서 하려는 일과 반복하는 일을 분리했다. 하지만 계산이 더 복잡해지면 함수형 도구 하나로 작업할 수 없다. 이 장에서는 여라 단계를 하나로 엮은 체인으로 복합적 계산을 표현하는 방법을 알아본다.

요구사항

#

각각의 우수 고객(3개 이상 구매)의 구매 중 가장 비싼 구매를 알고 싶다!

단계 나누기

#
  1. 우수 고객을 거른다. (filter)
  2. 우수 고객을 가장 비싼 구매로 바꾼다. (map)

예시

#
function biggestPurchasesBestCustomers(customers) {
  var bestCustomers = filter(customers, function(customer) {
    return customer.purchases.length >= 3;
  });
  var biggestPurchases = map(bestCustomers, function(customer) {
    return maxKey(customer.purchases, {total: 0}, function(purchase) {
      return purchase.total;
    });
  });
  return biggestPurchases;
}

function maxKey(array, init, f) {
  return reduce(array, init, function(biggestSoFar, element) {
    if(f(biggestSoFar) > f(element)) 
      return biggestSoFar;
    else
      return element;
  });

항등 함수 : 인자를 받은 값을 그대로 리턴하는 함수

위 예시를 리팩토링해 체인을 명확하게 만들어보자!

function biggestPurchasesBestCustomers(customers) {
  var bestCustomers = filter(customers, function(customer) {
    return customer.purchases.length >= 3;
  });
  var biggestPurchases = map(bestCustomers, function(customer) {
    return maxKey(customer.purchases, {total: 0}, function(purchase) {
      return purchase.total;
    });
  });
  return biggestPurchases;
}

function biggestPurchasesBestCustomers(customers) {
  var bestCustomers    = filter(customers, isGoodCustomer);
  var biggestPurchases = map(bestCustomers, getBiggestPurchase);
  return biggestPurchases;
}

function isGoodCustomer(customer) {
  return customer.purchases.length >= 3;
}

function getBiggestPurchase(customer) {
  return maxKey(customer.purchases, {total: 0}, getPurchaseTotal);
}

function getPurchaseTotal(purchase) {
  return purchase.total;
}

단계에 이름을 붙이고, 콜백에 이름을 붙여 더 명확해졌다.

반복문을 함수형 도구로 리팩터링하기

#
var answer = [];
var window = 5;
for(var i = 0; i < array.length; i++) {
  var sum = 0;
  var count = 0;
  for(var w = 0; w < window; w++) {
    var idx = i + w;
    if(idx < array.length) {
      sum += array[idx];
      count += 1;
    }
  }
  answer.push(sum/count);
}

위 코드를 함수형 도구로 바꿔보자!

var window = 5;
var indices = range(0, array.length);

var windows = map(indices, function(i) {
  return array.slice(i, i + window); // 하위 배열 만들기
});

var answer = map(windows, average); // 전에 만들었던 average 함수 사용

function range(start, end) {
  var ret = [];
  for(var i = start; i < end; i++)
    ret.push(i);
  return ret;
}

체이닝 팁 요약

#

데이터 만들기

#

배열 일부에 대해 동작하는 반복문이 있다면 배열 일부를 새로운 배열로 나눌 수 있다. 그리고 map, filter, reduce 등 함수형 도구를 사용하자

배열 전체를 다루기

#

어떻게 하면 반복문을 대신해 전체 배열을 한 번에 처리할 수 있을지 생각해보자

작은 단계로 나누기

#

알고리즘이 한 번에 너무 많은 일을 한다고 생각된다면 여러 단계로 나눠보자

Chapter 14 : 중첩된 데이터에 함수형 도구 사용하기

#

중첩된 구조는 자주 사용한다. 하지만 중첩된 데이터를 불변 데이터로 다루는 것은 어렵다. 이 장에서는 중첩된 데이터를 쉽게 다룰 수 있는 함수형 도구를 소개한다.

update() 도출하기

#
function incrementField(item, field) {
  var value = item[field];
  var newValue = value + 1;
  var newItem = objectSet(item, field, newValue);
  return newItem;
}

// -> 

function incrementField(item, field) {
  return updateField(item, field, function(value) {
  return value + 1;
  });
}

function updateField(item, field, modify) {
  var value = item[field];
  var newValue = modify(value);
  var newItem = objectSet(item, field, newValue);
  return newItem;
}

// ->

function update(object, key, modify) {
  var value = object[key];
  var newValue = modify(value);
  var newObject = objectSet(object, key, newValue);
  return newObject;
}

암묵적 인자 드러내기와 함수 본문을 콜백으로 바꾸기 리팩터링으로 update 함수를 도출했다.

사용 예시

#
var employee = {
  name: "Kim",
  salary: 120000
};

function raise10Percent(salary) {
  return salary * 1.1;
}

update(employee, 'salary', raise10Percent)

중첩된 데이터에 update() 사용하기

#

update 함수가 객체 바로 아래에 있는 데이터에는 잘 동작하는 것 같다. 그런데 중첩된 구조에서는 어떨까?

예시

#

리팩토링 전

var shirt = {
  name: "shirt",
  price: 13,
  options: {
    color: "blue",
    size: 3
  }
};

function incrementSize(item) {
  var options = item.options;
  var size = options.size;
  var newSize = size + 1;
  var newOptions = objectSet(options, 'size', newSize);
  var newItem = objectSet(item, 'options', newOptions);
  return newItem;
}

리팩토링 후

function incrementSize(item) {
  var options = item.options;
  var newOptions = update(options, 'size', increment);
  var newItem = objectSet(item, 'options', newOptions);
  return newItem;
}


->

function incrementSize(item) {
  return update(item, 'options', function(options) {
    return update(options, 'size', increment);
  });
}

두 번의 리팩토링을 통해 중첩된 데이터에 update()를 사용할 수 있었다.

하지만 incrementSize 함수 에서는 두 가지 코드 smell이 난다.

바로 암묵적 인자를 두 가지 사용하는 것이다. Size 와 increment 말이다!

그럼 명시적 인자로 바꿔보자

function incrementOption(item, option) {
  return update(item, 'options', function(options) {
    return update(options, option, increment);
  });
}

function updateOption(item, option, modify) {
  return update(item, 'options', function(options) {
    return update(options, option, modify);
  });
}

잘 만든 것 같은데 같은 냄새가 또 생겼다! 바로 options 이다.

다시 리팩터링 해보자.

update2() 도출하기

#
 function update2(object, key1, key2, modify) {
  return update(object, key1, function(value1) {
    return update(value1, key2, modify);
  });
}

이제 update2 함수는 두 단계로 중첩된 어떤 객체에도 쓸 수 있는 함수이다.

원래 코드랑 update2 함수를 사용한 코드를 비교해보자!

// 기존 코드
function incrementSize(item) {
  var options = item.options;
  var size = options.size;
  var newSize = size + 1;
  var newOptions = objectSet(options, 'size', newSize);
  var newItem = objectSet(item, 'options', newOptions);
  return newItem;
}

/// update2() 사용
function incrementSize(item) {
  return update2(item, 'options', 'size', function(size) {
    return size + 1;
  });
}

update3() 도출하기

#

update 함수와 update2 함수를 도출한 것처럼 update3 함수로 도출할 수 있다.

function update3(object, key1, key2, key3, modify) {
  return update(object, key1, function(object2) {
    return update2(object2, key2, key3, modify);
  });
}

nestedUpdate() 도출하기

#

update3 함수 깊이(depth)라는 인자를 추가해보자

function updateX(object, depth, key1, key2, key3, modify) {
  return update(object, key1, function(value1) {
    return updateX(value1, depth-1, key2, key3, modify);
  });
}

// ->

function updateX(object, keys, modify) {
  var key1 = keys[0];
  var restOfKeys = drop_first(keys);
  return update(object, key1, function(value1) {
    return updateX(value1, restOfKeys, modify);
  });
}

depth 인자와 실제 키 개수는 달라질 수 있어서 버그가 생길 수 있으니 keys 를 사용해서 다시 리팩터링한다.

이제 정상적으로 작동할 것처럼 보이지만 아직 keys 배열 길이가 0일 때 처리를 안했다. 해보자!

function updateX(object, keys, modify) {
  if(keys.length === 0)
    return modify(object);
  var key1 = keys[0];
  var restOfKeys = drop_first(keys);
  return update(object, key1, function(value1) {
    return updateX(value1, restOfKeys, modify);
  });
}

// ->

function nestedUpdate(object, keys, modify) {
  if(keys.length === 0)
    return modify(object);
  var key1 = keys[0];
  var restOfKeys = drop_first(keys);
  return update(object, key1, function(value1) {
    return nestedUpdate(value1, restOfKeys, modify);
  });
}

이름을 nestedUpdate 로 바꾸었다. 이제 여러 단계로 중첩된 객체를 마음껏 조작할 수 있다.

Chapter 15 : 타임라인 격리하기

#

이번 장에서는 시간에 따라 실행되는 액션의 순서를 나타내기 위해 타임라인 다이어그램에 대해 알아보자

예시

#

제품을 장바구니에 추가할 때 빠르게 두 번 클릭하면 버그가 생기는 코드가 있다.

function add_item_to_cart(name, price, quantity) {
  cart = add_item(cart, name, price, quantity);
  calc_cart_total();
}

function calc_cart_total() {
  total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
    shipping_ajax(cart, function(shipping) {
      total += shipping;
      update_total_dom(total);
    });
  });
}

다이어그램

#
Notion image

첫번째 클릭 시 일어나는 액션이 다 끝나지않은 상태에서 두번째 클릭 액션이 실행되어 생기는 문제이다.

  1. 첫번째 타임라인의 shipping_ajax() 의 응답이 조금 늦게 도착함.
  2. 두번째 타임라인의 cart 읽기 가 시작됨.

어떻게 코드를 바꿔서 문제를 해결할 수 있을까?

일단 암묵적 인자부터 없애자!

function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
    shipping_ajax(cart, function(shipping) {
      total += shipping;
      callback(total);
    });
  });
}

function add_item_to_cart(name, price, quant) {
  cart = add_item(cart, name, price, quant);
  calc_cart_total(cart, update_total_dom);
}

하지만 아직 버그는 남아 있다. 다음 장에서 해결해보자..

좋은 타임라인의 원칙

#
  1. 타임라인은 적을수록 이해하기 쉽다.
  2. 타임라인은 짧을수록 이해하기 쉽다.
  3. 공유하는 자원이 적을수록 이해하기 쉽다.
  4. 자원을 공유한다면 서로 조율해야 한다.
  5. 시간을 일급으로 다룬다.

비동기 호출 원칙

#

비동기 호출에서 명시적인 출력을 위해 리턴값 대신 콜백을 사용할 수 있다.

Chapter 16 : 타임라인 사이에 자원 공유하기

#

이전 장의 장바구니 버그를 다시 보자

다이어그램

#
Notion image

위 다이어그램은 DOM을 업데이트하는 두 액션이 실행할 때 가능한 순서이다.

왼쪽부터 [왼쪽 먼저], [오른쪽 먼저], [동시에] 이다.

자바스크립트는 싱글 스레드 모델이라 동시에 실행될 수는 없다 따라서 가능한 순서는 [왼쪽 먼저], [오른쪽 먼저] 이다.

자바스크립트에서 큐 만들기

#

우리가 해결해야하는 문제는 오른쪽이 먼저 실행되는 것을 막는 것이다.

큐를 활용해서 막아보자!

// 기존 코드
function add_item_to_cart(item) {
  cart = add_item(cart, item);
  calc_cart_total(cart, update_total_dom);
}

function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
    shipping_ajax(cart, function(shipping) {
      total += shipping;
      callback(total);
    });
  });
}

// -> Step 1

function add_item_to_cart(item) {
  cart = add_item(cart, item);
  update_total_queue(cart);
}

function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
    shipping_ajax(cart, function(shipping) {
      total += shipping;
      callback(total);
    });
  });
}

var queue_items = [];

function update_total_queue(cart) {
  queue_items.push(cart);
}

// -> Step 2

function add_item_to_cart(item) {
  cart = add_item(cart, item);
  update_total_queue(cart);
}

function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
    shipping_ajax(cart, function(shipping) {
      total += shipping;
      callback(total);
    });
  });
}

var queue_items = [];

function runNext() {
  var cart = queue_items.shift();
  calc_cart_total(cart, update_total_dom);
}

function update_total_queue(cart) {
  queue_items.push(cart);
  setTimeout(runNext, 0);
}

// -> Step 3

function add_item_to_cart(item) {
  cart = add_item(cart, item);
  update_total_queue(cart);
}

function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
    shipping_ajax(cart, function(shipping) {
      total += shipping;
      callback(total);
    });
  });
}

var queue_items = [];
var working = false;

function runNext() {
  if(working)
    return;
  working = true;
  var cart = queue_items.shift();
  calc_cart_total(cart, update_total_dom);
}

function update_total_queue(cart) {
  queue_items.push(cart);
  setTimeout(runNext, 0);
}

// -> Step 4

var queue_items = [];
var working = false;

function runNext() {
  if(working)
    return;
  working = true;
  var cart = queue_items.shift();
  calc_cart_total(cart, function(total) {
    update_total_dom(total);
    working = false;
    runNext();
  });
}

function update_total_queue(cart) {
  queue_items.push(cart);
  setTimeout(runNext, 0);
}

// -> End

function Queue() {
  var queue_items = [];
  var working = false;

  function runNext() {
    if(working)
      return;
    if(queue_items.length === 0)
      return;
    working = true;
    var cart = queue_items.shift();
    calc_cart_total(cart, function(total) {
      update_total_dom(total);
      working = false;
      runNext();
    });
  }

  return function(cart) {
    queue_items.push(cart);
    setTimeout(runNext, 0);
  };
}

var update_total_queue = Queue();

여러 단계에 걸쳐 큐를 만들었다.

큐를 재사용할 수 있도록 만들기

#
function Queue(worker) {
  var queue_items = [];
  var working = false;

  function runNext() {
    if(working)
      return;
    if(queue_items.length === 0)
      return;
    working = true;
    var item = queue_items.shift();
    worker(item.data, function(val) {
      working = false;
      setTimeout(item.callback, 0, val);
      runNext();
    });
  }

  return function(data, callback) {
    queue_items.push({
      data: data,
      callback: callback || function(){}
    });
    setTimeout(runNext, 0);
  };
}

function calc_cart_worker(cart, done) {
  calc_cart_total(cart, function(total) {
    update_total_dom(total);
    done(total);
  });
}

var update_total_queue = Queue(calc_cart_worker);

다이어그램

#
Notion image

타임라인은 위와 같이 표현된다. 이로써 장바구니 버그가 드디어 해결되었다.

Chapter 17 : 타임라인 조율하기

#

장바구니에 큐를 적용해서 배포한 지 일주일이 지났다. 그 후 UI 속도를 개선해달라는 요청이 많이 있었다.

그리고 최적화를 했는 데 버그가 생겼다.

// 원래 코드
function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
    shipping_ajax(cart, function(shipping) {
      total += shipping;
      callback(total);
    });
  });
}

// 최적화한 코드
function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
    });
    shipping_ajax(cart, function(shipping) {
      total += shipping;
      callback(total);
  });
}

닫은 괄호가 옮겨졌다. cost_ajax 의 콜백 안에서 shipping_ajax 가 호출되는 대신, cost_ajax , shipping_ajax 가 동시에 실행되게 하여 속도를 높였다. 하지만 버그가 있다.

다이어그램

#
Notion image

위와 같은 타임라인을 가졌기 때문에 오른쪽이 먼저 실행 될때 버그가 발생한다.

우리가 원하는 결과는 아래와 같다

Notion image

두 콜백이 서로 끝나기를 기다리고 다음 작업을 수행한다. 이 점선을 컷(cut)이라고 부른다.

코드에 Cut() 적용하기

#
function Cut(num, callback) {
  var num_finished = 0;
  return function() {
    num_finished += 1;
    if(num_finished === num)
      callback();
  };
}

// example
var done = Cut(3, function() {
  console.log("3 timelines are finished");
});
  
done();
done();
done();

자바스크립트의 스레드는 하나이다. Cut 함수는 이런 장점을 활용해 변경할 수 있는 값을 안전하게 공유한다.

적용

#
// 적용 전

function calc_cart_total(cart, callback) {
  var total = 0;
  cost_ajax(cart, function(cost) {
    total += cost;
  });
  shipping_ajax(cart, function(shipping) {
    total += shipping;
    callback(total);
  });
}

// Cut() 적용

function calc_cart_total(cart, callback) {
  var total = 0;
  var done = Cut(2, function() {
    callback(total);
  });
  cost_ajax(cart, function(cost) {
    total += cost;
    done();
  });
  shipping_ajax(cart, function(shipping) {
    total += shipping;
    done();
  });
}

이로써 동시에 실행하면서 속도도 개선하고 순서대로 실행하는 것과 같은 올바른 결과도 얻었다.

JustOnce()

#
function sendAddToCartText(number) {
  sendTextAjax(number, "Thanks for adding something to your cart. Reply if you have any questions!");
}

function JustOnce(action) {
  var alreadyCalled = false;
  return function(a, b, c) {
    if(alreadyCalled) return;
    alreadyCalled = true;
    return action(a, b, c);
  };
}

sendAddToCartTextOnce("555-555-5555-55");
sendAddToCartTextOnce("555-555-5555-55");
sendAddToCartTextOnce("555-555-5555-55");
sendAddToCartTextOnce("555-555-5555-55");

딱 한 번만 호출하는 함수도 위와 같이 만들 수 있다.

Chapter 18 : 반응형 아키텍처와 어니언 아키텍처

#

반응형 아키텍처

#

애플리케이션을 구조화하는 방법이다.

반응형 아키텍처는 코드에 나타난 순차적 액션의 순서를 뒤집는다.

반응형 아키텍처의 핵심 원칙은 이벤트에 대한 반응으로 일어날 일을 지정하는 것이다.

셀은 일급 상태입니다.

#

우리가 살펴본 장바구니 예제에서 전역 상태는 장바구니이다. 필요한 것은 장바구니가 변경될 때 Y를 하는 것이다.

상태를 일급 함수로 만들자. 전역변수를 몇 가지 동작과 함께 객체로 만들자.

// 기존 코드

var shopping_cart = {};

function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart = add_item(shopping_cart, item);
  var total = calc_total(shopping_cart);
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart);
  update_tax_dom(total);
}

// 셀을 적용한 코드

function ValueCell(initialValue) {
  var currentValue = initialValue;
  return {
    val: function() {
      return currentValue;
    },
    update: function(f) {
      var oldValue = currentValue;
      var newValue = f(oldValue);
      currentValue = newValue;
    }
  };
}

var shopping_cart = ValueCell({});

function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart.update(function(cart) {
    return add_item(cart, item);
  });
  var total = calc_total(shopping_cart.val());
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart.val());
  update_tax_dom(total);
}

다음은 ValueCell 코드에 감시자 개념을 추가하자

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);
    }
  };
}

감시자 개념을 사용해 셀이 바뀔 때 배송 아이콘을 갱신할 수 있다.

// 기존 코드

var shopping_cart = ValueCell({});

function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart.update(function(cart) {
    return add_item(cart, item);
  });
  var total = calc_total(shopping_cart.val());
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart.val());
  update_tax_dom(total);
}

// 감시자 적용한 코드

var shopping_cart = ValueCell({});

function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart.update(function(cart) {
    return add_item(cart, item);
  });
  var total = calc_total(shopping_cart.val());
  set_cart_total_dom(total);
  update_tax_dom(total);
}

shopping_cart.addWatcher(update_shipping_icons);

함수형 프로그래밍과 변경 가능한 상태

#

중요한 것은 상태를 가능한 한 안전하게 사용하는 것이다.

셀은 변경할 수 있지만 변경 불가능한 변수에 값을 담아두기 때문에 전역변수보다 더 안전하다.

ValueCell 의 update 사용하면 현재 값을 항상 올바르게 유지할 수 있다.

왜냐 ? update 는 계산을 넘기기 때문이다. 계산은 현재 값을 받아 새로운 값을 리턴한다.

ValueCell 는 순서를 보장하지는 않는다. 하지만 어떤 값이 저장되어도 그 값이 항상 올바른 값이라는 것을 보장한다.

ValueCell는 Redux store, Recoil atom, React useState 등과 컨셉이 매우 유사하다.

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

#
  1. 원인과 효과가 결합된 것을 분리한다.
  2. 여러 단계를 파이프라인으로 처리한다.
  3. 타임라인이 유연해진다.

어니언 아키텍처

#

현실세계와 상호작용하기 위한 서비스 구조를 만드는 방법이다.

어니언 아키텍처는 서비스 전체를 구성하는 데 사용하기 때문에 바깥 세계와 상호작용하는 부분을 다룬다.

어니언 아키텍처는 양파 모양을 하고 있고 아래 계층들로 구성되어 있다.

인터랙션 계층

#
  • 바깥세상에 영향을 주거나 받는 액션

도메인 계층

#
  • 비즈니스 규칙을 정의하는 계산

언어 계층

#
  • 언어 유틸리티와 라이브러리

어니언 아키텍처 규칙

#
  1. 현실 세계와 상호작용은 인터렉션 계층에서 해야한다.
  2. 계층에서 호출하는 방향은 중심 방향이다.
  3. 계층은 외부에 어떤 계층이 있는 지 모른다.

어니언 아키텍처

Notion image

어니언 아키텍처는 인터렉션 계층을 바꾸기 쉽다. 그래서 도메인 계층을 재사용하기 좋다.

19장은 이전 Chapter들을 돌아보는 Chapter로 생략하겠다!

  • Chapter 13 : 함수형 도구 체이닝
  • 요구사항
  • 단계 나누기
  • 예시
  • 반복문을 함수형 도구로 리팩터링하기
  • 체이닝 팁 요약
  • 데이터 만들기
  • 배열 전체를 다루기
  • 작은 단계로 나누기
  • Chapter 14 : 중첩된 데이터에 함수형 도구 사용하기
  • update() 도출하기
  • 사용 예시
  • 중첩된 데이터에 update() 사용하기
  • 예시
  • update2() 도출하기
  • update3() 도출하기
  • nestedUpdate() 도출하기
  • Chapter 15 : 타임라인 격리하기
  • 예시
  • 다이어그램
  • 좋은 타임라인의 원칙
  • 비동기 호출 원칙
  • Chapter 16 : 타임라인 사이에 자원 공유하기
  • 다이어그램
  • 자바스크립트에서 큐 만들기
  • 큐를 재사용할 수 있도록 만들기
  • 다이어그램
  • Chapter 17 : 타임라인 조율하기
  • 다이어그램
  • 코드에 Cut() 적용하기
  • 적용
  • JustOnce()
  • Chapter 18 : 반응형 아키텍처와 어니언 아키텍처
  • 반응형 아키텍처
  • 셀은 일급 상태입니다.
  • 함수형 프로그래밍과 변경 가능한 상태
  • 반응형 아키텍처가 바꾼 시스템의 결과
  • 어니언 아키텍처
  • 인터랙션 계층
  • 도메인 계층
  • 언어 계층
  • 어니언 아키텍처 규칙