Feature/#1 - Swift Data 모델링 및 AppSchema 구현#3
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Warning
|
| Layer / File(s) | Summary |
|---|---|
Core Domain Entities Projects/DVData/Sources/Storage/Local/Models/Project.swift, Projects/DVData/Sources/Storage/Local/Models/Secret.swift |
Project 및 Secret 모델 추가: 고유 식별자, 필드(타입/메타/상태/타임스탬프), 관계(SecretProjectLink 등) 및 계산 속성(secrets, projects). |
Relationship & Payload Models Projects/DVData/Sources/Storage/Local/Models/SecretProjectLink.swift, Projects/DVData/Sources/Storage/Local/Models/SecretPayload.swift, Projects/DVData/Sources/Storage/Local/Models/SecretMetadata.swift, Projects/DVData/Sources/Storage/Local/Models/SecretAuditLog.swift |
SecretProjectLink(연결·타임스탬프), SecretPayload(암호화 데이터 + keyTag + schemaVersion), SecretMetadata(JSON 메타 + 버전), SecretAuditLog(이벤트·actorContext·의심 플래그) 추가. |
Supporting Models Projects/DVData/Sources/Storage/Local/Models/AppAuditLog.swift, Projects/DVData/Sources/Storage/Local/Models/BackupRecord.swift |
AppAuditLog(앱 수준 이벤트), BackupRecord(백업 파일/경로/범위/키태그/시크릿 수 등) 추가. |
Schema Definition Projects/DVData/Sources/Storage/Local/Models/AppSchema.swift |
모든 모델을 포함하는 Schema.appSchema(version 1.0.0) 정의 및 SwiftDataModel 네임스페이스 자리 표시자 추가. |
Tooling / Reviewer Config .coderabbit.yaml |
리뷰 톤·지침을 한국어 리뷰자로 변경(자동화 가능한 스타일 권고 포함). |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related issues
- #2: Main issue implements the SwiftData model classes; 이 PR의 모델 목록이 해당 이슈의 요구 항목과 직접적으로 일치합니다.
코멘트 — 개선 제안 (간단)
- 관계·삭제 규칙 가시성 부족: 여러 관계에 대해 deleteRule(.cascade/.nullify)와 optional 여부가 분산되어 있습니다. 각 모델 파일 상단에 관계 의도(예: 소유권, nullify 시나리오)와 마이그레이션 영향 간단 주석을 추가하세요. (참고: Apple SwiftData 관계 문서)
- 중복 key 생성 로직:
secretKey/linkKey를 생성자에서 반복하고 있습니다. 공용 팩토리(예: static func secretKey(from: Secret))로 중앙화해 중복을 줄이세요. (참고: Swift API Design Guidelines) - metadataJSON 스키마 진화 대비: raw Data 저장은 마이그레이션 부담을 키웁니다. Schema.Version 기반 마이그레이션 유닛 또는 버전별 디코더를 도입해 변환 경로를 명시하세요. (참고: Apple의 데이터 마이그레이션 권장사항)
- 자동화 가능한 스타일: 접근 제어·주석·포맷 일관성이 일부 파일에서 다릅니다. SwiftLint/SwiftFormat을 CI에 도입해 규칙을 자동 적용하세요.
Poem
🐰 스키마의 터널이 반짝 열리네,
프로젝트와 비밀들이 손을 맞잡네.
모델, 연결, 감사, 백업까지 한 줄로,
데이터 집이 튼튼히 서 가는구나!
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | 제목은 PR의 주요 변경점인 SwiftData 모델링 및 AppSchema 구현을 명확하게 요약하고 있습니다. |
| Linked Issues check | ✅ Passed | 모든 필수 모델(Secret, Project, SecretProjectLink, SecretPayload, SecretMetadata, SecretAuditLog, BackupRecord, AppAuditLog)이 구현되었고 AppSchema 스키마 버전 관리가 추가되었습니다. |
| Out of Scope Changes check | ✅ Passed | .coderabbit.yaml의 tone_instructions 변경을 제외한 모든 수정사항이 SwiftData 모델 정의 및 스키마 관리라는 목표에 부합합니다. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
feature/#1/SwiftData_Modeling
Tip
💬 Introducing Slack Agent: The best way for teams to turn conversations into code.
Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
- Generate code and open pull requests
- Plan features and break down work
- Investigate incidents and troubleshoot customer tickets together
- Automate recurring tasks and respond to alerts with triggers
- Summarize progress and report instantly
Built for teams:
- Shared memory across your entire org—no repeating context
- Per-thread sandboxes to safely plan and execute work
- Governance built-in—scoped access, auditability, and budget controls
One agent for your entire SDLC. Right inside Slack.
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.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Projects/DVData/Sources/Storage/Local/Models/Secret.swift`:
- Around line 30-31: The Relationship on Secret.auditLogs currently uses
deleteRule: .cascade which hard-deletes SecretAuditLog entries when a Secret is
removed; update the relationship declaration on the Secret model (the auditLogs
property) to use deleteRule: .nullify so audit logs are orphaned rather than
deleted, keeping inverse: \SwiftDataModel.SecretAuditLog.secret and preserving
the existing soft-delete semantics (deletedAt) for compliance/audit purposes.
- Around line 48-49: The initializer for Secret currently uses Date() separately
for createdAt and updatedAt causing different timestamps; change the initializer
(init in Secret) to derive a single timestamp: accept createdAt: Date = Date()
and updatedAt: Date? = nil (or keep updatedAt optional/default) and inside the
init set self.updatedAt = updatedAt ?? createdAt so when updatedAt is not
provided it equals createdAt; apply the same pattern for the other similar
initializer/struct where createdAt/updatedAt defaults are declared.
In `@Projects/DVData/Sources/Storage/Local/Models/SecretPayload.swift`:
- Around line 13-22: Make the Secret relationship non-optional to prevent
orphaned child records: in SecretPayload change the `@Relationship` var secret:
Secret? to `@Relationship` var secret: Secret (remove the optional and the = nil
default in the init), update the initializer signature to require a Secret
parameter (remove secret: Secret? = nil and accept secret: Secret), and update
all call sites; apply the same change to SecretMetadata, SecretAuditLog, and
SecretProjectLink (remove ? and default nil, require Secret in their
initializers and fix usages).
In `@Projects/DVData/Sources/Storage/Local/Models/SecretProjectLink.swift`:
- Around line 10-24: SecretProjectLink's relationships (project and secret) are
optional but the init requires them and this allows orphaned links and
duplicates; change `@Relationship` var project: Project? and `@Relationship` var
secret: Secret? to required relationships (remove the optionals) and update the
initializer to assign the required non-optional members
(SecretProjectLink.init(project:secret:linkedAt:)) so the model cannot be
created without both sides. Also add a uniqueness constraint for the (project,
secret) pair—either add a composite unique index in the model/migration or
introduce a deterministic unique identifier (e.g., a composite id derived from
project+secret) to prevent duplicate join rows and avoid duplicate entries
appearing in Project.secrets.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4818f012-16b1-4d39-9cbe-d6e9a8829266
📒 Files selected for processing (9)
Projects/DVData/Sources/Storage/Local/Models/AppAuditLog.swiftProjects/DVData/Sources/Storage/Local/Models/AppSchema.swiftProjects/DVData/Sources/Storage/Local/Models/BackupRecord.swiftProjects/DVData/Sources/Storage/Local/Models/Project.swiftProjects/DVData/Sources/Storage/Local/Models/Secret.swiftProjects/DVData/Sources/Storage/Local/Models/SecretAuditLog.swiftProjects/DVData/Sources/Storage/Local/Models/SecretMetadata.swiftProjects/DVData/Sources/Storage/Local/Models/SecretPayload.swiftProjects/DVData/Sources/Storage/Local/Models/SecretProjectLink.swift
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Projects/DVData/Sources/Storage/Local/Models/Secret.swift`:
- Around line 7-69: The Secret model and its members lack explicit access
control; annotate the class declaration and every stored property, the computed
property projects, and the init initializer with an explicit access level (e.g.,
internal or public depending on intended API exposure) so the intent is clear
(update the declaration of Secret, all vars like secretId, name, secretType,
subType, service, environment, expiresAt, memo, liked, deletedAt, createdAt,
updatedAt, projectLinks, payload, metadata, auditLogs, the computed property
projects, and the init signature to include the chosen access modifier); apply
the same explicit-access change pattern to AppAuditLog, BackupRecord, and
Project or add a SwiftLint/SwiftFormat rule to enforce it across models.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 70cae649-5b13-409b-ae43-fec018678929
📒 Files selected for processing (1)
Projects/DVData/Sources/Storage/Local/Models/Secret.swift
| @Model final class Secret { | ||
| @Attribute(.unique) var secretId: UUID | ||
| var name: String | ||
| var secretType: String | ||
| var subType: String? | ||
| var service: String? | ||
| var environment: String? | ||
| var expiresAt: Date? | ||
| var memo: String? | ||
| var liked: Bool | ||
| var deletedAt: Date? | ||
| var createdAt: Date | ||
| var updatedAt: Date | ||
|
|
||
| @Relationship(deleteRule: .cascade, inverse: \SwiftDataModel.SecretProjectLink.secret) | ||
| var projectLinks: [SecretProjectLink] | ||
|
|
||
| @Relationship(deleteRule: .cascade, inverse: \SwiftDataModel.SecretPayload.secret) | ||
| var payload: SecretPayload? | ||
|
|
||
| @Relationship(deleteRule: .cascade, inverse: \SwiftDataModel.SecretMetadata.secret) | ||
| var metadata: [SecretMetadata] | ||
|
|
||
| @Relationship(deleteRule: .nullify, inverse: \SwiftDataModel.SecretAuditLog.secret) | ||
| var auditLogs: [SecretAuditLog] | ||
|
|
||
| var projects: [Project] { | ||
| projectLinks.compactMap(\.project) | ||
| } | ||
|
|
||
| init( | ||
| secretId: UUID = UUID(), | ||
| name: String, | ||
| secretType: String, | ||
| subType: String? = nil, | ||
| service: String? = nil, | ||
| environment: String? = nil, | ||
| expiresAt: Date? = nil, | ||
| memo: String? = nil, | ||
| liked: Bool = false, | ||
| deletedAt: Date? = nil, | ||
| createdAt: Date = Date(), | ||
| updatedAt: Date? = nil | ||
| ) { | ||
| let initialCreatedAt = createdAt | ||
| self.secretId = secretId | ||
| self.name = name | ||
| self.secretType = secretType | ||
| self.subType = subType | ||
| self.service = service | ||
| self.environment = environment | ||
| self.expiresAt = expiresAt | ||
| self.memo = memo | ||
| self.liked = liked | ||
| self.deletedAt = deletedAt | ||
| self.createdAt = initialCreatedAt | ||
| self.updatedAt = updatedAt ?? initialCreatedAt | ||
| self.projectLinks = [] | ||
| self.payload = nil | ||
| self.metadata = [] | ||
| self.auditLogs = [] | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 접근 제어자 미명시 선언 탐지 (수정 후 결과가 없어야 정상)
rg -nP '^\s*(?!public\b|internal\b|private\b|fileprivate\b|open\b)(`@Model`\s+)?(final\s+class\b|var\b|init\s*\()' \
Projects/DVData/Sources/Storage/Local/Models/Secret.swiftRepository: DevaultProject/Devault-macOS
Length of output: 676
🏁 Script executed:
# 확인 차원의 추가 검증: 코드베이스에서 유사한 SwiftData 모델들의 패턴 확인
find Projects/DVData/Sources/Storage/Local/Models -name "*.swift" -type f | head -5 | while read f; do
echo "=== $f ==="
head -20 "$f" | grep -E "(class|var|init)"
doneRepository: DevaultProject/Devault-macOS
Length of output: 1581
모델 선언부에 접근 제어자를 명시해 주세요.
Secret 클래스와 모든 프로퍼티, projects 계산 속성, init 메서드에 접근 제어자가 생략되어 있습니다. Swift 기본값은 internal이지만, 명시적으로 선언하면 모듈 API 경계가 명확해지고 SwiftData 모델 확장 시 의도가 분명해져 유지보수가 수월해집니다. Swift 공식 Access Control 가이드에서도 이를 권장합니다.
적용 예시
extension SwiftDataModel {
- `@Model` final class Secret {
- `@Attribute`(.unique) var secretId: UUID
- var name: String
+ `@Model` internal final class Secret {
+ `@Attribute`(.unique) internal var secretId: UUID
+ internal var name: String
...
- var projects: [Project] {
+ internal var projects: [Project] {
projectLinks.compactMap(\.project)
}
- init(
+ internal init(
secretId: UUID = UUID(),
...
) {
...
}
}
}같은 패턴이 AppAuditLog, BackupRecord, Project 모델에도 적용되므로, SwiftLint 커스텀 룰이나 SwiftFormat 설정으로 전사(rollout)하는 것을 추천합니다. 이렇게 자동화하면 향후 신규 모델도 일관되게 관리할 수 있습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVData/Sources/Storage/Local/Models/Secret.swift` around lines 7 -
69, The Secret model and its members lack explicit access control; annotate the
class declaration and every stored property, the computed property projects, and
the init initializer with an explicit access level (e.g., internal or public
depending on intended API exposure) so the intent is clear (update the
declaration of Secret, all vars like secretId, name, secretType, subType,
service, environment, expiresAt, memo, liked, deletedAt, createdAt, updatedAt,
projectLinks, payload, metadata, auditLogs, the computed property projects, and
the init signature to include the chosen access modifier); apply the same
explicit-access change pattern to AppAuditLog, BackupRecord, and Project or add
a SwiftLint/SwiftFormat rule to enforce it across models.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
Projects/DVData/Sources/Storage/Local/Models/SecretMetadata.swift (1)
7-27: ⚡ Quick win접근 제어자를 각 선언부에 명시해주세요.
현재
SecretMetadata의 타입/프로퍼티/이니셜라이저가 모두 기본 접근 수준(internal)에 의존하고 있습니다. 팀 규칙상 선언부마다 접근 제어자를 명시하는 쪽이 좋습니다.제안 패치
extension SwiftDataModel { - `@Model` final class SecretMetadata { - `@Attribute`(.unique) var id: UUID - `@Attribute`(.unique) var secretKey: String - var metadataJSON: Data - var schemaVersion: Int + `@Model` internal final class SecretMetadata { + `@Attribute`(.unique) internal var id: UUID + `@Attribute`(.unique) internal var secretKey: String + internal var metadataJSON: Data + internal var schemaVersion: Int `@Relationship` - var secret: Secret + internal var secret: Secret - init( + internal init( id: UUID = UUID(), metadataJSON: Data, schemaVersion: Int, secret: Secret ) {As per coding guidelines, "접근 제어자(access control)가 각 선언부에 명시적으로 기재되어 있는지 확인하세요."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVData/Sources/Storage/Local/Models/SecretMetadata.swift` around lines 7 - 27, 명시적 접근 제어자가 빠져 있는 SecretMetadata 선언부와 멤버들에 접근 제어자(internal 또는 팀 규칙에 맞는 수준)를 추가하세요: 클래스 선언 SecretMetadata에 접근 제어자(internal final class SecretMetadata)와 각 프로퍼티 id, secretKey, metadataJSON, schemaVersion, secret에 대해 개별 접근 제어자를 명시하고 생성자 init도 access modifier(internal init(...))를 붙여 선언부마다 명시적으로 접근 수준을 설정하도록 수정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Projects/DVData/Sources/Storage/Local/Models/SecretPayload.swift`:
- Around line 7-30: The SecretPayload type and every declaration inside it
currently lack explicit access control; add the intended visibility (e.g.,
public or internal) to the class declaration, to each stored property (id,
secretKey, encryptedData, keyTag, schemaVersion, and the `@Relationship` var
secret) and to the initializer signature (init) so the module API surface is
explicit—update the declarations in SecretPayload accordingly (adjust to
public/internal consistent with DVData module API).
---
Nitpick comments:
In `@Projects/DVData/Sources/Storage/Local/Models/SecretMetadata.swift`:
- Around line 7-27: 명시적 접근 제어자가 빠져 있는 SecretMetadata 선언부와 멤버들에 접근 제어자(internal
또는 팀 규칙에 맞는 수준)를 추가하세요: 클래스 선언 SecretMetadata에 접근 제어자(internal final class
SecretMetadata)와 각 프로퍼티 id, secretKey, metadataJSON, schemaVersion, secret에 대해
개별 접근 제어자를 명시하고 생성자 init도 access modifier(internal init(...))를 붙여 선언부마다 명시적으로 접근
수준을 설정하도록 수정하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: fd9495eb-d8b9-48b7-9c68-60b31e5238fb
📒 Files selected for processing (6)
Projects/DVData/Sources/Storage/Local/Models/Project.swiftProjects/DVData/Sources/Storage/Local/Models/Secret.swiftProjects/DVData/Sources/Storage/Local/Models/SecretAuditLog.swiftProjects/DVData/Sources/Storage/Local/Models/SecretMetadata.swiftProjects/DVData/Sources/Storage/Local/Models/SecretPayload.swiftProjects/DVData/Sources/Storage/Local/Models/SecretProjectLink.swift
✅ Files skipped from review due to trivial changes (1)
- Projects/DVData/Sources/Storage/Local/Models/SecretProjectLink.swift
🚧 Files skipped from review as they are similar to previous changes (3)
- Projects/DVData/Sources/Storage/Local/Models/Project.swift
- Projects/DVData/Sources/Storage/Local/Models/SecretAuditLog.swift
- Projects/DVData/Sources/Storage/Local/Models/Secret.swift
| @Model final class SecretPayload { | ||
| @Attribute(.unique) var id: UUID | ||
| @Attribute(.unique) var secretKey: String | ||
| var encryptedData: Data | ||
| var keyTag: String | ||
| var schemaVersion: Int | ||
|
|
||
| @Relationship | ||
| var secret: Secret | ||
|
|
||
| init( | ||
| id: UUID = UUID(), | ||
| encryptedData: Data, | ||
| keyTag: String, | ||
| schemaVersion: Int, | ||
| secret: Secret | ||
| ) { | ||
| self.id = id | ||
| self.secretKey = secret.secretId.uuidString | ||
| self.encryptedData = encryptedData | ||
| self.keyTag = keyTag | ||
| self.schemaVersion = schemaVersion | ||
| self.secret = secret | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
모든 선언부에 접근 제어자(access control) 명시 필요
클래스 자체(SecretPayload)와 모든 저장 프로퍼티, init 어느 곳에도 접근 제어자가 명시되어 있지 않습니다. Swift에서 기본값은 internal이지만, 코딩 가이드라인에 따르면 각 선언부에 반드시 명시해야 합니다. DVData 모듈의 공개 API 경계를 명확히 하기 위해, 의도한 가시성(예: public/internal)을 선언에 직접 기재해주세요.
✏️ 개선 예시 (의도한 접근 수준에 맞게 조정)
extension SwiftDataModel {
- `@Model` final class SecretPayload {
- `@Attribute`(.unique) var id: UUID
- `@Attribute`(.unique) var secretKey: String
- var encryptedData: Data
- var keyTag: String
- var schemaVersion: Int
+ `@Model` public final class SecretPayload {
+ `@Attribute`(.unique) public var id: UUID
+ `@Attribute`(.unique) public var secretKey: String
+ public var encryptedData: Data
+ public var keyTag: String
+ public var schemaVersion: Int
`@Relationship`
- var secret: Secret
+ public var secret: Secret
- init(
+ public init(
id: UUID = UUID(),
encryptedData: Data,
keyTag: String,
schemaVersion: Int,
secret: Secret
) {As per coding guidelines, "접근 제어자(access control)가 각 선언부에 명시적으로 기재되어 있는지 확인하세요."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVData/Sources/Storage/Local/Models/SecretPayload.swift` around
lines 7 - 30, The SecretPayload type and every declaration inside it currently
lack explicit access control; add the intended visibility (e.g., public or
internal) to the class declaration, to each stored property (id, secretKey,
encryptedData, keyTag, schemaVersion, and the `@Relationship` var secret) and to
the initializer signature (init) so the module API surface is explicit—update
the declarations in SecretPayload accordingly (adjust to public/internal
consistent with DVData module API).
…tedAt, updatedAt)
8bfe9b2 to
8874a53
Compare
| var occurredAt: Date | ||
|
|
||
| @Relationship | ||
| var secret: Secret |
There was a problem hiding this comment.
SecretAuditLog.secret이 non-optional이라 Secret에서 deleteRule: .nullify 옵션 사용할 경우에 런타임 충돌이나 저장 실패 가능해집니다. var secret: Secret? 로 바꾸는게 맞을 것 같아요.
There was a problem hiding this comment.
.nullify의 의도는 "보안/감시 로그는 Secret 삭제시에도 보존해야 한다"로 결정했으므로 옵셔널로 수정하겠습니다! 다만 Secret 삭제시, 관계가 끊기기 때문에 어떤 Secret에 대한 로그였는지 알기 위한 스냅샷 필드 추가 고려중인데 의견이 궁금합니다!
There was a problem hiding this comment.
스냅샷 필드 추가 좋아용. 최소한 식별용으로 secretId(UUID), secretName, secretType 정도가 포함되면 좋을 것 같네요.
There was a problem hiding this comment.
스냅샷 필드를 채우는 시점도 고민해보면 좋을 것 같은데, 일단 안전하게 로그 생성 시점에 하는 거 어떨까요?
doyeonk429
left a comment
There was a problem hiding this comment.
전부 다 반영할 필요는 없으니 accept할 것만 받아들이기! 👍
| @Relationship | ||
| var project: Project | ||
|
|
||
| @Relationship | ||
| var secret: Secret |
There was a problem hiding this comment.
링크 자체가 직접 삭제될 때의 동작이 정의되어 있지 않고, 양쪽이 non-optional이라 단독 삭제 시 무결성이 깨질 수 있습니다. Project/Secret 쪽 cascade에 의해서만 삭제되도록 옵셔널 처리 강화가 필요해보입니다.
There was a problem hiding this comment.
Project/Secret 쪽 cascade에 의해서만 삭제되는 것이 아닌 Project에서 Secret을 제거하는 경우도 고려해서 설계하였습니다! 이 동작은 Secret 삭제가 아니라 Project-Secret 연결 해제로 정의했는데, 이때 실제로 삭제되는 것은 SecretProjectLink(Project, Secret) row뿐이고, Project와 Secret 자체는 유지됩니다. Secret이 다른 Project에도 연결되어 있다면 그 연결도 그대로 유지됩니다. 이 경우 link row 자체가 삭제되기 때문에 project나 secret을 nil로 만들 필요가 없습니다. 오히려 optional로 두면 한쪽 endpoint가 없는 invalid link row가 저장될 수 있어, join row의 양 끝점은 non-optional로 유지하는 것이 맞다고 봤습니다.
또한 Project 또는 Secret이 삭제되는 경우에도 cascade 대상은 반대편 모델이 아니라 연결된 SecretProjectLink row로 한정하려고 합니다.
There was a problem hiding this comment.
상세한 설계 의도 설명 감사합니다 🙏
Project 또는 Secret이 삭제될 때 연결된 SecretProjectLink row가 같이 정리되도록 양쪽 모두에 deleteRule: .cascade로 설정되어야할 것 같네요 😸
| secret: Secret | ||
| ) { | ||
| self.id = id | ||
| self.secretKey = secret.secretId.uuidString |
There was a problem hiding this comment.
Secret과 1:1 관계이므로 secret 관계만으로도 무결성 확보가 가능하고 secretKey는 파생값으로 보이는데, 현재 코드 기준으로는 secretKey가 초기화 시점에만 동기화되고 이후 secret.secretId가 변경돼도 자동 갱신되지 않는데 이렇게 되면 단방향 일관성만 보장됩니다.
|
|
||
| extension SwiftDataModel { | ||
| @Model final class Secret { | ||
| @Attribute(.unique) var secretId: UUID |
There was a problem hiding this comment.
secretId만 id가 아닌 네이밍으로 지은 이유가 있나요?
| @Attribute(.unique) var id: UUID | ||
| var fileName: String | ||
| var filePath: String | ||
| var backupScope: String |
There was a problem hiding this comment.
이렇게 도메인이 한정적인 필드의 경우는 enum + String 저장 형태로 바꾸는 것이 더 안전해보입니다. RawRepresentable 참고
There was a problem hiding this comment.
extension SwiftDataModel {
enum BackupScope: String, Codable, CaseIterable, Sendable {
case optionA
case optionB
case optionC
var displayName: String {
switch self {
case .optionA: "옵션 A"
case .optionB: "옵션 B"
case .optionC: "옵션 C"
}
}
}
}| enum SwiftDataModel { } | ||
|
|
||
| extension Schema { | ||
| private static let actualVersion: Schema.Version = Version(1, 0, 0) |
There was a problem hiding this comment.
actual의 의미가 좀 모호한 것 같아요. currentVersion 또는 schemaVersion 같은 네이밍이 좋을 것 같아요.(이건 취향차이긴 함)
| extension Schema { | ||
| private static let actualVersion: Schema.Version = Version(1, 0, 0) | ||
|
|
||
| static var appSchema: Schema { |
There was a problem hiding this comment.
appSchema는 호출 시마다 Schema를 생성하는 것 같은데 캐싱하면 어떨까요?
…vaultProject/Devault-macOS into feature/#1/SwiftData_Modeling
✨ What’s this PR?
📌 관련 이슈 (Related Issue)
🧶 주요 변경 내용 (Summary)
📸 스크린샷 (Optional)
🧪 테스트 / 검증 내역
💬 기타 공유 사항
🙇🏻♀️ 리뷰 가이드 (선택)
Summary by CodeRabbit