[250408] 추상화와 구조화의 균형

트라이캐치·2025년 4월 8일

출퇴근미니스터디

목록 보기
2/23
post-thumbnail

출근!

Notification 도메인 설계 고민 – 추상화와 구조화의 균형

📌 문제의식

알림(Notification)이라는 도메인을 설계할 때,
단순히 메서드 하나로 퉁치기보다는 다음과 같은 철학적 고민이 필요했다.

“알림의 본질은 무엇인가?
단순히 알리는 것일까, 아니면 더 복합적인 흐름을 가진 도메인인가?”

이번 설계에서는 알림 시스템을 구성할 때,
Notification 객체의 책임과 경계를 어디까지 잡을지에 대해 집중적으로 고민했다.


🎯 설계 철학

Notification 도메인은 다음과 같은 세 가지 책임만 갖는다:

  1. 메시지를 만든다 (makeMessage())
  2. 해당 메시지를 전송한다 (send())
  3. 전송 결과를 반환한다 (SendResult)

트리거(trigger)저장 여부 결정,
즉 알림을 “언제” 보내고, “기록할지 말지”는 Notification 외부에서 처리하는 것으로 책임을 분리했다.


✅ 설계 코드 (템플릿 메서드 + 제네릭 적용)

public abstract class Notification<T extends NotificationPayload> {
    protected T payload;

    public Notification(T payload) {
        this.payload = payload;
    }

    public SendResult executeSend() {
        Message message = makeMessage();
        return send(message);
    }

    protected abstract Message makeMessage();
    protected abstract SendResult send(Message message);
}

구현 예시 – Email 알림

public class EmailNotification extends Notification<EmailNotificationPayload> {

    public EmailNotification(EmailNotificationPayload payload) {
        super(payload);
    }

    @Override
    protected Message makeMessage() {
        // 이메일 제목, 본문 등을 조합
        return new Message("제목", "본문", payload.getEmailAddress());
    }

    @Override
    protected SendResult send(Message message) {
        // 이메일 발송 처리 로직
        return new SendResult(true, "EMAIL", "전송 성공");
    }
}

💡 설계 포인트 회고

  • 추상화는 본질을 좁히되, 지나치게 축소하면 실제 도메인 복잡성을 담지 못함
  • Map이나 Object로 파라미터를 넘기면 유연성은 생기지만 타입 안정성이 깨질 수 있음 → 전용 Payload 모델 도입이 필요
  • executeSend() 내 템플릿 메서드 패턴은 흐름 제어에 좋았고, 확장 가능성도 확보됨

🤔 배운 점

  • “본질을 인터페이스화한다”는 접근이 굉장히 중요하지만,
    본질 정의 자체가 지나치게 축소될 위험이 있다.
  • 설계는 결국,

    “얼마나 유연하게 구조화하되,
    필요한 책임만 정확히 잡고 있는가”
    에 대한 밸런스 문제라는 걸 다시 느꼈다.


퇴근

Template Method → Strategy Pattern 전환 설계 회고

✨ 스터디 주제

오늘은 실무에서 자주 활용되는 Template Method 패턴을 중심으로,
그 한계 상황과 Strategy Pattern으로의 전환 필요성에 대해 탐구했다.


1. Template Method 패턴 요약

정의

알고리즘의 골격(Flow)을 상위 클래스에서 정의하고,
세부 동작은 하위 클래스에서 구현하도록 강제하는 구조

예시 (Notification 예제 기반)

public abstract class Notification<T extends NotificationPayload> {
    public SendResult executeSend() {
        Message message = makeMessage();
        return send(message);
    }

    protected abstract Message makeMessage();
    protected abstract SendResult send(Message message);
}

장점

  • 공통 흐름 통제
  • 중복 제거
  • 하위 클래스에 핵심 책임만 위임 가능

2. 실무에서 마주친 한계

  • 알림 수단이 많아지고, 처리 흐름이 예외 상황에 따라 다양해짐
  • 하위 클래스 수 증가 → 코드 관리 어려움
  • 동적으로 흐름을 조합해야 할 필요가 생김

→ 템플릿 패턴의 고정된 흐름이 오히려 발목을 잡는 구조로 전락


3. Strategy Pattern으로의 전환 아이디어

구조 변화 요약

역할기존 Template 구조Strategy 구조로 전환
전처리추상 메서드 오버라이드PreProcessor 전략 객체
실행추상 메서드 오버라이드ApiCaller 전략 객체
후처리추상 메서드 오버라이드PostProcessor 전략 객체

전략 기반 예시

public class DataCollector {
    private final PreProcessor pre;
    private final ApiCaller caller;
    private final PostProcessor post;

    public void collect(Request request) {
        pre.prepare(request);
        String response = caller.call(request);
        post.handle(response);
    }
}

장점

  • 조립식 구조: 역할별 전략을 끼워넣듯 바꿀 수 있음
  • 테스트 용이
  • 예외 흐름 대응 유연함 (OCP 만족)

4. 회고

템플릿 메서드 패턴은 단순하고 반복적인 흐름에 강력하다.
하지만 복잡도와 예외 흐름이 늘어나는 순간,
전략 기반 설계로의 전환이 필수적이라는 걸 실감했다.

내일은 실제 코드 리팩토링을 통해
이 이론을 실전 구조로 바꿔볼 예정이다.


✍️ 다음 목표

  • 기존 Notification or DataCollector 구조를 Strategy 기반으로 리팩토링
  • 전략 조합 예시 작성 + 회고 정리

0개의 댓글