[SB 3기] 코드잇 스프린트 미션 3, 4 회고

JHLee·2025년 6월 2일

📖 미션 내용

🚀 스프린트 미션 3

  • Java 프로젝트를 Spring 프로젝트로 마이그레이션
  • 의존성 관리를 IoC Container에 위임하도록 리팩토링
  • 비지니스 로직 고도화
    - 새로운 도메인 추가
    • DTO 활용
    • 서비스 고도화

🚀 스프린트 미션 4

  • 컨트롤러 레이어 추가 및 웹 API 구현
  • Postman을 활용한 API 테스트 수행

💡 코드 리뷰 사항 및 개선 포인트

📌 스프린트 미션 3

1. Optional은 반환값에 사용

Optional은 반환값 처리에 사용하는 것이 권장되며, 파라미터로 사용하는 것은 안티패턴으로 지양해야 한다.

// AS IS
public User create(UserCreateRequestDTO userCreateRequestDTO, 
			Optional<BinaryContentCreateRequestDTO> profileCreateRequestDTO)


// TO BE 
public User create(UserCreateRequest userCreateRequest,
            BinaryContentCreateRequest profileCreateRequestDTO)

2. 인스턴스 생성시 Builder 패턴 적용

new 생성자로 생성하는 건 파라미터의 순서와 갯수를 고려하여 작성해야 하므로, 코드의 가독성이 떨어지는 단점이 있다.
반면 Builder 패턴을 적용하면 가독성과 유지보수성이 높아진다. 특히 파라미터가 많을 경우 유리하다.

UserStatus userStatus = UserStatus.builder()
         .userId(user.getId())
         .lastActiveAt(Instant.now())
         .build();

3. 메서드에 접근 제한자 명시

메서드 작성시, 접근 제한자를 명시적으로 작성하는 것이 권장된다.
이를 작성함으로써 클래스 외부에 노출되는 범위를 명확히 하여 코드의 가독성과 유지보수성이 높아진다.

4. 부가 정보도 DTO에 포함하여 단일 요청으로 관리 (ex.파일 데이터)

유저 생성, 파일 생성 요청 정보를 별도로 전달하기보다는
요청 DTO에 함께 포함시켜 API 요청을 단일화하는 것이 구조적으로 더 깔끔하고 직관적이다.

단, 미션의 베이스코드에서는 DTO를 2개로 나눠 파라미터로 받았기에, 해당 구조는 유지하였다.

➕ 추가 사항

  • UserStatus 등 다른 도메인의 상태를 갱신할 때는, 서비스 간 의존보다는 컨트롤러에서 서비스들을 조율하는 방식이 더 적절하다.

📌 스프린트 미션 4

1. @Controller 대신 @RestController 활용

@RestController = @Controller + @ResponseBody 로, 이를 활용하는 것이 적절하다.

2. N+1 문제 고려

아래의 코드들에서 요청 1건당 쿼리가 N번 실행될 수 있는 구조로 N+1문제가 발생할 수 있다. 이를 개선하는 것이 좋다.

// 1
privateChannelCreateRequestDTO.participantIds().forEach(userService::find);

// 2
return ids.stream()
          .map(id -> binaryContentRepository.findById(id)
                    .orElseThrow(() -> new NoSuchElementException("존재하지 않는 BinaryContent입니다.")))
                        
// 3
createRequestDTO.participantIds().stream()
        .map(userId -> ReadStatus.builder()
                 .userId(userId)
                 .channelId(ch.getId())
                 .lastReadAt(Instant.now())
                 .build())
        .forEach(readStatusRepository::save);

3. 일관된 네이밍

userId,participantIds 등 동일한 의미의 필드가 여러 명칭으로 횬용되어 있을 경우, 의미 파악에 혼란을 줄 수 있다.
일관된 네이밍 (userIds 등)을 사용하는 것이 바람직하다.

4. NullPointerException 방지를 위한 equals() 호출

equals() 호출시, null이 될 가능성이 있는 객체에서 호출하면 NPE가 발생할 수 있으므로, NPE 발생 가능성이 없는 쪽에서 호출하는 것이 적절하다.

// AS IS 
if (ch.getType().equals(ChannelType.PRIVATE)) 

// TO BE
if (ChannelType.PRIVATE.equals(ch.getType()))

회고

👍 좋았던 점

  • Controller 레이어를 추가함으로써, 각 계층간의 로직 흐름을 명확히 파악할 수 있었고 MVC 패턴에 대한 이해를 통해 코드 작성에 감이 잡혔던 것 같다.
  • Postman를 활용해볼 수 있었던 게 좋았다. 이를 통해 API 요청과 응답을 직관적으로 확인해 볼 수 있었고, 테스트 결과도 export하여 쉽게 공유할 수 있었다.

😅 아쉬운 점

  • 요구사항이 좀 더 구체적이었다면 좋았을 것 같다.
    특히 각 도메인에 필요한 필드가 명시되어 있었다면, 이후 수정 작업이 줄고 설계의 방향도 명확했을 것 같다. 앞선 미션의 코드 구조와 달라지는 점이 많아, 초기 구조에서 베이스 코드를 활용하여 상당 부분 수정해야 했고 이 과정에서 내가 처음 작성한 코드의 흔적이 점점 사라지는 점이 아쉬웠다.
    자유로운 설계의 취지는 이해되지만, 후속 미션과 연계된 범위 내에서의 설계 가이드라인이 주어졌다면 더 효율적인 학습이 되었을 것 같다.
  • 베이스코드 형식을 따르지 않으면 프론트엔드 연동 테스트가 어려웠는데 이 점 또한 아쉬었다.

🧠 배운 점

  • Optional은 파라미터가 아닌 반환값 처리용으로 사용하는 것이 적절하다.
  • Builder 패턴을 활용하면 객체 생성시 가독성이 향상된다.
  • 서비스 간 강한 의존보다는, Controller 단에서 필요한 서비스를 조합하는 방식이 유지보수 측면에서 유리하다.
  • Postman을 활용한 API 테스트는 실제 클라이언트 입장에서의 흐름을 파악하는 데 도움이 되고, API의 구조를 직관적으로 검증할 수 있다.
profile
개발자로 성장하기

0개의 댓글