-
하나의 작업이 세 개의 작업으로 분신술을 쓰는 기적Today I Learned 2022. 11. 19. 23:47
프로젝트에 새 모델을 추가하는 작업을 진행했다. 각 게시물에 연결된 경기인 Game 모델의 참가자가 누구인지 알기 위해 사용하던 Member 모델을 신청 상태와 취소, 수락, 거절 상태를 한번에 관리할 수 있는 Register 모델로 변경했다.
기존 Member 모델에는 참여 경기를 식별하는 gameId 필드와 사용자를 식별하는 userId 필드가 존재했는데, 여기에 사용자의 신청 상태를 식별하는 RegisterStatus 값 객체를 필드로 추가했다.
RegisterStatus에는 사용할 수 있는 상태들을 static final 타입의 상수로 정의해 Register를 생성할 때 상수 값들을 이용해 데이터를 부여할 수 있도록 했다. 상태의 변경은 RegisterStatus 객체가 직접 자신의 상태를 변경할 수 있도록 Register에 RegisterStatus가 값을 직접 변경하도록 하는 메서드를 정의했다. RegisterStatus는 Register에서 호출하는 메서드에 따라 자신의 Status 값을 변경한다.
// Register.java @Entity @Table(name = "registers") public class Register { @Id @GeneratedValue private Long id; private Long userId; private Long gameId; @Embedded private RegisterStatus status; // constructors // getters public void cancelRegister() { status.changeToCanceled(); } public void acceptRegister() { status.changeToAccepted(); } public void rejectRegister() { status.changeToRejected(); } // fake methods }
// RegisterStatus.java @Embeddable public class RegisterStatus { public static final String PROCESSING = "processing"; public static final String CANCELED = "canceled"; public static final String ACCEPTED = "accepted"; public static final String REJECTED = "rejected"; @Column(name = "status") private String value; private RegisterStatus() { } // constructor // getter public void changeToCanceled() { this.value = RegisterStatus.CANCELED; } public void changeToAccepted() { this.value = RegisterStatus.ACCEPTED; } public void changeToRejected() { this.value = RegisterStatus.REJECTED; } // equals, hashCode, toString }
Member Entity가 처리하던 동작 방식이 Register라는 다른 Entity로 바뀌고, 신청 상태를 관리하는 방식이 바뀌면서 생각했던 것보다 많은 영역의 내용들이 변경되어야 했다. 스토리 포인트를 작업 설계를 포함해 처음에 예상했던 6 스토리 포인트를 썼음에도 백엔드 로직 수정이 목표치 대비 3~40%밖에 진전이 없자 뭐가 문제인지 고민해보았다.
단순히 모델의 이름이나 시그니쳐만 변경되는 것이었다면 14주차 강의에서 accountNumber String 필드를 AccountNumber 값 객체로 바꾼 뒤 하나씩 추적하는 것처럼 시간이 걸려도 오류들을 잡아주는 게 가능했다. 작업을 계획할 때까지만 해도 그렇게 생각했다.
문제는 기존에는 신청을 했다가 신청을 취소하면 삭제하는 DELETE 요청을 처리하는 방식으로 동작했다면, 이제는 신청을 취소하면 신청이 취소된 상태로 변경되는 PATCH 요청을 이행하는 방식으로 동작하게 백엔드 레이어들이 수정되어야 했다. 그리고 Member 모델에서 바로 신청을 취소하는 동작의 책임 주체 레이어가 이제는 Register 모델과 관련된 레이어로 넘어와야 했다.
즉 하나의 작업이라고 생각했던 Entity 모델을 변경하는 작업은 다음의 3가지 작업이 묶여 있는 형태였다.
1. 모델 변경, 변경에 따른 시그니쳐 오류 수정 (Member >> Register)
2. REST API 요청 및 처리 방식 변경 (DELETE >> PATCH)
3. 각 레이어의 Register 관련 작업 주체 변경 (MemberStore, MemberApiService, MemberController, ... >> Register 관련)두 번째, 세 번째 문제를 해결하기 위해 설계 문서로 돌아가 REST API 설계를 수정했다.
- 설계 문서
설계를 다시 하고, 작업을 진행해 해당 작업에서 목표한 수준까지 동작하게 하는 데 거의 모든 하루를 다 보냈다. 그로 인해 오늘 마쳐야 했던 신청취소/수락/거절 처리 작업이 뒤로 밀리게 되어 모집 게시글 작성, 상태 관리 라이브러리 UseStore 사용 리팩터링 작업을 일요일에 모두 마칠 수 있을지 불투명해졌다.
그 와중에도 설계를 보완하던 과정에서 희망적인 부분을 찾은 것이 있다면 신청취소/수락/거절을 하나의 컨트롤러 메서드에서 쿼리 파라미터로 분기해 작업을 처리할 수 있음을 발견했고, 목표한 작업들을 '신청' 모델에게 메세지를 전달하는 것만으로도 수행할 수 있는 구조를 만들었다는 점이다.
월요일까지 목표한 작업량을 마치면서 해내는 경험을 한번 더 쌓을 수 있도록 조금만 더 힘내보자.
'Today I Learned' 카테고리의 다른 글
이제야 조금은 앱 같다 (0) 2022.11.22 미신청자/신청자/참가자/작성자 별 컴포넌트 구분하기 (0) 2022.11.20 '신청' 모델 추가를 위한 객체 설계 (0) 2022.11.18 밀린다 (0) 2022.11.17 @RequestBody에 매핑되는 DTO는 왜 빈(empty) 생성자가 필요한가? (0) 2022.11.16