2024년 사내 알림톡 발송 코드 구조를 변경한 경험을 공유합니다.
문제를 설명하기 전에 개발하고 있는 서비스에 대해서 간략하게 설명을 드리겠습니다. 저는 디버에서 디포스트라는 서비스를 개발/유지보수하고 있습니다. 고객들이 편하게 퀵, 택배, 등기 그리고 국제우편을 보내실 수 있도록 서비스를 제공합니다. 물건을 보내는 것뿐만 아니라 고객분들께 전달되는 물품을 잘 수령하실 수 있도록 보관하고 인계해드리는 서비스도 제공합니다.
디포스트에는 지점에 매니저가 상주하면서 고객들을 응대하는 유인 지점도 있고, 매니저 상주 없이 고개들께서 자율적으로 물건을 발송하는 무인 지점도 있습니다. 퀵 서비스를 예로 들자면 고객들이 직접 퀵 기사님에게 물건을 전달하는 케이스도 있고, 디포스트 지점에 맡긴 후 퀵을 보내는 케이스도 있습니다.
구구절절 설명을 드렸지만, 각 물류 서비스마다 고객분들께 알림톡을 보내서 정보를 전달해야 하는 케이스가 많습니다.서로 다른 알림톡을 발송해야하는 케이스가 43가지입니다.
변경 전 알림톡 발송코드는 대략적으로 아래와 같습니다. (대략적인 코드를 공유드립니다.)
// *** 비즈니스 코드 시작 ***
// 알림톡 발송에 필요한 데이터 준비
const alimtalkField: IAlimtalkObPaidRegisteredMailAtPortal = {
// 생략
};
let content = '';
let templateCode = '';
// 무인 지점인지 유인지점인지 판단하여 알림톡 템플릿 코드 매칭
if (!center.staff) {
content = this.alimtalkContentsService.getNoStaffObPaidEmsAndDhlAtPortal(alimtalkField);
templateCode = 'newOutbound27';
} else {
content = this.alimtalkContentsService.getObPaidRegisteredMailAtPortal(alimtalkField);
templateCode = 'newOutbound12';
}
// 알림톡 발송 비동기로 전송
this.alimtalkRequestService
.sentAlimtalk(
'알림톡 채널', // 알림톡 채널 상수 값 전달
templateCode, // 각 상황에 맞는 템플릿 코드 전달
// ------- 실제 고객에게 발송된 알림톡에 필요한 데이터 --------
[
{
title: updatedBundle.bundleNumber,
to: alimtalkField.clientPhoneNumber,
content,
buttons: [],
},
],
// --------------------------------------------------
'알림톡 전송을 위한 API KEY', // 알림톡 전송을 위한 API KEY 전달
)
.then((sentResult) => {
if (sentResult) {
// 발송 성공 시 로그를 남기기 위한 데이터
const textMessageHistoryInfo: ITextMessageHistoryByMaster = {
//
};
// 알림톡 발송 내역 저장
this.textMessageCreateService
.createTextMessageHistory(textMessageHistoryInfo)
.catch((err) => {
slackService.sendMessage(/* 데이터 */);
});
}
})
// 알림톡 실패 시 슬랙 알림 전송
.catch((err) => {
slackService.sendMessage(/* 데이터 */);
});
제가 생각하는 구조 변경 전 알림톡 발송 코드의 문제는 아래와 같습니다.
변경 전에는 알림톡을 발송하는 위와 비슷한 코드가 최소 43 곳에 작성되어 있었습니다. 비동기적으로 호출하는 코드를 예외 처리하기 위해서 불필요한 중복이 많이 발생했습니다.
변경 전에는 알림톡 내용을 수정할 때, 수정 대상 코드를 찾는 과정이 번거로웠습니다. 우선 PM님에게 해당하는 상황의 템플릿 코드가 무엇인지 물어보고 템플릿 코드를 IDE에서 검색 후 수정 대상 코드를 찾은 후 비즈니스 로직을 살펴보고 수정 대상 코드인지 확인을 자주 했습니다. 불필요한 커뮤니케이션이 필요했고, 이 때문에 알림톡 관련 코드들을 건드리는 게 점점 성가신 작업으로 다가왔습니다.
변경 전 예시 코드를 간추려서 작성했지만 약 50줄 입니다. IDE에서 스크롤 없이 한 번에 확인이 힘든 코드 라인이었고(IDE 설정 따라 다르겠지만요), 정작 비즈니스 로직을 수정하거나 읽어야할 때는 이 알림톡 발송 코드들 때문에 방해가 되었습니다.
위의 알림톡을 발송하는 코드를 보면 알림톡을 발송하기 위해 데이터를 준비하는 코드, 그리고 적절한 알림톡 템플릿 코드를 선택해나가는 코드들이 있습니다. 그리로 상수 값으로 사용하고 있는 타켓 알림톡 채널, API KEY 역시 호출하는 쪽에서 값을 전달하고 있습니다. 알림톡을 한 번 전송하기 위해서 호출하는 쪽에서 불필요한 코드들이 수반된다고 생각했습니다.
앞서 언급한 네 가지 이유가 있었더라도 수정 요청이 없거나 방해가 되지 않았더라면 개선을 진행하지 않았을 것 같습니다. 하지만 기획, 사업, 운영팀으로부터 알림톡 관련 작업 요청이 자주 들어왔습니다. (2024년 기준 약 15회) 알림톡의 메시지를 수정하는 것은 정말 간단한 일인데, 수정 대상 코드를 찾고 작업 후 코드가 문제 없는지 확인하는데 시간이 더 소요됐습니다.
알림톡 대행사를 변경하게 되면서 알림톡을 발송하는 코드를 모두 수정하고 확인할 수 있는 작업 시간이 주어졌습니다. 좋은 기회라고 생각하고 대행사 변경과 함께 코드 구조 변경을 진행했습니다. 아래와 같은 목적을 가지고 개선 작업을 진행했습니다.
변경 전
변경 후
@Injectable()
export class WhenInternationalMailPaidWithNoStaffAlimtalkService {
private readonly aAlimtalkProviderTemplateCode = 'alimtalkCode13';
constructor(
private readonly slackService: SlackService,
private readonly alimtalkService: AlimtalkService,
) {}
async sendAlimtalk(
sendAlimtalkCommand: SendInternationalMailPaidWithNoStaffAlimtalkCommand,
) {
const alimtalkField = {
clientName: sendAlimtalkCommand.clientName,
};
const messageBody = this.getMessageTemplateAndMergeData(alimtalkField);
const buttons = [];
const isRequestSuccess = await this.alimtalkService.send({
providerTemplateCode: this.aAlimtalkProviderTemplateCode,
messageBody,
buttons,
});
return isRequestSuccess;
}
private getMessageTemplateAndMergeData(
alimtalkField: SendInternationalMailPaidWithNoStaffAlimtalkField,
) {
const messageTemplate = `✈️ #{clientName}님 안녕하세요.`;
const mergeData = {
clientName: alimtalkField.clientName,
};
return {
messageTemplate,
mergeData,
};
}
}
@Injectable()
export class AlimtalkService {
constructor(
/*
- 발송 대행사에 요청을 담당하는 클래스
- 내부적으로 로그를 쌓기 위한 레포지토리 클래스
*/
) {}
// 알림톡을 전송하고 내부 로그를 쌓는 메서드
async sendWithAlimtalkHistory(/*매개변수*/) {
// 생략
}
// 알림톡을 전송하고 로그를 남기지 않는 메서드
async send(/*매개변수*/) {
// 생략
}
// 위의 두 케이스에 해당하지 않아서 별도의 케이스를 처리하는 메서드
async sendForInbound(/*매개변수*/) {
// 생략
}
}
@Injectable()
export class AalimtalkProviderRequestService {
constructor(
private readonly httpService: HttpService,
) {}
// 알림톡 대행사에 발송 요청을 전송하는 메서드
public async requestAlimtalk(/*매개변수*/) {
// 생략
}
}
// 비즈니스 로직
const alimtalkField = {
// 데이터
};
if (!center.staff) {
this.whenInternationalMailPaidWithNoStaffService.sendAlimtalk(alimtalkField);
} else {
this.whenInternationalMailPaidWithStaffService.sendAlimtalk(alimtalkField);
}
이번 알림톡 코드 구조 변경 작업을 통해 유지보수성을 높이고, 중복을 제거함으로써 코드의 가독성을 개선했다고 생각합니다. 특히, 각 알림톡 케이스를 독립적인 클래스로 분리함으로써, 타켓 코드를 찾는 시간을 대폭 줄일 수 있었습니다. 이는 곧 개발 속도 향상으로 이어졌습니다. 또 외부에 HTTP 요청을 책임지는 객체, 사내 룰에 따라 로그를 쌓는 객체로 책임을 분리했기 때문에 이전 코드에 비해 테스트 가능한 구조로 변경되었다고 생각합니다.
반면에 케이스 별로 클래스를 분리한만큼 클래스의 개수는 많아졌습니다. 즉, 파일의 개수는 많아졌습니다. 늘어난 파일, 클래스 만큼이나 이름 짓기를 잘해야 개발하기 편하겠다는 생각을 했습니다. 이름 짓기가 어렵다면 한글 주석을 잘 작성해서 클래스 찾기를 쉽게 해야할까라는 고민도 하게 되네요.
긴 글 읽어주셔서 감사합니다.