Skip to content

[Feat] 루틴 등록 / 수정 로직 구현#33

Merged
choijungp merged 7 commits into
developfrom
feat/routine-creation
Aug 4, 2025
Merged

[Feat] 루틴 등록 / 수정 로직 구현#33
choijungp merged 7 commits into
developfrom
feat/routine-creation

Conversation

@taipaise
Copy link
Copy Markdown
Collaborator

@taipaise taipaise commented Aug 3, 2025

🌁 Background

  • 루틴 등록, 수정 로직 구현 및 API 연결
  • UI 변경 사항 없음!

👩‍💻 Contents

  • 루틴 등록/수정에서 사용할 Entity 정의 (리뷰 노트를 확인해주세요!!)
  • 루틴 수정 로직 구현
  • 루틴 등록, 수정 API 정의 및 연결

📝 Review Note

개발 지능이 많이 하락했구나~ 라는 것을 느낄 수 있었던 PR입니다. 그 이유는 다름 아닌 작업 순서 때문인데요, 지금도 왜 그랬는지 모르겠는데, DataSource 레이어쪽 작업을 먼저 진행했습니다. 때문에 이후 Domain 쪽 작업을 진행하며 Domain에서 약간의 구조 변경이 생김에 따라 DataSource를 다시 수정하게 되는 등 일을 두 번 하게 되었습니다. 반성하면서 다시 개발 지능을 쌓아가기로 다짐하며,, 본격적으로 약식 PR 설명회(라 쓰고 진짜 고봉밥 ㅠ)를 진행하겠습니다~

루틴 등록 / 수정하기

image - 루틴등록(수정) 화면에서 루틴을 모두 작성한 이후, `RoutineUseCase`의 `saveRoutine`을 통해 루틴을 저장할 수 있도록 구현했습니다. - 신규 루틴인지, 기존 루틴인지 판단하는 기준은 루틴의 ID 값이 있느냐 없느냐입니다. 해당 값을 기준으로 useCase 안에서 분기처를 통해 각각 루틴 등록/루틴 수정을 실행하도록 구현했습니다. 정리하자면 Presentation 단에서는 루틴 수정인지, 등록인지 상관쓰지 않고 일관적으로 동작할 수 있도록 구현하고자 했습니다.

고민했던 부분들

1. 루틴 등록(createRoutine), 루틴 수정(updateRoutine)을 View에서는 루틴 저장(saveRoutine) 하나로 동작하게 하는 것이 의미 있는가?

  • 아시다시피, 등록과 수정은 API가 다릅니다. 그리고 body에 넘겨줘야 하는 parameter의 형식도 다릅니다. 수정같은 경우는 각 루틴과 서브루틴의 ID값을 같이 넘겨주어야 하고, 서브루틴의 경우 sortOrder를 또 넘겨줘야 합니다.
  • 그리고 위에서 말씀드린대로, Presentation은 루틴 Usecase를 사용할 때, 수정요청인지, 등록 요청인지 구분하지 않고 '루틴 저장' 이라는 메서드 하나만 바라보고 동작하게 했습니다. 왜 이렇게 했냐면!!! Presentation 레이어에서 '등록', 그리고 '수정' 에 따른 분기처리를 하는 것 보다, Domain에서 등록/수정 여부를 판단하고 분기처리하는 것이 Presentation쪽을 더 멍청하게 만들기 좋다 생각했기 때문입니다.
  • 하지만.. 이게 그렇게 의미가 있나 싶기도 합니다. Presentation 측에서 분기처리를 통해 대단한 로직을 실행하는 것도 아니고, 분기처리도 단순히 routine에 id가 있냐 없냐로 처리할 수 있거든요
  • 그리고 View쪽에서 아예 분기처리를 하지 않는것도 아닙니다. navigation 바에 제목을 표시하기 위해 분기처리를 한 번하고 있거든요 ㅎㅎ.. 그래서 이렇게 구현한게 의미가 있을까 하는 고민이 들었습니다~

2. Entity를 어떻게 정의 해야할까~

  • 클린 아키텍쳐의 핵심은 잘 아시다시피, 의존성의 방향이 안쪽으로만 흐른다는 것입니다. 다시 말해 중앙에 있는 Domain이 중심을 잡고, Domain 외부의 layer가 Domain에 의존성이 있는 구조가 되는 것이죠. 처음에 구조를 나름 잘 잡아서, 전체 구조를 보면 의존성 역전을 통해 의존성이 안쪽 한 방향으로만 흐르게 구현이 되어있다 생각합니다.

  • 한편, 루틴등록/수정을 구현하기 위해 RoutineSummaryEntitySubRoutineSummaryEntity를 구현했는데요, 각 구조체들은 아래와 같이 생겼습니다.

public struct RoutineSummaryEntity {
    public let routineId: String?
    public let routineName: String
    public let repeatDay: [WeekType]
    public let executionTime: String

    public init(
        routineId: String?,
        routineName: String,
        repeatDay: [String]?,
        executionTime: String
    ) {
        let weekType: [WeekType] = repeatDay?.compactMap(WeekType.init(rawValue:)) ?? []

        self.routineId = routineId
        self.routineName = routineName
        self.repeatDay = weekType
        self.executionTime = executionTime
    }
}

public struct SubRoutineSummaryEntity: Decodable, Hashable {
    public let subRoutineId: String?
    public let subRoutineName: String?
    public let sortOrder: Int?

    public init(
        subRoutineId: String?,
        subRoutineName: String?,
        sortOrder: Int?
    ) {
        self.subRoutineId = subRoutineId
        self.subRoutineName = subRoutineName
        self.sortOrder = sortOrder
    }
}

뭔가 이상하다는 생각이 드셨다면, 맞습니다 ㅠ 거의 이후 데이터 요청을 위해 변환될 DTO의 구조와 동일합니다. 아래는 DTO의 구조입니다.

struct RoutineUpdateDTO: Codable {
    let routineId: String
    let routineName: String
    let repeatDay: [String]
    let executionTime: String
    let subRoutineInfos: [SubRoutineUpdateDTO]
}

struct SubRoutineUpdateDTO: Codable {
    let subRoutineId: String?
    let subRoutineName: String?
    let sortOrder: Int?
}
  • 이렇게 SummaryEntity를 구현하며 쉽게 API를 연결할 수는 잇엇지만,, 그리고 구조만 따져봤을때는 DomainDataSourceimport 하지 않으므로 의존성 방향을 잘 지키는 것처럼 보이지만,, 직접 구현한 저는 알고 있습니다 ㅠ 이 Entity는 직접적으로 DataSource를 모르는 것일 뿐, 서버에서 내려주는 response와 요구하는 request에 엄청나게 의존하고 있다는 것을요..,.,., 다시 말해 DTO의 변경에 따라 이 Entity의 구조도 바뀔 가능성이 높다는 것이죠 (굉장히 잘못된 구조)

  • 원래 계획은, 루틴 등록, 수정 시에 UseCase에서는 RoutineEntity를 들고 있고, Repository 쪽에서 RoutineEntity 안에서 필요한 정보만 뽑아 쓸 수 있도록 하는 것을 생각했습니다. 하지만 RoutineEntity에 생각보다 등록/수정 시에 사용하지 않는 정보가 많이 있었고, 아래 두 가지 방법을 고려했습니다.

    1. RoutineEntity 에서 등록/수정에서 사용하지 않는 프로퍼티는 옵셔널로 변경하기
    2. 등록/수정에서 사용하지 않는 프로퍼티는 기본값을 넣어주기 (String에는 "", Int에는 0 등)
  • 1번 방법은 제가 구현하지 않는 부분에 대해서 많은 변경을 해야한다는 점에서, 말씀드리지 않고 진행하는 것은 문제가 있다 생각해서 진행하지 못했습니다. 또한 이 방법대로 진행하면, RoutineEntity의 대부분의 프로퍼티가 옵셔널 상수가 된다는 점에서 적절한 방법이 아니라 생각했습니다. (엔티티의 거의 모든 프로퍼티가 옵셔널이라면, 차라리 엔티티의 프로퍼티를 쪼개는 것이 낫지 않나 하는 생각했습니다. 비유하자면 엔티티의 프로퍼티도 역할 분리를 하듯 더 작은 단위로 쪼개는 것이죠)

  • 2번 방법은 직관적으로도 지양하고 싶은 방법이라 느꼈습니다. 다른 사람이 유지보수를 위해 코드를 봤는데, 임의로 기본 값을 넣어주는 코드를 보면 왜 이 값을 넣어줘야 하는지 파악하기 힘들 것입니다. 또한 어떤 경우에는 기본 값을 넣어주고, 어떤 경우에는 넣지 않아야 하는지 파악하기 힘들 것이라 생각했습니다.

  • 결론을 말씀드리자면, 엔티티를 한 번 같이 정리하고, 정의하면 좋을 것 같다는 생각이 들었습니다. 서버도 V2로 리팩토링이 예정된 만큼, 엔티티를 확실하게 정의하고 넘어가야 추후 외부의 변경에도 Domain의 변경을 최소화할 수 있을 것이라 생각하기 떄문입니다!

논의해보고 싶은 부분들

드디어.. Domain레이어를 처음 건드려 본 만큼, 구현하면서 논의해보면 좋겠다~ 하는 부분들이 있었습니다.
물론 아래서 드리는 말씀이 현재 방법이 틀렸다는 것은 절대 아니고, 가볍게 나눠보면 좋겠다~ 라고 들어주시면 감사하겠습니다 🙇‍♂️🙇‍♂️🙇‍♂️

1. View의 네이밍

  • 현재는 ViewController의 이름도 View라고 명명되어 있습니다. 화면, 즉 View를 의미하기 때문인 것으로 기억하는데요, 조심스럽게.. ViewController라는 풀 네임은 어떤지 의견을 내봅니다. 이유는 아래와 같습니다
    1. 많은 iOS 개발자 분들이 이름 축약을 지양
    2. VC 역시 View는 맞지만, binding, 화면 전환 등 UIView 의 서브 클래스보다 하는 일이 많음
    3. 사실은 가장 큰 이유인데요.. 관성적인 부분이긴 하지만, 뒤에 ViewController가 붙어 있어야 필요한 파일을 찾을 때 더 빠르게 눈에 들어와서 찾을 수 있다고 느껴집니다
  • 1번 이유같은 경우는 물론 지금도 완전 축약형 이름은 아니긴 하지만, ViewController 서브클래스를 View로 명명했다는 점에서 약간의 축약이 들어갔다 볼 수 있지 않을까 했습니다!

2. Repository의 역할

  • 현재 DTO 객체에서 Entity로의 변환을 하고 있습니다. DataSource가 Domain을 알고 있는 것은 자연스럽기 때문에 문제가 되는 구조는 아니지만, 해당 책임을 Repository로 옮기는 것은 어떻게 생각하실까요?
  • 현재 구조는 DTO의 구조를 파악하면서 Entity로의 변환 과정을 한눈에 볼 수 있어 가독성에서는 장점이 있다 생각합니다. 다만 위와같은 의견을 드리는 이유는, 역할 분리 (관심사 분리) 측면에서는 Repository나, Mapper 객체가 데이터 간 매핑 책임을 가져가는게 유리하다고 생각하기 때문입니다.
  • 또한 현재 Bitnagil의 API가 대부분 그렇지 않지만, 일부 경우에는 Entity 에서 DTO의 매핑이 필요한 경우가 있습니다. DTO -> Entity의 매핑은 DTO객체에서 진행할 수 있지만, Entity -> DTO의 매핑은 Entity가 DTO를 알 수 없기 때문에 DataSource layer에서 이뤄져야 합니다. 아마 이 과정은 Repository에서 일어나게 될 텐데요, Repository에서 Entity -> DTO의 매핑 책임을 가져간다면 반대의 경우도 repository가 가져가는 것이 조금 더 일관성 있을 것이라고 생각했습니다
  • 마지막으로, Domain과 DataSource 레이어가 만나는 접점이 Repository입니다. Domain은 Repository 프로토콜(추상체)만 알며, DataSource는 이 프로토콜을 채택해 실제 구현을 담당합니다. 그래서 DTO <-> Entity 변환도 이 접점(Repository)에서 이뤄지는 것이 적절하다고 생각했습니다.
  • 물론, 데이터 매핑 책임을 repository가 가져간다면, 일부 경우에서 데이터 매핑 로직 때문에 가독성이 떨어질 수 있습니다. 복잡하게 매핑을 해야 하는 경우가 많아질 수록 더욱 그럴 것입니다. 그런 경우가 생긴다면, 사실 데이터 매핑을 어디서 해주든 가독성이 떨어지는 것은 마찬가지 일 것이라 생가갛비낟. 따라서 해당 문제가 발생하면 데이터를 매핑해주는 Mapper 객체의 도입을 고려보면 좋을 것 같습니다.

위 두 가지 안건의 경우, 꼭 당장 정하거나 수정할 부분은 아니기 때문에 시간이 정말로 부족한 지금은 살짝 넘어가고 나중에 다시 이야기 해봐도 좋을 것 같습니다!

Summary by CodeRabbit

  • 신규 기능

    • 루틴 생성, 조회, 수정 기능이 추가되었습니다.
    • 루틴 및 서브루틴 정보를 담는 다양한 엔터티와 DTO가 도입되었습니다.
    • 요일 선택을 위한 열거형(WeekType)이 추가되었습니다.
  • 기능 개선

    • 루틴 및 서브루틴 ID, 반복 요일 등 데이터 구조가 개선되어 더 유연한 관리가 가능합니다.
    • 루틴 생성/수정 화면에서 기존 루틴 불러오기 및 수정이 지원됩니다.
    • 루틴 생성/수정 시 유효성 검사가 강화되었습니다.
  • 버그 수정

    • 루틴 및 서브루틴 변환 과정에서 ID 누락 시 오류를 방지하도록 처리되었습니다.
  • 리팩터링/구조 개선

    • 도메인 및 데이터 계층의 구조가 확장되어, 유지보수성과 확장성이 향상되었습니다.

@taipaise taipaise requested a review from choijungp August 3, 2025 13:40
@taipaise taipaise self-assigned this Aug 3, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 3, 2025

Walkthrough

이 변경 사항은 루틴 생성, 조회, 수정 기능의 전체적인 도메인, 데이터소스, 프레젠테이션 계층에 걸친 확장 및 리팩터링을 포함합니다. 새로운 DTO와 엔티티, Enum이 추가되고, 프로토콜 및 레포지토리, 유스케이스에 관련 메서드가 도입되었습니다. 뷰와 뷰모델은 루틴 편집/수정 플로우를 지원하도록 대폭 확장되었습니다.

Changes

Cohort / File(s) Change Summary
Encodable 확장
Projects/DataSource/Sources/Common/Extension/Encodable+.swift
Encodable 프로토콜에 인스턴스를 [String: Any] 딕셔너리로 변환하는 dictionary 프로퍼티 추가
루틴/서브루틴 DTO 추가
Projects/DataSource/Sources/DTO/RoutineCreationDTO.swift, .../RoutineUpdateDTO.swift, .../SubRoutineUpdateDTO.swift
루틴 생성/수정, 서브루틴 수정용 DTO 신규 추가. 각 DTO는 Codable 준수 및 관련 프로퍼티 정의
루틴 응답 DTO 정렬 로직
Projects/DataSource/Sources/DTO/RoutineResponseDTO.swift
toRoutineEntity()에서 서브루틴 DTO를 sortOrder 기준으로 정렬 후 매핑하도록 구현 변경
루틴 엔드포인트 확장
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift
루틴 생성, 단일 조회, 수정용 enum case 및 관련 path/method/bodyParameters 로직 추가
루틴 레포지토리 확장
Projects/DataSource/Sources/Repository/RoutineRepository.swift
루틴 생성, 단일 조회, 수정용 비동기 메서드 3종 추가
WeekType Enum 추가
Projects/Domain/Sources/Entity/Enum/WeekType.swift
요일을 나타내는 public enum WeekType 신설
루틴/서브루틴 엔티티 변경 및 신설
Projects/Domain/Sources/Entity/RoutineEntity.swift, .../RoutineSummaryEntity.swift, .../SubRoutineEntity.swift, .../SubRoutineSummaryEntity.swift
루틴/서브루틴 엔티티의 ID/요일 타입을 옵셔널 및 Enum으로 변경, 요약 엔티티 신규 추가
미사용 파일
Projects/Domain/Sources/Entity/Untitled.swift
파일 헤더만 존재하는 빈 소스 파일 추가 (기능 없음)
레포지토리/유스케이스 프로토콜 확장
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift, .../UseCase/RoutineUseCaseProtocol.swift
루틴 생성, 단일 조회, 수정 등 메서드 추가
루틴 유스케이스 확장
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift
루틴 단일 조회 및 저장(생성/수정) 로직 신규 구현 및 내부 분기 처리
DI 등록 로직 변경
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
RoutineCreationViewModel에 DI로 RoutineUseCaseProtocol 주입하도록 등록 로직 변경
Home 모델/뷰/뷰모델 변경
Projects/Presentation/Sources/Home/Model/MainRoutine.swift, .../SubRoutine.swift, .../View/HomeView.swift, .../ViewModel/HomeViewModel.swift
엔티티 변환 메서드의 반환 타입을 옵셔널로 변경, 변환 실패시 필터링, 루틴 생성 뷰에 routineId 전달 등
루틴 생성/수정 뷰 및 뷰모델 확장
Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift, .../ViewModel/RoutineCreationViewModel.swift
루틴 수정 지원: routineId 파라미터 및 동적 타이틀, 루틴 데이터 fetch, 서브루틴 엔티티 관리, 삭제/수정/검증/저장 로직 대폭 확장

Sequence Diagram(s)

sequenceDiagram
    participant View as RoutineCreationView
    participant ViewModel as RoutineCreationViewModel
    participant UseCase as RoutineUseCase
    participant Repo as RoutineRepository

    View->>ViewModel: action(input: .fetchRoutine(id))
    ViewModel->>UseCase: fetchRoutine(routineId)
    UseCase->>Repo: fetchRoutine(routineId)
    Repo-->>UseCase: RoutineEntity?
    UseCase-->>ViewModel: RoutineEntity?
    ViewModel-->>View: 상태 업데이트

    View->>ViewModel: action(input: .registerRoutine)
    ViewModel->>UseCase: saveRoutine(routineSummary, subRoutineSummaries, deletedSubRoutineSummaries)
    UseCase->>Repo: createRoutine or updateRoutine
    Repo-->>UseCase: (완료)
    UseCase-->>ViewModel: (완료)
    ViewModel-->>View: (UI 갱신)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40분

Poem

🐇
루틴의 아침, 요일별 hop,
새로운 엔티티, DTO가 톡톡!
수정·생성 모두 지원,
뷰모델도 똑똑—
서브루틴도 ID 품고,
토끼는 기뻐 깡총깡총,
코드 정원에 봄이 왔어요!
🌱✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/routine-creation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @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 or @coderabbitai 요약 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.

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
Copy Markdown

@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)
Projects/Domain/Sources/Entity/SubRoutineEntity.swift (1)

18-28: 초기화자 파라미터 일관성 검토 필요

프로퍼티는 옵셔널(String?)이지만 초기화자 파라미터는 non-optional(String)입니다. 이는 혼란을 야기할 수 있습니다.

초기화자도 옵셔널 파라미터를 받도록 수정하는 것을 고려해보세요:

 public init(
-    subRoutineId: String,
+    subRoutineId: String?,
     historySeq: Int,
     subRoutineName: String,
     modifiedYn: Bool,
     sortOrder: Int,
     routineCompletionId: Int?,
     completeYn: Bool,
     routineType: String
 ) {
🧹 Nitpick comments (11)
Projects/Domain/Sources/Entity/Untitled.swift (1)

1-8: 불필요한 빈 파일 및 의미 없는 파일명 정리 권장

현재 파일은 주석만 존재하며 실제 코드가 없습니다. Untitled.swift라는 이름도 도메인 계층의 역할을 설명하지 못합니다. 의도된 구현이 없다면 컴파일에는 영향이 없더라도 레포지터리에 불필요한 잡음을 남기게 됩니다.

  • 구현 계획이 없다면 파일을 제거하세요.
  • 추후 사용 예정이라면 파일명과 타입명을 도메인 개념에 맞게 명확히 하고, 최소한의 스켈레톤(struct/class) 선언을 추가해 주세요.
Projects/Domain/Sources/Entity/Enum/WeekType.swift (1)

1-7: 파일명과 타입명 불일치 수정 필요

파일명이 Week.swift이지만 내부에 정의된 타입은 WeekType입니다. 일관성을 위해 파일명을 WeekType.swift로 변경하는 것이 좋겠습니다.

-//  Week.swift
+//  WeekType.swift
Projects/DataSource/Sources/Common/Extension/Encodable+.swift (1)

10-18: 에러 처리 개선 고려

현재 구현에서는 인코딩 실패 시 빈 딕셔너리를 반환하여 오류를 숨길 수 있습니다. 네트워크 요청에서 사용될 때 예상치 못한 동작을 유발할 수 있습니다.

다음과 같은 개선 방안을 고려해보세요:

 extension Encodable {
     var dictionary: [String: Any] {
         guard
             let data = try? JSONEncoder().encode(self),
             let dictionary = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
-        else { return [:] }
+        else { 
+            print("⚠️ Failed to convert \(type(of: self)) to dictionary")
+            return [:]
+        }
         return dictionary
     }
 }

또는 throwing 버전을 추가로 제공하는 것도 고려할 수 있습니다.

Projects/DataSource/Sources/DTO/SubRoutineUpdateDTO.swift (1)

8-12: DTO 구조는 적절하지만 도메인 엔티티와의 차별화를 고려해보세요.

API 통신을 위한 DTO로서 옵셔널 프로퍼티들이 적절하게 정의되었습니다. 하지만 SubRoutineSummaryEntity와 구조가 거의 동일하여 PR 목표에서 언급된 tight coupling 우려가 있습니다.

향후 서버 V2 리팩터링 시 도메인 엔티티와 DTO 간의 명확한 책임 분리를 고려해보세요. Repository 계층에서 매핑 로직을 통해 API 변경이 도메인에 미치는 영향을 최소화할 수 있습니다.

Projects/DataSource/Sources/DTO/RoutineCreationDTO.swift (1)

2-2: 파일명 오타를 수정해주세요.

파일명에 오타가 있습니다: "RoutienCreationDTO.swift" → "RoutineCreationDTO.swift"

Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (1)

20-27: path 속성에서 더 명시적인 케이스 처리를 고려해보세요.

현재 default 케이스를 사용하고 있는데, 향후 새로운 케이스가 추가될 때의 실수를 방지하기 위해 명시적으로 각 케이스를 나열하는 것을 고려해보세요.

 var path: String {
     switch self {
+    case .createRoutine, .fetchRoutines, .updateRoutine:
+        baseURL
     case .fetchRoutine(let routineId):
         "\(baseURL)/\(routineId)"
-    default:
-        baseURL
     }
 }
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)

18-21: 변수명 오타를 수정해주세요.

Line 19에서 routineEnityroutineEntity로 수정해야 합니다.

-        let routineEnity = try await routineRepository.fetchRoutine(routineId: routineId)
-        return routineEnity
+        let routineEntity = try await routineRepository.fetchRoutine(routineId: routineId)
+        return routineEntity
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (4)

121-121: TODO 주석 해결 필요

에러 처리에 대한 TODO 주석이 있습니다. 적절한 에러 처리 로직 구현이 필요합니다.

에러 처리 구현을 도와드릴까요? 다음과 같은 접근을 제안드립니다:

  • 네트워크 에러, 파싱 에러 등을 구분하여 처리
  • 사용자에게 적절한 피드백 제공
  • 에러 발생 시 이전 화면으로 돌아가거나 재시도 옵션 제공

Also applies to: 150-151


161-161: 매직 넘버 상수화 필요

서브루틴 개수 제한(3)이 하드코딩되어 있습니다.

+private enum Constants {
+    static let maxSubRoutineCount = 3
+}

-guard subRoutines.count <= 3 else { return }
+guard subRoutines.count <= Constants.maxSubRoutineCount else { return }

172-187: 서브루틴 삭제 시 sortOrder 재정렬 고려

서브루틴을 삭제한 후 남은 서브루틴들의 sortOrder가 연속적이지 않을 수 있습니다. 이는 추후 정렬 시 문제가 될 수 있습니다.

삭제 후 남은 서브루틴들의 sortOrder를 재정렬하는 로직을 추가하는 것을 고려해보세요:

subRoutines.remove(at: index)
// sortOrder 재정렬
subRoutines = subRoutines.enumerated().map { index, entity in
    SubRoutineSummaryEntity(
        subRoutineId: entity.subRoutineId,
        subRoutineName: entity.subRoutineName,
        sortOrder: index + 1
    )
}

66-73: 엔티티 사용에 대한 아키텍처 고려사항

PR 목표에서 언급하신 대로, 프레젠테이션 레이어에서 도메인 엔티티를 직접 사용하는 것은 결합도를 높일 수 있습니다. 향후 서버 API 변경 시 영향을 최소화하기 위해 다음을 고려해보세요:

  1. 프레젠테이션 레이어 전용 모델 도입
  2. 도메인 엔티티를 프레젠테이션 모델로 매핑하는 mapper 사용
  3. 이를 통해 API 변경이 프레젠테이션 레이어까지 전파되는 것을 방지
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5eff16 and b63c33a.

📒 Files selected for processing (23)
  • Projects/DataSource/Sources/Common/Extension/Encodable+.swift (1 hunks)
  • Projects/DataSource/Sources/DTO/RoutineCreationDTO.swift (1 hunks)
  • Projects/DataSource/Sources/DTO/RoutineResponseDTO.swift (1 hunks)
  • Projects/DataSource/Sources/DTO/RoutineUpdateDTO.swift (1 hunks)
  • Projects/DataSource/Sources/DTO/SubRoutineUpdateDTO.swift (1 hunks)
  • Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (3 hunks)
  • Projects/DataSource/Sources/Repository/RoutineRepository.swift (2 hunks)
  • Projects/Domain/Sources/Entity/Enum/WeekType.swift (1 hunks)
  • Projects/Domain/Sources/Entity/RoutineEntity.swift (3 hunks)
  • Projects/Domain/Sources/Entity/RoutineSummaryEntity.swift (1 hunks)
  • Projects/Domain/Sources/Entity/SubRoutineEntity.swift (1 hunks)
  • Projects/Domain/Sources/Entity/SubRoutineSummaryEntity.swift (1 hunks)
  • Projects/Domain/Sources/Entity/Untitled.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (1 hunks)
  • Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1 hunks)
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1 hunks)
  • Projects/Presentation/Sources/Home/Model/MainRoutine.swift (1 hunks)
  • Projects/Presentation/Sources/Home/Model/SubRoutine.swift (1 hunks)
  • Projects/Presentation/Sources/Home/View/HomeView.swift (1 hunks)
  • Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1 hunks)
  • Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift (3 hunks)
  • Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (11 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: onboardingrecommendedroutineview에서 viewwillappear에 registeronboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 ...
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.

Applied to files:

  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
  • Projects/Presentation/Sources/Home/View/HomeView.swift
  • Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift
📚 Learning: choijungp의 homeviewmodel에서 fetchroutines(for date: date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 ...
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#31
File: Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift:97-103
Timestamp: 2025-08-01T06:38:18.687Z
Learning: choijungp의 HomeViewModel에서 fetchRoutines(for date: Date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 데이터 청크를 가져오기 위해 oldestDate와 latestDate를 모두 업데이트한다. 과거 날짜 요청 시에는 해당 날짜의 1주 전~1일 전, 미래 날짜 요청 시에는 해당 날짜의 1일 후~1주 후 범위로 설정한다.

Applied to files:

  • Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift
  • Projects/Presentation/Sources/Home/View/HomeView.swift
  • Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift
  • Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift
  • Projects/DataSource/Sources/Repository/RoutineRepository.swift
  • Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift
📚 Learning: baseviewcontroller의 viewdidload() 메서드에서 이미 configureattribute(), configurelayout(), bind()를 호출하므로, 하...
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.

Applied to files:

  • Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift
🧬 Code Graph Analysis (6)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
  • register (14-16)
  • resolve (18-25)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
Projects/Presentation/Sources/Home/Model/MainRoutine.swift (1)
  • toMainRoutine (21-35)
Projects/DataSource/Sources/DTO/RoutineResponseDTO.swift (1)
Projects/DataSource/Sources/DTO/SubRoutineResponseDTO.swift (1)
  • toSubRoutineEntity (22-32)
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (3)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (3)
  • fetchRoutine (18-21)
  • fetchRoutines (23-29)
  • saveRoutine (31-44)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (2)
  • fetchRoutine (26-31)
  • fetchRoutines (33-43)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (1)
  • fetchRoutine (118-153)
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (3)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (4)
  • createRoutine (46-48)
  • fetchRoutine (18-21)
  • fetchRoutines (23-29)
  • updateRoutine (50-72)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (4)
  • createRoutine (13-24)
  • fetchRoutine (26-31)
  • fetchRoutines (33-43)
  • updateRoutine (45-64)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (2)
  • fetchRoutines (85-108)
  • fetchRoutines (110-125)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (3)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (3)
  • createRoutine (46-48)
  • fetchRoutine (18-21)
  • updateRoutine (50-72)
Projects/DataSource/Sources/NetworkService/NetworkService.swift (1)
  • request (24-47)
Projects/DataSource/Sources/DTO/RoutineResponseDTO.swift (1)
  • toRoutineEntity (28-42)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift

[Warning] 121-121: TODOs should be resolved (- routine fetch 실패 시 처리 방안 필요 ...)

(todo)


[Warning] 150-150: TODOs should be resolved (- 요기도 마찬가지 (ViewModel 공통 todo))

(todo)

🔇 Additional comments (30)
Projects/Domain/Sources/Entity/Enum/WeekType.swift (1)

8-16: 타입 안전성 개선이 잘 구현됨

문자열 배열 대신 강타입 enum을 사용하여 요일 표현의 타입 안전성을 크게 개선했습니다. StringCaseIterable 프로토콜 준수도 적절하며, 대문자 raw value 형식도 API 통신에 적합합니다.

Projects/Domain/Sources/Entity/SubRoutineEntity.swift (1)

9-9: 옵셔널 처리가 적절함

subRoutineId를 옵셔널로 변경한 것은 생성/수정 플로우를 지원하기 위한 적절한 설계입니다. 새로운 서브루틴 생성 시에는 ID가 없고, 수정 시에는 ID가 필요한 시나리오를 잘 반영했습니다.

Projects/Presentation/Sources/Home/View/HomeView.swift (1)

532-532: 루틴 편집 플로우 구현이 적절함

기존 루틴 편집을 위해 routineId 파라미터를 전달하는 구현이 깔끔합니다. PR 목표에서 언급한 통합된 저장/편집 접근 방식과 잘 일치하며, 프레젠테이션 레이어에서 도메인 레이어로 적절히 책임을 위임하고 있습니다.

Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)

101-101: 방어적 프로그래밍 패턴이 잘 적용됨

compactMap을 사용하여 변환에 실패한 루틴들을 필터링하는 것은 좋은 방어적 프로그래밍 패턴입니다. MainRoutine.toMainRoutine()이 옵셔널을 반환하도록 변경된 것과 일치하며, 유효하지 않은 데이터로 인한 UI 오류를 방지합니다.

Projects/DataSource/Sources/DTO/RoutineResponseDTO.swift (1)

29-37: 서브루틴 정렬 로직이 올바르게 구현되었습니다.

sortOrder 기준으로 오름차순 정렬 후 엔티티로 변환하는 로직이 적절합니다. 이를 통해 API 응답에서 순서가 보장되지 않더라도 일관된 순서로 서브루틴을 제공할 수 있습니다.

Projects/Presentation/Sources/Home/Model/SubRoutine.swift (1)

18-20: 안전한 옵셔널 처리가 적절하게 구현되었습니다.

subRoutineId가 옵셔널로 변경됨에 따라 guard 문을 사용한 안전한 변환 로직이 추가되었습니다. 새로 생성되는 서브루틴의 경우 ID가 없을 수 있으므로 이러한 처리가 필요합니다.

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)

69-74: 의존성 주입 설정이 일관성 있게 구현되었습니다.

RoutineCreationViewModelRoutineUseCaseProtocol 의존성을 주입하는 로직이 다른 ViewModel들과 동일한 패턴으로 구현되어 일관성을 유지하고 있습니다.

Projects/DataSource/Sources/DTO/RoutineUpdateDTO.swift (1)

8-14: LGTM!

DTO 구조체가 명확하고 간결하게 구현되었습니다. Codable 프로토콜 준수로 네트워크 통신에 적합하며, 불변 프로퍼티 사용으로 데이터 무결성을 보장합니다.

Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift (4)

80-95: 등록/수정 모드 구분 로직이 잘 구현되었습니다.

routineId 파라미터를 통해 등록과 수정 모드를 명확히 구분하고, 수정 모드일 때 자동으로 기존 루틴 데이터를 로드하는 로직이 적절합니다.


103-103: 동적 네비게이션 타이틀 설정이 적절합니다.

모드에 따라 "루틴 등록"과 "루틴 수정"을 구분하여 사용자 경험을 개선했습니다.


178-178: 버튼 타이틀 일관성이 유지되었습니다.

네비게이션 타이틀과 일치하는 버튼 텍스트로 UI 일관성을 유지했습니다.


527-532: registerButton 액션 연결이 적절합니다.

뷰모델의 registerRoutine 액션과 명확하게 연결되어 있습니다.

Projects/Presentation/Sources/Home/Model/MainRoutine.swift (2)

21-27: 안전한 변환 로직이 잘 구현되었습니다.

옵셔널 반환과 guard 절을 사용하여 실패 가능한 변환을 안전하게 처리했습니다. 모든 서브루틴이 성공적으로 변환되었는지 확인하는 로직도 적절합니다.


33-33: WeekType enum과의 매핑이 정확합니다.

rawValue를 사용한 매핑으로 타입 안전성을 유지하면서 적절히 변환하고 있습니다.

Projects/DataSource/Sources/DTO/RoutineCreationDTO.swift (1)

8-13: LGTM!

루틴 생성을 위한 DTO 구조체가 명확하고 적절하게 구현되었습니다. Codable 준수로 네트워크 통신에 적합합니다.

Projects/Domain/Sources/Entity/RoutineSummaryEntity.swift (1)

8-27: 도메인 엔티티가 잘 설계되었습니다.

  • Public 접근 제어자로 모듈 간 사용이 가능합니다
  • 초기화 시 String 배열을 WeekType으로 안전하게 변환하는 로직이 적절합니다
  • compactMap을 사용하여 실패한 변환을 안전하게 처리합니다
  • nil 기본값 처리로 견고성을 확보했습니다
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (1)

11-19: 프로토콜 설계가 잘 되어 있습니다.

새로 추가된 메서드들이 적절하게 설계되었습니다:

  • fetchRoutine이 옵셔널을 반환하여 루틴이 존재하지 않는 경우를 적절히 처리
  • saveRoutine이 생성과 수정을 모두 처리할 수 있도록 설계
  • 삭제된 서브루틴을 별도로 처리하여 업데이트 로직을 명확히 분리
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (2)

9-12: 새로운 엔드포인트 케이스들이 적절하게 추가되었습니다.

각 케이스가 명확한 목적을 가지고 있고 DTO 타입과 적절히 연결되어 있습니다.


60-68: Encodable 확장에 var dictionary 구현 확인됨

  • Projects/DataSource/Sources/Common/Extension/Encodable+.swift 파일에서 Encodable 프로토콜 확장으로 var dictionary: [String: Any]가 구현되어 있습니다.
  • 따라서 RoutineEndpoint.swiftroutine.dictionary 사용은 유효하며, 추가 조치가 필요 없습니다.
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (1)

10-33: 레포지토리 프로토콜이 잘 설계되었습니다.

새로 추가된 메서드들이 도메인 계층의 요구사항을 적절히 반영하고 있습니다:

  • 도메인 엔티티를 사용하여 계층 간 분리가 잘 되어 있음
  • 메서드 시그니처가 유스케이스와 일관성 있게 설계됨
  • 한글 문서화가 명확하고 이해하기 쉬움
Projects/Domain/Sources/Entity/RoutineEntity.swift (3)

9-9: routineId를 옵셔널로 변경한 것이 적절합니다.

새로운 루틴의 경우 아직 ID가 할당되지 않은 상태를 명확히 표현할 수 있습니다.


12-12: WeekType 열거형 사용으로 타입 안전성이 향상되었습니다.

문자열 배열 대신 WeekType 열거형을 사용하여 요일 데이터의 타입 안전성이 크게 개선되었습니다.


32-37: WeekType 변환 로직이 안전하게 구현되었습니다.

compactMap과 nil 병합 연산자를 사용하여 변환 실패 시에도 안전하게 빈 배열로 처리하는 로직이 좋습니다.

Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (2)

31-44: saveRoutine 메서드의 분기 처리가 명확합니다.

routineId의 존재 여부로 생성과 수정을 구분하는 로직이 직관적이고 주석도 명확합니다. 프레젠테이션 계층에서 단일 메서드로 호출할 수 있어 사용성이 좋습니다.


50-72: updateRoutine의 서브루틴 처리 로직이 잘 구현되었습니다.

복잡한 업데이트 로직을 단계별로 처리하고 있습니다:

  • 수정된 서브루틴의 정렬 순서 재계산 (1부터 시작)
  • 삭제된 서브루틴의 name과 sortOrder를 nil로 설정
  • 두 배열을 병합하여 단일 요청으로 처리

이 접근 방식은 API 호출 횟수를 최소화하면서도 모든 변경사항을 처리할 수 있어 효율적입니다.

Projects/DataSource/Sources/Repository/RoutineRepository.swift (2)

13-24: LGTM!

createRoutine 메서드가 올바르게 구현되었습니다. compactMap을 사용하여 nil 값을 필터링하고, DTO 변환이 적절하게 처리되었습니다.


26-31: LGTM!

fetchRoutine 메서드가 안전하게 구현되었습니다. 옵셔널 처리와 DTO-Entity 변환이 적절합니다.

Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (3)

206-219: LGTM!

switch 문을 사용하여 반복 타입 설정 로직이 더 명확해졌습니다.


245-261: LGTM!

검증 로직이 포괄적이고 잘 구성되어 있습니다. 특히 allSatisfy를 사용한 서브루틴 이름 검증이 우아합니다.


275-275: 시간 포맷 일관성 확인 완료

DateType.timeformatString"HH:mm:ss"로 정의되어 있어, allDay의 하드코딩 값("00:00:00")과 일치합니다. 내부 변환 로직은 올바르게 작동하나, 서버 API 스펙이 해당 포맷을 요구하는지 최종 확인 부탁드립니다.

Comment on lines +45 to +46
func updateRoutine(routineSummary: RoutineSummaryEntity, subRoutineSummaries: [SubRoutineSummaryEntity]) async throws {
guard let routineId = routineSummary.routineId else { return }
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 3, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

routineId가 nil일 때 조용히 실패하는 문제

현재 routineId가 nil일 경우 아무런 오류 없이 메서드가 종료됩니다. 이는 호출자가 업데이트 실패를 인지하지 못하게 할 수 있습니다.

다음과 같이 오류를 던지는 것을 고려해보세요:

-guard let routineId = routineSummary.routineId else { return }
+guard let routineId = routineSummary.routineId else {
+    throw RoutineRepositoryError.missingRoutineId
+}

또는 메서드 시그니처를 변경하여 옵셔널 결과를 반환하거나, 완료 상태를 나타내는 Bool을 반환하는 것도 고려해볼 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Repository/RoutineRepository.swift at lines
45-46, the method updateRoutine silently returns when routineId is nil, causing
callers to miss update failures. Modify the method to throw a specific error
when routineId is nil instead of returning quietly. Define a suitable error type
for this case and update the method signature to reflect that it can throw. This
ensures callers are properly informed of the failure.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

추후 디버깅을 위해서라도 추가하는 것이 좋을 것 같습니다. 감사합니다~!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@taipaise 동의해주셔서 감사합니다! 명시적인 에러 처리는 디버깅 시 문제를 빠르게 파악하는 데 정말 도움이 됩니다.

구현 시 다음과 같은 형태로 진행하시면 됩니다:

enum RoutineRepositoryError: Error {
    case missingRoutineId
}

func updateRoutine(routineSummary: RoutineSummaryEntity, subRoutineSummaries: [SubRoutineSummaryEntity]) async throws {
    guard let routineId = routineSummary.routineId else {
        throw RoutineRepositoryError.missingRoutineId
    }
    // 기존 로직 계속...
}

이렇게 하면 호출하는 쪽에서도 업데이트 실패를 명확히 인지할 수 있어 더 안정적인 코드가 될 것입니다.

  /|   /|  
 ( :v:  )
  |(_)|
 /     \
/       \

✏️ Learnings added
Learnt from: taipaise
PR: YAPP-Github/Bitnagil-iOS#33
File: Projects/DataSource/Sources/Repository/RoutineRepository.swift:45-46
Timestamp: 2025-08-04T23:43:28.481Z
Learning: taipaise는 디버깅을 위해 명시적인 에러 처리를 선호함. nil 체크 시 조용히 실패하는 것보다 에러를 던지는 방식을 추후 디버깅에 도움이 된다고 생각함.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.

Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines +8 to +22
public struct SubRoutineSummaryEntity: Decodable, Hashable {
public let subRoutineId: String?
public let subRoutineName: String?
public let sortOrder: Int?

public init(
subRoutineId: String?,
subRoutineName: String?,
sortOrder: Int?
) {
self.subRoutineId = subRoutineId
self.subRoutineName = subRoutineName
self.sortOrder = sortOrder
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 3, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

엔티티 설계에서 비즈니스 규칙을 고려한 개선이 필요합니다.

현재 모든 프로퍼티가 옵셔널로 정의되어 있어 PR 목표에서 언급된 API DTO와의 tight coupling 우려가 현실화되고 있습니다. 도메인 관점에서 다음 사항들을 검토해보세요:

  1. subRoutineName은 필수값이어야 하지 않을까요?
  2. sortOrder는 기본값(예: 0)을 가져야 하지 않을까요?

도메인 중심의 설계로 개선을 제안합니다:

public struct SubRoutineSummaryEntity: Decodable, Hashable {
    public let subRoutineId: String?
-   public let subRoutineName: String?
+   public let subRoutineName: String
-   public let sortOrder: Int?
+   public let sortOrder: Int

    public init(
        subRoutineId: String?,
-       subRoutineName: String?,
+       subRoutineName: String,
-       sortOrder: Int?
+       sortOrder: Int = 0
    ) {
        self.subRoutineId = subRoutineId
        self.subRoutineName = subRoutineName
        self.sortOrder = sortOrder
    }
}
📝 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 struct SubRoutineSummaryEntity: Decodable, Hashable {
public let subRoutineId: String?
public let subRoutineName: String?
public let sortOrder: Int?
public init(
subRoutineId: String?,
subRoutineName: String?,
sortOrder: Int?
) {
self.subRoutineId = subRoutineId
self.subRoutineName = subRoutineName
self.sortOrder = sortOrder
}
}
public struct SubRoutineSummaryEntity: Decodable, Hashable {
public let subRoutineId: String?
public let subRoutineName: String
public let sortOrder: Int
public init(
subRoutineId: String?,
subRoutineName: String,
sortOrder: Int = 0
) {
self.subRoutineId = subRoutineId
self.subRoutineName = subRoutineName
self.sortOrder = sortOrder
}
}
🤖 Prompt for AI Agents
In Projects/Domain/Sources/Entity/SubRoutineSummaryEntity.swift lines 8 to 22,
the properties are all optional, causing tight coupling with the API DTO and
weakening domain integrity. Change subRoutineName to a non-optional property to
enforce it as a required field, and assign a default value (e.g., 0) to
sortOrder to avoid optionality. Adjust the initializer accordingly to reflect
these changes and ensure domain-driven design principles are followed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

관련해서 회의 진행하고 리팩터링 예정입니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@taipaise 좋은 접근 방식이네요! 팀과 함께 엔티티 설계에 대해 논의하고 체계적으로 리팩터링을 진행하시는 것이 현명한 판단입니다.

도메인 무결성과 API와의 결합도 문제를 함께 고려하시면서 진행하시길 바랍니다. 추후 리팩터링 결과가 궁금하네요! 🚀


🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

let repeatType: RepeatType = weekDay.count == Week.allCases.count ? .daily : .week
let executionType: ExecutionType

if routine.executionTime == "00:00:00" {
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 3, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

매직 스트링 상수화 필요

"00:00:00"이 하드코딩되어 있습니다. 이는 서버와의 계약이므로 상수로 정의하는 것이 좋습니다.

+private enum Constants {
+    static let allDayTimeValue = "00:00:00"
+}

-if routine.executionTime == "00:00:00" {
+if routine.executionTime == Constants.allDayTimeValue {
📝 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
if routine.executionTime == "00:00:00" {
// Add this at the top of RoutineCreationViewModel.swift (inside or just above the class)
private enum Constants {
static let allDayTimeValue = "00:00:00"
}
// …later in your method…
// before
- if routine.executionTime == "00:00:00" {
// after
+ if routine.executionTime == Constants.allDayTimeValue {
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift
at line 134, the string "00:00:00" is hardcoded as a magic string. Define this
value as a constant, preferably with a clear and descriptive name, to represent
the agreed server contract time format. Replace the hardcoded string with this
constant to improve maintainability and clarity.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

저도 그렇게 생각합니다. 다만 곧 시작 시간, 종료 시간에 대한 정책이 추가될 예정이라 우선은 하드 코딩으로 진행했습니다.
리뷰 감사합니다~

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@taipaise 네, 이해합니다! 시작 시간과 종료 시간 정책이 추가될 예정이라면 그때 함께 리팩토링하는 것이 더 효율적이겠네요. 전체적인 시간 관련 로직을 한 번에 정리하는 것이 좋은 접근 방법이라고 생각합니다.

향후 정책 추가 시에 시간 관련 상수들을 모두 정리해서 관리하시면 될 것 같습니다! 🐰

🐰💭
╭─────────────╮
│ 합리적인 판단! │
╰─────────────╯


🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#31
File: Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift:97-103
Timestamp: 2025-08-01T06:38:18.687Z
Learning: choijungp의 HomeViewModel에서 fetchRoutines(for date: Date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 데이터 청크를 가져오기 위해 oldestDate와 latestDate를 모두 업데이트한다. 과거 날짜 요청 시에는 해당 날짜의 1주 전~1일 전, 미래 날짜 요청 시에는 해당 날짜의 1일 후~1주 후 범위로 설정한다.

Comment on lines +292 to +294
} catch {

}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

빈 catch 블록 문제

에러가 발생해도 사용자에게 알리지 않고 조용히 실패합니다. 적절한 에러 처리가 필요합니다.

최소한 로깅이라도 추가하거나, 사용자에게 피드백을 제공해야 합니다:

} catch {
-
+    // TODO: 에러 처리 구현
+    print("루틴 저장 실패: \(error)")
+    // 에러 상태를 PublishSubject로 전달하여 UI에서 처리
}
📝 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
} catch {
}
} catch {
// TODO: 에러 처리 구현
print("루틴 저장 실패: \(error)")
// 에러 상태를 PublishSubject로 전달하여 UI에서 처리
}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift
around lines 292 to 294, the catch block is empty, causing errors to be silently
ignored. To fix this, add error handling by logging the error or providing user
feedback within the catch block to ensure errors are properly reported or
handled.

Copy link
Copy Markdown
Contributor

@choijungp choijungp left a comment

Choose a reason for hiding this comment

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

루등, 루수가 너무너무 복잡하네요 ...... 진짜 고생하셨습니다 띵 !!!!!!!
하루에 몇 PR을 하신건지 !!!! 🔥🔥🔥🔥


일단 고민하셨던 부분에 대해 ...

1. 루틴 등록(createRoutine), 루틴 수정(updateRoutine)을 View에서는 루틴 저장(saveRoutine) 하나로 동작하게 하는 것이 의미 있는가?

흠 .. 어차피 분기처리를 하고 있으면 애초에 viewmodel 단에서 create - update를 분리하는 것두 ㄱㅊ아 보입니다 !!! (주관적인 판단임)

RoutineUseCase의 saveRoutine 함수에서 등록 및 수정에 필요한 값을 파라미터로 넘겨주는데
사실 등록할 때에는 수정에 필요한 deletedSubRoutineSummaries 이 녀석이 필요 없기 때문에 ... 함수를 2개 만들어두 된다고 생각하지만 ..........

그것은 추후 ....

2. Entity를 어떻게 정의 해야할까

이것슨 .. 제가 저번 PR에서 넘 대충 DTO - Entity를 서버에서 받은 Response와 동일하게 가져가자 .. 라고 해서 그 영향이 크지 않을까 싶습니다 ㅜㅜ

그리고 RoutineEntity를 제가 사용하고 있어서 1번 방법을 사용하지 않아주신것 .. . 매우 감동이 됩니다. . .......

아하 !!!! 좋습니다 !!!! 회의 때 Entity 정의를 함 하고 갑시두아 !!!!


논의해보고 싶은 부분들

1. View의 네이밍

우선 제가 첨에 View라고 정의한 이유는 정말정말 View의 역할만 했으면 하는 방향에서 .. 그르케 했습니다 ㅜㅜ

xxxView는 정말 멍청하게 View 역할만 하고 ViewController에 contentView에 xxxView를 넣어주자 라는 의미였지만
띵이 들어오기 전부터 View는 매우매우 똑똑했어요 .....
그래서 ViewController 네이밍 동의합니다 !!!

2. Repository의 역할

Entity에서 DTO로 매핑이 필요한 경우는 Request DTO일 때를 말씀하신거 맞으실까요 ??

띵의 말씀도 이해가 되지만 ...
Entity to DTO도 사실 DTO 객체에서 함수로 만들어서 매핑할 수 있지 않을까요 ?

struct DTO {
    func toDTO(entity: Entity) -> DTO {
        ///
    }
}

하지만 이것은 정말 단순히 코드 가독성을 위해서 말씀드리는 말이긴 합니다. ..
(딩의 말씀대로 데이터 매핑을 repository가 가져가면 넘 복잡해질 수 있다고 판단되기도 해가주구요 ..)

하 .. 그른데 저렇게 하면 또 Entity랑 DTO랑 찐하게 엮여있긴 하네요 그럼 ...

DTO가 매핑의 역할까지 해야 하나 ??? 는 저도 좀 생각을 더 해보고 ..
회의에 참여하도록 해보겠습니다 !!!!

Comment on lines +8 to +16
public enum WeekType: String, CaseIterable {
case monday = "MONDAY"
case tuesday = "TUESDAY"
case wednesday = "WEDNESDAY"
case thursday = "THURSDAY"
case friday = "FRIDAY"
case saturday = "SATURDAY"
case sunday = "SUNDAY"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

요 WeekType이 Public인 이유가 있을까용 ???
Review Note에서 말씀해주신 Entity <-> DTO를 Repository에서 해야 한다 ! 라는 것에서 일단 Public으로 열어두신 것인지 궁금함니다 !!!

또한 Presentation에 Week과 WeekType이 비슷한 역할을 한다고 생각하는데
그럼 차라리 Domain의 WeekType만 public으로 남겨두고 Presentation에서 필요한 값만 extension 해서 사용하는 것은 어떻게 생각하시나요 ?? (물론 배포 먼저)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

이 부분에 대해 리뷰 노트를 남기는 것을 깜빡했습니다!!! 역시 조이네요.!.!
말씀해주신대로 역할이 200% 동일한 녀석입니다.. 요 녀석을 추가한 이유는, 엔티티 내에서 사용하는 repeatDay를 문자열이 아닌 제한된 네임스페이스 안에서 다루는 것이 조금 더 적절하지 않을까하여 추가했습니다! 하지만 Presentation에서 사용하는 녀석과 역할이 겹쳐버려서,, 마음같아서는 Presentation의 Week을 도메인으로 옮기고 싶었지만!! 관련 내용에 대해 말씀드리고 진행하는 것이 좋을 것 같다 생각했습니다!

제안해주신 extension으로 활용하는 것은 좋아 입니다!! (생각지도 못한 방법 bbb)

Comment on lines +1 to +7
//
// Untitled.swift
// Domain
//
// Created by 이동현 on 8/3/25.
//

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

엠티 파일 발견 !!!!!!!!!!!!!!! 🚨🚨🚨🚨🚨🚨🚨🚨🚨

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

악 이게 뭘까요.,.,.,.,.,.,.,.,.,. 수정하겠습니다

Comment on lines +27 to 28
let routineEntities = try await routineRepository.fetchRoutines(from: start, to: end)
return routineEntities
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

따봉 감사합니다 ㅎㅎㅎ (머쓱

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

저도 몰랐는데!!!!Xcode 님이 알려주시길래 스윽 했습니다 ㅎㅎ

@choijungp choijungp merged commit 8e6610b into develop Aug 4, 2025
2 checks passed
@choijungp choijungp deleted the feat/routine-creation branch August 4, 2025 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants