🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

A

AI Generated

2026. 2. 1. · 14 Views

실전 프로젝트 커스텀 채널 추가하기

확장 가능한 메시징 시스템에 새로운 채널을 추가하는 방법을 실전 프로젝트로 배웁니다. 어댑터 패턴을 활용한 채널 설계부터 테스트, 배포까지 전 과정을 다룹니다.


목차

  1. 확장_가능한_시스템의_가치
  2. 요구사항_분석
  3. 채널_어댑터_설계
  4. 메시지_핸들러_구현
  5. 테스트와_디버깅
  6. 실전_배포와_운영

1. 확장 가능한 시스템의 가치

김개발 씨가 입사한 스타트업은 빠르게 성장하고 있었습니다. 처음에는 이메일로만 고객에게 알림을 보내면 충분했는데, 어느 날 기획팀에서 "카카오톡 알림도 보내야 해요"라는 요청이 들어왔습니다.

그리고 일주일 뒤에는 "슬랙 연동도 필요해요"라는 추가 요청이 이어졌습니다.

확장 가능한 시스템이란 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 쉽게 확장할 수 있는 구조를 말합니다. 마치 레고 블록처럼 새로운 조각을 끼워 넣기만 하면 되는 것입니다.

이런 설계가 되어 있으면 비즈니스 요구사항 변화에 빠르게 대응할 수 있습니다.

다음 코드를 살펴봅시다.

// 확장이 어려운 구조 - 새 채널 추가 시 코드 수정 필요
class NotificationService {
  send(channel: string, message: string) {
    if (channel === 'email') {
      // 이메일 발송 로직
    } else if (channel === 'sms') {
      // SMS 발송 로직
    } else if (channel === 'kakao') {
      // 카카오톡 발송 로직 - 매번 여기에 추가해야 함
    }
    // 채널이 늘어날수록 if문도 계속 늘어납니다
  }
}

김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 회사의 알림 시스템을 담당하게 되었는데, 처음 인수인계 받았을 때만 해도 코드가 그리 복잡해 보이지 않았습니다.

그런데 문제는 시간이 지나면서 시작되었습니다. 이메일만 보내던 시스템에 SMS가 추가되었고, 카카오톡이 추가되었고, 슬랙이 추가되었습니다.

어느새 send 메서드 하나가 300줄이 넘어가고 있었습니다. 선배 개발자 박시니어 씨가 코드 리뷰를 하다가 한숨을 쉬었습니다.

"이 구조로는 한계가 있어요. 새 채널 하나 추가할 때마다 이 파일을 건드려야 하잖아요." 확장 가능한 시스템이란 무엇일까요?

쉽게 비유하자면 콘센트와 같습니다. 우리 집 벽에 있는 콘센트는 어떤 전자제품을 꽂든 상관없이 전기를 공급합니다.

TV를 꽂든, 에어컨을 꽂든, 새로 산 공기청정기를 꽂든 콘센트 자체를 바꿀 필요가 없습니다. 소프트웨어도 마찬가지입니다.

잘 설계된 시스템은 새로운 기능(채널)을 추가할 때 핵심 코드를 건드리지 않습니다. 새 채널에 해당하는 코드만 작성해서 "꽂아주면" 됩니다.

이것이 바로 **개방-폐쇄 원칙(OCP)**입니다. 확장에는 열려 있고, 수정에는 닫혀 있어야 한다는 것입니다.

새 기능 추가는 쉽게, 기존 코드 변경은 최소화하는 것이 핵심입니다. 위 코드의 문제점을 살펴보겠습니다.

if-else 문이 계속 늘어나는 구조는 여러 위험을 안고 있습니다. 첫째, 실수로 다른 채널의 로직을 건드릴 수 있습니다.

둘째, 한 파일이 너무 비대해집니다. 셋째, 여러 개발자가 동시에 작업하면 충돌이 잦아집니다.

반면 확장 가능한 구조라면 어떨까요? 카카오톡 채널을 담당하는 개발자는 카카오톡 관련 파일만 만들면 됩니다.

슬랙 담당 개발자는 슬랙 파일만 작성합니다. 서로의 코드를 건드리지 않으니 충돌도 없고, 버그가 생겨도 해당 채널 파일만 확인하면 됩니다.

이런 구조를 만들기 위해 우리는 어댑터 패턴을 활용할 것입니다. 다음 장에서 본격적으로 요구사항을 분석하고, 어떤 채널들을 지원해야 하는지 정리해 보겠습니다.

실전 팁

💡 - 새 기능 추가 시 기존 파일 수정이 필요하다면 설계를 의심해보세요

  • if-else가 3개 이상 늘어나면 패턴 적용을 고려하세요

2. 요구사항 분석

김개발 씨는 화이트보드 앞에 섰습니다. 기획팀에서 받은 요구사항을 정리해야 했습니다.

"일단 현재 지원해야 하는 채널이 뭐가 있지?" 이메일, SMS, 카카오톡, 슬랙... 그리고 앞으로 텔레그램이나 디스코드도 추가될 수 있다고 했습니다.

요구사항 분석은 코드를 작성하기 전에 반드시 거쳐야 하는 단계입니다. 어떤 채널을 지원해야 하는지, 각 채널의 공통점과 차이점은 무엇인지, 메시지 형식은 어떻게 다른지 파악해야 합니다.

이 단계를 건너뛰면 나중에 전체 구조를 뜯어고쳐야 하는 상황이 발생합니다.

다음 코드를 살펴봅시다.

// 채널별 요구사항을 인터페이스로 정리
interface ChannelRequirement {
  name: string;           // 채널 이름
  maxLength: number;      // 메시지 최대 길이
  supportsHtml: boolean;  // HTML 지원 여부
  supportsAttachment: boolean;  // 첨부파일 지원 여부
  rateLimit: number;      // 분당 발송 제한
}

// 각 채널의 특성 정리
const channelSpecs: ChannelRequirement[] = [
  { name: 'email', maxLength: 100000, supportsHtml: true, supportsAttachment: true, rateLimit: 100 },
  { name: 'sms', maxLength: 90, supportsHtml: false, supportsAttachment: false, rateLimit: 60 },
  { name: 'kakao', maxLength: 1000, supportsHtml: false, supportsAttachment: true, rateLimit: 1000 },
  { name: 'slack', maxLength: 40000, supportsHtml: false, supportsAttachment: true, rateLimit: 50 },
];

박시니어 씨가 김개발 씨 옆에 앉았습니다. "먼저 요구사항을 제대로 정리해봐요.

무작정 코드부터 짜면 안 돼요." 김개발 씨는 고개를 끄덕였습니다. 이전 프로젝트에서 요구사항 분석을 대충 하고 넘어갔다가 나중에 전체 구조를 뜯어고친 아픈 기억이 있었습니다.

요구사항 분석의 첫 번째 단계는 지원해야 할 채널 목록을 정리하는 것입니다. 현재는 이메일, SMS, 카카오톡, 슬랙 네 가지지만, 미래에 추가될 가능성이 있는 채널도 고려해야 합니다.

텔레그램, 디스코드, 라인, 위챗 등이 후보로 거론되었습니다. 두 번째 단계는 각 채널의 공통점을 찾는 것입니다.

모든 채널은 결국 "누군가에게 메시지를 보낸다"는 동일한 목적을 가집니다. 받는 사람이 있고, 보내는 내용이 있습니다.

이것이 공통 인터페이스의 기반이 됩니다. 세 번째 단계는 각 채널의 차이점을 파악하는 것입니다.

이메일은 HTML을 지원하지만 SMS는 순수 텍스트만 가능합니다. 카카오톡은 템플릿 기반이고, 슬랙은 마크다운을 지원합니다.

메시지 최대 길이도 SMS 90자, 카카오톡 1000자, 이메일 거의 무제한으로 천차만별입니다. 네 번째로 고려할 것은 API 제한입니다.

대부분의 메시징 서비스는 분당 또는 시간당 발송 횟수를 제한합니다. 이 제한을 넘기면 일시적으로 차단당할 수 있으므로 반드시 고려해야 합니다.

위 코드에서 ChannelRequirement 인터페이스를 정의했습니다. 이렇게 채널별 특성을 데이터로 관리하면 나중에 채널 추가 시 코드 대신 설정만 추가하면 되는 경우도 생깁니다.

박시니어 씨가 한 가지 더 짚어주었습니다. "실패 처리도 생각해야 해요.

카카오톡 발송이 실패하면 어떻게 할 건가요? SMS로 대체 발송할 건가요, 아니면 재시도할 건가요?" 김개발 씨는 메모를 추가했습니다.

재시도 정책, 대체 채널 설정, 실패 알림 등 예외 상황에 대한 요구사항도 함께 정리해야 했습니다. 이렇게 요구사항을 꼼꼼히 정리하고 나니, 어떤 구조로 코드를 작성해야 할지 윤곽이 잡히기 시작했습니다.

다음 단계에서는 이 요구사항을 바탕으로 채널 어댑터를 설계해 보겠습니다.

실전 팁

💡 - 요구사항은 코드가 아닌 문서(스프레드시트, 노션 등)로 먼저 정리하세요

  • 미래에 추가될 가능성이 있는 채널도 미리 고려하면 설계가 유연해집니다

3. 채널 어댑터 설계

요구사항 분석을 마친 김개발 씨는 이제 본격적인 설계에 들어갔습니다. 박시니어 씨가 힌트를 주었습니다.

"어댑터 패턴을 써보는 게 어때요? 마치 해외여행 갈 때 쓰는 멀티 어댑터처럼요." 김개발 씨의 눈이 반짝였습니다.

어댑터 패턴은 서로 다른 인터페이스를 가진 클래스들을 함께 사용할 수 있게 해주는 디자인 패턴입니다. 220V 콘센트에 110V 기기를 연결하려면 변환 어댑터가 필요하듯이, 각기 다른 메시징 API를 통일된 인터페이스로 사용할 수 있게 해줍니다.

이를 통해 새 채널 추가 시 어댑터만 만들면 됩니다.

다음 코드를 살펴봅시다.

// 모든 채널이 구현해야 하는 공통 인터페이스
interface MessageChannel {
  readonly name: string;
  send(recipient: string, message: MessagePayload): Promise<SendResult>;
  validateRecipient(recipient: string): boolean;
  getStatus(): ChannelStatus;
}

// 메시지 페이로드 타입 정의
interface MessagePayload {
  title?: string;
  body: string;
  attachments?: Attachment[];
  metadata?: Record<string, unknown>;
}

// 발송 결과 타입
interface SendResult {
  success: boolean;
  messageId?: string;
  error?: string;
  timestamp: Date;
}

어댑터 패턴이란 무엇일까요? 해외여행을 가본 적이 있다면 멀티 어댑터를 사용해 본 경험이 있을 것입니다.

한국 플러그를 유럽 콘센트에 꽂으려면 중간에 변환 어댑터가 필요합니다. 어댑터는 서로 다른 형태의 인터페이스를 연결해주는 역할을 합니다.

소프트웨어에서도 마찬가지입니다. 이메일 API, 카카오톡 API, 슬랙 API는 모두 다른 형태를 가지고 있습니다.

이메일은 sendMail(), 카카오톡은 sendAlimtalk(), 슬랙은 postMessage()처럼 메서드 이름도 다르고 파라미터 형식도 다릅니다. 어댑터 패턴을 적용하면 이 모든 API를 send()라는 하나의 메서드로 통일할 수 있습니다.

핵심 비즈니스 로직에서는 "어떤 채널로 보내든 send만 호출하면 된다"는 단순함을 얻게 됩니다. 위 코드에서 MessageChannel 인터페이스를 정의했습니다.

이것이 바로 "콘센트 규격"입니다. 모든 채널 어댑터는 이 인터페이스를 구현해야 합니다.

send 메서드는 모든 채널의 핵심 기능입니다. 받는 사람(recipient)과 메시지 내용(MessagePayload)을 받아서 발송 결과(SendResult)를 반환합니다.

Promise를 반환하는 이유는 외부 API 호출이 비동기로 이루어지기 때문입니다. validateRecipient 메서드도 중요합니다.

이메일 주소 형식과 전화번호 형식은 다릅니다. 각 채널별로 받는 사람 정보가 올바른 형식인지 검증하는 로직이 필요합니다.

getStatus 메서드는 채널의 현재 상태를 확인합니다. API 장애, Rate Limit 초과 등의 상황을 파악하여 대응할 수 있게 해줍니다.

MessagePayload 인터페이스도 살펴보겠습니다. title은 이메일의 제목이나 푸시 알림의 타이틀에 사용됩니다.

모든 채널이 제목을 지원하지는 않으므로 옵셔널(?)로 정의했습니다. body는 필수이고, attachments와 metadata는 필요한 경우에만 사용합니다.

박시니어 씨가 추가로 조언했습니다. "인터페이스를 너무 복잡하게 만들지 마세요.

처음에는 단순하게 시작하고, 필요할 때 확장하는 게 좋아요." 김개발 씨는 이 조언을 마음에 새겼습니다. 당장 필요하지 않은 기능까지 인터페이스에 포함시키면 모든 어댑터에서 불필요한 구현을 해야 합니다.

YAGNI(You Aren't Gonna Need It) 원칙을 기억해야 합니다. 이제 설계가 완료되었습니다.

다음 장에서는 이 인터페이스를 바탕으로 실제 메시지 핸들러를 구현해 보겠습니다.

실전 팁

💡 - 인터페이스는 처음에 최소한으로 시작하고 필요할 때 확장하세요

  • readonly 키워드로 변경되면 안 되는 속성을 보호하세요

4. 메시지 핸들러 구현

설계를 마친 김개발 씨는 드디어 코드를 작성하기 시작했습니다. 먼저 이메일 채널 어댑터를 만들고, 그 다음 슬랙 어댑터를 만들 계획입니다.

"패턴만 잡히면 나머지 채널은 금방 추가할 수 있겠네요."

메시지 핸들러는 실제로 메시지를 발송하는 핵심 로직을 담당합니다. 앞서 정의한 MessageChannel 인터페이스를 구현하는 구체적인 클래스들을 만들고, 이들을 관리하는 ChannelManager를 구현합니다.

각 채널은 독립적으로 동작하면서도 통일된 방식으로 사용할 수 있습니다.

다음 코드를 살펴봅시다.

// 이메일 채널 어댑터 구현
class EmailChannel implements MessageChannel {
  readonly name = 'email';
  private transporter: NodemailerTransporter;

  constructor(config: EmailConfig) {
    this.transporter = createTransporter(config);
  }

  async send(recipient: string, message: MessagePayload): Promise<SendResult> {
    try {
      const result = await this.transporter.sendMail({
        to: recipient,
        subject: message.title || '알림',
        html: message.body,
        attachments: message.attachments,
      });
      return { success: true, messageId: result.messageId, timestamp: new Date() };
    } catch (error) {
      return { success: false, error: error.message, timestamp: new Date() };
    }
  }

  validateRecipient(recipient: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient);
  }

  getStatus(): ChannelStatus {
    return { available: true, rateLimit: { remaining: 100, resetAt: new Date() } };
  }
}

이제 실제 구현을 시작할 시간입니다. 김개발 씨는 가장 익숙한 이메일 채널부터 만들기로 했습니다.

EmailChannel 클래스는 MessageChannel 인터페이스를 implements 키워드로 구현합니다. TypeScript에서 implements는 "이 클래스는 반드시 해당 인터페이스의 모든 메서드를 구현하겠다"는 약속입니다.

만약 send 메서드를 깜빡하고 구현하지 않으면 컴파일 에러가 발생합니다. name 속성은 readonly로 선언되어 있습니다.

이메일 채널의 이름은 항상 'email'이어야 하고, 실수로 변경되면 안 되기 때문입니다. 생성자에서는 이메일 발송에 필요한 설정을 받아 transporter를 초기화합니다.

Nodemailer 같은 이메일 라이브러리를 사용한다고 가정했습니다. 실제 프로젝트에서는 SMTP 서버 정보, 인증 정보 등이 config에 담깁니다.

send 메서드를 자세히 살펴보겠습니다. try-catch로 감싸져 있는 것이 보입니다.

외부 API 호출은 언제든 실패할 수 있으므로 예외 처리가 필수입니다. 성공하면 success: true와 함께 messageId를 반환하고, 실패하면 success: false와 에러 메시지를 반환합니다.

validateRecipient 메서드는 정규표현식으로 이메일 주소 형식을 검증합니다. 완벽한 검증은 아니지만, 기본적인 형식 오류는 걸러낼 수 있습니다.

실무에서는 더 정교한 검증 라이브러리를 사용하기도 합니다. 이제 슬랙 채널도 만들어 보겠습니다.

구조는 동일하고 내부 구현만 다릅니다. 슬랙은 웹훅 URL을 사용하고, 메시지 형식도 이메일과 다릅니다.

하지만 외부에서 보기에는 똑같이 send 메서드만 호출하면 됩니다. 박시니어 씨가 코드 리뷰를 하며 말했습니다.

"좋아요. 이제 채널이 10개가 되어도 비즈니스 로직은 바꿀 필요가 없겠네요." 채널들을 관리하는 ChannelManager도 필요합니다.

채널 등록, 조회, 발송 로직을 한 곳에서 처리합니다. Map 자료구조를 사용하면 채널 이름으로 빠르게 조회할 수 있습니다.

김개발 씨는 뿌듯했습니다. 처음에는 복잡해 보였던 구조가 패턴을 적용하니 깔끔하게 정리되었습니다.

새 채널을 추가하려면 해당 채널의 어댑터 클래스만 만들고 ChannelManager에 등록하면 끝입니다.

실전 팁

💡 - 외부 API 호출은 반드시 try-catch로 감싸세요

  • 각 채널 클래스는 하나의 파일로 분리하면 관리가 편합니다

5. 테스트와 디버깅

구현을 마친 김개발 씨는 자신감에 차서 배포 버튼에 손이 갔습니다. 그때 박시니어 씨가 말렸습니다.

"잠깐, 테스트는 작성했어요?" 김개발 씨는 멈칫했습니다. "아, 그게..." 테스트 없이 배포했다가 장애가 나면 밤새 디버깅해야 할 수도 있습니다.

테스트 코드는 버그를 사전에 발견하고, 코드 변경 시 기존 기능이 깨지지 않았음을 보장합니다. 채널 어댑터는 외부 API에 의존하므로 Mock을 활용한 단위 테스트가 특히 중요합니다.

또한 통합 테스트로 실제 환경과 유사한 조건에서 검증해야 합니다.

다음 코드를 살펴봅시다.

// Jest를 사용한 이메일 채널 테스트
describe('EmailChannel', () => {
  let emailChannel: EmailChannel;
  let mockTransporter: jest.Mocked<NodemailerTransporter>;

  beforeEach(() => {
    mockTransporter = { sendMail: jest.fn() } as any;
    emailChannel = new EmailChannel({ transporter: mockTransporter });
  });

  test('유효한 이메일로 발송 시 성공을 반환한다', async () => {
    mockTransporter.sendMail.mockResolvedValue({ messageId: 'msg-123' });

    const result = await emailChannel.send('user@example.com', { body: '안녕하세요' });

    expect(result.success).toBe(true);
    expect(result.messageId).toBe('msg-123');
  });

  test('발송 실패 시 에러 정보를 반환한다', async () => {
    mockTransporter.sendMail.mockRejectedValue(new Error('SMTP 연결 실패'));

    const result = await emailChannel.send('user@example.com', { body: '안녕하세요' });

    expect(result.success).toBe(false);
    expect(result.error).toBe('SMTP 연결 실패');
  });
});

"테스트 코드 작성은 시간 낭비 아닌가요?" 김개발 씨가 조심스럽게 물었습니다. 박시니어 씨는 고개를 저었습니다.

"장애 한 번 나면 테스트 작성하는 시간의 열 배는 디버깅에 쓰게 돼요. 그리고 마음의 평화는 값을 매길 수 없죠." 채널 어댑터 테스트가 특별히 어려운 이유가 있습니다.

실제 이메일을 보내거나 카카오톡 API를 호출하면 비용이 발생하고, 테스트 속도도 느려집니다. 게다가 외부 서비스 장애로 테스트가 실패할 수도 있습니다.

이 문제를 해결하는 것이 바로 Mock입니다. Mock은 실제 객체를 흉내 내는 가짜 객체입니다.

실제 이메일을 보내는 대신, "이메일을 보내는 척"하는 Mock 객체를 사용합니다. 위 코드에서 mockTransporter는 실제 Nodemailer가 아닌 Jest의 Mock 객체입니다.

sendMail 메서드를 호출하면 실제로 이메일이 발송되지 않고, 우리가 미리 설정한 값을 반환합니다. 첫 번째 테스트 케이스를 보겠습니다.

mockResolvedValue로 "성공 시 messageId를 반환한다"고 설정했습니다. 그러면 emailChannel.send를 호출했을 때 실제 이메일 없이 성공 시나리오를 테스트할 수 있습니다.

두 번째 테스트 케이스는 실패 상황입니다. mockRejectedValue로 에러를 발생시키도록 설정했습니다.

SMTP 서버 장애, 네트워크 오류 등 다양한 실패 상황을 시뮬레이션할 수 있습니다. 박시니어 씨가 추가로 조언했습니다.

"엣지 케이스도 테스트해야 해요. 빈 문자열 입력, 특수문자가 포함된 이메일 주소, 아주 긴 메시지 등을 넣어보세요." 디버깅 팁도 알려드리겠습니다.

채널 어댑터에서 문제가 생기면 대부분 세 가지 원인 중 하나입니다. 첫째, API 키나 인증 정보 오류입니다.

환경변수가 제대로 설정되었는지 확인하세요. 둘째, 네트워크 문제입니다.

타임아웃 설정을 확인하세요. 셋째, 데이터 형식 오류입니다.

API가 요구하는 형식과 우리가 보내는 형식이 일치하는지 확인하세요. 로그도 중요합니다.

발송 전후로 적절한 로그를 남기면 문제가 생겼을 때 원인을 빠르게 파악할 수 있습니다. 다만 개인정보가 포함된 내용은 로그에 남기지 않도록 주의해야 합니다.

실전 팁

💡 - 외부 API 호출 부분은 반드시 Mock으로 테스트하세요

  • 성공, 실패, 엣지 케이스 모두 테스트 케이스로 작성하세요

6. 실전 배포와 운영

테스트도 통과하고, 코드 리뷰도 받았습니다. 이제 진짜 배포할 시간입니다.

김개발 씨는 긴장되면서도 설렜습니다. 박시니어 씨가 마지막 체크리스트를 건넸습니다.

"배포는 시작일 뿐이에요. 진짜 중요한 건 운영이죠."

배포는 코드를 서버에 올리는 것으로 끝나지 않습니다. 환경변수 관리, 모니터링 설정, 장애 대응 계획까지 갖추어야 안정적인 운영이 가능합니다.

특히 메시징 시스템은 장애 시 고객 커뮤니케이션에 직접적인 영향을 미치므로 더욱 철저한 준비가 필요합니다.

다음 코드를 살펴봅시다.

// 채널 매니저와 모니터링을 포함한 최종 구조
class ChannelManager {
  private channels: Map<string, MessageChannel> = new Map();
  private logger: Logger;
  private metrics: MetricsCollector;

  registerChannel(channel: MessageChannel): void {
    this.channels.set(channel.name, channel);
    this.logger.info(`채널 등록 완료: ${channel.name}`);
  }

  async send(channelName: string, recipient: string, message: MessagePayload): Promise<SendResult> {
    const channel = this.channels.get(channelName);
    if (!channel) throw new ChannelNotFoundError(channelName);

    const startTime = Date.now();
    const result = await channel.send(recipient, message);

    // 메트릭 수집
    this.metrics.recordSendAttempt(channelName, result.success, Date.now() - startTime);

    if (!result.success) {
      this.logger.error(`발송 실패: ${channelName}`, { error: result.error, recipient });
    }
    return result;
  }
}

// 환경변수 기반 채널 설정
const config = {
  email: { host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT) },
  slack: { webhookUrl: process.env.SLACK_WEBHOOK_URL },
  kakao: { apiKey: process.env.KAKAO_API_KEY, senderId: process.env.KAKAO_SENDER_ID },
};

드디어 배포 날이 되었습니다. 김개발 씨는 체크리스트를 하나씩 확인했습니다.

첫 번째, 환경변수 확인입니다. API 키, 비밀번호 같은 민감한 정보는 절대 코드에 직접 넣으면 안 됩니다.

환경변수나 시크릿 매니저를 통해 주입해야 합니다. 개발 환경과 운영 환경의 설정이 다르므로, 배포 전 운영 환경변수가 올바르게 설정되었는지 반드시 확인해야 합니다.

두 번째, 점진적 배포입니다. 한 번에 모든 트래픽을 새 버전으로 전환하지 않습니다.

처음에는 전체의 5% 정도만 새 버전으로 보내고, 문제가 없으면 점차 비율을 높입니다. 이것을 카나리 배포라고 합니다.

세 번째, 롤백 계획입니다. 문제가 발생하면 즉시 이전 버전으로 되돌릴 수 있어야 합니다.

"배포 취소하는 방법을 모르면 배포하지 마세요"라는 박시니어 씨의 말이 떠올랐습니다. 위 코드에서 ChannelManager에 로깅과 메트릭 수집 기능을 추가했습니다.

발송 시도할 때마다 소요 시간과 성공 여부를 기록합니다. 이 데이터를 모아서 대시보드로 시각화하면 시스템 상태를 한눈에 파악할 수 있습니다.

모니터링에서 중요한 지표는 세 가지입니다. 첫째, 발송 성공률입니다.

갑자기 떨어지면 API 장애나 인증 만료를 의심해야 합니다. 둘째, 응답 시간입니다.

평소보다 느려지면 네트워크 문제나 외부 서비스 지연일 수 있습니다. 셋째, 발송 건수입니다.

급격히 늘거나 줄면 비정상 상황일 가능성이 있습니다. 알림 설정도 중요합니다.

성공률이 95% 아래로 떨어지면 슬랙 알림, 90% 아래면 전화 알림처럼 단계별로 설정합니다. 모든 알림에 즉시 대응할 필요는 없지만, 심각한 장애는 빠르게 인지할 수 있어야 합니다.

배포가 완료되고 며칠이 지났습니다. 김개발 씨는 대시보드를 보며 뿌듯해했습니다.

일 평균 10만 건의 메시지가 99.9%의 성공률로 발송되고 있었습니다. 박시니어 씨가 다가와 어깨를 두드렸습니다.

"잘했어요. 이제 새 채널 추가 요청이 와도 어렵지 않겠죠?" 김개발 씨는 자신 있게 고개를 끄덕였습니다.

확장 가능한 시스템을 구축해 놓으니, 텔레그램이든 디스코드든 어댑터 하나만 추가하면 됩니다.

실전 팁

💡 - 환경변수는 배포 전 반드시 검증하세요 - 누락된 변수 하나가 장애를 일으킵니다

  • 성공률 99% 이상을 목표로 하되, 100%는 불가능하다는 것을 인정하고 실패 처리 로직을 갖추세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#TypeScript#AdapterPattern#MessageChannel#Integration#SystemDesign#Integration,TypeScript

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.