-
Notifications
You must be signed in to change notification settings - Fork 3
[1주차 과제 제출] #2
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주차 과제 제출] #2
Changes from all commits
bca4efb
e98d3a5
631f762
ae926af
796c9f1
009df57
cdfed49
c9f04ab
2b4596a
cc88693
c36623f
07fcedb
39e577c
5496865
c1c3c60
7d1958b
219b47a
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,33 @@ | ||
| # Week1 | ||
| ## 요구사항 분석을 통한 기능 목록 도출 | ||
|
|
||
| - 할 일 생성 | ||
| - 할 일의 내용과 우선 순위를 포함해야 한다. | ||
| - `Todo.of` 메소드를 사용하여 `Todo` 객체를 생성해야한다. | ||
| - `InMemoryDB`를 사용하여 데이터를 저장한다. | ||
|
|
||
| - 할 일 수정 | ||
| - 할 일의 내용과 우선 순위 모두 수정할 수 있어야 한다. | ||
| - 할 일의 내용과 우선 순위 각각 수정할 수 있어야 한다. | ||
|
|
||
| - 할 일 조회 | ||
| - 단건만 조회할 수 있어야 한다. | ||
| - 모든 할 일을 조회할 수 있어야 한다. | ||
| - 삭제된 할 일은 제외하고 반환해야 한다. | ||
| - 완료된 할 일만 조회할 수 있어야 한다. | ||
| - 삭제된 할 일과 완료된 할 일은 제외하고 반환해야 한다. | ||
|
|
||
| - 할 일 삭제 | ||
| - 삭제는 논리 삭제를 사용하여 처리된다. | ||
|
|
||
| - 할 일 완료여부 | ||
| - 생성된 할 일에 대해 완료를 할 수 있어야 한다. | ||
|
|
||
| ### 핵심 구현 내용 | ||
| - `TodoTask` 클래스의 `from()` : 문자열을 입력받아 `TodoTask` 객체를 생성한다. 이렇게 메소드를 통해 객체를 생성함으로써, 객체 생성 규칙을 한 곳에서 관리 | ||
| - ConcurrentHashMap 사용 : 투두리스트 구현은 읽기 작업보다는 쓰기 작업에 성능이 중요한 상황이라 생각되어, 인메모리 데이터 저장을 위해 ConcurrentHashMap 사용 | ||
| - `InMemoryDB`의 `validateKey()` : `GlobalExceptionHandler`에서는 이 예외를 잡아서 적절한 에러 메세지와 함께 `RsData`를 반환한다. `InMemoryDB`의 메소드에서는 키의 유효성만 검사하고, 실제 예외 처리는 `GlobalExceptionHandler`에서 수행한다. | ||
| - `RsData` 클래스 Envelop Pattern 구현 | ||
| - resultCode, message, data의 응답 메타데이터를 포함했다. | ||
| - 추가로 다양한 경우의 API 응답을 쉽게 생성할 수 있도록 of, successOf, failOf 등의 정적 팩토리 메소드를 구현했다. | ||
| - isSuccess, isFail 메소드를 사용하여 API 응답의 성공 여부를 쉽게 확인하도록 했다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package study.todolist.base; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.concurrent.atomic.AtomicLong; | ||
|
|
||
| @Getter | ||
| public abstract class BaseEntity { | ||
| private static final AtomicLong idGenerator = new AtomicLong(0); | ||
| private Long id; | ||
| private LocalDateTime createDate; | ||
| private LocalDateTime modifyDate; | ||
| private Boolean isDeleted; | ||
|
|
||
| public BaseEntity() { | ||
| this.id = idGenerator.incrementAndGet(); | ||
| this.createDate = LocalDateTime.now(); | ||
| this.modifyDate = LocalDateTime.now(); | ||
| this.isDeleted = false; | ||
| } | ||
|
|
||
| public Long getId() { | ||
| return id; | ||
| } | ||
|
|
||
| public void delete() { | ||
| this.isDeleted = true; | ||
| this.modifyDate = LocalDateTime.now(); | ||
| } | ||
|
|
||
| public void update() { | ||
| this.modifyDate = LocalDateTime.now(); | ||
| } | ||
|
|
||
| public boolean isDeleted() { | ||
| return isDeleted; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package study.todolist.base; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| public class RsData<T> { | ||
| private String resultCode; | ||
| private String message; | ||
| private T data; | ||
|
|
||
| public static <T> RsData<T> of(String resultCode, String msg, T data) { | ||
| return new RsData<>(resultCode, msg, data); | ||
| } | ||
|
|
||
| public static <T> RsData<T> of(String resultCode, String msg) { | ||
| return of(resultCode, msg, null); | ||
| } | ||
|
|
||
| public static <T> RsData<T> successOf(T data) { | ||
|
Member
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. 여러 case를 위해 of를 별도로 두신걸로 보이는데, code는 에러 코드만 전달하는 요구사항이었던것 같은데 어떻게 생각하시나요
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. 앗 맞네요..! 이 부분은 제가 요구사항을 제대로 구현하지 못한 것 같습니다 |
||
| return of("S-1", "성공", data); | ||
| } | ||
|
|
||
| public static <T> RsData<T> failOf(T data) { | ||
| return of("F-1", "실패", data); | ||
| } | ||
|
|
||
| public static <T> RsData<T> failOf(String message) { | ||
| return of("F-1", message, null); | ||
| } | ||
|
|
||
| public boolean isSuccess() { | ||
| return resultCode.startsWith("S-"); | ||
| } | ||
|
|
||
| public boolean isFail() { | ||
| return !isSuccess(); | ||
| } | ||
|
|
||
| } | ||
|
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. 이런 형식으로 작성하는군요! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package study.todolist.base.exceptionHandler; | ||
|
|
||
| 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.base.RsData; | ||
| import study.todolist.domain.todo.exception.NotFoundException; | ||
|
|
||
| @RestControllerAdvice | ||
| public class GlobalExceptionHandler { | ||
| private static final String INVALID_KEY_VALUE = "키 값이 유효하지 않습니다."; | ||
|
Member
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. Property클래스들을 별도의 Enum으로 관리하는 편이 좋지않았을까요? |
||
| private static final String INVALID_INPUT_VALUE = "입력 값이 올바르지 않습니다."; | ||
|
|
||
| @ExceptionHandler(IllegalArgumentException.class) | ||
| public RsData<Void> handleIllegalArgumentException(IllegalArgumentException e) { | ||
| String errorMessage = e.getMessage(); | ||
| if (errorMessage.equals("키는 Null이 아니여야 합니다.")) { | ||
| return RsData.of("F-2", INVALID_KEY_VALUE); | ||
| } else { | ||
| return RsData.of("F-3", INVALID_INPUT_VALUE); | ||
| } | ||
| } | ||
|
|
||
| @ExceptionHandler(NotFoundException.class) | ||
| public RsData<String> handleNotFoundException(NotFoundException e) { | ||
| return RsData.failOf(e.getMessage()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package study.todolist.domain.todo.controller; | ||
|
|
||
| import org.springframework.web.bind.annotation.*; | ||
| import study.todolist.base.RsData; | ||
| import study.todolist.domain.todo.dto.request.TodoRequest; | ||
| import study.todolist.domain.todo.dto.response.ViewSingleResponse; | ||
| import study.todolist.domain.todo.exception.NotFoundException; | ||
| import study.todolist.domain.todo.service.TodoServiceImpl; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/todos") | ||
| public class TodoController { | ||
| private final TodoServiceImpl todoService; | ||
|
|
||
| public TodoController(TodoServiceImpl todoService) { | ||
| this.todoService = todoService; | ||
| } | ||
|
|
||
| @PostMapping | ||
| public RsData<ViewSingleResponse> createTodo(@RequestBody TodoRequest request) { | ||
| ViewSingleResponse response = todoService.createTodo(request); | ||
| return RsData.successOf(response); | ||
| } | ||
|
|
||
| @PatchMapping("/{id}") | ||
| public RsData<ViewSingleResponse> updateTodo(@PathVariable("id") Long id, | ||
| @RequestBody TodoRequest request) { | ||
| try { | ||
| ViewSingleResponse response = todoService.updateTodo(id, request.getTask()); | ||
| return RsData.successOf(response); | ||
| } catch (NotFoundException e) { | ||
| return RsData.failOf(e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| public RsData<ViewSingleResponse> deleteTodo(@PathVariable("id") Long id) { | ||
| try { | ||
| ViewSingleResponse response = todoService.deleteTodo(id); | ||
| return RsData.successOf(response); | ||
| } catch (NotFoundException e) { | ||
| return RsData.failOf(e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| @GetMapping | ||
| public RsData<List<ViewSingleResponse>> getAllTodos() { | ||
| List<ViewSingleResponse> responses = todoService.getAllTodos(); | ||
| return RsData.successOf(responses); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| public RsData<ViewSingleResponse> getSingleTodo(@PathVariable("id") Long id) { | ||
| try { | ||
|
Member
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. try-catch를 controller에서 처리한 이유가 따로있나요? |
||
| ViewSingleResponse response = todoService.getSingleTodo(id); | ||
| return RsData.successOf(response); | ||
| } catch (NotFoundException e) { | ||
| return RsData.failOf(e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package study.todolist.domain.todo.database; | ||
|
|
||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.Optional; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Component | ||
| public class InMemoryDB<K, V> { | ||
|
Member
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. K,V로 처리한건 아주 좋은 방법인것같네요 배워갑니다 |
||
| private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>(); | ||
|
|
||
| public Optional<V> findById(K key) { | ||
| validateKey(key); | ||
| return Optional.ofNullable(map.get(key)); | ||
| } | ||
|
|
||
| public void save(K key, V value) { | ||
| validateKey(key); | ||
| map.put(key, value); | ||
| } | ||
|
|
||
| public void deleteById(K key) { | ||
| validateKey(key); | ||
| map.remove(key); | ||
| } | ||
|
|
||
| public List<V> findAll() { | ||
| return map.values().stream().collect(Collectors.toList()); | ||
| } | ||
|
|
||
| private void validateKey(K key) { | ||
| if (key == null) { | ||
| throw new IllegalArgumentException("키는 Null이 아니여야 합니다."); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package study.todolist.domain.todo.dto.request; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import study.todolist.domain.todo.util.Priority; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| public class TodoRequest { | ||
| private String task; | ||
| private Priority priority; | ||
|
|
||
| public TodoRequest(String task, String priority) { | ||
| this.task = task; | ||
| this.priority = Priority.valueOf(priority.toUpperCase()); | ||
| } | ||
|
|
||
| public String getTask() { | ||
| return task; | ||
| } | ||
|
|
||
| public Priority getPriority() { | ||
| return priority; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package study.todolist.domain.todo.dto.response; | ||
|
|
||
| import study.todolist.domain.todo.util.Priority; | ||
|
|
||
| public class ViewAllResponse { | ||
|
Member
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. record 타입을 고려안하신이유가 있나요? |
||
| private Long id; | ||
| private String task; | ||
| private Boolean isCompleted; | ||
| private Priority priority; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package study.todolist.domain.todo.dto.response; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import study.todolist.domain.todo.entity.Todo; | ||
| import study.todolist.domain.todo.util.Priority; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| public class ViewSingleResponse { | ||
| private Long id; | ||
| private String task; | ||
| private Priority priority; | ||
| private Boolean deleted; | ||
|
|
||
| public ViewSingleResponse(Todo todo) { | ||
| this.id = todo.getId(); | ||
| this.task = todo.getTask().getTask(); | ||
| this.priority = todo.getPriority(); | ||
| this.deleted = todo.isDeleted(); | ||
| } | ||
|
|
||
| public Long getId() { | ||
|
Member
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. Getter어노테이션 사용후 별도로 getter를 구현하신이유가 있나요? |
||
| return id; | ||
| } | ||
|
|
||
| public String getTask() { | ||
| return task; | ||
| } | ||
|
|
||
| public Priority getPriority() { | ||
| return priority; | ||
| } | ||
|
|
||
| public boolean isDeleted() { | ||
| return deleted; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package study.todolist.domain.todo.entity; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import study.todolist.base.BaseEntity; | ||
| import study.todolist.domain.todo.util.Priority; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @AllArgsConstructor | ||
| public class Todo extends BaseEntity { | ||
| private TodoTask task; | ||
| private Boolean isCompleted; | ||
| private Priority priority; | ||
|
|
||
| public static Todo of(TodoTask task, Boolean isCompleted, Priority priority) { | ||
| return new Todo(task, isCompleted, priority); | ||
| } | ||
|
|
||
| public TodoTask getTask() { | ||
| return task; | ||
| } | ||
|
|
||
| public void setTask(TodoTask task) { | ||
| this.task = task; | ||
| update(); | ||
| } | ||
|
|
||
| public Priority getPriority() { | ||
| return priority; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isDeleted() { | ||
| return super.isDeleted(); | ||
| } | ||
| } |
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.
무엇을 위한 필드값인가요?
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.
넵! 네 개 중 어떤 필드를 말씀하신 걸까요 ??
우선 생성일자랑 수정일자, 삭제 여부를 위한 필드값입니다 !