-
Notifications
You must be signed in to change notification settings - Fork 3
[1주차 과제 제출] #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[1주차 과제 제출] #3
Changes from all commits
7eec086
6f3348b
86a67b6
9d7bfd8
6fac3f6
76a5f2a
dc45e96
03c77a0
3301448
3c15a4d
d218f49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
| } | ||
| } | ||
|
|
| 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(); | ||
| } | ||
| } | ||
| } | ||
|
|
| 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; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다른 필드와 다르게 원시형 타입으로 쓰신 이유가 있을까요?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 형변환 가능성이 없다고 판단한 부분과 null을 허용하지 않고자 했으며 이외에는 특별하게 고려한 이유는 없었습니다! |
||
|
|
||
| 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; | ||
| } | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. toEnvelope()가 현재 build를 감싸기 역할만 하고 있는 것처럼 보이는데 맞을까요 ??
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞습니다! 봉투 패턴을 사용하는 부분이 익숙치 않다보니 말씀해주신 부분 이외에도 많이 미숙하네요 ㅎ... |
| 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(); | ||
| } | ||
| } |
| 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("]"); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. StringBuilder로 문자열 빌드하고 계시는데 꼭 사용해야하는 이유가 아니라면,,
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 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(); | ||
| } |
There was a problem hiding this comment.
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로 각각 반환해줍니다 ㅎㅎ
There was a problem hiding this comment.
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를 분기하는 방식을 사용하였으며 해당 방식이 개인적으로 마음에 들어 지속적으로 사용하고 있습니다!
다른 좋은 방법이 있다면 또 얘기 나누어보고 싶네요 ㅎㅎ🥹