리액티브를 위한 스프링 프레임워크의 초기 해법

1. 관찰자 (Observer)패턴

관찰자 패턴은 관찰자라고 불리는 주체(subject)와 자손리스트로 구성된다. 주체는 자신의 메서드중 하나를 호출해 상태변경을 알려준다. 이 패턴은 MVC패턴의 중요한 부분으로 거의 대부분의 UI라이브러리가 이를 지원한다.

예컨대 테크포털중 하나의 뉴스레터 구독을 비유해보자. 관심있는 사이트에 이메일 주소를 등록하면 뉴스레터 형태로 알림을 받을 수 있다.

Observer Pattern UML

Observer Pattern UML

위 다이어그램에서 알 수 있듯이 일반적인 관찰자패턴은 Subject와 Observer 2개의 인터페이스로 구성된다.

관찰자는 subject에 등록되고 subject로부터 알림을 수신받는다.

public interface Subject<T>{
	void registerObserver(Observer<T> observer);
	void unregisterObserver(Observer<T> observer);
	void notifyObservers(T event);
}

이벤트를 브로드캐스팅하는 구독관리 메서드들이 포함되었다.

public interface Observer<T>{
	void Observe(T event);
}

관찰자는 이벤트를 처리하는 하나의 observe메서드만 가지고있다. 관찰자, 주체 모두 인터페이스 기술 외에는 서로에 대해 연관관계가 없다.

따라서 Observer구현체는 구독절차를 담당할 수도 있고 Subject의 존재자체를 모르는 경우도 있다.

후자의 경우 Subject의 모든 인스턴스를 찾고, 각 Subject에 Observer를 등록하는 (의존성 주입) 컴포넌트가 필요할 수 있다.

스프링에서는 @EventListener라는 어노테이션을 통해 Observer인스턴스를 검색하고 발견된 컴포넌트를 Subject에 등록한다.

아래는 String메시지를 수신해 출력하는 두개의 간단한 Observer구현체이다.

public class ConcreteObserverA implements Observer<String>{
	@Override
	public void observe(String event){
		log.info("Observer A : "+event);
	}
}
public class ConcreteObserverB implements Observer<String>{
	@Override
	public void observe(String event){
		log.info("Observer B : "+event);
	}
}
public class ConcreteSubject implements Subject<String>{
	private final Set<Observer<String>> observers = new CopyOnWirteArraySet<>();
	public void registerObserver(Observer<String>observer){
		observers.add(observer);
	}
	public void unregisterObserver(Observer<String> observer){
		observers.remove(observer);
	}
	public void notifyObservers(String event){
		observers.forEach(observer -> observer.observe(event));
	}
}

→ 구독자 목록은 자주 변경되지 않으므로 Thread-safe한 CopyOnWriteArraySet을 사용하자.

관찰자패턴 테스트코드

@Test
public void observerHandlerEventsFromSubject(){
	Subject<String> subject = new ConcreateSubject();
	Observer<String> observerA = Mockito.spy(new ConcreateObserverA());
	Observer<String> observerB = Mokito.spy(new ConcreateObserverB());

	subject.notifyObservers("No listeners");

	subject.registerObserver(observerA);
	subject.notifyObservers("Message for A");

	subject.registerObserver(observerB);
	subject.notifyObservers("Message for A&B");

	subject.unregisterObserver(observerA);
	subject.notifyObservers("Message for B");

	subject.unregisterObserver(observerB);
	subject.notifyObservers("No listeners");

	Mockito.verify(observerA, times(1)).observe("Message for A"); // 해당 구문 호출 확인
	Mockito.verify(observerA, times(1)).observe("Message for A&B");
	Mockito.verifyNoMoreInteractions(observerA); // 인터랙션 검증

	Mockito.verify(observerB, times(1)).observe("Message for A&B");
	Mockito.verify(observerB, times(1)).observe("Message for B");
	Mockito.verifyNoMoreInteractions(observerB); // 인터랙션 검증
}