주문을 한 번 바꿨는데 같은 알림이 여러 번 오면 사람은 무엇이 새 정보인지 다시 확인해야 한다. 나는 알림의 중복을 버튼 클릭 수만으로 판단하지 않기로 했다.
새 SQLite 실험에서는 업무 사건과 알림 대기 기록을 연결했다. 실제 주소나 회사 계정은 쓰지 않으며, 외부 메시지를 보내지 않고도 예약과 전달 결과의 경계를 확인할 수 있게 만들었다.
무슨 사건을 누구에게 알리는지부터 정했다
문서 번호만 알림의 식별자로 쓰면 그 문서가 나중에 다시 바뀌었을 때 필요한 알림까지 막을 수 있다. 요청 키만 쓰면 같은 사건이 다른 경로로 들어왔을 때 중복을 알아보기 어렵다. 이번 대기 기록은 회사, 업무 사건 번호와 합성 수신 대상의 조합에 고유 조건을 둔다. 어떤 변화에 대한 알림인지를 먼저 정한 것이다.
합성 대상은 실제 사람의 이메일이 아닌 고정 이름이다. 실무에서는 역할로 보낼지 특정 담당자로 보낼지, 같은 사람이 여러 역할을 가질 때 어떻게 합칠지 별도 정책이 필요하다. 누가 읽어야 하는지 정하지 않은 채 모든 사람에게 보내는 방식은 업무의 다음 행동을 흐릴 수 있다. 이 예제는 그 판단을 대신하지 않고 식별할 구조를 공유한다.
예약됨과 전달됨을 같은 말로 쓰지 않았다
문서를 고친 성공 사건과 함께 outbox에 queued 행이 생긴다. 이 행은 알림할 일이 남았다는 기록이다. 네트워크 전송이나 수신 성공이 아니다. 현재 상태를 설명하는 화면에서도 대기와 전달을 구분해야 한다. 예약 행이 있다는 이유로 담당자가 이미 내용을 확인했다고 보여 줄 수는 없다.
이번 코드는 실제 발송기 대신 delivery_result로 합성 결과를 남긴다. failed와 delivered는 다르게 저장되고 시도 횟수도 증가한다. 이 값 역시 사람이 실제 메시지를 읽었다는 뜻은 아니다. 실무에서는 제공자의 접수와 최종 전달, 사용자의 확인이 어떤 근거로 구분되는지 정해야 한다. 존재하지 않는 확인을 완료 통계에 섞지 않으려는 선택이다.
같은 저장을 재응답해도 대기 행은 늘지 않는다
저장 응답을 못 받은 상황에서 같은 요청을 보내면 패키지는 확정 결과를 다시 읽는다. 문서와 업무 사건이 새로 생기지 않으므로 같은 사건의 알림도 추가되지 않는다. 테스트는 별도 두 연결의 같은 요청을 동시에 실행해 사건 한 개와 대기 행 한 개만 남는지 확인한다. 화면에서 버튼을 막는 것과 별도의 저장소 조건이다.
외부 이벤트를 여러 번 받는 경우에도 같은 사건을 식별할 계약이 필요하다. 이번 모듈은 외부 웹훅이나 서명 검증은 구현하지 않았다. 알림 대기 행의 고유 조건을 원격 이벤트 전체의 중복 방지로 넓혀 설명하지 않는다. 다른 입력 경로를 연결하려면 그 경로의 식별자와 신뢰 경계부터 추가로 검증해야 한다.
실패 뒤 다시 보내는 경우와 모르는 경우
합성 failed 뒤에는 같은 대기 행에 delivered를 남길 수 있다. 이미 delivered인 행은 반복 처리하지 않고 unknown도 자동으로 진행하지 않는다. 후자의 경우는 단순한 실패가 아니라 상대가 받았는지 모르는 상태다. 다시 보내서 해결하려고 하면 실제로 받은 메시지를 한 번 더 보낼 수 있다.
이 실험에는 상대 제공자의 결과 조회나 운영자의 확인 기능이 없다. unknown을 안전하게 풀어 주는 완성된 발송 프로그램이라고 부르지 않는다. 실제 서비스의 재시도 간격, 최대 횟수, 결과 확인과 수동 처리 경로를 별도로 설계해야 한다. 합성 정책을 작게 제한한 덕분에 무슨 일이 아직 구현되지 않았는지도 읽을 수 있다.
알림 실패가 주문을 다시 만들지 않도록
알림을 재처리할 때 원래 주문 수정 명령부터 반복할 이유는 없다. 독립 테스트는 전달 실패와 재처리 뒤에도 문서 사건이 한 개인지 확인한다. 이 원칙을 지키려면 운영 화면도 주문 상태와 알림 상태를 함께 보여 주되 다른 행동으로 구분해야 한다. 담당자가 어떤 버튼을 눌러야 하는지 정확히 알 수 있어야 한다.
공유 기록 뷰어에는 failed와 unknown의 대기 결과가 따로 있다. 실제 로컬 실행의 합성 기록을 보여 주는 기능이며 브라우저에서 메일을 보내지 않는다. 실제 업체 정보나 수신 주소도 공개하지 않았다. 나는 알림을 많이 보내는 기능보다 필요한 사건을 설명하고 중복과 미확인을 다룰 수 있는 구조를 제작자의 노하우로 남기고 싶었다.