주문을 종결 처리했는데 어떤 화면에서는 여전히 진행 중으로 남아 있었다. 종결이 한 곳의 상태만 바꾸는 것이 아니었다. 그 주문에 걸려 있는 여러 자리를 함께 정리해야 하는 처리였다.
Table of contents
Open Table of contents
무엇이 걸려 있었나
그 주문 번호를 참조하는 곳을 information_schema 로 전부 찾았다.
SELECT table_name, column_name FROM information_schema.columns
WHERE table_schema = 'shop' AND column_name IN ('order_no', 'order_id');
order_no 나 order_id 를 가진 표가 열한 개 나왔고 그중 넷이 종결 시 처리가 필요했다. 그런데 close() 는 orders 와 order_task 와 settle_target 셋만 하고 있었다.
public function close(int $orderNo): void {
$this->orders->updateState($orderNo, 'CLOSED');
$this->tasks->cancelPending($orderNo);
$this->settle->finalize($orderNo);
// 알림과 재고가 빠졌다
}
notify_schedule 과 stock_reserve 가 빠져서 그 둘이 주문을 여전히 진행 중으로 취급하니 화면에 그렇게 나온 것이다.
남길 것과 정리할 것
전부 지우면 되는 것도 아니었는데 order_log 는 그대로 남아 있어야 나중에 무슨 일이 있었는지 알 수 있기 때문이다.
그래서 열한 표를 남길 것과 정리할 것으로 갈랐다.
처리 필요
order_task 진행 중 작업 취소
notify_schedule 예약 알림 취소
stock_reserve 예약 해제
settle_target 확정 또는 제외
처리 불필요
order_log 이력이므로 그대로 둔다
order_item 주문 내용이므로 그대로 둔다
payment 결제 기록이므로 그대로 둔다
종결이라는 한 단어가 자리마다 다른 동작을 뜻하고 있었다.
한 자리에 모으고 묶기
정리해야 할 다섯 동작을 close() 한 곳에 모았다.
public function close(int $orderNo, string $reason): void {
$this->db->transaction(function () use ($orderNo, $reason) {
$this->orders->updateState($orderNo, 'CLOSED', $reason);
$this->tasks->cancelPending($orderNo);
$this->notify->cancelScheduled($orderNo);
$this->stock->releaseReserved($orderNo);
$this->settle->finalize($orderNo);
});
}
cancelScheduled 와 releaseReserved 를 더한 다섯이 한 transaction 안에서 돌므로 중간에 실패하면 ROLLBACK 되고 일부만 반영되는 상태가 안 생긴다.
전에는 각 화면이 updateState 만 부르거나 cancelPending 까지만 부르는 식이라 어느 경로로 종결했느냐에 따라 결과가 달랐다. 같은 종결인데 결과가 다른 것이 그 자체로 문제였다. 모으고 나니 어느 경로로 들어와도 같은 상태가 됐다.
검증 — 다른 코드로 확인하기
제대로 정리되는지 확인할 때 close() 를 다시 불러서 확인하면 아무 의미가 없다. 같은 실수가 확인 쪽에도 그대로 들어가기 때문이다.
그래서 확인은 CLOSED 인 주문을 order_task · notify_schedule · stock_reserve 와 JOIN 하는 별도 SELECT 로 만들어서 남아 있는 것이 있는지 직접 찾게 했다. 만드는 코드와 확인하는 코드를 다르게 쓰는 것이 이런 확인의 기본이었다. 실제로 그 조회로 앞서 어긋난 건들을 찾아냈다.
매일 훑고 새 자리를 막기
이미 어긋난 notify_schedule 과 stock_reserve 를 전부 정리한 뒤에 그 SELECT 를 cron 으로 매일 돌게 걸었다. 새로 어긋나는 것이 있으면 그날 알게 된다.
그리고 새 표가 생길 때 close() 에 넣는 것을 잊지 않도록 만들었다. order_no 를 참조하는 표를 새로 만들면 그 목록에 등록하게 하고 등록 안 된 것이 있으면 확인에서 걸린다. 되돌리는 처리도 같은 구조로 만들어서 종결을 취소할 때 대칭이 되게 했다.
정리
- 끝났다는 것을 여러 곳에 반영해야 하면 한 곳이 빠진다
information_schema.columns로 그 식별자를 참조하는 표를 전부 찾는다order_log는 남기고stock_reserve는 정리한다- 종결이
order_task에서는 취소이고settle_target에서는 확정이다 - 한 자리에 모으고
COMMIT까지 한 트랜잭션에 넣는다 - 경로마다 결과가 다른 것이 그 자체로 문제다
- 확인은
close()가 아니라 별도SELECT로 한다 - 새 표가 생길 때
close()에 넣지 않을 수 없게 만든다