Skip to content
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

[FIX] 할 일 생성, 수정, 삭제 기능 수정 #59

Merged
merged 3 commits into from
Feb 11, 2025
Merged

Conversation

khw7385
Copy link
Collaborator

@khw7385 khw7385 commented Feb 8, 2025

Issue Number

close #46

As-Is

할 일 변경(생성, 수정, 삭제) 기능을 수정한다.

To-Be

  • 오늘, 내일, 즐겨찾기 할 일 별로 별도의 클래스를 분리한다.
    • 전략 패턴을 적용한다.
    • 요청 시 받은 타입 정보로 적절한 서비스의 기능을 호출한다.
  • 내일 스케줄이 존재하지 않은 경우 내일 할 일 수정, 삭제 시 내일 스케줄을 생성한다.
    • 내일 스케줄을 생성한다.
    • 오늘 할 일로부터 내일 할 일을 생성한다.
  • 할 일 변경 api 수정
    • 할 일 전용 성공 응답 생성 (내일 스케줄 생성 여부, 데이터가 포함된다.)
    • 할 일 생성, 수정, 삭제 api 에서 내일 스케줄이 생성된 경우 응답 상태 코드 Created
    • 할 일 삭제 api 에서 내일 스케줄이 생성되지 않은 경우 응답 상태 코드 OK(기존 NO CONTENT)
  • 최근 스케줄 조회 쿼리에서 fetch join(할 일) 과 LIMIT 이 모두 적용되지 않은 문제 해결
    • LIMIT 그대로 두고, fetch join 제거, 필요하다면 할 일에 대한 쿼리 호출

Check List

  • 테스트가 전부 통과되었나요?
  • 모든 commit이 push 되었나요?
  • merge할 branch를 확인했나요?
  • Assignee를 지정했나요?
  • Label을 지정했나요?

Test Screenshot

(Optional) Additional Description

LIMIT 절 과 FETCH JOIN 같이 사용하면 발생하는 문제

일대다 관계에서 다 쪽에 FETCH JOIN을 걸면 JPA는 몇 개의 데이터를 가져와야 할 지 모른다. 따라서, 해당되는 모든 데이터를 어플리케이션 메모리로 가져와서 메모리 단에서 LIMIT 을 수행한다.

Summary by CodeRabbit

  • New Features

    • Added distinct todo request types (e.g., for today, tomorrow, or saving) to enhance task management.
    • Delivered clear, descriptive error messages for invalid todo submissions.
  • Refactor

    • Streamlined backend scheduling queries for improved efficiency.

@khw7385 khw7385 added fix 버그 수정 (기능을 잘못 구현한 경우) BE 백엔드 작업 labels Feb 8, 2025
@khw7385 khw7385 added this to the [BE] 3차 스프린트 milestone Feb 8, 2025
@khw7385 khw7385 self-assigned this Feb 8, 2025
Copy link

coderabbitai bot commented Feb 8, 2025

Walkthrough

This change introduces a new enum TodoRequestType with constants TODAY, TOMORROW, and SAVE along with a helper method to convert strings to enum values. It also adds a dedicated exception InvalidTodoRequestTypeException and a corresponding error code in ErrorCode to handle invalid todo request types. Additionally, the repository method in ScheduleRepository has been simplified by removing explicit query parameter annotations.

Changes

File(s) Change Summary
backend/src/main/java/com/dubu/backend/todo/dto/.../TodoRequestType.java
backend/src/main/java/com/dubu/backend/todo/exception/InvalidTodoRequestTypeException.java
backend/src/main/java/com/dubu/backend/global/exception/ErrorCode.java
Added a new enum to define todo request types with a static conversion method, a custom exception for invalid types, and an error code constant for handling invalid requests.
backend/src/main/java/com/dubu/backend/todo/repository/ScheduleRepository.java Removed the query annotations (@Query and @Param) and updated the method signature for findScheduleByMemberAndDate to a simplified declaration.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant TodoRequestType
    participant ExceptionHandler
    Client->>TodoRequestType: fromString(input)
    alt Valid Request Type
        TodoRequestType-->>Client: Returns corresponding enum constant
    else Invalid Request Type
        TodoRequestType->>ExceptionHandler: throw InvalidTodoRequestTypeException
        ExceptionHandler-->>Client: Error using INVALID_TODO_REQUEST_TYPE
    end
Loading

Assessment against linked issues

Objective (Issue #46) Addressed Explanation
Separating business logic for 오늘, 내일, 즐겨찾기 and ensuring todo type is input on update/delete
Creating a schedule for 내일 todos when updating/deleting Repository changes hint at adjustments, but explicit schedule creation logic is not present.

Possibly related PRs

Suggested reviewers

  • codrin2

Poem

I’m a rabbit on a coding spree,
Hopping through enums so merrily.
Today, Tomorrow, and a SAVE or two,
Exceptions leap when wrong types come through.
In fields of code, I nibble and play,
Celebrating each change in a bunny way!
🥕🐇 Happy coding all the day!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be73ad4 and 5f64d69.

📒 Files selected for processing (2)
  • backend/src/main/java/com/dubu/backend/global/exception/ErrorCode.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/repository/ScheduleRepository.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/src/main/java/com/dubu/backend/global/exception/ErrorCode.java
  • backend/src/main/java/com/dubu/backend/todo/repository/ScheduleRepository.java

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (1)
backend/src/main/java/com/dubu/backend/todo/repository/CustomScheduleRepositoryImpl.java (1)

27-30: Remove unused helper method.

The applyJoinTodo method is no longer used after removing the conditional fetch join logic.

Apply this diff to remove the unused method:

-    private JPAQuery<Schedule> applyJoinTodo(JPAQuery<Schedule> jpaQuery){
-        return jpaQuery.leftJoin(schedule.todos, todo)
-                .fetchJoin();
-    }
🧹 Nitpick comments (15)
backend/src/main/java/com/dubu/backend/todo/dto/response/TodoInfo.java (1)

19-22: Consider refactoring for better code reuse and null safety.

The implementation could be improved by:

  1. Reusing the existing fromEntity method
  2. Adding null safety check
  3. Improving formatting for better readability

Here's a suggested improvement:

     public static List<TodoInfo> fromEntities(List<Todo> todos){
+        if (todos == null) {
+            return List.of();
+        }
-        return todos.stream()
-                .map(todo -> new TodoInfo(todo.getId(), todo.getCategory(), todo.getDifficulty(), todo.getTitle(), todo.getMemo())).toList();
+        return todos.stream()
+                .map(TodoInfo::fromEntity)
+                .toList();
     }

This refactoring:

  • Handles null input gracefully
  • Reuses the existing fromEntity method, reducing code duplication
  • Improves readability with proper formatting
backend/src/main/java/com/dubu/backend/todo/controller/TodoController.java (2)

49-57: Check REST status code for patch requests.
Returning 201 (Created) when a tomorrow schedule is generated may be confusing for typical REST conventions (PATCH usually returns 200). However, this may be a valid requirement given the business logic.


60-68: Consider aligning deletion response status with REST conventions.
Using 201 for a resource deletion might be unusual. If your domain logic truly requires 201 in certain cases, ensure it’s well-documented. Otherwise, 200 (OK) or 204 (No Content) is more common for successful deletions.

backend/src/main/java/com/dubu/backend/todo/service/impl/SaveTodoManagementService.java (1)

1-34: Class definition and dependencies.
All required repositories are properly injected. Check concurrency controls or potential indexing if large volumes of data are expected, as dynamic schedule lookups might become a bottleneck.

backend/src/main/java/com/dubu/backend/todo/service/impl/TomorrowTodoManagementService.java (3)

68-98: Refactor duplicated logic between createTodo and createTodoFromArchived.
Both methods share checks for schedule existence, the “3 tasks limit,” and conditional creation of tomorrow’s schedule. To reduce maintenance and potential errors, consider extracting this logic into helper methods or a reusable service routine.


101-149: Efficiency concern in identifying the target todo within todayTodos.
Using an index-based search (lines 117-122) is adequate for small lists. However, as the list scales, repeated linear scans can become a performance bottleneck. If the list could grow larger, consider using a map keyed by Todo IDs or adjusting the query to directly fetch the needed entity.


177-186: Method naming might be misleading.
The method createTomorrowTodosFromTodayTodos can be called even if the original schedule is not actually today’s schedule. Consider renaming it to better reflect its generic “todo cloning” behavior.

backend/src/main/java/com/dubu/backend/todo/dto/request/TodoCreateFromArchivedRequest.java (1)

3-4: Consider additional business validations for 'todoId'.
If the “todoId” must be positive or meet certain constraints, use validators or domain checks to enforce this. If the intention is to always reference a valid archived Todo, clarifying constraints helps ensure robust domain logic.

backend/src/main/java/com/dubu/backend/todo/dto/response/TodoSuccessResponse.java (1)

3-4: Consistent naming for boolean fields.
Renaming “isTomorrowScheduleCreated” to “tomorrowScheduleCreated” can improve readability and match conventional Java naming for booleans. This is a minor style preference but can provide consistency throughout the codebase.

backend/src/main/java/com/dubu/backend/todo/converter/StringToTodoRequestTypeConverter.java (1)

7-12: Consider adding explicit error handling.

While the converter correctly implements the Spring Converter interface, it might benefit from explicit error handling for invalid type strings.

Consider wrapping the conversion in a try-catch block:

 @Override
 public TodoRequestType convert(String type) {
+    try {
         return TodoRequestType.fromString(type);
+    } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException("Invalid todo request type: " + type);
+    }
 }
backend/src/main/java/com/dubu/backend/todo/dto/request/TodoCreateRequest.java (1)

8-18: Add Javadoc to document the entity creation method.

The toEntity method would benefit from documentation explaining its parameters and behavior.

Add Javadoc:

+    /**
+     * Creates a Todo entity from the request data.
+     *
+     * @param member   The member creating the todo
+     * @param category The resolved category entity
+     * @param schedule The associated schedule
+     * @param type     The type of todo
+     * @return A new Todo entity
+     */
     public Todo toEntity(Member member, Category category, Schedule schedule, TodoType type) {
backend/src/main/java/com/dubu/backend/todo/dto/enums/TodoRequestType.java (2)

8-16: Consider decoupling service names from the enum.

The enum directly contains service bean names, which creates tight coupling. Consider moving these to a configuration file or using a more flexible mapping strategy.

Example approach using a configuration file:

@Configuration
public class TodoServiceConfig {
    @Value("${todo.service.today}")
    private String todayServiceName;
    
    @Value("${todo.service.tomorrow}")
    private String tomorrowServiceName;
    
    @Value("${todo.service.save}")
    private String saveServiceName;
    
    // getters
}

18-23: Enhance string conversion robustness.

The current implementation:

  1. Only matches exact case-insensitive strings
  2. Doesn't handle null values
  3. Doesn't trim whitespace

Consider this more robust implementation:

    public static TodoRequestType fromString(String value){
+        if (value == null) {
+            throw new ConstraintViolationException("TodoRequestType value cannot be null", null);
+        }
+        String normalizedValue = value.trim().toUpperCase();
        return Arrays.stream(values())
-                .filter(type -> type.name().toLowerCase().equals(value))
+                .filter(type -> type.name().equals(normalizedValue))
                .findFirst()
                .orElseThrow(() -> new ConstraintViolationException("Invalid TodoRequestType value: " + value, null));
    }
backend/src/main/java/com/dubu/backend/todo/entity/Todo.java (2)

57-60: Remove or implement the commented Route entity relationship.

The commented code suggests a planned feature. Either implement the Route entity relationship or remove the comments to maintain clean code.

-    // Route 엔티티 구현되면 포함
-    // @ManyToOne(fetch = FetchType.LAZY)
-    // @JoinColumn(name = "route_id")
-    // private Route route;

41-55: Consider indexing foreign key columns for better query performance.

The entity has multiple @ManyToOne relationships with lazy loading. Consider adding indexes on foreign key columns to optimize the fetch join queries mentioned in the PR objectives.

Add the following indexes to improve query performance:

 @Table(uniqueConstraints = {
         @UniqueConstraint(columnNames = {"parent_id", "schedule_id"})
-})
+}, indexes = {
+        @Index(name = "idx_todo_member", columnList = "member_id"),
+        @Index(name = "idx_todo_category", columnList = "category_id"),
+        @Index(name = "idx_todo_parent", columnList = "parent_id"),
+        @Index(name = "idx_todo_schedule", columnList = "schedule_id")
+})
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f530a01 and 1ffc824.

📒 Files selected for processing (28)
  • backend/src/main/java/com/dubu/backend/global/config/WebConfig.java (2 hunks)
  • backend/src/main/java/com/dubu/backend/todo/controller/TodoController.java (2 hunks)
  • backend/src/main/java/com/dubu/backend/todo/converter/StringToTodoRequestTypeConverter.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/enums/TodoRequestType.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/request/CreateTodoFromArchivedRequest.java (0 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/request/TodoCreateFromArchivedRequest.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/request/TodoCreateRequest.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/request/TodoUpdateRequest.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/response/TodoInfo.java (2 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/response/TodoManageResult.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/response/TodoSuccessResponse.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/entity/Schedule.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/entity/Todo.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/registry/TodoManagementServiceRegistry.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/repository/CustomScheduleRepository.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/repository/CustomScheduleRepositoryImpl.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/repository/ScheduleRepository.java (0 hunks)
  • backend/src/main/java/com/dubu/backend/todo/repository/TodoRepository.java (2 hunks)
  • backend/src/main/java/com/dubu/backend/todo/service/TodoManagementService.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/service/impl/SaveTodoManagementService.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/service/impl/TodayTodoManagementService.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/service/impl/TodoManagementServiceImpl.java (0 hunks)
  • backend/src/main/java/com/dubu/backend/todo/service/impl/TodoQueryServiceImpl.java (4 hunks)
  • backend/src/main/java/com/dubu/backend/todo/service/impl/TomorrowTodoManagementService.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/support/ScheduledDateResolver.java (0 hunks)
  • backend/src/test/java/com/dubu/backend/config/TestQueryDslConfig.java (1 hunks)
  • backend/src/test/java/com/dubu/backend/todo/service/impl/TodoCreateFromArchivedTest.java (0 hunks)
  • backend/src/test/java/com/dubu/backend/todo/service/impl/TodoCreateTest.java (0 hunks)
💤 Files with no reviewable changes (6)
  • backend/src/main/java/com/dubu/backend/todo/dto/request/CreateTodoFromArchivedRequest.java
  • backend/src/main/java/com/dubu/backend/todo/repository/ScheduleRepository.java
  • backend/src/test/java/com/dubu/backend/todo/service/impl/TodoCreateTest.java
  • backend/src/main/java/com/dubu/backend/todo/support/ScheduledDateResolver.java
  • backend/src/test/java/com/dubu/backend/todo/service/impl/TodoCreateFromArchivedTest.java
  • backend/src/main/java/com/dubu/backend/todo/service/impl/TodoManagementServiceImpl.java
✅ Files skipped from review due to trivial changes (2)
  • backend/src/test/java/com/dubu/backend/config/TestQueryDslConfig.java
  • backend/src/main/java/com/dubu/backend/todo/dto/request/TodoUpdateRequest.java
🔇 Additional comments (32)
backend/src/main/java/com/dubu/backend/todo/dto/response/TodoInfo.java (1)

1-24: Well-structured DTO implementation!

The record implementation with its conversion methods aligns well with the PR objectives. It provides a clean interface for both single and bulk entity conversion, supporting the new response structure and facilitating the strategy pattern implementation for different task types.

backend/src/main/java/com/dubu/backend/todo/repository/CustomScheduleRepositoryImpl.java (1)

20-24: LGTM! Query optimization looks good.

The simplified query correctly addresses the LIMIT and FETCH JOIN issue mentioned in the PR objectives by using fetchFirst() without fetch joins.

Let's verify the query performance and usage:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check if the method is used with proper error handling and verify if todos are accessed efficiently

# Test: Search for method usage to verify error handling
rg -A 5 "findLatestSchedule.*Member.*LocalDate"

# Test: Search for potential N+1 issues by checking if todos are accessed after fetching the schedule
ast-grep --pattern 'schedule.getTodos()'

Length of output: 1463


Simplified Query Implementation Verified

The findLatestSchedule method now correctly retrieves the latest schedule using fetchFirst(). Our searches confirm:

  • The repository method is declared and implemented as intended.
  • The service layer accesses schedule.getTodos(), indicating that related todos are loaded (albeit lazily), which aligns with the optimized query approach.

As a next step, consider removing any unused helper methods (for example, applyJoinTodo) if they are indeed no longer required.

backend/src/main/java/com/dubu/backend/todo/repository/CustomScheduleRepository.java (1)

9-11: LGTM! Interface simplification aligns with query optimization goals.

The simplified interface with a single focused method is a good improvement. The removal of the includeJoinTodo parameter suggests that fetch join handling has been moved to the implementation layer where it belongs.

Let's verify the implementation to ensure proper query optimization:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Find and analyze the implementation of CustomScheduleRepository
# to verify proper handling of fetch joins and LIMIT

# Search for the implementation class
echo "Searching for CustomScheduleRepository implementation..."
ast-grep --pattern 'class $_ implements CustomScheduleRepository {
  $$$
}'

# Search for usage of fetch joins in the implementation
echo "Checking fetch join usage..."
rg -A 5 'fetch|join' --type java

Length of output: 8362


Verified: Implementation correctly applies fetch join and limit optimizations.

The implementation in CustomScheduleRepositoryImpl.java shows that the fetch join is now handled through the applyJoinTodo method (using leftJoin with fetchJoin) and the use of fetchFirst() aligns with the objective to optimize the query for retrieving the latest schedule. The interface simplification and corresponding implementation adjustments meet the query optimization goals.

backend/src/main/java/com/dubu/backend/todo/repository/TodoRepository.java (4)

16-17: LGTM! Good use of JOIN FETCH and clear naming.

The use of JOIN FETCH is appropriate for eager loading of a single entity, and the method name clearly indicates what's being fetched.


19-20: LGTM! Appropriate use of JOIN and clear naming.

The use of regular JOIN is correct here since schedule is only used in the WHERE clause. The method name clearly indicates the fetch strategy.


22-23: LGTM! Simple and efficient query.

The query is straightforward and efficient, using direct property access without unnecessary joins.


31-32: Consider pagination requirements with JOIN FETCH.

The use of JOIN FETCH with potentially large result sets could cause memory issues. If pagination is needed, be aware that FETCH JOIN doesn't work with LIMIT in JPA.

Consider these alternatives if pagination is needed:

  1. Use @EntityGraph instead of FETCH JOIN
  2. Split the query into two parts: first fetch IDs with pagination, then fetch entities with those IDs

Would you like me to verify if pagination is used with this query in the codebase?

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for usage of findTodosWithCategoryBySchedule with Pageable or LIMIT
rg -A 5 "findTodosWithCategoryBySchedule.*Page"

Length of output: 49


Attention: Consider potential memory issues with unbounded JOIN FETCH usage.

Based on the search results, there’s no evidence that the method findTodosWithCategoryBySchedule is currently used with pagination (such as Pageable or LIMIT). As highlighted, if this query returns a large result set, the unbounded JOIN FETCH could lead to performance or memory issues. It’s worth considering alternatives like using an @entitygraph or splitting the query if pagination becomes necessary.

  • No usage of Pageable or explicit LIMIT was detected with findTodosWithCategoryBySchedule.
  • Continued use in an unbounded context could be problematic if the number of results grows.
backend/src/main/java/com/dubu/backend/todo/service/impl/TodoQueryServiceImpl.java (3)

49-49: LGTM! More specific exception type.

The change from MemberNotFoundException to MemberCategoryNotFoundException better reflects the actual error condition, improving error clarity for API consumers.


44-44: Verify the behavior of findLatestSchedule.

The change to findLatestSchedule improves readability and aligns with the query optimization objective. Please ensure that:

  1. The method maintains the same ordering (by date desc)
  2. The method correctly handles the date parameter

Also applies to: 73-73

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Verify the implementation of findLatestSchedule

# Search for the repository method implementation
ast-grep --pattern 'findLatestSchedule($_, $_) {
  $$$
}'

# Search for test coverage
rg -A 5 'findLatestSchedule'

Length of output: 9735


Verified Implementation of findLatestSchedule

The implementation in CustomScheduleRepositoryImpl sorts by date in descending order using orderBy(schedule.date.desc()) and correctly applies the date filter via schedule.date.loe(date). The method is being consistently used across various services (for today and tomorrow schedules), confirming that the change meets the intended design and functionality.


63-63: 🏁 Script executed:

#!/bin/bash
# Description: Verify the implementation and test coverage of findTodosWithCategoryBySchedule

# Search for the repository method implementation to verify join fetch
ast-grep --pattern 'findTodosWithCategoryBySchedule($_) {
  $$$
}'

# Search for performance tests
rg -A 5 'findTodosWithCategoryBySchedule.*performance'

Length of output: 125

backend/src/main/java/com/dubu/backend/todo/controller/TodoController.java (5)

5-13: Imports look appropriate.
These new imports for request, response, and service registry classes align with the updated design. No immediate issues found.


18-18: Valid addition of ResponseEntity.
This import is necessary for the new return types across the controller methods.


28-28: Initialization of TodoManagementServiceRegistry.
No concerns here; using a final field for the registry is a good pattern.


32-36: Ensure the service lookup won't fail.
If the registry fails to find a matching service, consider throwing a user-friendly error or verifying the service’s existence before usage.


41-45: Archived todo creation flow.
The logic is consistent with the “postTodoFromArchived” approach. Returning a specialized response is in line with the changes in the PR.

backend/src/main/java/com/dubu/backend/todo/service/impl/TodayTodoManagementService.java (4)

1-31: Class definition and field injection.
The service follows Spring’s standard patterns (@service, @requiredargsconstructor, @transactional). Ensure careful handling of concurrency for daily schedule lookups to prevent data race or exceeding the todo limit when multiple requests happen concurrently.


47-64: createTodoFromArchived: duplication checks.
This method properly throws an exception if the same archived todo is already added. Overall approach looks good, but be mindful of concurrency issues in the same daily schedule.


66-89: modifyTodo: clearing parent relationships.
Breaking parent-child relationships when users update basic fields is consistent with the stated requirements. Confirm that partial updates on memo or other attributes not covered by this logic remain desired.


91-99: removeTodo: straightforward removal.
The service deletes the todo and returns a result with no tomorrow schedule creation. Implementation appears correct.

backend/src/main/java/com/dubu/backend/todo/service/impl/SaveTodoManagementService.java (4)

35-45: createTodo: no schedule lookup.
Saving a SAVE-type todo directly with no associated schedule is reasonable. Ensure the logic meets all business constraints around maximum items or concurrency edge cases.


47-57: createTodoFromArchived: duplication check for SAVE.
The check prevents archived items from being duplicated. Implementation is sound.


59-91: modifyTodo: clearing relationships on updating category/title/difficulty.
The approach is similar to TodayTodoManagementService. The logic to clear the parent relationship is consistent. Ensure partial update is tested thoroughly.


93-107: removeTodo logic.
The code removes parent references from potential child todos scheduled for today or tomorrow. This helps maintain integrity when removing favorites.

backend/src/main/java/com/dubu/backend/todo/service/impl/TomorrowTodoManagementService.java (3)

127-131: Parent relationship reset when changing title, category, or difficulty.
Clearing the parent todo upon any modification might unintentionally remove desired archival references. Ensure this is the intended business logic, as it may cause data lineage issues.


152-175: Verify orphaned relations or children when removing a todo.
While this code removes the targeted todo and replicates the remainder if tomorrow’s schedule does not exist, ensure that child todos or references (e.g., archived, subtasks) are handled correctly. You might need to consider orphan removal or cascade rules in the underlying entity relationships to avoid data inconsistency.


37-65: 🏁 Script executed:

#!/bin/bash
# Searching the codebase for 'findLatestSchedule' and relevant methods
rg "findLatestSchedule"

Length of output: 2652

backend/src/main/java/com/dubu/backend/todo/dto/response/TodoManageResult.java (1)

3-8: LGTM! Well-structured response DTO.

The generic record with a factory method effectively supports the new API response requirements, allowing flexible response types while tracking tomorrow's schedule creation status.

backend/src/main/java/com/dubu/backend/todo/service/TodoManagementService.java (1)

9-12: Consider specifying the generic type parameter.

The methods return TodoManageResult<?> with a wildcard type parameter. Consider specifying the exact type parameter if the return type is known, to provide better type safety and clarity.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for TodoManageResult usage to determine the actual type parameter
rg -A 5 'TodoManageResult<[^?]'

Length of output: 756


Actionable: Consider updating the return types to use a concrete type parameter

We've verified that the record in
backend/src/main/java/com/dubu/backend/todo/dto/response/TodoManageResult.java
is declared as a generic record (TodoManageResult), and the service interface in
backend/src/main/java/com/dubu/backend/todo/service/TodoManagementService.java
currently uses a wildcard (<?>). If the returned "info" is always expected to be a specific type (for example, a TodoDto or similar), then replacing the wildcard with that concrete type would improve type safety and clarity.

  • The generic type parameter (D) in TodoManageResult is intended to be specified.
  • Returning TodoManageResult<?> loses the benefit of compile-time type checks.
  • If a consistent concrete type is in use, updating the method signatures (e.g. TodoManageResult) is recommended.
backend/src/main/java/com/dubu/backend/todo/entity/Schedule.java (1)

27-27: LGTM! Removing final enables dynamic list management.

The removal of the final modifier allows for list reference modification, which aligns with the PR objectives for dynamic schedule creation and task management.

backend/src/main/java/com/dubu/backend/global/config/WebConfig.java (2)

28-33: Review CORS configuration for security.

The current CORS configuration allows all origins (*), which might be too permissive for a production environment.

Consider restricting allowed origins to specific domains based on your deployment environment.


37-37: LGTM! Converter registration is properly placed.

The StringToTodoRequestTypeConverter registration is correctly placed in the addFormatters method.

backend/src/main/java/com/dubu/backend/todo/entity/Todo.java (1)

15-17: Verify the impact of the unique constraint on schedule creation.

The unique constraint on parent_id and schedule_id ensures data integrity by preventing duplicate entries. However, verify that this constraint doesn't interfere with the new schedule creation functionality for tomorrow's tasks.

Run this script to check for potential conflicts:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check for potential conflicts with the unique constraint

# Search for schedule creation logic
rg -A 10 "createSchedule|createTomorrowSchedule" --type java

# Search for parent-child todo relationships
ast-grep --pattern 'parentTodo = $_' 

Length of output: 7713


Unique Constraint is Effectively Integrated with Schedule Creation

  • The search confirms that the schedule creation logic (using methods like createTomorrowSchedule and findByParentTodoAndSchedule) properly checks for duplicate TODO entries.
  • The unique constraint on (parent_id, schedule_id) acts as an additional safeguard to prevent duplicate entries without interfering with schedule creation.
  • There’s no indication that the constraint is causing conflicts in the existing functionality.

Comment on lines 25 to 26
@Query("SELECT t From Todo t WHERE t.member = :member AND t.parentTodo = :parentTodo AND t.type = :type")
Optional<Todo> findByMemberAndParentTodoAndType(@Param("member") Member member, @Param("parentTodo") Todo parentTod, @Param("type") TodoType type);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in parameter name.

There's a typo in the parameter name "parentTod" which should be "parentTodo".

Apply this diff to fix the typo:

-    Optional<Todo> findByMemberAndParentTodoAndType(@Param("member") Member member, @Param("parentTodo") Todo parentTod, @Param("type") TodoType type);
+    Optional<Todo> findByMemberAndParentTodoAndType(@Param("member") Member member, @Param("parentTodo") Todo parentTodo, @Param("type") TodoType type);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Query("SELECT t From Todo t WHERE t.member = :member AND t.parentTodo = :parentTodo AND t.type = :type")
Optional<Todo> findByMemberAndParentTodoAndType(@Param("member") Member member, @Param("parentTodo") Todo parentTod, @Param("type") TodoType type);
@Query("SELECT t From Todo t WHERE t.member = :member AND t.parentTodo = :parentTodo AND t.type = :type")
Optional<Todo> findByMemberAndParentTodoAndType(@Param("member") Member member, @Param("parentTodo") Todo parentTodo, @Param("type") TodoType type);

Comment on lines 32 to 45
@Override
public TodoManageResult<?> createTodo(Long memberId, TodoCreateRequest todoCreateRequest) {
Member member = memberRepository.findById(memberId).orElseThrow(() -> new MemberNotFoundException(memberId));
Category category = categoryRepository.findByName(todoCreateRequest.category()).orElseThrow(() -> new CategoryNotFoundException(todoCreateRequest.category()));
Schedule schedule = scheduleRepository.findLatestSchedule(member, LocalDate.now()).orElseThrow(ScheduleNotFoundException::new);

if(schedule.getTodos().size() == 3){
throw new TodoLimitExceededException();
}

Todo todo = todoCreateRequest.toEntity(member, category, schedule, TodoType.SCHEDULED);

return TodoManageResult.of(false, TodoInfo.fromEntity(todoRepository.save(todo)));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

createTodo: concurrency check on 3-todo limit.
Using a simple size check may lead to race conditions if multiple calls occur in parallel. Consider enforcing an optimistic or pessimistic lock, or a database constraint, to ensure the limit.

Comment on lines 14 to 16
public TodoManagementService getService(String serviceName){
return todoManagementServices.get(serviceName);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add null checking and logging for service lookup.

The service lookup could be more robust with null checking and logging for better debugging and error handling.

Consider this implementation:

+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;

 @Component
 @RequiredArgsConstructor
 public class TodoManagementServiceRegistry {
+    private static final Logger logger = LoggerFactory.getLogger(TodoManagementServiceRegistry.class);
     private final Map<String, TodoManagementService> todoManagementServices;

     public TodoManagementService getService(String serviceName) {
+        logger.debug("Looking up service for name: {}", serviceName);
+        TodoManagementService service = todoManagementServices.get(serviceName);
+        if (service == null) {
+            throw new IllegalArgumentException("No service found for name: " + serviceName);
+        }
+        return service;
     }
 }

@@ -3,17 +3,17 @@
import com.dubu.backend.member.domain.Member;
import com.dubu.backend.todo.entity.*;

public record CreateTodoRequest(String title, String category, String difficulty, String memo){
public record TodoCreateRequest(String title, String category, String difficulty, String memo){
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation annotations to request fields.

Consider adding validation annotations to ensure data integrity.

Add validation annotations:

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;

-public record TodoCreateRequest(String title, String category, String difficulty, String memo) {
+public record TodoCreateRequest(
+    @NotBlank(message = "Title is required")
+    String title,
+    
+    @NotBlank(message = "Category is required")
+    String category,
+    
+    @NotBlank(message = "Difficulty is required")
+    @Pattern(regexp = "EASY|MEDIUM|HARD", message = "Invalid difficulty level")
+    String difficulty,
+    
+    String memo) {

@khw7385 khw7385 changed the title [FIX]할 일 수정, 할 일 삭제 API 기능 수정 [FIX] 할 일 생성, 수정, 삭제 기능 수정 Feb 8, 2025
@khw7385 khw7385 requested a review from codrin2 February 8, 2025 12:50
Copy link
Member

@codrin2 codrin2 left a comment

Choose a reason for hiding this comment

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

굿굿 고생하셨습니다~ 코멘트 한번 확인해주세요

private final TodoQueryService todoQueryService;
private final TodoManagementServiceRegistry todoManagementServiceRegistry;
Copy link
Member

Choose a reason for hiding this comment

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

전략패턴을 통해서 케이스별로 복잡한 비즈니스 로직을 잘 풀어내신 것 같습니다👍

return Arrays.stream(values())
.filter(type -> type.name().toLowerCase().equals(value))
.findFirst()
.orElseThrow(() -> new ConstraintViolationException("Invalid TodoRequestType value: " + value, null));
Copy link
Member

Choose a reason for hiding this comment

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

예외를 따로 만들어서 ERROR CODE를 반환하면 클라이언트가 처리하기 좋을 것 같아요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image

TodoRequestType 의 경우 path variable 이나 쿼리 파라미터로 전달된 값을 받는 enum 입니다.
따라서, 경로 변수나 쿼리 파라미터로 들어온 값을 enum 객체로 변화합니다. 이 때, enum 객체에 해당되는 것이 없으면 제가 설정한 예외를 발생시킵니다.

이 때, 어떤 예외를 던져도 conversionUtils 에서 잡아 MethodArgumentTypeMismatchException 를 다시 던집니다. 그래서, 응답의 이미지에서 처럼 옵니다.

이런 경우에도 예외를 따로 만들어서 처리하는 것이 나을까요? 동작 상 차이가 없다고 해도 의미 상 커스텀 예외를 정의해서 던지는 것이 나은 것 같긴 합니다.
class InvalidTodoRequestTypeException extends BadRequestException 로 구현할 생각입니다.

@@ -24,7 +24,7 @@ public class Schedule extends BaseTimeEntity {
private LocalDate date;

@OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true)
private final List<Todo> todos = new ArrayList<>();
private List<Todo> todos = new ArrayList<>();
Copy link
Member

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.

엔티티에서 final 을 사용하지 않는(못하는) 이유는 보통 엔티티는 기본 생성자를 무조건 정의해야 하는 특성 상 final 설정이 불가능합니다. 하지만, 일대다 관계에서 일 쪽의 엔티티가 다 쪽의 엔티티와 연관관계를 갖게 할 때 보통 리스트를 선언과 동시에 초기화를 진행합니다. 그래서 final 을 붙어도 괜찮지 않을까 생각합니다!!

코드 상 제거한 이유는 final 이 없어도 잘 동작해서 일단은 제거해 두었습니다. 프로젝트 시간이 촉박하여 완벽하게 공부하지 못하고 답변 드립니다. 나중에라도 공부하고 나누겠습니다!!

Category category = categoryRepository.findByName(todoCreateRequest.category()).orElseThrow(() -> new CategoryNotFoundException(todoCreateRequest.category()));
Schedule schedule = scheduleRepository.findLatestSchedule(member, LocalDate.now()).orElseThrow(ScheduleNotFoundException::new);

if(schedule.getTodos().size() == 3){
Copy link
Member

Choose a reason for hiding this comment

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

동시성 문제를 생각하면 Schedule 조회쿼리에 Pessimistic Lock을 적용해서 3이상으로 수정되는 것을 방지할 수 있을 것 같습니다. 그리고 범위도 >=3 으로 최대한 가능성을 없애는 방향으로 작성하면 좋을 것 같아요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

희상님 코멘트의 동시성 문제가 잘 이해가 되지 않습니다. 해당 코드는 오늘/내일 할 일의 경우 최대 3개까지 저장하지 못하는 정책을 바탕으로 구현된 것입니다.

범위를 >=3를 하는 것은 동의합니다!! 수정하겠습니다.

List<Todo> tomorrowTodos = null;

// 할 일 3개 이미 존재
if(todos.size() == 3){
Copy link
Member

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.

반복된 코드는 동의합니다!! 나중에 리팩토링을 진행하겠습니다. 현재 생각 중인 방법은 추상 클래스를 도입하여 공통된 코드는 메소드로 리팩토링할 생각입니다!!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffc824 and 83dd205.

📒 Files selected for processing (3)
  • backend/src/main/java/com/dubu/backend/global/exception/ErrorCode.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/dto/enums/TodoRequestType.java (1 hunks)
  • backend/src/main/java/com/dubu/backend/todo/exception/InvalidTodoRequestTypeException.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/src/main/java/com/dubu/backend/todo/dto/enums/TodoRequestType.java
🔇 Additional comments (2)
backend/src/main/java/com/dubu/backend/todo/exception/InvalidTodoRequestTypeException.java (1)

12-15: LGTM! Clean implementation of error code handling.

The getErrorCode implementation correctly returns the enum name for consistent error code handling.

backend/src/main/java/com/dubu/backend/global/exception/ErrorCode.java (1)

53-54: LGTM! Well-structured error code addition.

The new error code is properly defined with:

  • Appropriate HTTP status (BAD_REQUEST)
  • Clear error message with type parameter
  • Logical placement in the Todo section

Comment on lines +8 to +10
public InvalidTodoRequestTypeException(String message) {
super(message.formatted(message));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect string formatting in constructor.

The current implementation message.formatted(message) is incorrect as it uses the message as both the format string and the argument. This could cause unexpected behavior if the message contains format specifiers.

Apply this diff to fix the string formatting:

    public InvalidTodoRequestTypeException(String message) {
-        super(message.formatted(message));
+        super(message);
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public InvalidTodoRequestTypeException(String message) {
super(message.formatted(message));
}
public InvalidTodoRequestTypeException(String message) {
super(message);
}

@khw7385 khw7385 merged commit 9682118 into develop Feb 11, 2025
2 checks passed
@khw7385 khw7385 deleted the fix/#46 branch February 11, 2025 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BE 백엔드 작업 fix 버그 수정 (기능을 잘못 구현한 경우)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FIX] 할 일 생성, 수정, 삭제 기능 수정
2 participants