실무를 하면서 운영 중인 시스템의 외부 연동 구조를 변경하는 작업을 맡게 되었다.
기존에는 L발송사를 통해 SMS, LMS, MMS, 알림톡을 발송하고 있었지만,
회사 구조 변경으로 인해 K발송사로 전환이 필요해졌다.
단순히 교체하는 작업이 아니라, 운영 중인 시스템을 중단 없이 전환해야 했고 일정 기간 두 발송사를 병행 운영해야 하는 상황이었고 이 작업을 혼자 맡아서 하게되었다.
요구사항 및 제약조건
이번 작업에서 가장 중요했던 조건은 다음과 같다.
- 클라이언트 코드 변경 최소화
- 기존 API 스펙 유지
- L사와 K사 에이전트 병행 운영 (약 2~3개월)
- 발송사별 메시지 스펙 차이 존재
특히 이미 여러 클라이언트에서 사용 중인 API였기 때문에 백엔드에서 모든 차이를 흡수하는 구조가 필요했다.
설계 고민
기존 API 유지 vs 신규 API 생성
처음에는 기존 API를 유지하면서 내부에서 발송사를 분기하는 방식을 고민했다. 발송사를 properties 등으로 분기하는 방식이다.
클라이언트측 코드 변경이 거의 없게 하고 싶었기 때문이다. 신규 API를 생성하는 경우 클라이언트 코드에서 url을 바꿔주어야 했고, 실수할 확률이 올라가기 때문이다.
하지만 L사와 K사는 다음과 같은 차이가 있었다.
- msgType 값이 서로 다름 (예: SMS → L사: 0, K사: 3)
- SMS, LMS, MMS, 알림톡 구분 기준 상이
- MMS의 경우 첨부파일 컬럼 필수 여부 차이 존재
- 알림톡 관련 컬럼 및 스펙 차이
- 발송 결과 및 상태 코드 구조 상이
이러한 차이를 하나의 API에서 처리할 경우, 발송사별 조건 분기가 과도하게 증가하고 유지보수가 어려워질 것으로 판단했다.
결과적으로 오히려 코드 복잡도가 증가하고 유지보수 난이도가 올라갈 것으로 생각이 되어 신규 API를 별도로 생성하는 방식을 선택했다.
발송사별 API를 분리해 책임을 명확히 하는 방향을 선택했다.
병행 운영 구조
발송사 변경 후 안정적인 운영이 되는지 검증해야 하는 시간이 필요했다.
따라서 두 발송사를 일정 기간 함께 사용해야 했기 때문에, 단일 API에서 분기할지, 발송사별 API를 분리할지 고민이 필요했다.
최종적으로는 발송사별 API를 분리하고, 내부에서 각각 독립적으로 처리하는 구조를 선택했다.
이를 통해
- 기존 발송 로직 안정성 유지
- 신규 발송사 점진적 적용
이 가능하도록 설계했다.
인터페이스 추상화 미적용
이상적으로는 발송사를 인터페이스로 추상화하여 L사와 K사를 동일한 구조로 처리하는 방식이 적합했다.
하지만
- 기존 코드 구조
- 작업 일정
을 고려했을 때, 해당 구조를 적용하기에는 리스크가 있었다.
기존 코드가 메시지 발송 인터페이스로 공통 추상화가 잘 되어있지 않았기 때문이다.
이번 작업에서는 구조적인 완성도보다 일정 내 안정적인 전환을 우선시하는 방향으로 결정했다.
해당 부분은 추후 개선이 필요한 영역으로 남겨두었다.
구현 내용
메시지 타입 매핑
L사와 K사는 동일한 메시지 타입(SMS, LMS 등)에 대해 서로 다른 값을 사용하고 있었다.
이를 해결하기 위해 메시지 타입을 Enum으로 정의하여 내부 공통 타입으로 관리하고, 발송사별로 필요한 값으로 변환하는 매핑 로직을 분리했다.
이를 통해 발송사별 msgType 차이를 외부로 노출하지 않고 내부에서 일관된 방식으로 처리할 수 있도록 구성했다.
VO 재사용을 통한 클라이언트 영향 최소화
기존 클라이언트에서는 L사 기준 VO를 사용하고 있었기 때문에 이를 그대로 유지하는 것이 중요했다.
따라서 K사 연동 시에도 새로운 모델을 그대로 노출하지 않고, 기존 L사 VO를 기준으로 데이터를 매핑하여 반환하는 방식을 선택했다.
즉, 내부적으로는 K사 모델을 사용하지만
외부로는 기존 L사 VO 형태를 유지하도록 구성했다.
이를 통해 클라이언트 코드 수정 없이 발송사 변경을 적용할 수 있었다.
MMS 및 알림톡 처리
발송사 간 스펙 차이도 존재했다.
- MMS: 첨부파일 필수 여부 차이
- 알림톡: 템플릿 및 컬럼 구조 차이
각 발송사별 요구사항을 별도로 처리하도록 구현하여 잘못된 요청으로 인한 발송 실패를 방지했다.
발송 결과 통합 처리 (CTE 활용)
발송 결과 구조 역시 발송사마다 차이가 있었다.
- L사: result 단일 컬럼 사용
- K사: (예시) SMSResult, KkoResult 등 메시지 유형별 컬럼 분리
이 차이를 그대로 노출할 경우 클라이언트 수정이 불가피했기 때문에,
MyBatis 쿼리에서 CTE를 활용하여 발송 결과를 하나의 컬럼으로 통합하는 방식으로 처리했다.
CTE를 통해 메시지 유형에 따라 적절한 결과 값을 선택하고, 이를 기존 L사 VO 구조에 맞춰 반환하도록 구성했다.
이 방식으로 발송사별 결과 구조 차이를 SQL 레벨에서 흡수하여 애플리케이션 레벨의 복잡도를 줄일 수 있었다.
핵심은 다음과 같다.
서로 다른 메시지 발송 벤더의 스펙 차이를 백엔드에서 흡수하여 클라이언트 변경 없이 동일한 방식으로 사용할 수 있도록 했다.
서비스단이 아닌 SQL에서 처리한 이유?
처음에는 서비스 레이어에서 결과 컬럼을 선택하는 방식도 고려했었다.
- 서비스 계층에 발송사별 컬럼 구조 지식이 필요해짐
- 조건 분기 증가로 코드 복잡도 상승
이러한 문제로 CTE를 선택했고, 이를 통해 서비스 계층에서는 발송사에 대한 구체적인 차이를 인지하지 않고, 기존 L사 VO 구조 그대로 처리할 수 있도록 했다.
만약 서비스 계층에서 처리할 경우 여러 컬럼을 모두 조회한 뒤 메시지 타입에 따라 다시 조합해야 했다. SQL에서 처리하면 조회 시점에 이미 원하는 형태로 데이터를 받을 수 있다.
CTE로 처리했을 경우 비즈니스 로직이 아닌 외부 시스템 구현 세부사항이 서비스에 유입되는 것 또한 방지된다. 이미 VO 기반으로 조회 결과를 바로 매핑하는 구조였기 때문에 SQL에서 컬럼을 맞추는 것이 전체 변경 범위를 최소화한다고 판단했다.
다만 SQL에서 처리하는 경우 성능에 대한 고려를 안할 수 없었다.
메시지 타입에 따른 결과 선택을 단순 CTE 수준으로 제한하고 불필요한 JOIN이나 서브쿼리 추가 없이 인덱스를 타는 조건절은 그대로 유지해 Full Scan을 방지하도록 했다.
결과 컬럼 선택 로직은 데이터 가공 수준이기 때문에 전체 쿼리 성능에 미치는 영향은 적다고 판단했다.
결과
- 운영 중단 없이 발송사 전환 완료
- 약 2~3개월 동안 두 발송사 병행 운영 안정적으로 수행
- 클라이언트 코드 거의 변경 없이 전환 성공
- 기존 기능 영향 없이 신규 발송사 적용
개선 방향
이번 작업에서는 일정과 안정성을 우선으로 구현했지만, 구조적으로 개선할 수 있는 부분도 존재한다.
- 발송사별 인터페이스 추상화
- 신규 발송사 추가를 고려한 확장 구조 설계
- 메시지 타입 및 스펙의 공통 도메인화
향후 리팩토링 시, 발송사 변경이나 추가 상황에서도 유연하게 대응할 수 있는 구조로 개선할 계획이다.