안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

회사 프로젝트 작업 일지

[오픈갤러리] Django 서버 정리 작업 ⑥ - 카카오 알림톡 및 네이버웍스 메시지 전송 API 정리

피그브라더 2021. 5. 31. 13:08

목차
⦁ 시작하기 전에 큰 그림부터 그리기 (작업 범위 산정하기)
⦁ 알림톡 모델링 개선
⦁ 알림톡 템플릿 딕셔너리 정의
⦁ 알림톡 관련 뷰/템플릿 개선
⦁ 알림톡 발송 코드 이관 (Lambda 함수 → 서버)
⦁ Lambda 함수 및 API 게이트웨이 리소스 삭제
⦁ 네이버웍스 API 관련 키/토큰 값 정리
⦁ 네이버웍스 API 관련 코드 정리
⦁ 네이버웍스 메시지 봇/방 정리
⦁ 네이버웍스 메시지 봇/방 목록 조회 페이지 개발
⦁ 크론 작업 코드 정리 및 주석 작성
⦁ 크론 작업 목록 조회 페이지 개발

 

오픈갤러리 프로젝트에서는 메시지 전송 관련 API를 크게 두 종류 사용하고 있다. 하나는 카카오 알림톡 API이고, 다른 하나는 네이버웍스 메시지 전송 API이다. 카카오 알림톡 API는 고객분들께 어떠한 알림을 주고자 할 때 사용하고, 네이버웍스 메시지 전송 API는 사내 메신저인 네이버웍스 메시지에서 봇을 통해 실무진분들께 어떠한 알림을 주고자 할 때 사용한다. 그런데 이 두 종류의 API는 자주 사용되는 것임에도 불구하고 리팩토링이 잘 되어 있지 않았기 때문에 개발이나 유지보수가 매우 힘들었다. 그래서 이번 기회에 확실하게 한 번 정리해보기로 하였다.

 

카카오 알림톡 API

카카오톡 친구추가 없이 카카오톡 앱을 통해 정보성 메시지를 고객에게 보내는 메시지 API 상품 1. 기업의 정보성 메시지 발송 기능 (주문/예약/결제, 카드결제/취소, 은행 입출금, 택배사 배송,

www.apistore.co.kr

 

메시지 전송 - 메시지 Bot - NAVER WORKS Developers Document

메시지 전송 메시지 Bot이 특정 사용자에게 메시지를 전송할 수 있다. accountId에 계정 정보를 입력하여 메시지를 전송한다. 계정 정보는 이메일 형태 또는 그룹명 형태로 되어 있다. 클라이언트

developers.worksmobile.com

 


▎시작하기 전에 큰 그림부터 그리기 (작업 범위 산정하기)

카카오 알림톡 API와 네이버웍스 메시지 전송 API를 정리하는 것은 대단히 큰 작업이 될 것이 뻔했다. 그래서 무작정 바로 작업에 들어가지 않고 큰 그림부터 그려보기로 하였다. 즉, 작업해야 하는 범위가 대략적으로 어느 정도 되는지 미리 한 번 산정해보면서 정리해야 하는 것들의 목록을 나열해보는 것이다. 이러한 모든 과정은 Google Docs에 기록하면서 진행하였다. 기록한 내용을 순서대로 정리하자면 대략 다음과 같다.

 

1. 알림톡 목록 정리

오픈갤러리 프로젝트는 API 스토어를 통해 카카오 알림톡 API를 이용하고 있기 때문에, API 스토어에 들어가서 등록되어 있는 모든 알림톡 템플릿 코드들의 목록을 가져왔다. 그리고 각 템플릿 코드별로 템플릿의 이름, 템플릿이 사용되고 있는 코드의 위치, 그리고 호출 경로를 파악하여 정리하였다. 여기서 말하는 호출 경로란 해당 템플릿에 대한 카카오 알림톡 API가 AWS의 Lambda 함수에서 호출되는지, 아니면 서버에서 호출되는지를 뜻한다.

 

2. Lambda 함수 목록 정리

오픈갤러리 프로젝트에는 AWS Lambda 함수에 의해 발송되는 알림톡도 있고, 서버에서 직접 발송되는 알림톡도 있었다. 그렇지만 사실 대부분의 알림톡들은 Lambda 함수에 의해 발송되고 있었기 때문에 Lambda 함수들의 개수도 상당히 많았다. 그런데 오랜 기간 개발자분들과 많은 얘기를 나눴던 주제 중 하나가 바로 Lambda 함수를 사용하는 것이 과연 이득인가에 대한 것이었다. 우리는 다음과 같은 세 가지 근거를 토대로 Lambda 함수를 사용하는 것은 오히려 단점이 더 크다는 결론을 내리게 되었다.

첫째, 현재 구현 방식을 봤을 때 서버가 API 게이트웨이를 통해 Lambda 함수를 호출하고 나면 어차피 서버도 Lambda 함수가 카카오 알림톡 API 호출에 대한 응답을 돌려줄 때까지 기다려야 했다. 이럴 바에야 그냥 서버에서 직접 카카오 알림톡 API를 호출하는 것이 더 이득일 것이라는 판단을 내리게 되었다. (가장 큰 이유)

둘째, 오픈갤러리의 주 기술 스택이 Python 기반의 Django이다 보니 Node.js에 미숙한 개발자분들이 더러 있었다. 그런데 그렇다고 해서 Node.js를 따로 공부할 만한 큰 동기가 존재하지도 않았다. 대부분의 알림톡 관련 개발은 기존의 Lambda 함수 코드를 잘 복붙 하면 어떻게든 되었기 때문이다. 그러나 Node.js 코드 한 줄 한 줄에 대한 이해가 깊지 못했기에 조금만 변칙적인 코드를 작성하는 것도 큰 부담으로 다가오는 경우가 많았다.

셋째, 지금까지 Lambda 함수 코드의 작성 및 배포가 매우 체계적이지 못한 방식으로 이뤄져 왔다. 단순히 압축해서 zip 파일로 업로드를 하기도 했고, AWS 콘솔에서 직접 Node.js 코드를 작성하기도 했다. 그렇다 보니 개발이나 유지보수의 측면에서 상당히 비효율적이었다. 물론 Lambda 함수 코드의 작성 및 배포를 체계적으로 하기 위한 방법을 더 검토해볼 수도 있었지만, 그냥 서버에서 직접 카카오 알림톡 API를 호출하면 되는 것을 불필요하게 리소스를 낭비한다는 느낌이 강했기 때문에 거부감이 있어 왔던 것 같다.

 

이러한 이유로, 우리는 모든 알림톡 발송 코드를 Lambda에서 서버로 이관하는 것에 합의하였다. 즉, 서버에서 API 게이트웨이를 통하여 Lambda 함수를 호출하는 것이 아니라, 직접 카카오 알림톡 API를 호출하는 것으로 통일하자는 것이다. 물론 이미 그러한 방식으로 발송되고 있는 알림톡들도 존재하긴 했지만, 매우 소수였을 뿐이다. 이제는 모든 알림톡들을 이 방식으로 이관하는 것이 최종 목표가 되었다.

Lambda 함수 목록을 정리하기로 한 것은 이러한 맥락 때문이다. 우선 현재 정의되어 있는 Lambda 함수들을 전부 파악하고 있어야 이후 알림톡 발송 코드의 이관을 더욱 수월하게 할 수 있고, 이관을 마치고 난 후에도 지워야 하는 Lambda 함수들의 목록을 쉽게 파악할 수 있을 것이기 때문이다.

 

각 Lambda 함수별로 함수의 이름과 호출 경로를 파악하여 정리하였다. 여기서 말하는 호출 경로란 해당 Lambda 함수가 API 게이트웨이에 의해 호출되는 것인지, S3에 의해 호출되는 것인지, 또 다른 Lambda 함수에 의해 간접 호출되는 것인지, 혹은 AWS 크론에 의해 호출되는 것인지를 뜻한다.

그런데 이렇게 정리하고 보니, 당장 사용하지 않는 Lambda 함수들도 꽤나 발견되었었다. 이러한 Lambda 함수들은 모니터링 결과를 참조하여 최근에 호출되고 있지 않음을 더블 체킹 한 후 바로 삭제하였다. 이렇게라도 Lambda 함수들의 개수를 최소화시켜야 이후의 작업이 더욱 수월해질 것 같았기 때문이다.

 

3. API 게이트웨이 리소스 목록 정리

바로 위에서 언급한 것과 같은 맥락이다. 우리는 모든 알림톡 발송 코드를 Lambda에서 서버로 이관할 것이기 때문에, 최종적으로 이관을 마치고 난 후에는 Lambda 함수뿐 아니라 그것에 연결된 API 게이트웨이 리소스들도 함께 지워야 한다. 따라서 현재 정의되어 있는 모든 API 게이트웨이 리소스들의 목록을 정리하기로 하였다. 각 API 게이트웨이 리소스별로 정의되어 있는 메소드(GET, POST 등)들의 종류와 연결되어 있는 Lambda 함수의 이름을 파악하여 정리하였다.

이것도 마찬가지로, 정리하고 보니 당장 사용하지 않는 API 게이트웨이 리소스들이 꽤나 발견되었었다. 이후 작업의 편의를 위해 이러한 리소스들도 바로 삭제하여 리소스들의 개수를 최소화시켰다.

 

4. 네이버웍스 API 사용 목록 정리

카카오 알림톡 API 관련 정리가 끝나고 나면, 이후의 작업은 네이버웍스 메시지 전송 API 관련 정리가 진행될 것이었다. 따라서 네이버웍스 메시지 전송 API가 사용되고 있는 코드의 위치를 파악하여 정리하였다. 그리고 추가적으로 네이버웍스 일정 목록 조회 API가 사용되고 있는 코드의 위치도 함께 파악하여 정리하였다. 일정 목록 조회 API는 중요하게 사용되는 기능은 아니라 크게 다루지는 않았지만, 간단히 정리는 할 수도 있을 것 같아서 함께 정리하였다.

 

5. 크론 작업 목록 정리

크론 작업이란 서버 단에서 정해진 시각에(혹은 정해진 주기로) 자동 실행되는 예약 스크립트를 말한다. 오픈갤러리 프로젝트에서 상당량의 크론 작업들이 카카오 알림톡 발송 API를 사용하므로 크론 작업 목록 정리도 이 작업에 포함시켰다. 이를 위해, 각 크론 작업별로 스크립트 파일의 이름과 실행이 되는 시각(혹은 주기)를 파악하여 정리하였다.

 


▎알림톡 모델링 개선

오픈갤러리 프로젝트에는 알림톡을 Notitalk라는 이름의 모델로 모델링하고 있다. 이름이 의미하는 것처럼 하나의 알림톡이 발송될 때마다 하나의 Notitalk 객체가 생성되는 것이 의도였다. 그러나 실제로는 서버에서 발송하는 소수의 알림톡들에 대해서만 적용되는 이야기였기 때문에 유의미한 기능을 하지 못하고 있었다. 즉, Lambda에 의해 발송되는 알림톡들의 발송 내역은 데이터베이스에 저장되고 있지 않았던 것이다. 따라서 앞서 말한 이관 작업을 통해 모든 알림톡들의 발송 내역이 Notitalk라는 모델을 통해 데이터베이스에 저장되도록 하는 것이 중요했다.

 

그러나 이관 작업보다 먼저 진행해야 하는 작업이 있었다. 바로 Notitalk 모델의 개선이다. 기존의 Notitalk 모델은 필드와 메소드들이 정교하게 설계되지 못해서 관련된 개발이나 유지보수를 하는 것에 있어 늘 어려움을 겪곤 하였기 때문이다. 그래서 일단은 Notitalk 모델의 필드와 메소드들을 다시 올바르게 재정의해보기로 하였다.

그런데 모델링의 개선이 생각보다 쉽지 않았다. 왜냐하면 API 스토어에서 카카오 알림톡 API 호출에 대해 반환해주는 응답 데이터가 API 스토어에서 제공하는 API 명세와 완벽하게 일치하지는 않았기 때문이다. API 스토어에서 제공하는 API 명세에 따르면 응답 데이터로 X, Y가 올 수 있다고 되어 있는데, 실제로는 Z가 오는 경우도 있었던 것이다. 이러한 경우 Z라는 값이 무엇을 의미하는 것인지 알 방법이 없었기 때문에 팀 내부에서 여러 번 실험해 보며 그 값의 의미를 추론해야만 했다. 어쩌면 과거에 Notitalk 모델이 정교하게 설계되기 어려웠던 이유 중 하나가 이것이 아니었을까 하고 추측해본다.

 

그렇게 API 스토어의 명세를 열심히 보완한 끝에, 드디어 Notitalk 모델을 새로 설계하는 데 성공하였다. 기존의 필드와 메소드들 중 불필요한 것은 삭제하고, 필요한 것은 훨씬 확장성 있는 구조로 개선하였다. 다만 하나의 큰 걱정거리는 기존 Notitalk 데이터의 마이그레이션이었다. 왜냐하면 기존의 Notitalk 모델과 달라진 점이 많아서 필드 값들의 이관이 어려웠기 때문이다. 그런데 다행히도 PM분과의 논의 끝에 기존 데이터들은 유의미한 기능을 하지 못하고 있었기에 전부 삭제해도 된다는 결론을 내리게 되었다. 기존 데이터까지 유지해야 했다면 마이그레이션 스크립트를 짜느라 또 상당히 골머리를 썩였을 것이다.

 

최종적으로 개선된 Notitalk 모델의 코드는 (대략) 다음과 같다. 각 줄의 의미에 대해서는 아래에서 설명한다.

class Notitalk(TimeStampedModel):
    BTN_TYPE_CHOICES = ('배송조회', '웹링크', '앱링크', '봇키워드', '메시지전달')
    RESULT_CHOICES = (
        ...
    )
    REPORT_STATUS_CHOICES = (
        ...
    )
    REPORT_RESULT_CHOICES = (
        ...
    )
    REPORT_FAILED_RESULT_CHOICES = (
        ...
    )

    template_code = models.CharField('템플릿 코드', max_length=16)
    template_params = JSONField('템플릿 변수', default=dict)
    receiver_name = models.CharField('수신자 이름', max_length=32)
    receiver_phone = models.CharField('수신자 전화번호', max_length=32)

    # 버튼 정보
    btn_types = ArrayField(
        verbose_name='버튼 타입',
        base_field=models.CharField(max_length=8),  # BTN_TYPE_CHOICES 참고
        size=5,
        default=list
    )
    btn_txts = ArrayField(
        verbose_name='버튼 이름',
        base_field=models.CharField(max_length=16),
        size=5,
        default=list
    )
    btn_urls1 = ArrayField(
        verbose_name='버튼 링크 주소 1',
        base_field=models.CharField(max_length=200),
        size=5,
        default=list
    )
    btn_urls2 = ArrayField(
        verbose_name='버튼 링크 주소 2',
        base_field=models.CharField(max_length=200),
        size=5,
        default=list
    )

    # 연결 모델
    target_object_type = models.ForeignKey(
        ContentType,
        related_name='target_notitalk_set',
        related_query_name='target_notitalk',
        on_delete=models.CASCADE,
        null=True
    )
    target_object_id = models.PositiveIntegerField('연결된 타겟 모델 ID', null=True)
    target_object = GenericForeignKey('target_object_type', 'target_object_id')
    related_object_type = models.ForeignKey(
        ContentType,
        related_name='related_notitalk_set',
        related_query_name='related_notitalk',
        on_delete=models.CASCADE,
        null=True
    )
    related_object_id = models.PositiveIntegerField('연결된 기타 모델 ID', null=True)
    related_object = GenericForeignKey('related_object_type', 'related_object_id')

    # 알림톡 발송 요청 관련
    cmid = models.CharField('cmid', max_length=64)
    result = models.CharField('알림톡 발송 요청 결과', max_length=3, choices=RESULT_CHOICES)
    result_msg = models.TextField('알림톡 발송 요청 결과 메시지')  # result 필드의 값이 '999'일 때 에러 메시지를 저장하는 역할

    # 알림톡 발송 결과 조회 관련
    report_status = models.CharField('알림톡 발송 상태', max_length=1, choices=REPORT_STATUS_CHOICES)
    report_result = models.CharField('알림톡 발송 결과', max_length=1, choices=REPORT_RESULT_CHOICES)
    report_failed_result = models.CharField('문자메시지 발송 결과', max_length=2, choices=REPORT_FAILED_RESULT_CHOICES)
    
    @classmethod
    def set(cls, template_code, template_params, receiver_name, receiver_phone, **kwargs):
        ...

    def send(self, save=True):
        ...

    def report(self, save=True):
        ...

 

1. template_code, template_params, receiver_name, receiver_phone

template_code, receiver_name, receiver_phone은 이름 그대로 이해하면 되므로 설명을 생략한다.

 

template_params는 템플릿 변수들을 담는 딕셔너리가 저장되는 필드로, 대략 다음과 같은 형태이다.

template_params = {
    'name1': '피그',
    'name2': '브라더'
}

이 필드의 값은 발송된 알림톡의 내용을 동적으로 계산할 때 사용된다. 즉, 발송된 알림톡의 내용을 특정 필드에 저장하지는 않는다. 이는 수많은 알림톡 발송 건들의 내용을 모두 저장하면 데이터베이스에 무리가 갈 것 같아서 내린 판단이었다. 어떻게 이 필드의 값을 이용하여 발송된 알림톡의 내용을 계산할 수 있는지에 대해서는 아래에서 다룬다.

 

2. btn_types, btn_txts, btn_urls1, btn_urls2

btn_types, btn_txts, btn_urls1, btn_urls2는 각각 버튼의 타입들, 이름들, 첫 번째 링크 주소들, 두 번째 링크 주소들을 담는 배열이 저장되는 필드로, 같은 인덱스의 값은 같은 버튼의 정보를 표현한다. 버튼의 링크 주소는 해당 버튼의 타입이 '웹링크' 혹은 '앱링크'일 때만 필요하다. 만약 버튼의 타입이 '웹링크'라면 첫 번째 링크 주소는 모바일 웹의 주소, 두 번째 링크 주소는 PC 웹의 주소가 된다. 그리고 버튼의 타입이 '앱링크'라면 첫 번째 링크 주소는 iOS 앱의 주소, 두 번째 링크 주소는 안드로이드 앱의 주소가 된다.

 

3. target_object, related_object 관련 필드

target_object_type, target_object_id, target_object는 해당 알림톡을 수신한 사람에 해당하는 모델 객체를 연결해주기 위한 GenericForeignKey 필드를 구성한다.

 

related_object_type, related_object_id, related_object는 해당 알림톡 발송과 밀접한 연관이 있는 모델 객체를 연결해주기 위한 GenericForeignKey 필드를 구성한다.

 

4. 알림톡 발송 요청 관련 필드 (cmid, result, result_msg)

API 스토어를 대상으로 알림톡 발송 요청 API를 호출했을 때 받는 응답 데이터와 관련된 필드들이다. 알림톡 발송 요청이란, API 스토어에게 "이 메시지를 알림톡으로 발송해달라고 카카오 서버에게 말해줘"라고 말하는 것과 같다.

 

cmid는 해당 알림톡 발송 요청 건에 대해 API 스토어가 발급해주는 고유한 문자열 값이 저장되는 필드이다.

 

result는 알림톡 발송 요청 결과가 저장되는 필드이다. '200'이라면 발송 요청에 성공한 것이고, 아니라면 발송 요청에 실패한 것이다. 단, 발송 요청에 성공했어도 실제로 발송이 성공했다는 보장은 없다. 실제로 발송이 성공했는지는 다시 API 스토어를 대상으로 알림톡 발송 결과 조회 API를 호출해야 알 수 있다.

 

result_msg는 서버 단의 에러에 의해 발송 요청 자체가 실패한 경우 그 에러 메시지를 저장하는 필드이다. 이는 발송 요청 자체가 API 스토어에게 전달되지 못한 것이기 때문에, API 스토어의 응답 데이터와는 무관한 필드이다.

 

5. 알림톡 발송 결과 조회 관련 필드 (report_status, report_result, report_failed_result)

API 스토어를 대상으로 알림톡 발송 결과 조회 API를 호출했을 때 받는 응답 데이터와 관련된 필드들이다. 알림톡 발송 결과 조회란, "내가 아까 요청한 이 알림톡이 실제로 잘 발송되었는지 알려줘"라고 말하는 것과 같다.

 

report_status는 알림톡 발송 상태가 저장되는 필드이다. '1'은 API 스토어가 아직 카카오 서버에게 알림톡 발송 요청을 하지 않은 상태이고, '2'는 API 스토어가 카카오 서버에게 알림톡 발송 요청을 완료한 상태이다. 그리고 '3'은 성공이든 실패든 알림톡 발송 결과를 (카카오 서버로부터) 수신한 상태를 의미한다. 마지막으로 '4'는 성공이든 실패든 문자메시지(LMS) 발송 결과를 수신한 상태를 의미한다. 기본적으로 '1' → '2' → '3' → '4'의 방향으로 상태가 변화하는 듯하며, '4'의 상태에 도달하려면 알림톡 발송이 실패했어야 한다.

 

report_result는 알림톡 발송 결과가 저장되는 필드이다. '0'이라면 알림톡 발송에 성공한 것이고, 아니라면 알림톡 발송에 실패한 것이다. 알림톡 발송에 실패한 경우 문자메시지 발송을 시도하게 된다.

 

report_failed_result는 문자메시지 발송 결과가 저장되는 필드이다. '0'이라면 문자메시지 발송에 성공한 것이고, 아니라면 문자메시지 발송에 실패한 것이다. '00'이라는 값은 API 명세에 존재하지 않는데, 기본적으로 알림톡 발송에 성공하여 문자메시지 발송을 시도조차 하지 않은 경우 이 값으로 응답해주는 것을 확인하였다. 그러나 이 경우가 아닌, 알 수 없는 이유로 문자메시지 발송에 실패하는 경우에도 이 값으로 응답해주는 것 같았다.

 

6. 알림톡 객체 생성, 알림톡 발송 요청, 알림톡 발송 결과 조회 메소드 (set, send, report)

set()은 알림톡 객체를 생성하고 이를 반환하는 클래스 메소드이다. save() 메소드를 호출하진 않는다.

 

send()는 API 스토어를 대상으로 알림톡 발송 요청 API를 호출하는 메소드이다. 기본적으로는 save() 메소드를 호출하여 그 발송 요청 결과를 저장한다.

 

report()는 API 스토어를 대상으로 알림톡 발송 결과 조회 API를 호출하는 메소드이다. 기본적으로는 save() 메소드를 호출하여 그 조회 결과를 저장한다.

 


▎알림톡 템플릿 딕셔너리 정의

별도의 파일을 생성하여 그곳에 API 스토어에 등록된 템플릿들의 정보를 딕셔너리로 정의해두었다. 딕셔너리의 형태는 대략 다음과 같다. 만약 나중에 어떠한 템플릿이 더 이상 사용되지 않게 된다면 그 템플릿은 NOTITALK_TEMPLATE_ARCHIVED 딕셔너리로 옮기도록 규칙을 합의하였다.

# 현재 유효하게 사용하고 있는 템플릿
NOTITALK_TEMPLATE = {
    'ABC0123': {
        'name': '오픈갤러리 가입 환영',
        'msg': (
            '[오픈갤러리]\n'
            '{name1} {name2} 고객님, 오픈갤러리 가입을 환영합니다.\n'
            '저희에게 보내주신 관심에 보답할 수 있도록 더욱 노력하겠습니다. 감사합니다.'
        )
    },
    ...
}

# 현재는 사용하지 않지만 과거에 사용하던 템플릿
NOTITALK_TEMPLATE_ARCHIVED = {
    ...
}

템플릿 정보를 이렇게 별도로 정의해둠으로써, 각 Notitalk 객체에 해당하는 알림톡 발송 내용을 복원하는 것을 가능하게 하였다. 해당 Notitalk 객체의 template_code 필드 값을 이용하여 해당 템플릿 정보를 가져온 다음에, template_params 필드 값을 이용하여 문자열 formatting을 수행하면 되기 때문이다. 다음과 같이 말이다.

NOTITALK_TEMPLATE[notitalk.template_code]['msg'].format(
    **notitalk.template_params
)

따라서 알림톡 발송 내용을 굳이 필드로 저장하지 않아도 되어 데이터베이스에 주는 부담을 덜 수 있게 되었다.

 


▎알림톡 관련 뷰/템플릿 개선

기존에도 알림톡 발송 내역을 조회하는 페이지와 알림톡을 수동으로 발송할 수 있는 페이지 등이 존재했다. 그런데 알림톡 모델링을 대폭 개선하면서 이러한 페이지들도 개선이 필요해졌다. 전반적으로 여러 뷰/템플릿들을 개선하였지만, 대표적인 것만 소개하자면 다음과 같이 두 가지이다.

 

1. 알림톡 발송 내역 조회 페이지

먼저 알림톡 발송 내역 조회 페이지의 경우, 개선된 알림톡 모델의 필드 값들을 최대한 사용자의 눈높이에 맞춰서 일상 용어로 풀어 노출하도록 개선하였고, 전체적인 레이아웃도 깔끔하게 정리하였다. 기존 페이지의 경우에는 데이터의 표현이 일반 사용자가 해석하기 어렵게 되어 있었다.

 

2. 알림톡 수동 발송 페이지

다음으로 알림톡 수동 발송 페이지의 경우, 개선이라기보다는 사실상 기존 페이지를 폐기하고 새로운 페이지를 재개발했다고 보는 게 맞다. 기능이 대폭 변화했기 때문이다. 기존 페이지에서는 이름과 전화번호만 변수로 존재하는 템플릿들에 대해서만 수동 발송이 가능했다. 그러나 정리하는 김에 확장성 있는 유용한 기능까지 개발해보고 싶다는 마음에, 모든 템플릿들에 대해서 수동 발송이 가능한 페이지를 새로 개발하였다. 이는 원하는 템플릿을 선택한 후 템플릿 변수에 직접 값을 입력하여 내용을 완성시켜 수동 발송을 할 수 있도록 구현하였다. 대략 다음과 같은 형태이다.

 

 


▎알림톡 발송 코드 이관 (Lambda 함수 → 서버)

본격적으로, Lambda 함수에 작성되어 있던 알림톡 발송 코드들을 전부 서버로 이관하였다. 대략 30개 정도의 템플릿에 대해 코드를 전부 옮겨야 했기에 리소스가 만만치 않게 들었다. 더군다나, 단순히 옮기는 게 아니라 Node.js 코드를 Python으로 옮기는 것이기 때문에 Node.js 코드를 전부 읽고 완벽히 이해해서 옮겨야만 했다. 단순히 발송 요청에서 그치는 것이 아니라 템플릿별로 커스터마이징 된 로직이 따로 있을 수도 있기 때문이었다.

 

한편, 코드를 이관할 때 가장 중요시한 것은 바로 코드 작성 방식의 일관성 통일이었다. Notitalk.set() 메소드와 Notitalk.send() 메소드의 호출 과정을 하나의 패턴에 일치시켰고, 무엇보다 예외 처리 방식까지도 통일시켰다.

이후의 알림톡 관련 개발은 기존 코드를 가져와서 진행할 가능성이 크다고 생각했고, 이를 위해서는 기존 코드가 전부 하나의 패턴으로 작성되어 있어야 한다고 판단했다. 또한 작성 방식의 통일은 개발의 효율성도 높인다고 생각했다. 작성 방법을 정해주면 작성 방법에 대한 고민의 시간이 필요 없어지기 때문이다.

 

단, 주의할 것은 그 하나의 패턴이라는 것이 최대한 모든 경우에서 적용이 가능한 것이어야 한다는 것이었다. 그 패턴을 적용할 수 없는 경우가 생겨버리면 기존 패턴이랑 어긋난 패턴으로 작성해야 해서 일관성을 해치게 되기 때문이다. 필자는 크게 두 종류로 나눠서 하나의 패턴을 고민했다. 하나는 알림톡을 하나만 보내는 경우였고, 다른 하나는 알림톡을 여러 개 보내는 경우였다. 이 두 가지 경우 각각에 대해 최대한 범용적인 패턴을 고안하여 적용하였다. (자세한 코드는 여기서 공개하기 어려운 점 양해 바란다.)

 


▎Lambda 함수 및 API 게이트웨이 리소스 삭제

알림톡 발송 코드를 서버로 이관하였기 때문에, 기존의 Lambda 함수들과 그것들에 연결된 API 게이트웨이 리소스들은 더 이상 필요가 없어졌다. 그래서 해당 Lambda 함수 및 API 게이트웨이 리소스들은 실서버 배포 후 몇 주 후에 삭제하였다. 바로 삭제하지 않은 이유는 정말 혹시나 하는 마음 때문이었고, 삭제할 때도 모니터링 결과를 일일이 참조하면서 더 이상 호출되지 않음을 확인하고 삭제하였다. 늘 그렇듯, 개발에서의 삭제 작업은 신중하고 또 신중하게 진행해야 하기 때문이다.

 


▎네이버웍스 API 관련 키/토큰 값 정리

네이버웍스 API 관련 코드를 정리하기 전에, 실제로 오픈갤러리 프로젝트가 사용하는 네이버웍스 API 관련 키/토큰 값들 중 필요한 것만 남기도록 정리해보기로 하였다. 실제로 네이버웍스 개발자 콘솔에 들어가서 현재 발급되어 있는 모든 키/토큰 값들을 확인해 보니, 더 이상은 사용하지 않는 키/토큰 값들이 더러 남아 있었다. 오픈갤러리 프로젝트는 서비스 API에 해당하는 일정 목록 조회 API서버 API에 해당하는 메시지 전송 API를 사용하고 있기 때문에, API ID 1개(공통), 서비스 API 컨슈머 키 1개, 서버 API 컨슈머 키 1개, 서버 토큰 1개를 남겨두었다.

서비스 API는 OAuth 2.0에 기반한 API로, 쉽게 말해서 인증(특정 계정의 권한)이 필요한 API를 말한다. 예를 들어 일정 목록 조회 API의 경우, 자신의 일정 목록을 확인하는 것이기 때문에 인증이 필요하다. 반면 서버 API는 그렇지 않은 API이다. 예를 들어 메시지 전송 API에는 특정 계정의 권한이 필요하지 않다.

 


▎네이버웍스 API 관련 코드 정리

키/토큰 값들을 정리한 후, 본격적으로 네이버웍스 API 관련 코드들을 정리하였다.

 

먼저, 일정 목록 조회 API를 사용하는 부분은 코드를 깔끔하게 다듬어서 가독성을 높이는 정도로만 정리하였다.

 

반면, 메시지 전송 API를 사용하는 부분은 리팩토링까지 함께 진행하였다. 봇을 이용하여 메시지를 전송하는 로직은 꽤나 자주 사용되기 때문에, 관련 기능을 별도의 함수(send_naverworks_message)로 정의해두고 언제든 가져다 사용할 수 있도록 하였다. 또한, 실제로 메시지 전송 API를 이용하여 전송을 하는 메시지들의 내용도 포맷을 전부 통일하였다. 왜냐하면 개발자가 새로운 기능을 개발할 때마다 매번 메시지 내용의 포맷을 임의로 정하다 보니 일관성이 전혀 없었기 때문이다. 다음은 send_naverworks_message() 함수의 코드이다.

def send_naverworks_message(bot, chat_room, message):
    # BOT and CHAT_ROOM are pre-defined dictionaries as constants.
    bot_id = BOT[bot]['id']
    chat_room_id = CHAT_ROOM[chat_room]['id'] if settings.PRODUCT_MODE else CHAT_ROOM['TEST_ROOM']['id']

    API_URL = 'https://apis.worksmobile.com/r/{}/'.format(settings.NAVERWORKS_API_ID)
    SUB_URL = 'message/v1/bot/{}/message/push'.format(bot_id)
    headers = {
        'Content-type': 'application/json',
        'consumerKey': settings.NAVERWORKS_SERVER_CONSUMER_KEY,
        'Authorization': 'Bearer {}'.format(settings.NAVERWORKS_SERVER_TOKEN)
    }
    payload = {
        'roomId': str(chat_room_id),
        'content': {
            'type': 'text',
            'text': message
        }
    }

    response = requests.post(API_URL + SUB_URL, headers=headers, json=payload)
    return response.json()

 


▎네이버웍스 메시지 봇/방 정리

오픈갤러리 프로젝트는 여러 봇들을 이용하여 여러 방들에 메시지를 전송해주고 있었다. 그러나 개발자들이 그 방들에 전부 들어가 있던 것도 아니어서 현재 무슨 방들이 봇 알림을 받고 있는지 파악하기 어려웠고, 그 방들의 이름도 규칙이 특별히 없어서 어떤 방이 봇 알림을 받는 방인지 찾는 것조차 어려웠다. 그렇다 보니 개발 과정에서의 유지보수도 힘들었을 뿐 아니라, 실무진분들도 업무 상에 혼란을 겪는 일이 잦았다. 따라서 이번 작업을 통해 네이버웍스 메시지 봇/방들을 깔끔하게 정리해보기로 하였다.

 

이를 위해서 가장 먼저 해야 하는 작업은, 현재 유효하게 사용되고 있는 봇/방들의 목록을 정확히 파악하고 각 방의 역할을 분명히 하는 것이었다. 그러나 안타깝게도 네이버웍스에서는 봇 알림을 받고 있는 방들의 목록을 조회할 수 있는 별다른 API를 제공하고 있지 않았기 때문에, 직접 직원분들을 한 분씩 찾아가면서 그분이 소속된 방, 그 방에서 알림을 주고 있는 봇, 그리고 그 방에서 받고 있는 알림의 내용을 파악해보는 것 말고는 별다른 방법이 없었다. 그렇게 직접 열심히 발로 뛰며 조사한 끝에, 현재 유효한 봇/방들의 정체에 대해 파악하고 정리할 수 있었다.

 

 

유효한 봇/방들의 정체를 전부 파악하였으니, 이제는 이런 혼란이 생기지 않도록 확실하게 규칙을 정해두는 것이 중요했다. 먼저, 봇 알림을 받고 있는 방들의 이름을 일관된 포맷으로 통일하였다. PM분과 논의하여 그 포맷을 정하였는데, 이때 가장 중요하게 고려된 것은 봇 알림을 받는 방이라는 것이 눈에 띄게 하는 포맷을 정하는 것이었다. 이로써 봇 알림을 받는 방을 검색하기 쉽게, 그리고 그 방들이 눈에 띄기도 쉽게 만들었다.

 

다음으로, 봇 알림을 받는 모든 방들에는 PM분이 소속되도록 하였다. 전체를 통솔할 수 있는 누군가가 모든 방들에 들어가 있는 것이 관리 측면에서 효율적이라고 판단했기 때문이다. 사실, 개발자분들도 모든 방들에 소속되도록 할까 고민을 했지만, 다음 섹션에서 언급할 기능 개발로 인해 그렇게까지는 할 필요가 없다고 판단하였다. 이에 대해서는 다음 섹션에서 설명한다.

 


▎네이버웍스 메시지 봇/방 목록 조회 페이지 개발

네이버웍스 메시지 봇/방들의 정체를 파악하는 과정에서, 직접 직원분들께 한 분씩 찾아가며 여쭤보는 것은 필자가 마지막이어야 한다고 생각했다. 이를 위해, 현재 유효하게 알림을 주고 있는 봇/방들의 목록, 더 나아가 각 봇/방의 역할 및 각 방에 소속된 구성원들의 목록까지 한눈에 파악이 가능한 보조 페이지를 개발하였다. 이것이 가능했던 이유는, 네이버웍스가 특정 봇이 소속된 특정 방의 구성원 목록을 조회할 수 있는 API를 제공했기 때문이다. 이로써 더 이상은 필자와 같이 직원분들을 직접 찾아가며 봇/방들의 정체를 파악할 필요가 없도록 하였다.

 

 


▎크론 작업 코드 정리 및 주석 작성

오픈갤러리에서는 정해진 시각에(혹은 정해진 주기로) 자동 실행되는 예약 스크립트인 크론 작업을 이용하고 있다. 그리고 상당량의 크론 작업들이 카카오 알림톡 발송 API를 사용하고 있다. 예를 들면, 매일 정해진 시각에 미납 고객에게 미납 안내 알림톡을 발송하는 것이다. 이 외에도 굉장히 많다. 따라서 카카오 알림톡 API를 정리함과 동시에 크론 작업들도 함께 정리하는 것이 좋겠다고 판단하였다.

 

우선 크론 작업에 해당하는 스크립트 파일을 하나씩 보면서, 코드를 읽고 이해하는 것을 선행하였다. 그러고 나서 가독성을 높이기 위해 코드를 깔끔하게 정리하고, 필요한 부분에 적절히 주석도 달아주었다. 후대의 개발자가 코드를 보고 쉽게 이해할 수 있게끔 말이다. 또한, 해당 크론 작업이 무슨 역할을 담당하는지에 대한 설명문도 일관된 포맷에 맞춰 작성하였다. 예를 들면 다음과 같은 식이다. 설명문을 help 변수의 값으로 작성하였다.

class Command(BaseCommand):
    # -h 또는 --help 옵션으로 실행했을 때 노출되는 메시지
    help = (
        """
        <매일 16:00>
        미납 고객분들께 미납 안내 알림톡을 발송한다.
        """
    )

    def handle(self, *args, **options):
        ...

 

마지막으로, 해당 스크립트 파일의 이름을 그 크론 작업의 역할에 걸맞게 적절히 수정해주었다. 상당히 과거에 작성된 스크립트 파일의 경우 이름이 매우 일반적으로(구체적이지 않게) 지정되어 있었는데, 나중에 크론 작업들이 더 추가되면서 그 일반적인 이름들이 꽤나 혼란을 주었기 때문이다.

 


▎크론 작업 목록 조회 페이지 개발

앞서 네이버웍스 메시지 봇/방들의 목록을 조회할 수 있는 보조 페이지를 개발한 것처럼, 크론 작업들의 목록을 조회할 수 있는 보조 페이지도 개발하였다. 어떠한 시각에(어떠한 주기로) 어떠한 스크립트가 어떠한 동작을 하는지 실무진분들께서도 아는 것이 좋다고 생각했기 때문이다.

 

 

이 페이지는 기본적으로 데이터베이스의 데이터를 읽는 것이 아니라 파일 시스템을 읽어서 동작한다. 즉, 모든 크론 작업 스크립트 파일들을 읽고, 각 파일 내에 정의된 help 변수의 값을 파싱 하여 결과를 보여주도록 하였다.