티스토리 뷰

 

이번달부터 학습 테스트로 배우는 Spring 강의를 듣고 있다.
해당 강의는 방탈출 예약 시스템 레거시 프로젝트를 리팩터링 하면서 Spring의 전반적인 기능들을 몸소 겪어볼 수 있는 강의이다.

기존에 알고 있던 개발 방식외에 실무자의 개발 방식이 궁금해서 강의를 신청하기로 결심했고, 1차로 받았던 코드 리뷰에 대해 정리해보고자 한다.

 

1. Entity 와 Dto 의존 관계

Service Layer에서 Repository 를 통해 반환한 Entity를 Controller로 반환할 때 편의성을 위해 toDto() 메서드를 만들었다.

매번 Entity를 Dto로 변환하는 과정이 많기에 메서드로 작업을 최소화 하고자 했다. 이에 대하여 아래와 같이 리뷰어님의 코멘트를 받았다.

Reservation 은 비즈니스 로직을 수행하는 중요한 객체가 될 것으로 예상되는 반면
ReservationDto는 기획, client의 요청에 따라 수시로 모양이 변경될 객체가 될 것 같아요.
현재 Reservation -> ReservationDto 의존 방향을 갖고 있는데, 이런 경우 ReservationDto의 변경에 Reservation 이 계속해서 영향을 받게 될 것 같아요. Reservation 이 잦은 변경으로부터 영향을 받지 않도록 의존 방향을 없애거나 제거하는 건 어떨까요?

 

Reservation.class

public class Reservation {

    private Long id;
    private String name;
    private String date;
    private String time;

    public Reservation(Long id, String name, String date, String time) {
        this.id = id;
        this.name = name;
        this.date = date;
        this.time = time;
    }

    public ReservationDto toDto() {
        return new ReservationDto(name, date, time);
    }
 
 }

 

client 여건에 따라 dto의 parameter는 언제든 바뀔 수 있고, 여러 클라이언트 환경에서 서버에 요청한다면 더 많은 dto가 필요할 텐데 모든 dto에 맞춰 entity class를 수정할 수 있기에 절대적으로 리뷰어님 코멘트에 동감했다. 

Entity가 Dto를 의존하는 것 자체가 추후 예상치 못한 이슈가 발생할 가능성도 많은 것 같다. 

그래서 entity에 해당 메서드는 삭제했고, 아래와 같이 dto에 Builder 패턴을 적용하는 것으로 개선했다.

ReservationRequestDto.class

public class ReservationRequestDto {
    private String name;
    private String date;
    private ReservationTimeRequestDto reservationTimeRequestDto;

    public static class Builder {
            private String name;
            private String date;
            private ReservationTimeRequestDto reservationTimeRequestDto;

            public Builder name(String name) {
                this.name = name;
                return this;
            }

            public Builder date(String date) {
                this.date = date;
                return this;
            }

            public Builder reservationTimeRequestDto(ReservationTimeRequestDto reservationTimeRequestDto) {
                this.reservationTimeRequestDto = reservationTimeRequestDto;
                return this;
            }

            public ReservationRequestDto build() {
                return new ReservationRequestDto(name, date, reservationTimeRequestDto);
            }
     }
 }

 

 

2. 인메모리를 이용한 데이터 관리

초기 리팩토링 조건으로 Database 없이 인메모리에 저장하도록 조건이 주어져 List를 이용해 데이터를 저장하는 것으로 했었다.

필드(index, 예약명, 날짜, 시간)가 많아 List 자료구조가 적절하다고 생각했었다.

아래 리뷰어님 코멘트를 통해 Map 자료구조를 이용하는 것으로 리팩토링을 했다.

인덱스를 통해 값을 조회하므로... List 대신 더 잘어울리는 자료구조가 있을 것 같은데요? 😉 (hint: key, value)

동시성 제어를 위한 AtomicLong 활용! 👍 시작 index 값이 무엇인지 new AtomicLong(1) 등으로 명세해 주면 가독성이 더욱 높아지겠네요!

 

ReservationController.class (As-is)

@RestController
public class ReservationController {

     private AtomicLong index = new AtomicLong();
     private List<Reservation> reservations = List.of(
             new Reservation(index.incrementAndGet(), "브라운", "2023-08-05", "15:40"),
             new Reservation(index.incrementAndGet(), "코니", "2023-08-05", "15:40"));
             
     // 예약 조회       
     @GetMapping("/reservations")
     public ResponseEntity<List<ReservationDto>> getReservations() {

         final List<ReservationDto> reservationDtos = reservations.stream().map(Reservation::toDto).toList();
         return ResponseEntity.ok().body(reservationDtos);

     }
 }

 

 

아래와 같이 HashMap으로 자료구조로 데이터를 저장하도록 개선했더니, 단건 조회(findById)나 데이터 존재여부(existById)를 확인할 때 id 값만으로 조회할 수 있도록 구조화된 향후 코드 작성 시 많은 이점이 있다고 느꼈다. 이후 Spring Data Jpa로 아키텍처를 변경한다면 코드 변경이 필요 없는 점도 많은 이점이라고 생각했다.

 

ReservationController.class (To-be)

@RestController
public class ReservationController {

     private static final HashMap<Long, Reservation> reservations = new HashMap<>();
     private final Reservation reservation1 = new Reservation("제이슨", "2023-08-05", "15:40");
     private final Reservation reservation2 = new Reservation("심슨", "2023-08-05", "15:40");
 
 	 @GetMapping("/reservations")
     public ResponseEntity<List<ReservationResponseDto>> getReservations() {
         reservations.put(reservation1.getId(), reservation1);
         reservations.put(reservation2.getId(), reservation2);

         final List<ReservationResponseDto> reservationResponseDtos = reservations.values().stream()
                 .map(
                         reservation -> new ReservationResponseDto.Builder()
                                 .id(reservation.getId())
                                 .name(reservation.getName())
                                 .date(reservation.getDate())
                                 .time(reservation.getTime())
                                 .build()
                 ).toList();

         return ResponseEntity.ok().body(reservationResponseDtos);

     }
}

 

Index 값을 정의할 때 AtombicLong 유틸객체를 이용했는데 초기화시 "1L"을 파라미터로 넣어주어 

`index는 1부터 시작해요~!` 라는 걸 코드만 보고도 알 수 있게 되어 코드 가독성이 높아졌다.

 

Reservation.class

public class Reservation {
     private static final AtomicLong index = new AtomicLong(1L);
     private Long id;
     private String name;
     private String date;
     private String time;

     public Reservation(String name, String date, String time) {
         this.id = index.incrementAndGet();
         this.name = name;
         this.date = date;
         this.time = time;
     }
}

 

 

※ AtomicLong 

AtomicLong은 Long 자료형을 갖고 있는 Wrapping 클래스이다.
Thread-safe로 구현되어 멀티쓰레드에서 synchronized 없이 사용할 수 있다.
또한 synchronized 보다 적은 비용으로 동시성을 보장할 수 있습니다.

 

- AtomicLong 참고 블로그

 

Java - AtomicLong이란?

Java - AtomicLong 사용법

velog.io

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함