이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.
작성 시점: 2017-10-17
Publish-Subject 패턴 (발행-구독) 패턴은 Event-Driven Programming 에서 쓰이는 방법으로 발행자와 구독자로 역할을 나누어 발행자에서 구독자로 특정한 이벤트를 발송하는 것이다.
이미지 출처: http://reactivex.io/documentation/operators/images/S.PublishSubject.png
마블 다이어그램으로 설명하면 위와 비슷한데, 하나씩 사례를 들어가면서 설명하면...
맨 위가 발행자, 밑이 구독자들 로 첫번째 구독자(A1) 는 처음부터 발행자를 구독하고 두번째 구독자(B1) 는 중간에 발행자를 구독한다.
여기서 구독한다는 말은 발행자의 행동을 전부 받는다는 뜻이다.
흔히 '구독' 개념이 쓰이는 유투브의 사례가 이 패턴을 자세히 설명한다.
시청자가 채널을 '구독' 하고, 채널이 새로운 영상을 올리면 시청자에게 알림으로 영상을 올렸다는 알림이나 리스트가 뜨게 된다.
발행자가 $a, $b, $c 라는 이벤트를 시간 차를 두고 보낸다고 하면
구독자 A1는 $a, $b, $c 라는 이벤트를 받을 수 있고, 구독자 B1는 $b가 발행자로부터 발송된 이후 구독하였으므로 $c만 받을 수 있다.
보통 프로그래밍에서 A와 B라는 클래스를 서로 연결하기 위해서는 상속 또는 Listener 등의 패턴으로 단단한 커플링을 결합하나 이 Publish-Subject는 매우 느슨한 커플링을 결합할 수 있게 해주어 A와 B가 구조로부터 분리되어 각자에 영향을 덜 미치게 만들 수 있다.
Otto, EventBus 등으로 쉽게 구현할 수 있고, 언어 자체가 Observable 하게 구성된 RxJava를 이용할 수 있다.
위에서도 언급했었다 싶이, A와 B라는 클래스를 서로 연결하기 위해서는 상속(Inherit) 나 Listener 등의 패턴으로 구성하게 된다.
상속이나 Listener 패턴의 문제점은 A와 B라는 클래스를 서로 강력하게 결합한다는 것에 있다. 그 말인 즉슨, A와 B 한쪽이 변하면 다른 한쪽도 변한다는 의미이다.
그에 비해 Publish-Subject 패턴을 사용하게 되면 A와 B는 느슨한 커플링으로 결합되며 이는 독립적인 프로세스를 유지할 수 있게 해준다.
한 쪽을 많이 변경한다고 해도 그에 생길 수 있는 B에 대한 사이드 이펙트, 상태(State) 고려를 하지 않아도 된다.
또한 결합할 때에 있어 다른 클래스 간의 코드 디자인을 고려하지 않아도 된다.
한 발행자에는 여러 구독자가 붙을 수 있어 같은 행동을 받아야 한다면 여러 곳에서 쉽게 받을 수 있는 등 그 자체의 유연성이 있다.
하지만 과하면 부하가 걸리기도 한다.
기존 패턴에 비해 강력한 장점을 제공하는 만큼, 그 만큼의 단점이 생긴다.
장점에서 말했듯이 구독자와 발행자는 서로 느슨하게 연결된다.
그렇기에 메세지가 성공적으로 발송되었는지 실패하였는지 알기 어렵다.
또한 상태 고려를 하지 않기에 구독자는 발행자의 상태를 알 수 없고, 마찬가지로 발행자는 구독자의 상태를 알 수 없다.
느슨한 커플링이기 때문에 발행자가 아무리 행동을 보내도 구독자가 못 받을 수 있다.
구독자와 발행자 간의 주고 받는 메세지 수가 늘어나면서 자연스럽게 부하가 생긴다.
한꺼번에 많은 양을 전송하거나, 짧은 주기로 전송하게 되면 메세지가 정상적으로 발송되지 않던 문제가 있다.
만약 UI 갱신을 Publish-Subject 패턴이 담당하게 된다면 어느 한 순간부터 UI 갱신이 멈추는 일도 허다하다.
구독자와 발행자 간의 주고 받는 것은 메세지 이다. 서로의 상태를 알 수 없기에 발행자가 누구인지 구독자는 모르게 된다. 그렇기에 악성적인 발행자가 침입하여 구독자가 악성 발행자가 발행한 이벤트를 받게 되면 전체적인 구조가 파괴될 수 있다.
구독자와 발행자 간의 주고 받는 메세지의 규약, 구독자의 규칙 등으로 코드에 추가적인 작업이 필요하다.
분명히 Publish-Subject 는 기존 구조가 제공하던 강력한 결합에서 느슨한 결합을 제공해서 독립적인 프로세스를 유지하면서도 서로간의 클래스를 연결시킬 수도 있다.
하지만 그에 해당하는 단점은 분명히 있어 쓰더라도 조금 생각해서 쓸 필요는 있다.
최근 EventBus 계열로 서비스 <-> 액티비티 <-> 다이얼로그 간 통신을 구현했었는데 결국 부하가 걸려서 작동을 멈추는 사건이 있었다.
그 이슈 이후로 EventBus 계열이 그 프로젝트에서는 전부 빠지게 되고 상속이나 바인드 서비스 등으로 처리했고 최소한의 느슨한 통신이 필요한 경우에는 Broadcast로 대체했다는 이야기는 접어두자...