Skip to content

Commit e1b9e6a

Browse files
authored
[#344] Firestore의 undo 작업에 대해 DB I/O 빈도수를 개선한다 (#346)
* refactor: 푸시알람 Task 중 임시 문서가 생성되지 않도록 개선 * style: Receipt 접미사 대신 Dispatches 접미사로 수정 * style: 주석 추가 * refactor: 불필요한 payload 및 필드 제거 * style: 변수명 receipt -> dispatch 변경 * refactor: Firestore 경로 상수화 * refactor: 동일 로직 헬퍼 메서드로 빼기 * refactor: 에러 정규화 일원화 * refactor: 날짜 관련 함수 모듈화 * refactor: 카테고리 자동 수정 시 임시 Firestore문서를 만들지 않도록 개선 * refactor: 웹페이지 제거의 undo 시 별도의 Task 문서를 생성하지 않도록 개선 * refactor: Todo 제거 undo 시 별도의 Task 문서를 생성하지 않도록 개선 * refactor: 푸시알람 리스트 제거 undo 시 별도의 Task 문서를 생성하지 않도록 개선 * chore: 복합 인덱스 json화 * fix: 인덱스 순서가 쿼리와 다른 부분 해결
1 parent f5d1a8f commit e1b9e6a

File tree

24 files changed

+1270
-576
lines changed

24 files changed

+1270
-576
lines changed

DevLog/Data/DTO/TodoDTO.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct TodoRequest: Encodable {
1212
let isPinned: Bool
1313
let isCompleted: Bool
1414
let isChecked: Bool
15+
let isDeleted: Bool
1516
let number: Int?
1617
let title: String
1718
let content: String

DevLog/Data/DTO/WebPageDTO.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct WebPageRequest: Encodable {
1212
let url: String
1313
let displayURL: String
1414
let imageURL: String
15+
let isDeleted: Bool
1516
}
1617

1718
struct WebPageResponse {

DevLog/Data/Mapper/TodoMapping.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extension TodoRequest {
1212
isPinned: entity.isPinned,
1313
isCompleted: entity.isCompleted,
1414
isChecked: entity.isChecked,
15+
isDeleted: false,
1516
number: entity.number,
1617
title: entity.title,
1718
content: entity.content,

DevLog/Data/Repository/WebPageRepositoryImpl.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ final class WebPageRepositoryImpl: WebPageRepository {
4848
title: metadata.title,
4949
url: urlString,
5050
displayURL: metadata.displayURL,
51-
imageURL: metadata.imageURL
51+
imageURL: metadata.imageURL,
52+
isDeleted: false
5253
)
5354
try await webPageService.upsertWebPage(request)
5455
}
@@ -101,7 +102,8 @@ private extension WebPageRepositoryImpl {
101102
title: metadata.title,
102103
url: response.url,
103104
displayURL: metadata.displayURL,
104-
imageURL: metadata.imageURL
105+
imageURL: metadata.imageURL,
106+
isDeleted: false
105107
)
106108
try await webPageService.upsertWebPage(request)
107109

DevLog/Infra/Service/PushNotificationService.swift

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ final class PushNotificationService {
128128
let items = snapshot.documents.compactMap { makeResponse(from: $0) }
129129

130130
let nextCursor: PushNotificationCursorDTO? = snapshot.documents.last.map { document in
131-
guard let receivedAt = document.data()[Key.receivedAt.rawValue] as? Timestamp else {
131+
guard let receivedAt = document.data()[PushNotificationFieldKey.receivedAt.rawValue] as? Timestamp
132+
else {
132133
return nil
133134
}
134135

@@ -184,6 +185,7 @@ final class PushNotificationService {
184185
let subject = PassthroughSubject<Int, Error>()
185186
let listener = store.collection(FirestorePath.notifications(uid))
186187
.whereField("isRead", isEqualTo: false)
188+
.whereField(PushNotificationFieldKey.isDeleted.rawValue, isEqualTo: false)
187189
.addSnapshotListener { snapshot, error in
188190
if let error {
189191
subject.send(completion: .failure(error))
@@ -192,7 +194,7 @@ final class PushNotificationService {
192194

193195
guard let snapshot else { return }
194196
let unreadPushCount = snapshot.documents.filter { document in
195-
!(document.data()[Key.deletingAt.rawValue] is Timestamp)
197+
!(document.data()[PushNotificationFieldKey.deletingAt.rawValue] is Timestamp)
196198
}.count
197199
subject.send(unreadPushCount)
198200
}
@@ -238,7 +240,10 @@ final class PushNotificationService {
238240
}
239241

240242
let collection = store.collection(FirestorePath.notifications(uid))
241-
let snapshot = try await collection.whereField("todoId", isEqualTo: todoId).getDocuments()
243+
let snapshot = try await collection
244+
.whereField("todoId", isEqualTo: todoId)
245+
.whereField(PushNotificationFieldKey.isDeleted.rawValue, isEqualTo: false)
246+
.getDocuments()
242247

243248
guard let document = snapshot.documents.first else {
244249
logger.error("Notification not found for todoId: \(todoId)")
@@ -265,6 +270,7 @@ private extension PushNotificationService {
265270
query: PushNotificationQuery
266271
) -> Query {
267272
var firestoreQuery: Query = store.collection(FirestorePath.notifications(uid))
273+
.whereField(PushNotificationFieldKey.isDeleted.rawValue, isEqualTo: false)
268274

269275
if let thresholdDate = query.timeFilter.thresholdDate {
270276
firestoreQuery = firestoreQuery.whereField(
@@ -286,7 +292,7 @@ private extension PushNotificationService {
286292
func makeNextCursor(from document: QueryDocumentSnapshot?) -> PushNotificationCursorDTO? {
287293
guard
288294
let document,
289-
let receivedAt = document.data()[Key.receivedAt.rawValue] as? Timestamp else {
295+
let receivedAt = document.data()[PushNotificationFieldKey.receivedAt.rawValue] as? Timestamp else {
290296
return nil
291297
}
292298

@@ -298,16 +304,17 @@ private extension PushNotificationService {
298304

299305
func makeResponse(from snapshot: QueryDocumentSnapshot) -> PushNotificationResponse? {
300306
let data = snapshot.data()
301-
if data[Key.deletingAt.rawValue] is Timestamp {
307+
if data[PushNotificationFieldKey.deletingAt.rawValue] is Timestamp ||
308+
(data[PushNotificationFieldKey.isDeleted.rawValue] as? Bool) == true {
302309
return nil
303310
}
304311
guard
305-
let title = data[Key.title.rawValue] as? String,
306-
let body = data[Key.body.rawValue] as? String,
307-
let receivedAt = data[Key.receivedAt.rawValue] as? Timestamp,
308-
let isRead = data[Key.isRead.rawValue] as? Bool,
309-
let todoId = data[Key.todoId.rawValue] as? String,
310-
let todoCategory = data[Key.todoCategory.rawValue] as? String else {
312+
let title = data[PushNotificationFieldKey.title.rawValue] as? String,
313+
let body = data[PushNotificationFieldKey.body.rawValue] as? String,
314+
let receivedAt = data[PushNotificationFieldKey.receivedAt.rawValue] as? Timestamp,
315+
let isRead = data[PushNotificationFieldKey.isRead.rawValue] as? Bool,
316+
let todoId = data[PushNotificationFieldKey.todoId.rawValue] as? String,
317+
let todoCategory = data[PushNotificationFieldKey.todoCategory.rawValue] as? String else {
311318
return nil
312319
}
313320

@@ -322,13 +329,14 @@ private extension PushNotificationService {
322329
)
323330
}
324331

325-
enum Key: String {
332+
enum PushNotificationFieldKey: String {
326333
case title
327334
case body
328335
case receivedAt
329336
case isRead
330337
case todoId
331338
case todoCategory
332-
case deletingAt // 삭제 요청은 되었지만, 5초 유예 후 최종 삭제되기 전 상태
339+
case deletingAt // 삭제 요청으로 앱의 로컬 데이터에서 deletion이 된 상태
340+
case isDeleted // 삭제 요청으로 서버에서 soft deletion이 된 상태
333341
}
334342
}

DevLog/Infra/Service/TodoService.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,12 @@ final class TodoService {
227227
logger.info("Fetching todo")
228228

229229
do {
230-
let docRef = store.document(FirestorePath.todo(uid, todoId: todoId))
231-
let snapshot = try await docRef.getDocument()
232-
guard snapshot.exists, let todo = makeResponse(from: snapshot) else {
230+
let snapshot = try await store.collection(FirestorePath.todos(uid))
231+
.whereField(FieldPath.documentID(), isEqualTo: todoId)
232+
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
233+
.limit(to: 1)
234+
.getDocuments()
235+
guard let document = snapshot.documents.first, let todo = makeResponse(from: document) else {
233236
throw FirestoreError.dataNotFound("Todo")
234237
}
235238

@@ -253,6 +256,7 @@ final class TodoService {
253256
group.addTask {
254257
let snapshot = try await collection
255258
.whereField(TodoFieldKey.number.rawValue, in: chunk)
259+
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
256260
.getDocuments()
257261
return snapshot.documents
258262
}
@@ -269,6 +273,7 @@ final class TodoService {
269273
let data = document.data()
270274
guard
271275
!(data[TodoFieldKey.deletingAt.rawValue] is Timestamp),
276+
(data[TodoFieldKey.isDeleted.rawValue] as? Bool) != true,
272277
let response = makeResponse(from: document)
273278
else {
274279
return
@@ -355,6 +360,7 @@ private extension TodoService {
355360

356361
func makeQuery(uid: String, query: TodoQuery) -> Query {
357362
let collection = store.collection(FirestorePath.todos(uid))
363+
.whereField(TodoFieldKey.isDeleted.rawValue, isEqualTo: false)
358364

359365
switch query.sortTarget {
360366
case .dueDate:
@@ -426,7 +432,8 @@ private extension TodoService {
426432
}
427433

428434
func makeResponse(from snapshot: QueryDocumentSnapshot) -> TodoResponse? {
429-
if snapshot.data()[TodoFieldKey.deletingAt.rawValue] is Timestamp {
435+
if snapshot.data()[TodoFieldKey.deletingAt.rawValue] is Timestamp ||
436+
(snapshot.data()[TodoFieldKey.isDeleted.rawValue] as? Bool) == true {
430437
return nil
431438
}
432439
return makeResponse(documentID: snapshot.documentID, data: snapshot.data())
@@ -440,6 +447,10 @@ private extension TodoService {
440447
}
441448

442449
func makeResponse(documentID: String, data: [String: Any]) -> TodoResponse? {
450+
if data[TodoFieldKey.deletingAt.rawValue] is Timestamp ||
451+
(data[TodoFieldKey.isDeleted.rawValue] as? Bool) == true {
452+
return nil
453+
}
443454
guard
444455
let isPinned = data[TodoFieldKey.isPinned.rawValue] as? Bool,
445456
let isCompleted = data[TodoFieldKey.isCompleted.rawValue] as? Bool,
@@ -487,7 +498,8 @@ private extension TodoService {
487498
case dueDate
488499
case tags
489500
case category
490-
case deletingAt // 삭제 요청은 되었지만, 5초 유예 후 최종 삭제되기 전 상태
501+
case deletingAt // 삭제 요청으로 앱의 로컬 데이터에서 deletion이 된 상태
502+
case isDeleted // 삭제 요청으로 서버에서 soft deletion이 된 상태
491503
}
492504

493505
enum CounterFieldKey: String {

DevLog/Infra/Service/WebPageService.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ final class WebPageService {
3131

3232
do {
3333
let collectionRef = store.collection(FirestorePath.webPages(uid))
34+
.whereField(WebPageFieldKey.isDeleted.rawValue, isEqualTo: false)
3435
let snapshot = try await collectionRef.getDocuments()
3536
let items: [WebPageResponse] = snapshot.documents.compactMap { makeResponse(from: $0) }
3637

@@ -128,6 +129,7 @@ private extension WebPageService {
128129
return nil
129130
}
130131
guard
132+
(data[WebPageFieldKey.isDeleted.rawValue] as? Bool) != true,
131133
let title = data[WebPageFieldKey.title.rawValue] as? String,
132134
let url = data[WebPageFieldKey.url.rawValue] as? String,
133135
let displayURL = data[WebPageFieldKey.displayURL.rawValue] as? String,
@@ -149,6 +151,7 @@ private extension WebPageService {
149151
case url
150152
case displayURL
151153
case imageURL
152-
case deletingAt // 삭제 요청은 되었지만, 5초 유예 후 최종 삭제되기 전 상태
154+
case deletingAt // 삭제 요청으로 앱의 로컬 데이터에서 deletion이 된 상태
155+
case isDeleted // 삭제 요청으로 서버에서 soft deletion이 된 상태
153156
}
154157
}

Firebase/firebase.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
]
1616
}
1717
],
18+
"firestore": {
19+
"indexes": "firestore.index.json"
20+
},
1821
"emulators": {
1922
"functions": {
2023
"port": 5001

0 commit comments

Comments
 (0)