특정 게시물을 조회할 때 이런 상황이 있을 수 있다. 작성자가 글을 작성하면서 설정한 모집 인원이 5명이고, 4명이 참가자로 들어간 상태에서 어떤 사용자가 게시글의 상세 내용을 확인한다.
사용자의 화면에는 잔여석이 1석 남았기 때문에, 신청하기 버튼이 화면에 나타난다. 사용자는 신청하기 버튼을 누른다.
그러나 사용자가 신청하기 버튼을 누르려는 사이에 작성자가 다른 신청자의 신청을 수락해 참가 상태로 전환한다. 이제는 잔여석이 없는 상황이기 때문에, 사용자가 신청하기 버튼을 누를 수는 있겠지만 눌러도 신청이 생성되지 않아야 한다. (사실 신청과 참가는 다르기 때문에 신청이 생성되어도 오류가 발생하는 것은 아니지만, 정원이 가득 찼는데도 참가신청을 할 수 있다는 것은 혼란을 가져올 수 있다고 생각되어 막는 게 낫겠다고 판단했다.)
기존에는 여전히 신청이 생성되는 상황이었기 때문에, 이런 경우에는 신청을 생성하지 않도록 하는 예외처리 구현을 시도했다.
백엔드에서의 로직 추가는 크게 어렵지 않았다. 특정 Game 객체가 가지고 있는 목표 모집 정원 수와, 해당 Game Id를 갖고 있는 '참가확정' 상태의 Register 객체의 개수를 비교해 Register의 개수가 같거나 크다면 예외를 발생시키도록 하면 되었다.
// PostRegisterToGameService.java
public RegisterGameResultDto registerGame(Long gameId,
Long accessedUserId) {
Game game = gameRepository.findById(gameId)
.orElseThrow(() -> new RegisterGameFailed(
"주어진 게임 번호에 해당하는 게임을 찾을 수 없습니다."));
List<Register> applicantsAndMembers = registerRepository.findByGameId(gameId);
applicantsAndMembers.forEach(person -> {
if (person.userId().equals(accessedUserId)
&& (person.status().value().equals(RegisterStatus.PROCESSING)
|| person.status().value().equals(RegisterStatus.ACCEPTED))) {
throw new RegisterGameFailed("이미 신청 중이거나 신청이 완료된 운동입니다.");
}
});
User user = userRepository.findById(accessedUserId)
.orElseThrow(() -> new RegisterGameFailed(
"주어진 사용자 번호에 해당하는 사용자를 찾을 수 없습니다."));
//
// 해당 위치에 다음 코드블럭의 소스코드가 추가됨
//
Register register = new Register(
accessedUserId,
gameId,
new RegisterStatus(RegisterStatus.PROCESSING)
);
Register saved = registerRepository.save(register);
return new RegisterGameResultDto(saved.gameId());
}
List<Register> members = applicantsAndMembers.stream()
.filter(person -> person.status().value().equals(RegisterStatus.ACCEPTED))
.toList();
if (members.size() >= game.targetMemberCount().value()) {
throw new RegisterGameFailed("참가 정원이 모두 차 참가를 신청할 수 없습니다.", game.id());
}
이제 프론트엔드에 발생한 에러 메시지를 전달해 상태로 관리하고, UI에 출력되도록 했다. 다음 소스코드는 에러 메세지를 상태로 관리하는 것과 게시물 상세 내용 UI에서 에러 메세지를 출력하는 구조이다.
게시글 상세 내용을 조회할 때는 하나의 게시물에서만 내용을 보여주면 되니까 큰 문제가 없었다. 문제는 게시물 목록을 조회할 때였다.
현재 앱에서는 게시물 목록을 조회하는 화면에서도 참가 신청을 할 수 있다. 게시물 목록은 여러 개의 Game 객체로부터 데이터를 가져와 각각의 목록을 구성하는데, Register는 여러 개의 게시물 목록 중 하나의 게시물에서만 발생한다.
모든 게시물에 똑같은 에러 메시지를 전달하자, 모든 게시물의 버튼 하단에 에러 메세지가 출력되는 괴현상이 발생했다.
오류가 발생한다면, 어떤 게시물의 Game에서 발생한 오류인지 구분해야 했다.
생각해보면 예외가 발생하는 시점에 예외가 발생한 Game의 id를 알 수 있었다. 예외에 Game Id를 같이 담아 발생시키고, Controller의 예외처리 메서드에서 에러에 해당 Game Id를 같이 담아 응답으로 전달하도록 해보았다. 프런트엔드에서는 전달받은 Game Id로 어떤 Game에서 에러가 발생했는지 식별하는 것을 시도했다.
// PostRegisterGameService.java의 예외 처리 구문을 다음과 같이 수정
if (members.size() >= game.targetMemberCount().value()) {
throw new RegisterGameFailed("참가 정원이 모두 차 참가를 신청할 수 없습니다.", game.id());
}
// RegisterController.java
@ExceptionHandler(RegisterGameFailed.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public RegisterGameFailedErrorDto registerGameFailed(RegisterGameFailed exception) {
Integer errorCode = setCodeFromMessage(exception.getMessage());
String errorMessage = errorCode.equals(DEFAULT)
? "알 수 없는 에러입니다."
: exception.getMessage();
return new RegisterGameFailedErrorDto(
errorCode,
errorMessage,
exception.getGameId()
);
}
// stores/RegisterStore.js의 catch 구문을 다음과 같이 수정
catch (error) {
const { errorCode, errorMessage, gameId } = error.response.data;
this.registerErrorCodeAndMessage = {
errorCode,
errorMessage,
gameId,
};
this.publish();
return '';
}
// components/PostRegisterButton.jsx의 에러 메세지 출력 구문을 다음과 같이 수정
{registerErrors.errorCode
&& gameId === registerErrors.gameId ? (
<p>{registerErrors.errorMessage}</p>
) : (
null
)}
이제는 게시글 목록 조회 화면에서도 에러가 발생한 게시글에 대해서만 에러 메세지를 출력하는 것을 확인할 수 있다.