연동 채널 여섯을 하나의 ChannelOrder 모델로 다루려 했는데 예외가 늘면서 모델이 계속 복잡해졌다.
Table of contents
Open Table of contents
문제 — 하나의 모델에 담기
여섯 채널의 주문을 ChannelOrder 한 클래스로 담고 채널마다 다른 것이 나올 때마다 public ?string 필드를 하나씩 늘려 갔다.
public ?string $giftMessage = null; // A 채널만
public ?bool $isSubscription = null; // B 채널만
public ?string $deliveryWindow = null; // C 채널만
필드가 서른 개 가까이 됐고 대부분이 특정 채널에만 있었다. 한 모델로 담는다는 목표는 지켜졌지만 그 모델이 여섯 채널의 합집합이 됐다.
원인 — 쓰는 쪽의 부담
쓰는 쪽에서 어떤 필드가 어느 채널에 있는지 알 수 없었다. 값을 읽기 전에 항상 null 여부를 확인해야 했고 결국 channel 까지 함께 봤다.
if ($order->channel === 'B' && $order->isSubscription) { ... }
모든 예외를 담으려 하니 담은 쪽에서 다시 갈라야 했고 그러면 하나로 담은 의미가 없다. 채널을 안 보려고 만든 모델인데 쓰는 자리마다 채널을 본다.
해결 — 공통과 예외의 분리
두 가지 방법이 있었는데 채널마다 ChannelAOrder 같은 하위 클래스를 만드는 것과 여섯 채널에 모두 있는 공통 필드 다섯만 남기고 나머지는 $extra 배열에 담는 것이다. 뒤쪽을 골랐다. 이유는 하위 클래스로 갈라도 쓰는 쪽이 instanceof 로 채널을 다시 봐야 해서다. 대신 extra() 로 꺼내는 값은 타입 검사가 안 되는 것을 감수했다.
public function extra(string $key): mixed {
return $this->extra[$key] ?? null;
}
이렇게 하면 채널별 값이라는 사실이 코드에 드러난다. 같은 값이라도 공통 필드로 읽는 것과 extra() 로 꺼내는 것은 읽는 사람에게 다른 신호를 준다.
채널별 처리도 따로 뒀는데 채널마다 OrderMapper 구현을 두고 공통 흐름에서는 채널을 아예 보지 않는다.
판단 기준 — 무엇이 공통인지 세는 기준
무엇을 공통으로 둘지는 짐작하지 않고 채널별 응답 표본을 jq 로 세어서 정했고 여섯 채널에 모두 있는 것만 공통으로 뒀다.
shipping_fee 가 애매했다. 넷에 있고 둘에 없어서 공통으로 둘까 했다. 없는 두 곳에서 0으로 두면 되는지 물어봤다. 배송비 개념이 없는 채널이라 0이 아니라 해당 없음이었다. 대부분에 있다고 공통이 아니고 없는 곳에서 그 값이 뜻을 갖는지를 봐야 했다.
새 필드가 나올 때의 판단 순서도 정했다. 몇 개 채널에 있는지 세고 전부에 있으면 공통으로 두고 일부면 extra 에 둔다. extra 의 값을 공통 흐름이 봐야 하면 구조가 안 맞는 신호로 본다.
실제로 그런 것이 하나 나왔다. 정산에서 선물 여부에 따라 수수료가 달라져 공통 흐름이 그 값을 봐야 했다. giftMessage 내용이 아니라 isGift 만 공통으로 올리고 내용은 extra 에 그대로 뒀다.
주의 — 담을 수 없는 것
한 채널은 주문 구조가 아예 달랐다. 다른 채널이 주문 하나에 상품 여럿을 담는 것과 달리 그 채널은 상품마다 주문이 따로 생긴다.
같은 모델로 담으려니 억지가 돼서 그 채널만 OrderMapper 에서 상품별 주문을 하나로 묶어 우리 구조로 바꾸고 그 뒤로는 다른 채널과 같은 흐름을 타게 했다.
전부 같은 모델로 담는 것이 목적이 아니라 공통 흐름을 타게 하는 것이 목적이었다. 목적을 바꿔 잡고 나니 담을 수 없는 것을 억지로 담지 않아도 됐다.
정리
- 모든 예외를
ChannelOrder에 담으려 하면 담은 쪽에서 다시 갈라야 한다 - 채널을 안 보려고 만든 모델인데 쓰는 자리마다 채널을 보게 된다
- 공통과 예외를 나누고 예외는
extra별도 자리에 담는다 - 같은 값이라도 어디서 꺼내는지가 읽는 사람에게 신호를 준다
- 무엇이 공통인지 세어서 정한다
- 대부분에 있다고 공통이 아니고 없는 곳에서 뜻이 있는지 본다
- 별도 자리의 값을 공통 흐름이 봐야 하면 구조가 안 맞는 것이다
- 같은 모델로 담는 것이 아니라 공통 흐름을 타게 하는 것이 목적이다