Medium - The Publisher/Subscriber Pattern in JavaScript

원문 - jsmanifest

Trend 파악을 Medium 기고문 요약 포스팅 - 자바스크립트에서 생산자/소비자 패턴; 간단히 Pub/sub 라고 합니다.

Photo by NordWood Themes on Unsplash

이 기사에서 우리는 자바스크립트에서 생산자/소비자 패턴과 자바스크립트 응용프로그램에서 얼마나 쉽게 구현할 수 있는지 알아볼 것입니다. 생산자/소비자 패턴은 강력한 동적 응용프로그램을 만들 수 있게해주는 디자인 패턴으로 모듈들은 직접적으로 연결되지 않아도 서로 통신을 할 수 있습니다.

해당 패턴은 자바스크립트에서 꽤 일반적인 것으로 옵저버 패턴이 동작하는 방식과 많이 유사합니다. 옵저버 패턴에서 관찰자는 subject에 의해 직접적으로 알림을 받게되지만 생산자/소비자 메소드에서는 subscriber가 생산자와의 사이에 있는 채널을 통해 알림을 받습니다.

이것을 구현하려면 우리는 publisher, subscriber, 그리고 subscribers들로부터 등록된 콜백들을 저장할 장소가 필요합니다. 그럼 바로 코드가 어떻게 되어있는지 봅시다. 우리는 factory 함수를 사용해서 publisher/subscriber를 구현할 것입니다.

처음으로 해야할 것은 함수 내에 로컬변수를 선언해서 subscriber 콜백들을 저장하는 것입니다.

function pubSub() {
  const subscribers = {}
}

다음으로 우리는 subscribe 메소드를 정의하여 subscribers에 콜백을 추가하는 임무를 수행하도록 할 것입니다.

function pubSub() {
  const subscribers = {}

  function subscribe(eventName, callback) {
    if (!Array.isArray(subscribers[eventName])) {
      subscribers[eventName] = []
    }
    subscribers[eventName].push(callback)
  }

  return {
    subscribe,
  }
}

위의 코드에서 일어나는 일은 이벤트 이름에 대해 콜백 리스너를 등록하기전 subscribers 저장소 안에 eventName 프로퍼티가 배열인지 아닌지 확인합니다. 만약 배열이 아니라면 이것이 해당 subscribers[eventName]의 첫번째 콜백등록이라고 보고 배열을 초기화합니다. 그리고 콜백을 배열에 추가합니다.

publish 이벤트가 발생하면 두 개의 매개변수를 갖습니다.

  1. eventName
  2. subscribers[eventName]안에 등록된 모든 콜백에 넘겨줄 수 있는 data
function pubSub() {
  const subscribers = {}

    function publish(eventName, data) {
      if (!Array.isArray(subscribers[eventName])) {
        return
      }
      subscribers[eventName].forEach((callback) => {
        callback(data)
        })
    }

    function subscribe(eventName, callback) {
      if (!Array.isArray(subscribers[eventName])) {
        subscribers[eventName] = []
      }
      subscribers[eventName].push(callback)
    }

    return {
      publish,
      subscribe,
    }
}

subscribers에 있는 콜백리스트를 순회하기 전에 먼저 객체안에 배열이 실제적으로 존재하는지 확인합니다. 그렇지 않다면 eventName은 전에 등록된 적이 없기 때문에 단순히 리턴을 합니다. 이것은 잠재적인 크래시를 막는 안전장치 입니다.

그다음 프로그램이 .forEach라인에 도달하면 eventName은 한개 혹은 그 이상의 콜백이 등록되었다는 것을 알 수 있습니다. 프로그램은 subscribers[eventName]을 안전하게 순회할 것입니다.

각 콜백에 대해서는 두번째 매개변수로 전달된 data로 작업을 하도록 콜백을 호출할 것입니다. 그래서 힘수를 subscribed하면 다음과 같은 일이 발생합니다.

function showMeTheMoney(money) {
  console.log(money)
}

const ps = pubSub()

ps.subscribe('show-money', showMeTheMoney)

그리고 우리가 나중에 publish 메소드를 호출하려면

ps.publish('show-money', 1000000)

그러면 우리가 등록한 showMeTheMoney 콜백은 1000000을 money 매개변수로 사용하며 호출될 것입니다.

function showMeTheMoney(money) {
  console.log(money) // result: 1000000
}

그리고 이게 publisher/subscriber 패턴이 동작하는 방식입니다. pubSub함수를 정의하고 콜백을 저장할 수 있는 지역적인 장소를 함수에 제공하고, 콜백들을 등록하기 위한 subscribe 메소드와 등록된모든 콜백들을 순회하는 메소드인 publish가 있습니다.

그래도 마지막 한가지 문제가 남았습니다. 실제 응용프로그램에서는 우리가 만은 콜백을 subscribe하면 끝나지 않는 메모리 릭으로 고통을 받게 될 것이고 우리가 그것에 대해 아무것도 하지 않는다면 더욱 낭비될 것입니다. 그래서 우리가 마지막으로 해야할 것은 subscribed된 콜백이 더 이상 필요하지 않을 때 제거되도록 하는 것입니다.

이 경우 unsubscribe 메소드가 어디에 위치되어야 합니다. 가장 간편하게 구현할 수 있는 위치는 subscribe의 리턴값입니다. 왜냐하면 제 생각에는 코드를 봤을 때 그게 가장 직관적이거든요.

function subscribe(eventName, callback) {
  if (!Array.isArray(subscribers[eventName])) {
    subscribers[eventName] = []
  }

  subscribers[eventName].push(callback)

  const index = subscribers[eventName].length - 1

  return {
    unsubscribe() {
      subscribers[eventName].splice(index, 1)
    },
  }
}

const unsubscribe = subscribe('food', function(data) {
  console.log(\`Received some food: ${data}\`)
  })

unsubscribe()

위의 예제에서는 인덱스가 필요합니다. 그래서 .splice를 사용할 때는 해당 인덱스가 우리가 삭제하길 원하는 아이템인지 확실히 확인해야 합니다. 다음의 코드로도 같은 것을 수행할 수 있습니다만 성능이 좀 낮습니다.

function subscribe(eventName, callback) {
  if (!Array.isArray(subscribers[eventName])) {
    subscribers[eventName] = []
  }

  subscribers[eventName].push(callback)

  const index = subscribers[eventName].length - 1

  return {
    unsubscribe() {
      subscribers[eventName] = subscribers[eventName].filter((cb) => {

        if(cb === callback) {
          return false
        }
        return true
        })
    },
  }
}

Disadvantages

해당 패턴에 큰 장점이 있지만 디버깅 시간이 많이 걸리는 지독한 단점이 있습니다. 우리가 어떻게 같은 이름의 콜백이 전에 subscribed된지 아닌지 알 수 있을까요? 해당 리스트를 map하는 유틸리티를 구현하지 않는이상 진짜 방법이 없습니다. 그리고 그것은 우리가 자바스크립트에게 더 많은 일을 시키는 것이 되죠

또한 현업에서는 이 패턴을 남용하면 유지보수를 하기가 매우 힘들어집니다. 이 패턴에서는 콜백들이 결합도가 낮아지지만 그만큼 각 스텝에 콜백이 어떤 것을 하고 있는지 추적하기가 어려워집니다.

Summary

  • Pub/Sub 패턴은 Observe 패턴과 유사하며 다른점은 Observe 패턴은 subject가 직접적으로 observe에게 알림을 주는 반면 Pub/Sub 패턴은 사이의 채널을 통해 간접적으로 알림을 받는다
  • 단점으로는 해당 패턴을 남용할 경우 유지보수가 어려워 질 수가 있다. 디버깅 타임도 오래걸린다. 결합도가 낮은만큼 콜백을 추적하기 어렵기 때문이다.

© 2019. All rights reserved.

Powered by Hydejack v8.1.1