Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package study.todolist.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import study.todolist.dto.TodoDto;
import study.todolist.global.Envelope;
import study.todolist.entity.TodoList;
import study.todolist.service.TodoService;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/todo")
public class TodoController {

private final TodoService todoService;


// 생성
@PostMapping("/create")
public ResponseEntity createTodo(@RequestBody TodoDto.Request request){

Long id = todoService.createTodo(request.getTitle());

return ResponseEntity.ok(Envelope.toEnvelope(todoService.findById(id)));
}

// 단건조회
@GetMapping("/find/{id}")
public ResponseEntity findById(@PathVariable("id") Long id){

TodoList todo = todoService.findById(id);
Envelope response = Envelope.toEnvelope(todo);

return ResponseEntity.ok(response);
}

// 전체조회
@GetMapping("/find/all")
public ResponseEntity findAll(){

Envelope envelope = Envelope.toEnvelope(todoService.findAll());

return ResponseEntity.ok(envelope);
}

// 수정
@PatchMapping("/update/{id}")
public ResponseEntity updateTodo(@PathVariable Long id,
@RequestBody TodoDto.Request request){

todoService.updateTitle(id, request.getTitle());

return ResponseEntity.ok(Envelope.toEnvelope(todoService.findById(id)));
}

// check
@PatchMapping("/check/{id}")
public ResponseEntity checkTodo(@PathVariable Long id){

todoService.updateCheck(id);

return ResponseEntity.ok(Envelope.toEnvelope(todoService.findById(id)));
}

// 삭제
@DeleteMapping("/delete/{id}")
public ResponseEntity deleteTodo(@PathVariable Long id){

todoService.delete(id);

return ResponseEntity.ok(true);
}
}

33 changes: 33 additions & 0 deletions todolist/src/main/java/study/todolist/dto/TodoDto.java
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한 클래스 안에 Rq, Rs를 두신 이유가 있을까요?
저는 Dto를 분리해서 TodoRequest, TodoResponse로 각각 반환해줍니다 ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저 또한 엔티티의 수가 많지 않다면 가시성있게 Request, Response를 분리하는 것이 유리한 점을 갖는다고 생각합니다!
반면 프로젝트를 진행하면서 그 수가 많아지게 된다면 Dto 클래스의 수가 매우 많아지고, 이를 분리하기 위한 패키지도 많아져 개인적으로 불편함을 느꼈던 경험이 있습니다.
당시 고민 후에 엔티티에 대한 Dto를 하나만 작성하고, 그 안에 static class로 Request, Response를 분기하는 방식을 사용하였으며 해당 방식이 개인적으로 마음에 들어 지속적으로 사용하고 있습니다!
다른 좋은 방법이 있다면 또 얘기 나누어보고 싶네요 ㅎㅎ🥹

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package study.todolist.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import study.todolist.entity.TodoList;

public class TodoDto {

@Data
public static class Request{
private String title;
}

@Data @Builder
@NoArgsConstructor @AllArgsConstructor
public static class Response{
private Long id;
private String title;
private boolean check;

public static Response of(TodoList todoList) {

return Response.builder()
.id(todoList.getId())
.title(todoList.getTitle())
.check(todoList.isChecked())
.build();
}
}
}

32 changes: 32 additions & 0 deletions todolist/src/main/java/study/todolist/entity/TodoList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package study.todolist.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter @Builder
@NoArgsConstructor
@AllArgsConstructor
public class TodoList{

private Long id;
private String title;
private boolean checked;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 필드와 다르게 원시형 타입으로 쓰신 이유가 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

형변환 가능성이 없다고 판단한 부분과 null을 허용하지 않고자 했으며 이외에는 특별하게 고려한 이유는 없었습니다!
하지만 애초에 Builder패턴으로 엔티티 생성 시 기본값으로 false를 지정해주기때문에 큰 의미가 없다고 느끼고 있어, 스터디 시간에 말한 바 있듯이 enum으로 처리하는 방식을 고려하여 수정 예정입니다 🤗


public void updateId(Long id){
this.id = id;
}

public void updateTitle(String title){
this.title = title;
}

public void updateChecked(){
if (this.checked)
this.checked = false;

else this.checked = true;
}
}

45 changes: 45 additions & 0 deletions todolist/src/main/java/study/todolist/global/Envelope.java
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toEnvelope()가 현재 build를 감싸기 역할만 하고 있는 것처럼 보이는데 맞을까요 ??
메서드를 추가로 작성하는 대신에 직접 build 메서드를 호출하는 것이 더 간단하고 명확할 것 같은데 어떻게 생각하시나요 !

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다! 봉투 패턴을 사용하는 부분이 익숙치 않다보니 말씀해주신 부분 이외에도 많이 미숙하네요 ㅎ...
일단 Response되는 데이터가 원하는대로 잘 나오는지에 대한 부분만 집중하다보니 이런 문제가...😱
말씀해주신 부분과 같이 직접 build를 호출하여 작성해보도록 하겠습니다!
좋은 말씀 감사드려요 😊

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package study.todolist.global;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import study.todolist.dto.TodoDto;
import study.todolist.entity.TodoList;

import java.util.List;
import java.util.stream.Collectors;

@Getter @Builder
@NoArgsConstructor @AllArgsConstructor
public class Envelope<T> {

private T data;
private String error;
private String message;

public static Envelope toEnvelope(TodoList data){

TodoDto.Response response = TodoDto.Response.builder()
.id(data.getId())
.title(data.getTitle())
.check(data.isChecked())
.build();

return Envelope.builder()
.data(response)
.build();
}

public static Envelope toEnvelope(List<TodoList> data){

List<TodoDto.Response> list = data.stream()
.map(TodoDto.Response::of)
.collect(Collectors.toList());

return Envelope.builder()
.data(list)
.build();
}
}
23 changes: 23 additions & 0 deletions todolist/src/main/java/study/todolist/global/error/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package study.todolist.global.error;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum ErrorCode {

NOT_FOUND_TODO(HttpStatus.NOT_FOUND, "T-001", "TODO를 찾을 수 없습니다.")
;


ErrorCode(HttpStatus httpStatus, String errorCode, String message) {
this.httpStatus = httpStatus;
this.errorCode = errorCode;
this.message = message;
}

private HttpStatus httpStatus;
private String errorCode;
private String message;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package study.todolist.global.error;

import lombok.Builder;
import lombok.Getter;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

import java.util.List;

@Getter
@Builder
public class ErrorResponse {

private String errorCode;
private String errorMessage;

public static ErrorResponse of(String errorCode, String errorMessage){

return ErrorResponse.builder()
.errorCode(errorCode)
.errorMessage(errorMessage)
.build();
}

public static ErrorResponse of(String errorCode, BindingResult bindingResult){

return ErrorResponse.builder()
.errorCode(errorCode)
.errorMessage(createErrorMessage(bindingResult))
.build();
}

private static String createErrorMessage(BindingResult bindingResult) {

StringBuilder sb = new StringBuilder();
boolean isFirst = true;

List<FieldError> fieldErrors = bindingResult.getFieldErrors();

for (FieldError error : fieldErrors){

if (!isFirst){
sb.append(", ");
} else isFirst = false;

sb.append("[");
sb.append(error.getField());
sb.append("]");
Copy link
Collaborator

@jhnyuk jhnyuk Dec 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StringBuilder로 문자열 빌드하고 계시는데 꼭 사용해야하는 이유가 아니라면,,
stream + String.join()을 사용해서 더 간결하게 처리할 수 있을 것 같아요 !

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 join이 있었군요!
코딩테스트 연습에서 StringBuilder에 익숙해져 사용했고 특별한 이유는 없었습니다 🥺
join은 평소에 비교적 사용하지 않아 놓친 것 같네요! 개선해보도록 하겠습니다!
좋은 말씀 감사드려요 😊

sb.append(error.getDefaultMessage());
}

return sb.toString();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package study.todolist.global.error;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import study.todolist.global.Envelope;
import study.todolist.global.error.exception.EntityNotFoundException;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* EntityNotFoundException 발생 메시지 처리
*/
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<Envelope> handleEntityNotFoundException(EntityNotFoundException e){
log.error("EntityNotFoundException", e);

Envelope<Object> envelope = Envelope.builder()
.error(e.getErrorCode().toString())
.message(e.getMessage())
.build();

return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(envelope);
}

/**
* 나머지 예외 발생
*/
@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleException(Exception e){
log.error("Exception", e);
ErrorResponse errorResponse = ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR.toString(), e.getMessage());

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(errorResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package study.todolist.global.error.exception;

import lombok.Getter;
import study.todolist.global.error.ErrorCode;

@Getter
public class BusinessException extends RuntimeException{

private ErrorCode errorCode;

public BusinessException(ErrorCode errorCode){
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package study.todolist.global.error.exception;

import study.todolist.global.error.ErrorCode;

public class EntityNotFoundException extends BusinessException{

public EntityNotFoundException(ErrorCode errorCode) {
super(errorCode);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package study.todolist.repository;

import java.util.List;
import java.util.Optional;

public interface MemoryDBRepository<T> {

T save(T entity);

Optional<T> findById(Long id);

void delete(Long id);

List<T> findAll();
}
Loading