Controller Facade Service 와 에러 코드 부분을 한번 보자!
Controller는 api 보고 짰다.
@RestController
@RequestMapping("/users/friends")
@RequiredArgsConstructor
public class FriendController {
private final FriendFacade friendFacade;
@PostMapping("/request/{toUserId}")
public Mono<ResponseEntity<String>> sendFriendRequest(
@AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long toUserId) {
Long fromUserId = userPrincipal.getUser().getId();
return friendFacade.sendFriendRequest(fromUserId, toUserId)
.then(Mono.just(ResponseEntity.ok(FriendSuccessCode.REQUEST_SENT.getMsg())));
}
@PostMapping("/accept/{requestId}")
public Mono<ResponseEntity<String>> acceptFriendRequest(@PathVariable Long requestId) {
return friendFacade.acceptFriendRequest(requestId)
.then(Mono.just(ResponseEntity.ok(FriendSuccessCode.REQUEST_ACCEPTED.getMsg())));
}
@PostMapping("/reject/{requestId}")
public Mono<ResponseEntity<String>> rejectFriendRequest(@PathVariable Long requestId) {
return friendFacade.rejectFriendRequest(requestId)
.then(Mono.just(ResponseEntity.ok(FriendSuccessCode.REQUEST_REJECTED.getMsg())));
}
@DeleteMapping("/{friendId}")
public Mono<ResponseEntity<String>> deleteFriend(
@AuthenticationPrincipal UserPrincipal userPrincipal, @PathVariable Long friendId) {
Long userId = userPrincipal.getUser().getId();
return friendFacade.deleteFriend(userId, friendId)
.then(Mono.just(ResponseEntity.ok(FriendSuccessCode.FRIEND_DELETED.getMsg())));
}
@GetMapping("/requests")
public Flux<RequestFriendResponseDto> getFriendRequests(@AuthenticationPrincipal UserPrincipal userPrincipal) {
Long userId = userPrincipal.getUser().getId();
return friendFacade.getFriendRequests(userId);
}
@GetMapping
public Flux<FriendshipResponseDto> getFriends(@AuthenticationPrincipal UserPrincipal userPrincipal) {
Long userId = userPrincipal.getUser().getId();
return friendFacade.getFriends(userId);
}
}
Facade이다 Facade에서는 다른 Service를 주입받을 수 있다. 그래서 이 파사드가 Service에 뿌려주면 된다
@RequiredArgsConstructor
@Component
public class FriendFacade {
private final FriendService friendService;
private final UserService userService;
public Mono<Void> sendFriendRequest(Long fromUserId, Long toUserId) {
return validateUserIds(fromUserId, toUserId)
.flatMap(valid -> friendService.sendFriendRequest(fromUserId, toUserId))
.then();
}
public Mono<Void> acceptFriendRequest(Long requestId) {
return friendService.acceptFriendRequest(requestId).then();
}
public Mono<Void> rejectFriendRequest(Long requestId) {
return friendService.rejectFriendRequest(requestId).then();
}
public Mono<Void> deleteFriend(Long userId, Long friendId) {
return validateUserIds(userId, friendId)
.flatMap(valid -> friendService.deleteFriend(userId, friendId))
.then();
}
public Flux<RequestFriendResponseDto> getFriendRequests(Long userId) {
return validateUserIds(userId)
.flatMapMany(valid -> friendService.getFriendRequests(userId));
}
public Flux<FriendshipResponseDto> getFriends(Long userId) {
return validateUserIds(userId)
.flatMapMany(valid -> friendService.getFriends(userId)
.flatMap(this::fetchFriendDetails)
);
}
private Mono<Boolean> validateUserIds(Long... userIds) {
return Flux.fromArray(userIds)
.flatMap(userId -> userService.findById(userId)
.hasElement()
.flatMap(exists -> exists ? Mono.just(true) : Mono.error(new CustomException(FriendErrorCode.USER_NOT_FOUND)))
)
.reduce(Boolean::logicalAnd)
.switchIfEmpty(Mono.just(true));
}
private Mono<FriendshipResponseDto> fetchFriendDetails(Friendship friendship) {
return userService.findById(friendship.getFriendId())
.map(user -> FriendshipResponseDto.builder()
.userId(friendship.getUserId())
.friendId(friendship.getFriendId())
.friendNickname(user.getNickname())
.friendEmail(user.getEmail())
.friendIntro(user.getIntro())
.build());
}
}
Service 부분이다. 친구 관련 비즈니스 로직을 처리한다
비슷한 Repository만 주입받을 수 있다.
@Service
@RequiredArgsConstructor
public class FriendService {
private final RequestFriendRepository requestFriendRepository;
private final FriendshipRepository friendshipRepository;
public Mono<RequestFriendResponseDto> sendFriendRequest(Long fromUserId, Long toUserId) {
if (fromUserId.equals(toUserId)) {
return Mono.error(new CustomException(FriendErrorCode.INVALID_USER_ID));
}
return checkExistingRequests(fromUserId, toUserId)
.flatMap(existingRequestStatus -> {
if (existingRequestStatus == RequestFriend.Status.PENDING) {
return Mono.error(new CustomException(FriendErrorCode.REQUEST_ALREADY_SENT));
} else if (existingRequestStatus == RequestFriend.Status.ACCEPTED) {
return Mono.error(new CustomException(FriendErrorCode.ALREADY_FRIENDS));
}
return saveFriendRequest(fromUserId, toUserId);
});
}
public Mono<Void> deleteFriend(Long userId, Long friendId) {
return friendshipRepository.existsByUserIdAndFriendId(userId, friendId)
.flatMap(exists -> {
if (!exists) {
return Mono.error(new CustomException(FriendErrorCode.NOT_FRIENDS));
}
return friendshipRepository.deleteByUserIdAndFriendId(userId, friendId)
.then(friendshipRepository.deleteByUserIdAndFriendId(friendId, userId))
.then();
});
}
private Mono<RequestFriend.Status> checkExistingRequests(Long fromUserId, Long toUserId) {
return requestFriendRepository.findAllByToUserIdAndStatus(toUserId, RequestFriend.Status.PENDING)
.filter(request -> request.getFromUserId().equals(fromUserId))
.hasElements()
.flatMap(hasPendingRequest -> {
if (hasPendingRequest) {
return Mono.just(RequestFriend.Status.PENDING);
}
return friendshipRepository.existsByUserIdAndFriendId(fromUserId, toUserId)
.flatMap(hasFriendship -> {
if (hasFriendship) {
return Mono.just(RequestFriend.Status.ACCEPTED);
}
return friendshipRepository.existsByUserIdAndFriendId(toUserId, fromUserId)
.map(reverseFriendship -> reverseFriendship ? RequestFriend.Status.ACCEPTED : RequestFriend.Status.REJECTED);
});
});
}
private Mono<RequestFriendResponseDto> saveFriendRequest(Long fromUserId, Long toUserId) {
RequestFriend request = RequestFriend.builder()
.fromUserId(fromUserId)
.toUserId(toUserId)
.status(RequestFriend.Status.PENDING)
.build();
return requestFriendRepository.save(request)
.map(savedRequest -> RequestFriendResponseDto.builder()
.fromUserId(savedRequest.getFromUserId())
.toUserId(savedRequest.getToUserId())
.status(savedRequest.getStatus().name())
.build());
}
public Mono<FriendshipResponseDto> acceptFriendRequest(Long requestId) {
return processFriendRequest(requestId, RequestFriend.Status.ACCEPTED)
.switchIfEmpty(Mono.error(new CustomException(FriendErrorCode.FRIEND_REQUEST_NOT_FOUND)))
.flatMap(request -> {
Mono<Friendship> saveFriendship1 = saveFriendship(request.getFromUserId(), request.getToUserId());
Mono<Friendship> saveFriendship2 = saveFriendship(request.getToUserId(), request.getFromUserId());
return Mono.zip(saveFriendship1, saveFriendship2)
.then(Mono.just(FriendshipResponseDto.builder()
.userId(request.getFromUserId())
.friendId(request.getToUserId())
.build()));
});
}
public Mono<Void> rejectFriendRequest(Long requestId) {
return processFriendRequest(requestId, RequestFriend.Status.REJECTED)
.switchIfEmpty(Mono.error(new CustomException(FriendErrorCode.FRIEND_REQUEST_NOT_FOUND)))
.then();
}
private Mono<RequestFriend> processFriendRequest(Long requestId, RequestFriend.Status newStatus) {
return requestFriendRepository.findById(requestId)
.flatMap(request -> {
if (request.getStatus() != RequestFriend.Status.PENDING) {
return Mono.error(new CustomException(FriendErrorCode.FRIEND_REQUEST_ALREADY_PROCESSED));
}
RequestFriend updatedRequest = request.changeStatus(newStatus);
return requestFriendRepository.save(updatedRequest);
});
}
private Mono<Friendship> saveFriendship(Long userId, Long friendId) {
return friendshipRepository.save(Friendship.builder()
.userId(userId)
.friendId(friendId)
.build());
}
public Flux<RequestFriendResponseDto> getFriendRequests(Long userId) {
return requestFriendRepository.findAllByToUserIdAndStatus(userId, RequestFriend.Status.PENDING)
.filterWhen(request -> friendshipRepository.existsByUserIdAndFriendId(request.getFromUserId(), request.getToUserId()).map(exists -> !exists))
.switchIfEmpty(Mono.error(new CustomException(FriendErrorCode.NO_FRIEND_REQUESTS)))
.map(request -> RequestFriendResponseDto.builder()
.fromUserId(request.getFromUserId())
.toUserId(request.getToUserId())
.status(request.getStatus().name())
.build());
}
public Flux<Friendship> getFriends(Long userId) {
return friendshipRepository.findAllByUserId(userId)
.switchIfEmpty(Mono.error(new CustomException(FriendErrorCode.NO_FRIENDS_FOUND)));
}
}
친구 관련 ErrorCode다.
@Getter
@AllArgsConstructor
public enum FriendErrorCode implements BaseCode {
REQUEST_ALREADY_SENT(HttpStatus.BAD_REQUEST, 5001, "이미 친구 요청을 보냈습니다.", "이미 보낸 친구 요청이 있습니다."),
ALREADY_FRIENDS(HttpStatus.BAD_REQUEST, 5002, "이미 친구입니다.", "이미 친구 상태입니다."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, 5003, "사용자를 찾을 수 없습니다.", "사용자가 존재하지 않습니다."),
INVALID_USER_ID(HttpStatus.BAD_REQUEST, 4001, "잘못된 사용자 ID입니다.", "잘못된 사용자 ID입니다."),
FRIEND_REQUEST_NOT_FOUND(HttpStatus.NOT_FOUND, 5004, "친구 요청을 찾을 수 없습니다.", "친구 요청이 존재하지 않습니다."),
FRIEND_REQUEST_ALREADY_PROCESSED(HttpStatus.BAD_REQUEST, 5005, "이미 처리된 친구 요청입니다.", "이미 처리된 친구 요청입니다."),
NO_FRIEND_REQUESTS(HttpStatus.NOT_FOUND, 5006, "친구 요청이 현재 없습니다.", "친구 요청이 존재하지 않습니다."),
NO_FRIENDS_FOUND(HttpStatus.NOT_FOUND, 5007, "친구가 없습니다.", "친구 목록이 비어 있습니다."),
NOT_FRIENDS(HttpStatus.BAD_REQUEST, 5008, "이 사람과 친구가 아닙니다.", "이 사람과 친구가 아닙니다."),
;
private final HttpStatus status;
private final int code;
private final String msg;
private final String remark;
@Override
public CommonReason getCommonReason() {
return CommonReason.builder()
.status(status)
.code(code)
.msg(msg)
.build();
}
}
이 코드는 친구 관련 성공 코드다.
Getter
@AllArgsConstructor
public enum FriendSuccessCode implements BaseCode {
REQUEST_REJECTED(3, "친구 요청이 거부 되었습니다.", "친구 요청이 거부 되었습니다."),
REQUEST_ACCEPTED(4, "친구 요청이 수락되었습니다.", "친구 요청이 수락되었습니다."),
REQUEST_SENT(5, "친구 요청이 성공적으로 전송되었습니다.", "친구 요청이 성공적으로 전송되었습니다."),
FRIEND_DELETED(6, "친구가 성공적으로 삭제되었습니다.", "친구가 성공적으로 삭제되었습니다."),
;
private final int code;
private final String msg;
private final String remark;
@Override
public CommonReason getCommonReason() {
return CommonReason.builder()
.status(HttpStatus.OK)
.code(code)
.msg(msg)
.build();
}
}
sql 문도 이렇게 추가를 해줬다.
CREATE TABLE IF NOT EXISTS friendship
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
friend_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL,
modified_at TIMESTAMP NOT NULL,
FOREIGN KEY (user_id) REFERENCES user (id),
FOREIGN KEY (friend_id) REFERENCES user (id)
);
CREATE TABLE IF NOT EXISTS request_friend
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
from_user_id BIGINT NOT NULL,
to_user_id BIGINT NOT NULL,
status VARCHAR(10) NOT NULL,
created_at TIMESTAMP NOT NULL,
modified_at TIMESTAMP NOT NULL,
FOREIGN KEY (from_user_id) REFERENCES user (id),
FOREIGN KEY (to_user_id) REFERENCES user (id)
);
아래는 이슈와 pr 링크이다.
내가 friend CRUD 부분을 하면서, 파사드 주입과 서비스 주입에 대해 많이 헷갈렸었다.
이슈: https://github.com/echo1241/echo/issues/45
'TIL' 카테고리의 다른 글
TIL - 2024/08/07 (0) | 2024.08.15 |
---|---|
TIL - 2024/08/06 (0) | 2024.08.15 |
TIL - 2024/08/02 (0) | 2024.08.10 |
TIL - 2024/08/01 (0) | 2024.08.10 |
TIL - 2024/07/31 (0) | 2024.08.05 |