이것이 백엔드 개발이다, 객체지향의 사실과 오해 등등 책을 읽어보면서 지금의 내 코드를 보면서 코드 리팩토링을 진행해보려고 한다. 변경 기록을 블로그에 작성.
1. DIP 원칙에 맞게 의존성 변경
public class MemberController {
private final MemberServiceImpl userService;
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
public class EmailController {
private final EmailServiceImpl emailService;
WHY?
기타 등등... 내 모든 컨트롤러 로직은 Impl이라는 구체적인 클래스에 의존하고 있었다.
여태 Service를 추상화시켜서 보는 이득을 아예 버리고 개발을 진행했던 문제가 있었다.
DIP는 최대한 추상화에 의존하라는 개발 원칙이다. 구체적인 클래스는 변경할 수가 있고, 변경되면 해당 클래스를 의존하는 다른 클래스도 전부 변경 사항이 생기게 된다. 그래서 최대한 추상화(즉 변하지 않는)에 의존하라는 것.
내 코드는 전부 구체화에 의존을 하고 있었던 상황이라, 나중에 확장되거나 변경이 될 경우 Controller의 코드도 변경이 되는 문제가 발생할 수 있다. 그래서 이런 잠재적인 문제를 미리 차단할 필요가 있다고 판단했다.
HOW?
public class MemberController {
private final MemberService memberService;
추상화에 의존하도록 변경을 진행했다.
2. 유저 정보 조회 API
// 유저 정보 조회
@Override
public MemberInfoResponseDTO getUserInfo() {
Long currentUserId = JwtService.getCurrentUserId();
Member member = memberRepository.findByIdLogin(currentUserId)
.orElseThrow(() -> new MemberException(MemberExceptionInfo.NOT_FOUND_USER, "유저 데이터 없음"));
LocalDateTime endOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
LocalDateTime startOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
Optional<Coupon> couponForRead = couponRepository.findCouponForRead(startOfDay, endOfDay);
if(couponForRead.isEmpty()){
return MemberInfoResponseDTO.fromEntity(member, null);
}
return MemberInfoResponseDTO.fromEntity(member, couponForRead.get());
}
WHY?
현재 유저 정보 조회 API에서는 닉네임, 유저 남은 토큰, 데일리 쿠폰, 역할을 넘겨주고 있다.
하나의 API에서 전부 가져오는 방식인데 굳이 Member에서 쿠폰에 대한 의존성을 가지고 있어야 하나 의문이 들었다. 귀찮음에 API를 하나로 통합시켜 모든 데이터를 반환하도록 만든 것 같다.
HOW?
결합도도 줄이고, 명확하게 하기 위해 데일리 쿠폰을 주는 API와 유저 정보를 반환하는 API를 분리하려고 한다.
쿠폰 조회 API, 유저 정보 조회 API 2개로 구성
유저 정보 조회 API
// 유저 정보 조회
@Override
public MemberInfoResponseDTO getUserInfo() {
Long currentUserId = JwtService.getCurrentUserId();
Member member = memberRepository.findByIdLogin(currentUserId)
.orElseThrow(() -> new MemberException(MemberExceptionInfo.NOT_FOUND_USER, currentUserId + "번 유저를 찾지 못했습니다."));
return MemberInfoResponseDTO.toDTO(member);
}
3. 데일리 쿠폰 조회 API
// 남은 선착순 쿠폰 조회
@Override
@Transactional(readOnly = true)
public int getCoupons(){
LocalDateTime endOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
LocalDateTime startOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
log.info("시작 시간 {}", startOfDay);
log.info("끝 시간 {}", endOfDay);
Coupon coupon = couponRepository.findCouponForRead(startOfDay, endOfDay)
.orElseThrow(() -> new CouponException(CouponExceptionInfo.FAIL_COUPON_DATA, "DB에 쿠폰 데이터 존재하지 않음."));
return coupon.getRemainingQuantity();
}
WHY?
데일리 쿠폰을 조회하는데 쿠폰 데이터가 없으면 500을 반환하도록 만들어놨다.
분명 서버에 데이터가 없어서 발생하는 문제는 맞는데... 이게 500을 줘야 하나? 생각이 들었다.
또한 만약에 서버에서 쿠폰이 없으면 그냥 0개로 리턴해주면 400, 500을 안 주고도 정상적으로 처리할 수 있다고 판단했다.
HOW?
서버에 쿠폰이 존재하지 않는 경우 0으로 리턴.(즉 실패가 없는 API)
// 남은 선착순 쿠폰 조회
@Override
@Transactional(readOnly = true)
public int getCoupons(){
LocalDateTime endOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
LocalDateTime startOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
return couponRepository.findCouponForRead(startOfDay, endOfDay)
.map(Coupon::getRemainingQuantity)
.orElse(0);
}
4. 시큐리티 컨텍스트 홀더 조회 통합
Long currentUserId = JwtService.getCurrentUserId();
Member member = memberRepository.findById(currentUserId)
.orElseThrow(() -> new MemberException(MemberExceptionInfo.FAIL_LOGIN, "로그인 실패"));
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Long currentUserId = JwtService.getCurrentUserId();
Member member = memberRepository.findByIdLogin(currentUserId)
.orElseThrow(() -> new MemberException(MemberExceptionInfo.NOT_FOUND_USER, currentUserId + "번 유저를 찾지 못했습니다."));
WHY?
모든 메서드마다 현재 로그인 유저를 찾기 위한 코드가 들어있다. 메서드마다 중복되는 로직이 너무 많이 존재한다고 판단.
HOW?
여기서 어떻게 만들지에 대한 고민이 생겼다.
1. 해당 Service에서 private 메서드로 묶기.
이렇게 만들 경우 해당 서비스에서 메서드를 사용하면 된다. 그러나 다른 서비스에서도 또 동일하게 private을 만들어서 묶어야 한다.
2. 유틸 클래스를 하나 만들고 public static으로 만들어서 인스턴스 생성 없이 쓰기
이렇게 만들 경우 빈이 하나 더 생기게 되지만, public으로 열어주고 static이라 생성 없이 간단하게 사용할 수 있다.
나는 여기서 1번을 선택했다. 2번으로 시도를 해봤는데 스태틱에서 repository에 접근할 수 없어서 1번으로 선택.
// 현재 로그인 유저 찾기
private Member getCurrentMember() {
Long currentUserId = JwtService.getCurrentUserId();
return memberRepository.findById(currentUserId)
.orElseThrow(() -> new MemberException(MemberExceptionInfo.NOT_FOUND_USER, currentUserId + "번 유저를 찾지 못했습니다."));
}
'프로젝트 > RESTAPI 추천 서비스' 카테고리의 다른 글
유저의 탈퇴, 추방 여부를 추가해보자. (0) | 2024.05.20 |
---|---|
Navbar를 어떻게 페이지마다 유동적으로 보여줄 수 있을까? (0) | 2024.05.15 |
jwt Filter 가독성 리팩토링 진행 (0) | 2024.04.13 |
웹소켓을 사용해서 간단하게 전체 채팅을 구현하자(1) (0) | 2024.04.11 |