이관으로 복사된 할인을 보정하는 커맨드를 설계하면서 products 만 고치면 channel_products 에 전파되는지를 확인해야 했다. 상위를 수정할 때 무엇이 도는지를 순서대로 읽었다.
Table of contents
Open Table of contents
배경 — 전 필드를 복사하는 전파
syncToChannels() 를 보니 상위의 전 필드를 toArray() 로 만들어 각 하위에 그대로 넘기고 있었다. 하위의 update() 는 그 배열을 모델의 $fillable 에 따라 적용한다.
ChannelProduct 의 $fillable 을 확인하니 discount_price 와 discount_rate 가 들어 있었다. Laravel 공식 문서대로 $fillable 은 대량 할당을 허용하는 속성 목록이므로 상위의 할인 값이 그대로 하위에 채워진다는 뜻이었다.
원인 — 값으로 전파되는 없음
여기서 문제가 드러난다. 상위에 할인이 없고 하위에 할인이 있는 상태에서 상위를 수정하면 상위의 NULL 이 값으로 복사돼 하위 할인이 사라진다.
내가 기대한 것은 하위 할인을 안 건드리는 유지이거나 상위 기준으로 다시 계산하는 재계산이었다. 실제 동작은 둘 다 아니고 삭제였으며 상위에 없는 것을 하위에도 없게 만든다.
주의 — 보호되는 예외
다만 예외가 하나 있었는데 promotion_id 가 걸린 하위는 그 필드를 except() 로 빼고 채우도록 보호돼 있었다. 보호 대상이 아닌 하위만 덮인다.
이 예외가 있다는 것 자체가 전파가 덮어쓴다는 사실을 코드가 이미 알고 있었다는 뜻이다. 다만 보호 범위가 프로모션 하나로 좁았다.
조치 — 시점이 예측 불가한 전파
원래 계획은 상위만 고치면 전파되리라는 것이었는데 전파는 되지만 지금이 아니라 다음에 상위를 수정할 때 된다. 그 시점이 언제일지 알 수 없다.
상위만 고치고 기다리면 그때까지 하위에 옛 할인이 남아 그 값으로 팔린다. 두 가지 방법이 있었는데 상위만 고치고 전파를 기다리는 것과 상위와 하위를 모두 직접 수정하는 것이다. 뒤쪽을 골랐다. 이유는 시점을 모르는 상태로 틀린 값이 팔리게 둘 수 없어서다. 대신 커맨드가 두 표를 같이 손대므로 트랜잭션과 검증 범위가 두 배가 되는 것을 감수했다.
검증 — 먼저 정해야 할 정책
이 조사에서 더 큰 것이 나왔는데 하위 단독 할인이 상위 수정 한 번으로 소실된다는 사실이다. 특정 채널에만 할인을 거는 것이 사실상 불가능하고 운영자는 걸어 놓고 왜 없어졌는지를 모른다.
지금 구조는 할인이 상위에서만 정해진다는 것을 전제하므로 하위 단독 할인을 허용하려면 전파 규칙을 먼저 손봐야 하고 안 허용하려면 화면에서 입력을 막아야 한다. 입력은 되는데 유지가 안 되는 상태가 가장 나쁘므로 둘 중 하나로 정해야 한다. 전 필드 복사는 부분 갱신이 아니라 전체 치환이고 외부 API에서 안 보낸 필드가 지워지는 것과 같은 구조가 내부 전파에서도 생긴다.
정리
- 전파가 전 필드를 복사하면 상위에 없는 값도 전파된다
- 할인 없음이 값으로 전파돼 하위 할인을 지운다
- 유지도 재계산도 아니라 삭제다
- 보호 조건이 있으면 그 대상만 예외가 된다
- 상위만 고치면 다음 수정 시점에 하위가 정리된다
- 그 시점이 예측 불가하므로 즉시 정리하려면 양쪽을 직접 수정한다
- 입력은 되는데 유지가 안 되는 상태가 가장 나쁘다
- 전 필드 복사는 부분 갱신이 아니라 전체 치환이다