Troubleshooting: 무엇이 문제였는가?/본캠프 3주 차: 일정 관리 앱 Develop

1단계: "200 OK인데 왜 username이 null일까?"

writingforever162 2024. 12. 15. 18:21

[문제]

400번 대이든 500번 대이든 오류 났다는 메시지가 뜨면 그나마 코드를 잘못 썼다고 짐작할 수 있는데, 200 OK 메시지와 다르게 입력한 값이 제대로 반영되지 않으면 더 골치 아팠다. null도 문제였으나, 수정됐다면서 일정 수정 날짜인 updatedAt의 값이 그대로인 점이 더 골치 아팠다. 도대체 원인이 뭔지 추측조차 못 한 채, '코드 또 전부 다 뜯어고쳐야겠구나.' 해탈한 심정으로 튜터님께 찾아갔다.

 

[원인]

package com.example.plan.plan.dto.request;

import lombok.Getter;

// 일정 수정 요청에 해당하는 request DTO
@Getter
public class UpdatePlanRequestDto {
    // (1) 속성 
    private final String newUsername;
    private final String newTitle;
    private final String newTask;

    // (2) 생성자 
    public UpdatePlanRequestDto(
            String newUsername,
            String newTitle,
            String newTask
    ) {
        this.newUsername = newUsername;
        this.newTitle = newTitle;
        this.newTask = newTask;
    }
    // (3) 기능 
}

원인은 일정 수정 요청에 해당하는 request DTO 클래스(class)에서 속성에 해당하는 변수 이름과 실제로 요청할 때 쓴 이름이 다른 데에 있었다. 원인을 발견한 직후, 입을 꾹 다문 채 다짐했다. 이름을 다 기억할 자신이 없다면, 쓸데없이 'new'를 붙이지 말자고. 가뜩이나 대문자로 바꿔서 쓰기도 번거로우니까.

 

[해결]

package com.example.plan.plan.dto.request;

import lombok.Getter;

// 일정 수정 요청에 해당하는 request DTO
@Getter
public class UpdatePlanRequestDto {
    // (1) 속성
    private final String title;
    private final String task;

    // (2) 생성자
    public UpdatePlanRequestDto(
            String title
            , String task
    ) {
        this.title = title;
        this.task = task;
    }
    // (3) 기능
}

우선 일정 수정 요청 시 이름을 잘못 쓰는 일이 없도록 UpdatePlanRequestDto 클래스의 변수 이름을 다른 클래스의 변수 이름과 똑같이 지었다. 그다음으로는 일정만 수정하도록 작성자 이름을 속성에서 제외했다.

더보기
package com.example.plan.plan.service;

import com.example.plan.plan.dto.response.PlanResponseDto;
import com.example.plan.plan.entity.Plan;
import com.example.plan.plan.repository.PlanRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/*
일정(Plan)의 service layer
PlanService 인터페이스(interface)를 오버라이딩했다.
 */
@Service
@RequiredArgsConstructor
public class PlanServiceImpl implements PlanService {
    // 속성
    private final PlanRepository planRepository;

    // 기능 [1/5] 일정 저장
    @Override
    public PlanResponseDto save(
            String username
            , String title
            , String task
    ) {
        Plan planToSave = new Plan(
                username
                , title
                , task
        );

        Plan savedPlan = planRepository.save(planToSave);

        return new PlanResponseDto(
                savedPlan.getId()
                , savedPlan.getUsername()
                , savedPlan.getTitle()
                , savedPlan.getTask()
        );
    }

    // 기능 [2/5] 일정 목록 찾기
    @Override
    public List<PlanResponseDto> findAll() {

        List<PlanResponseDto> allPlans = new ArrayList<>();

        allPlans = planRepository.findAll()
                .stream()
                .map(PlanResponseDto::toDto)
                .toList();

        return allPlans;
    }

    // 기능 [3/5] 일정 단건을 id로 찾기
    @Override
    public PlanResponseDto findById(Long id) {

        Plan foundPlan = planRepository.findByIdOrElseThrow(id);

        return new PlanResponseDto(
                foundPlan.getId()
                , foundPlan.getUsername()
                , foundPlan.getTitle()
                , foundPlan.getTask()
        );
    }

    // 기능 [4/5] 일정 단건 수정
    @Transactional
    @Override
    public PlanResponseDto updatePlan(
            Long id
            , String title
            , String task
    ) {
        Plan planToUpdate = planRepository.findByIdOrElseThrow(id);

        planToUpdate.update(
                title
                , task
        );

        return new PlanResponseDto(
                planToUpdate.getId()
                , planToUpdate.getUsername()
                , planToUpdate.getTitle()
                , planToUpdate.getTask()
        );
    }

    // 기능 [5/5] 일정 단건 삭제
    @Override
    public void delete(Long id) {
        Plan foundPlan = planRepository.findByIdOrElseThrow(id);

        planRepository.delete(foundPlan);
    }
}

변수 이름을 모두 통일한 다음에는 일정 생성 날짜와 일정 수정 날짜는 반환하지 않는 쪽으로 코드를 수정했다. 이번에 스프링(Spring)을 공부하면서 새롭게 배운 @Transactional 어노테이션(annotation)을 사용하고 싶었는데, 두 날짜까지 포함되면 시차가 발생했다. 데이터베이스(database)에 각 날짜와 시간이 잘 저장된다면 굳이 반환하지 않아도 된다는 튜터님의 말씀에 과감하게 제외했다. @Transactional을 쓰지 않는다면 데이터베이스와 한 번 더 소통해서 해결할 수 있다고 저번에 트러블슈팅(troubleshooting)을 정리하면서 알았으니까. 이렇게 문제 하나를 해결했다.