Skip to content

[#148] 데이터와 인프라 간 DTO 및 Mapper을 확인한다#155

Merged
opficdev merged 7 commits intodevelopfrom
refactor/#148-DTO
Feb 28, 2026
Merged

[#148] 데이터와 인프라 간 DTO 및 Mapper을 확인한다#155
opficdev merged 7 commits intodevelopfrom
refactor/#148-DTO

Conversation

@opficdev
Copy link
Owner

@opficdev opficdev self-assigned this Feb 28, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 데이터와 인프라 계층 간의 DTO 및 매퍼 구조를 재정비하여 아키텍처의 명확성을 높이고 Firebase 종속성을 줄이는 데 중점을 둡니다. DTO 파일들을 Data/DTO 경로로 이동하고, FirebaseFirestore.Timestamp 대신 Foundation.Date를 사용하도록 변경하며, Firestore 스냅샷에서 DTO를 생성하는 일관된 헬퍼 메서드를 도입하여 데이터 처리 로직을 개선했습니다.

Highlights

  • DTO 파일 재구성: 기존 Infra/DTO 경로에 있던 DTO 파일들이 Data/DTO 경로로 이동되어 데이터 계층과 인프라 계층 간의 분리가 강화되었습니다.
  • FirebaseFirestore.Timestamp 의존성 제거: PushNotificationCursorDTO, PushNotificationResponse, WebPageDTO 등 여러 DTO에서 FirebaseFirestore.Timestamp 타입이 Foundation.Date로 변경되어 Firebase 종속성이 줄어들었습니다.
  • Firebase 특정 어노테이션 제거: PushNotificationResponseWebPageResponse에서 @DocumentID 어노테이션이 제거되고 id 속성이 옵셔널 String으로 변경되었습니다.
  • 새로운 DTO 추가: TodoCursorDTOTodoDTOData/DTO 디렉토리에 새로 추가되었습니다.
  • TodoCursorDTO로 통일: TodoCursorResponseTodoCursorDTO로 대체되었으며, 관련 매핑 및 리포지토리 구현이 업데이트되었습니다.
  • 데이터 파싱 로직 개선: PushNotificationService, TodoService, WebPageService에서 Firestore 스냅샷으로부터 DTO를 생성하는 makeResponse 헬퍼 메서드가 도입되어 데이터 파싱 로직의 일관성과 재사용성이 향상되었습니다.
  • 인증 서비스 메서드명 변경: FirebaseAuth.User 확장 메서드인 toResponsemakeResponse로 변경되어 인증 서비스 전반에 걸쳐 일관된 명명 규칙을 따르게 되었습니다.
Changelog
  • DevLog/Data/DTO/TodoCursorDTO.swift
    • TodoCursorDTO 구조체 추가.
  • DevLog/Data/DTO/TodoDTO.swift
    • TodoRequestTodoResponse 구조체 추가.
  • DevLog/Data/Mapper/PushNotificationMapping.swift
    • FirebaseFirestore 임포트 제거.
    • PushNotificationResponsePushNotificationCursorDTOreceivedAt 매핑 로직에서 dateValue()Timestamp(date:) 호출 제거.
  • DevLog/Data/Mapper/TodoMapping.swift
    • FirebaseFirestore 임포트 제거.
    • TodoCursorResponseTodoCursorDTO로 변경하고 createdAt 매핑 로직에서 dateValue()Timestamp(date:) 호출 제거.
  • DevLog/Data/Protocol/UserPreferencesRepository.swift
    • DevLog/Domain/Protocol/UserPreferencesRepository.swift로 이름 변경됨.
  • DevLog/Data/Repository/TodoRepositoryImpl.swift
    • fetchTodos 메서드에서 TodoCursorResponse.fromDomainTodoCursorDTO.fromDomain으로 변경.
  • DevLog/Infra/DTO/AppleAuthResponse.swift
    • DevLog/Data/DTO/AppleAuthResponse.swift로 이름 변경됨.
  • DevLog/Infra/DTO/AuthDataResponse.swift
    • DevLog/Data/DTO/AuthDataResponse.swift로 이름 변경됨.
  • DevLog/Infra/DTO/NotificationKind.swift
    • DevLog/Data/DTO/NotificationKind.swift로 이름 변경됨.
  • DevLog/Infra/DTO/PushNotificationCursorDTO.swift
    • DevLog/Data/DTO/PushNotificationCursorDTO.swift로 이름 변경됨.
    • receivedAt 타입을 Timestamp에서 Date로 변경하고 FirebaseFirestore 임포트 제거.
  • DevLog/Infra/DTO/PushNotificationPageResponse.swift
    • DevLog/Data/DTO/PushNotificationPageResponse.swift로 이름 변경됨.
  • DevLog/Infra/DTO/PushNotificationResponse.swift
    • DevLog/Data/DTO/PushNotificationResponse.swift로 이름 변경됨.
    • id 속성에서 @DocumentID 제거, Decodable 프로토콜 제거, receivedAt 타입을 Timestamp에서 Date로 변경하고 FirebaseFirestore 임포트 제거.
  • DevLog/Infra/DTO/TodoCursorResponse.swift
    • 파일 제거됨.
  • DevLog/Infra/DTO/TodoDTO.swift
    • 파일 제거됨.
  • DevLog/Infra/DTO/TodoPageResponse.swift
    • DevLog/Data/DTO/TodoPageResponse.swift로 이름 변경됨.
    • nextCursor 타입을 TodoCursorResponse에서 TodoCursorDTO로 변경.
  • DevLog/Infra/DTO/UserProfileResponse.swift
    • DevLog/Data/DTO/UserProfileResponse.swift로 이름 변경됨.
  • DevLog/Infra/DTO/WebPageDTO.swift
    • DevLog/Data/DTO/WebPageDTO.swift로 이름 변경됨.
    • WebPageRequestWebPageResponse에서 FirebaseFirestore 임포트 제거, WebPageResponse에서 @DocumentID 제거 및 Decodable 프로토콜 제거.
  • DevLog/Infra/DTO/WebPageMetadataResponse.swift
    • DevLog/Data/DTO/WebPageMetadataResponse.swift로 이름 변경됨.
  • DevLog/Infra/Extension/FirebaseAuthUser.swift
    • toResponse 메서드 이름을 makeResponse로 변경.
  • DevLog/Infra/Service/PushNotificationService.swift
    • fetchPushNotifications 메서드에서 커서의 receivedAtTimestamp(date:)로 변환하여 사용하도록 변경.
    • snapshot.documents.compactMap 로직을 makeResponse 헬퍼 메서드 사용으로 변경.
    • nextCursor 생성 시 receivedAtdateValue()로 변환하도록 변경.
    • makeResponse 프라이빗 확장 메서드 추가.
  • DevLog/Infra/Service/SocialLogin/AppleAuthenticationService.swift
    • toResponse 메서드 호출을 makeResponse로 변경.
  • DevLog/Infra/Service/SocialLogin/GithubAuthenticationService.swift
    • toResponse 메서드 호출을 makeResponse로 변경.
  • DevLog/Infra/Service/SocialLogin/GoogleAuthenticationService.swift
    • toResponse 메서드 호출을 makeResponse로 변경.
  • DevLog/Infra/Service/TodoService.swift
    • fetchTodos 메서드의 cursor 파라미터 타입을 TodoCursorResponse?에서 TodoCursorDTO?로 변경.
    • 커서의 createdAtTimestamp(date:)로 변환하여 사용하도록 변경.
    • snapshot.documents.compactMap 로직을 makeResponse 헬퍼 메서드 사용으로 변경.
    • nextCursor 생성 시 createdAtdateValue()로 변환하도록 변경.
    • makeResponse 프라이빗 확장 메서드 추가.
  • DevLog/Infra/Service/WebPageService.swift
    • fetchWebPages 메서드에서 snapshot.documents.compactMap 로직을 makeResponse 헬퍼 메서드 사용으로 변경.
    • makeResponse 프라이빗 확장 메서드 추가.
Activity
  • 이 Pull Request는 이슈 #148을 해결합니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

전반적으로 훌륭한 리팩토링입니다. 데이터와 인프라 계층을 분리하여 Firebase 종속성을 제거하고, DTO와 Mapper를 Data 계층으로 이동시킨 점이 인상적입니다. 이는 클린 아키텍처 원칙에 부합하며 코드의 테스트 용이성과 유지보수성을 크게 향상시킬 것입니다. Infra 계층의 서비스(TodoService, PushNotificationService 등)가 Firebase 데이터 모델을 애플리케이션 DTO로 변환하는 책임을 갖도록 수정한 것은 매우 올바른 방향입니다. 몇 가지 추가적인 개선 사항을 제안드립니다.

struct PushNotificationResponse: Decodable {
@DocumentID var id: String?
struct PushNotificationResponse {
let id: String?

Choose a reason for hiding this comment

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

medium

PushNotificationServicemakeResponse 함수를 보면 snapshot.documentID를 사용하여 id를 초기화하고 있습니다. documentID는 nil이 될 수 없으므로, id 프로퍼티를 String? 대신 String으로 선언하여 타입 안정성을 높이는 것이 좋습니다.

Suggested change
let id: String?
let id: String

}

struct TodoResponse {
let id: String?

Choose a reason for hiding this comment

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

medium

TodoServicemakeResponse 함수를 보면 snapshot.documentID를 사용하여 id를 초기화하고 있습니다. documentID는 nil이 될 수 없으므로, TodoResponseid 프로퍼티를 String? 대신 String으로 선언하여 타입 안정성을 높이는 것이 좋습니다.

Suggested change
let id: String?
let id: String

struct WebPageResponse: Decodable {
@DocumentID var id: String?
struct WebPageResponse {
let id: String?

Choose a reason for hiding this comment

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

medium

WebPageServicemakeResponse 함수를 보면 snapshot.documentID를 사용하여 id를 초기화하고 있습니다. documentID는 nil이 될 수 없으므로, WebPageResponseid 프로퍼티를 String? 대신 String으로 선언하여 타입 안정성을 높이는 것이 좋습니다.

Suggested change
let id: String?
let id: String

@opficdev
Copy link
Owner Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 데이터 레이어와 인프라 레이어 간의 의존성을 분리하여 아키텍처를 개선하는 중요한 리팩토링 작업을 담고 있습니다. DTO를 데이터 레이어로 이동시키고 Firebase 종속성을 제거하여, 각 레이어의 역할과 책임을 명확히 한 점이 인상적입니다. 특히, Firestore 데이터 파싱 로직을 서비스 내의 makeResponse 팩토리 메소드로 옮겨 Infra 레이어에 캡슐화한 것은 매우 좋은 접근 방식입니다. 전반적으로 코드의 유연성과 테스트 용이성이 크게 향상될 것으로 기대됩니다. 몇 가지 유지보수성 향상을 위한 제안을 리뷰 코멘트로 남겼으니 확인 부탁드립니다.

Comment on lines 179 to 202
private extension PushNotificationService {
func makeResponse(from snapshot: QueryDocumentSnapshot) -> PushNotificationResponse? {
let data = snapshot.data()
guard
let title = data["title"] as? String,
let body = data["body"] as? String,
let receivedAt = data["receivedAt"] as? Timestamp,
let isRead = data["isRead"] as? Bool,
let todoID = data["todoID"] as? String,
let todoKind = data["todoKind"] as? String else {
return nil
}

return PushNotificationResponse(
id: snapshot.documentID,
title: title,
body: body,
receivedAt: receivedAt.dateValue(),
isRead: isRead,
todoID: todoID,
todoKind: todoKind
)
}
}

Choose a reason for hiding this comment

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

medium

Firestore 필드 이름을 수동으로 파싱하는 로직을 추가하셨네요. 문자열 리터럴을 직접 사용하는 것은 오타에 취약하여 런타임 에러를 유발할 수 있습니다. 필드 이름을 enum으로 정의하여 상수로 관리하면 타입-세이프하게 접근할 수 있어 코드의 안정성과 유지보수성을 높일 수 있습니다.

private extension PushNotificationService {
    private enum FieldKey: String {
        case title, body, receivedAt, isRead, todoID, todoKind
    }

    func makeResponse(from snapshot: QueryDocumentSnapshot) -> PushNotificationResponse? {
        let data = snapshot.data()
        guard
            let title = data[FieldKey.title.rawValue] as? String,
            let body = data[FieldKey.body.rawValue] as? String,
            let receivedAt = data[FieldKey.receivedAt.rawValue] as? Timestamp,
            let isRead = data[FieldKey.isRead.rawValue] as? Bool,
            let todoID = data[FieldKey.todoID.rawValue] as? String,
            let todoKind = data[FieldKey.todoKind.rawValue] as? String else {
            return nil
        }

        return PushNotificationResponse(
            id: snapshot.documentID,
            title: title,
            body: body,
            receivedAt: receivedAt.dateValue(),
            isRead: isRead,
            todoID: todoID,
            todoKind: todoKind
        )
    }
}

Comment on lines 136 to 177
private extension TodoService {
func makeResponse(from snapshot: QueryDocumentSnapshot) -> TodoResponse? {
makeResponse(documentID: snapshot.documentID, data: snapshot.data())
}

func makeResponse(from snapshot: DocumentSnapshot) -> TodoResponse? {
guard let data = snapshot.data() else {
return nil
}
return makeResponse(documentID: snapshot.documentID, data: data)
}

func makeResponse(documentID: String, data: [String: Any]) -> TodoResponse? {
guard
let isPinned = data["isPinned"] as? Bool,
let isCompleted = data["isCompleted"] as? Bool,
let isChecked = data["isChecked"] as? Bool,
let title = data["title"] as? String,
let content = data["content"] as? String,
let createdAtTimestamp = data["createdAt"] as? Timestamp,
let updatedAtTimestamp = data["updatedAt"] as? Timestamp,
let tags = data["tags"] as? [String],
let kind = data["kind"] as? String else {
return nil
}

let dueDate = (data["dueDate"] as? Timestamp)?.dateValue()
return TodoResponse(
id: documentID,
isPinned: isPinned,
isCompleted: isCompleted,
isChecked: isChecked,
title: title,
content: content,
createdAt: createdAtTimestamp.dateValue(),
updatedAt: updatedAtTimestamp.dateValue(),
dueDate: dueDate,
tags: tags,
kind: kind
)
}
}

Choose a reason for hiding this comment

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

medium

Firestore 필드 이름을 수동으로 파싱하는 로직을 추가하셨네요. 문자열 리터럴을 직접 사용하는 것은 오타에 취약하여 런타임 에러를 유발할 수 있습니다. 필드 이름을 enum으로 정의하여 상수로 관리하면 타입-세이프하게 접근할 수 있어 코드의 안정성과 유지보수성을 높일 수 있습니다.

private extension TodoService {
    private enum FieldKey: String {
        case isPinned, isCompleted, isChecked, title, content, createdAt, updatedAt, dueDate, tags, kind
    }

    func makeResponse(from snapshot: QueryDocumentSnapshot) -> TodoResponse? {
        makeResponse(documentID: snapshot.documentID, data: snapshot.data())
    }

    func makeResponse(from snapshot: DocumentSnapshot) -> TodoResponse? {
        guard let data = snapshot.data() else {
            return nil
        }
        return makeResponse(documentID: snapshot.documentID, data: data)
    }

    func makeResponse(documentID: String, data: [String: Any]) -> TodoResponse? {
        guard
            let isPinned = data[FieldKey.isPinned.rawValue] as? Bool,
            let isCompleted = data[FieldKey.isCompleted.rawValue] as? Bool,
            let isChecked = data[FieldKey.isChecked.rawValue] as? Bool,
            let title = data[FieldKey.title.rawValue] as? String,
            let content = data[FieldKey.content.rawValue] as? String,
            let createdAtTimestamp = data[FieldKey.createdAt.rawValue] as? Timestamp,
            let updatedAtTimestamp = data[FieldKey.updatedAt.rawValue] as? Timestamp,
            let tags = data[FieldKey.tags.rawValue] as? [String],
            let kind = data[FieldKey.kind.rawValue] as? String else {
            return nil
        }

        let dueDate = (data[FieldKey.dueDate.rawValue] as? Timestamp)?.dateValue()
        return TodoResponse(
            id: documentID,
            isPinned: isPinned,
            isCompleted: isCompleted,
            isChecked: isChecked,
            title: title,
            content: content,
            createdAt: createdAtTimestamp.dateValue(),
            updatedAt: updatedAtTimestamp.dateValue(),
            dueDate: dueDate,
            tags: tags,
            kind: kind
        )
    }
}

Comment on lines 100 to 119
private extension WebPageService {
func makeResponse(from snapshot: QueryDocumentSnapshot) -> WebPageResponse? {
let data = snapshot.data()
guard
let title = data["title"] as? String,
let url = data["url"] as? String,
let displayURL = data["displayURL"] as? String,
let imageURL = data["imageURL"] as? String else {
return nil
}

return WebPageResponse(
id: snapshot.documentID,
title: title,
url: url,
displayURL: displayURL,
imageURL: imageURL
)
}
}

Choose a reason for hiding this comment

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

medium

Firestore 필드 이름을 수동으로 파싱하는 로직을 추가하셨네요. 문자열 리터럴을 직접 사용하는 것은 오타에 취약하여 런타임 에러를 유발할 수 있습니다. 필드 이름을 enum으로 정의하여 상수로 관리하면 타입-세이프하게 접근할 수 있어 코드의 안정성과 유지보수성을 높일 수 있습니다.

private extension WebPageService {
    private enum FieldKey: String {
        case title, url, displayURL, imageURL
    }

    func makeResponse(from snapshot: QueryDocumentSnapshot) -> WebPageResponse? {
        let data = snapshot.data()
        guard
            let title = data[FieldKey.title.rawValue] as? String,
            let url = data[FieldKey.url.rawValue] as? String,
            let displayURL = data[FieldKey.displayURL.rawValue] as? String,
            let imageURL = data[FieldKey.imageURL.rawValue] as? String else {
            return nil
        }

        return WebPageResponse(
            id: snapshot.documentID,
            title: title,
            url: url,
            displayURL: displayURL,
            imageURL: imageURL
        )
    }
}

@opficdev
Copy link
Owner Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이 PR은 데이터와 인프라 레이어 간의 의존성을 줄이기 위해 DTO와 매퍼를 리팩터링하는 중요한 작업을 수행합니다. 전반적으로 변경 사항은 아키텍처를 개선하고 코드의 모듈성을 높이는 좋은 방향으로 진행되었습니다. DTO에서 FirebaseFirestore와 같은 특정 인프라 구현에 대한 의존성을 제거하고, Service 계층에서 수동으로 매핑하는 방식으로 변경한 것은 매우 훌륭한 결정입니다.

다만, Data 레이어에 남아있는 인프라 의존성 한 가지를 발견하여 리뷰 코멘트를 남겼습니다. 이 부분을 수정하면 PR의 목표를 더욱 완벽하게 달성할 수 있을 것입니다.


import Foundation

struct TodoRequest: Dictionaryable {

Choose a reason for hiding this comment

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

high

TodoRequestDictionaryable 프로토콜을 채택하고 있는데, Dictionaryable의 구현이 FirebaseFirestore에 의존하고 있습니다. 이 PR의 목표가 데이터 레이어와 인프라 레이어를 분리하는 것인 만큼, 데이터 레이어의 DTO가 특정 인프라(Firebase) 기술에 의존하지 않도록 하는 것이 중요합니다.

Dictionaryable 프로토콜 채택을 제거하고, TodoRequest를 딕셔너리로 변환하는 로직은 Infra 레이어(예: TodoService) 내부에서 처리하는 것을 제안합니다. 이렇게 하면 데이터 레이어의 독립성을 유지할 수 있습니다.

예를 들어, TodoService.swift에 다음과 같은 private extension을 추가할 수 있습니다.

// In TodoService.swift
private extension TodoRequest {
    func toDictionary() -> [String: Any] {
        // Firestore.Encoder를 사용한 직렬화 로직
    }
}
Suggested change
struct TodoRequest: Dictionaryable {
struct TodoRequest {

@opficdev
Copy link
Owner Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이 PR은 데이터 계층과 인프라 계층 간의 의존성을 분리하기 위한 중요한 리팩토링을 수행했습니다. DTO에서 Firebase 관련 코드를 제거하고, Infra 계층의 서비스 내에서 데이터 변환을 처리하도록 변경하여 아키텍처를 크게 개선했습니다. 이는 코드의 모듈성, 테스트 용이성 및 유지보수성을 향상시키는 좋은 변화입니다.

몇 가지 추가적인 개선을 위해 TodoRequest DTO에서 CodingKeys를 사용하여 인코딩을 더 명시적으로 제어하고, TodoService에서 Firestore.Encoder 설정을 통해 nil 값 처리를 자동화하는 방안을 제안했습니다. 전반적으로 매우 훌륭한 리팩토링입니다.

Comment on lines +10 to +23
struct TodoRequest: Encodable {
let id: String
let isPinned: Bool
let isCompleted: Bool
let isChecked: Bool
let title: String
let content: String
let createdAt: Date
let updatedAt: Date
let dueDate: Date?
let tags: [String]
let kind: TodoKind

}

Choose a reason for hiding this comment

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

medium

TodoRequest를 인코딩할 때 id 프로퍼티가 인코딩에서 제외되도록 CodingKeys를 명시적으로 정의하는 것을 고려해 보세요. id는 Firestore 문서의 ID로 사용되며, 데이터 페이로드에는 포함되지 않는 것이 일반적입니다. CodingKeys를 사용하면 TodoService에서 수동으로 id 키를 제거하는 로직을 없앨 수 있어, DTO의 역할이 더 명확해지고 코드가 간결해집니다.

이 변경을 적용하려면 TodoServiceupsertTodo 메서드에서 data.removeValue(forKey: TodoFieldKey.id.rawValue) 라인도 함께 제거해야 합니다.

struct TodoRequest: Encodable {
    let id: String
    let isPinned: Bool
    let isCompleted: Bool
    let isChecked: Bool
    let title: String
    let content: String
    let createdAt: Date
    let updatedAt: Date
    let dueDate: Date?
    let tags: [String]
    let kind: TodoKind

    enum CodingKeys: String, CodingKey {
        case isPinned, isCompleted, isChecked, title, content, createdAt, updatedAt, dueDate, tags, kind
    }
}

Comment on lines +92 to +94
if request.dueDate == nil {
data[TodoFieldKey.dueDate.rawValue] = NSNull()
}

Choose a reason for hiding this comment

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

medium

dueDatenil일 때 NSNull()을 수동으로 할당하는 대신, Firestore.EncoderencodeNilValues 프로퍼티를 true로 설정하여 이 로직을 자동화하는 것을 고려해 보세요. 이렇게 하면 TodoRequest에 다른 옵셔널 프로퍼티가 추가되더라도 이 부분을 수정할 필요가 없어 유지보수성이 향상됩니다.

TodoServiceencoder 프로퍼티를 다음과 같이 수정할 수 있습니다:

private let encoder: Firestore.Encoder = {
    let encoder = Firestore.Encoder()
    encoder.encodeNilValues = true
    return encoder
}()

이 변경 후에는 이 if 문을 제거할 수 있습니다.

@opficdev opficdev merged commit bd37457 into develop Feb 28, 2026
1 check passed
@opficdev opficdev deleted the refactor/#148-DTO branch February 28, 2026 03:54
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.

데이터 <-> 인프라 간 DTO 및 Mapper를 확인한다

1 participant