Easy Understanding

웹 백엔드 관점으로 본 디자인 패턴 정리(3) - 옵저버 패턴(Observer Pattern) 본문

Study

웹 백엔드 관점으로 본 디자인 패턴 정리(3) - 옵저버 패턴(Observer Pattern)

appleg1226 2022. 5. 6. 00:15

 

옵저버 패턴을 들어가면서 스타크래프트의 옵저버나 언급해야겠다고 생각했는데,

다들 하는 생각이 비슷비슷한 것 같다.

이미 많이들 사용하고 있어서 생각을 접었다.

 

게다가 난 이 패턴에 옵저버라는 단어가 들어간 순간부터 이해에 방해가 되어 왔다.

대충 개념만 보면 쉬운데 이 옵저버라는 단어 때문에 더 헷갈리는 느낌이다.

 

- 필요 개념: 상속
- 활용도: 부담없이 사용해볼만 함
- 난이도: 간단
- 패턴이 필요한 상황: 일대다 상황

 

일대다 상황이라 함은, 1개의 원인과 그로 인해 n개의 동작이 수행되어야 하는 상황이다.

예를 들면 다음과 같은 상황이다.

 

- 모니터링 시스템에서 특정 지표가 넘어가면(1) 메시지/메일/카톡 등의 채널로 알림이 발송(n)된다.

- 어떤 API를 통해서 요청이 들어왔을 때(1), 동시에 여러 개의 테이블을 업데이트(n) 해주어야 한다.(서로 독립적인 테이블)

 

옵저버 패턴은 적용할 만한 상황이 생각보다 적지 않게 나타난다.

특히 백엔드 시스템에서 은근히 이런 상황은 자주 발견할 수 있다.

 

이게 패턴을 적용하지 않고 쉽게 가려면 그냥 쉽게 갈 수가 있다.

알림 발송의 예시를 들면 이렇게 코딩하면 끝이긴 하다.

if(index > threshold){
  sendMessage();   // 문자 발송
  sendKakaoTalk(); // 카톡 발송
  sendMail();      // 메일 발송
}

 

그런데 이런 요구사항이 있을 수가 있다.

'난 카톡 알림 기능을 끄고 싶다.'

그럼 어떡하나 고민을 해 봤더니 이런 식으로 풀어갈 수 있을 것 같다.

각 Boolean 값들을 두고 그 값을 통해서 알림을 발송할지 말지를 결정하도록 했다.

if(index > threshold){
  if(receiveMessage){
    sendMessage();
  }
  
  if(receiveMessage){
    sendKakaoTalk();
  }
  
  if(receiveMessage){
    sendMail();
  }
}

 

어차피 비슷한 행동을 하는데 인터페이스로 묶으면 어떨까 하는 생각이 어떨까?

그리고 같은 인터페이스들을 아예 컬렉션으로 묶어서 관리하면 어떨까?

이걸 어떻게 런타임에서 조작할 수 있는 방법은 없을까?

라고 생각할 수가 있는데 그 때 옵저버 패턴을 사용하면 된다.

 

interface AlertSender{
  void send();
}
class MessageSender implement AlertSender{ ... }
class KakaoTalkSender implement AlertSender{ ... }
class EmailSender implement AlertSender{ ... }

이렇게 만들어 둔 다음에

 

서비스는 이렇게 구현하면 된다.

(알림과 관련된 로직은 엉망진창이지만 딱 add, remove, sendAlert 메서드만 보면 된다)

@Service
public class AlertService {
  private List<AlertSender> senders = new ArrayList<>();
  private int threshhold;
  private int index;
  
  public void add(AlertSender sender){
    senders.add(sender);
  }
  
  public void remove(AlertSender sender){
    senders.remove(sender);
  }
  
  public void updateIndex(int index){
    this.index = index;
    if(index > threshold){
      this.sendAlert();  
    }
  }
  
  private void sendAlert(){
    for(AlertSender sender : senders){
      sender.send();
    }
  }
}

 

이렇게 해 두면 동적으로 알림 수단을 조작할 수 있다.

 

예를 들어서 다음과 같이 api들을 만들어 둔 다음 필요할 때 부른다면,

코드를 변경할 필요도 없으니 어플리케이션을 재시작 하지 않고 런타임에서 사용할 수도 있다.

- 카카오 알림을 켜는 api

- 카카오 알림을 끄는 api

- 메일 알림을 켜는 api

- 메일 알림을 끄는 api

- 메시지 알림을 켜는 api

- 메시지 알림을 끄는 api

 

개발을 하다보면 분명히 1 : n의 행동을 유발할 상황이 생긴다.

이럴 때 이런 식으로 깔끔하게 처리할 수가 있어서 도입해볼만 한 것 같다.

패턴 자체는 읽어보았을 때 이해하기 어려울 정도로 복잡한 편은 아니다.

 

최근에 CQRS 코드를 개발하면서 Event를 받았을 때 동시에 여러 행동을 해야할 경우가 있는데,

이런 상황에 옵저버 패턴을 사용하면 어떨까 하는 생각이 들었다. 

현재는 위의 초기 코드 예시처럼 아무 패턴 없이 사용하고 있지만,

조금이라도 복잡해질 조짐이 보인다면 도입해볼만 한 것 같다.