MySQL 회원 표에 필수 값이 NULL 인 행이 있었다. 가입 화면에서는 그 값이 필수라 그 경로로는 들어올 수 없었는데 컬럼 자체에는 NOT NULL 이 없어 화면을 거치지 않으면 그대로 들어간다. 그러면 다른 경로가 있다는 뜻이라 member 에 행을 만드는 자리를 grep 으로 전부 찾아봤다.
$ grep -rn "insert('member'" --include=*.php src/
src/Auth/SignupService.php:88
src/Auth/SocialLinkService.php:142
src/Admin/MemberController.php:210
src/Partner/SyncService.php:77
Table of contents
Open Table of contents
들어오는 길이 넷이었다
넷이 나왔다. 가입 화면과 소셜 계정 연동과 관리자 직접 등록과 제휴사 연동 자동 생성이다.
넷이 각각 다른 코드로 만들어져 있었고 검사도 각자 달랐다. 어느 것으로 들어왔느냐에 따라 채워지는 값이 달랐던 것이다. 하나의 결과를 만드는 입구가 넷이면 그중 하나는 반드시 다르게 동작한다.
비교 — 표로 본 검사 차이
각 경로가 무엇을 검사하는지 표로 만들어 봤다.
검사 항목 가입 소셜 관리자 제휴
이메일 형식 O O X X
비밀번호 규칙 O - X -
휴대전화 필수 O X X X
약관 동의 O O X X
중복 확인 O O O X
제휴 경로가 거의 아무것도 안 보고 있었고 필수 값이 없는 행이 거기서 왔다. 표를 만들기 전에는 어느 경로가 무엇을 안 보는지 아무도 몰랐다. 흩어진 코드를 표 하나로 모으는 것이 이 조사에서 가장 효과가 컸다.
한 자리를 거치게 하기
넷을 하나로 합칠 수는 없었는데 각각 다른 입력을 받고 다른 흐름을 타기 때문이다. 대신 MemberFactory 를 하나 두고 넷이 그것을 부르게 했다.
final class MemberFactory {
public function create(MemberDraft $d, CreationSource $src): Member {
$this->validateCommon($d);
$this->validateBySource($d, $src);
$this->checkDuplicate($d);
return $this->repo->insert($d->toRow($src));
}
}
validateCommon 은 어느 경로로 들어와도 도는 것이고 경로마다 다른 것은 validateBySource 가 CreationSource 로 갈라 처리한다.
match ($src) {
CreationSource::SIGNUP => $this->requirePassword($d),
CreationSource::SOCIAL => $this->requireSocialId($d),
CreationSource::ADMIN, CreationSource::PARTNER => null,
};
공통과 개별을 나누니 새 경로가 생겨도 공통 검사는 자동으로 붙었다.
정말 달라야 하는가
표를 놓고 각 차이가 의도된 것인지 하나씩 물어봤다. 휴대전화 필수가 가입에만 있었는데 물어보니 관리자 등록도 필수여야 했고 제휴는 상대가 안 줄 수 있어 선택이 맞았다.
나머지 절반은 그냥 안 넣은 것이었다. 만들 때 필요 없다고 생각했거나 다른 경로에 있는 줄 알았던 것들이다. 다르게 두는 것에 이유가 있는지 묻는 것만으로 절반이 정리됐다.
경로를 기록에 남기기
앞으로 같은 문제가 생겼을 때 빨리 갈리도록 ALTER TABLE 로 컬럼을 더해 어느 경로로 등록됐는지를 행에 남기게 했다. 이상한 값이 나오면 그 값으로 어느 입구인지 바로 안다.
이미 NULL 인 행은 한꺼번에 UPDATE 하지 않고 접속할 때 채우도록 했다. 그리고 새 경로를 만들 때 공통 처리를 안 거치면 검사에 걸리게 했다. 구조를 바꿔도 그것을 안 따르는 자리가 생기는 것을 그 검사가 막았다.
정리
- 들어오는 경로가 여럿이면 각자 다른 검사를 한다
- 하나의 결과를 만드는 입구가 넷이면 하나는 다르게 동작한다
- 경로별 검사 항목을 표로 만들면 빈칸이 드러난다
- 실제로
INSERT하는 처리를 하나로 모으고 넷이 부르게 한다 - 공통 검사와 개별 검사를 나눈다
- 다르게 둔 것에 이유가 있는지 물어본다
- 어느 경로로 들어왔는지 컬럼에 남긴다
- 새 경로가 공통 처리를 안 거치면 걸리게 한다