-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
카테고리 CRUD를 CoreData와 연결하여 구현 #99
Changes from 27 commits
60930fa
196b2a7
945cefc
ec1a4a3
d0b950e
fb8d717
32a81e6
7f809c8
38275e1
53db6b7
bc048cc
67fd189
d7dacc8
0ad5b0f
9237ecc
0e49823
ffed81a
d6adc76
ccf78f0
8032520
d0e1c74
aa25a64
5456b0a
9478ab8
02cc76e
22cc775
a187d3c
2ee700d
d27f9fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import MHFoundation | ||
import MHDomain | ||
|
||
public struct BookCategoryDTO { | ||
let order: Int | ||
let name: String | ||
|
||
public init( | ||
order: Int, | ||
name: String | ||
) { | ||
self.order = order | ||
self.name = name | ||
} | ||
|
||
func convertToBookCategory() -> BookCategory { | ||
BookCategory( | ||
order: order, | ||
name: name | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import MHFoundation | ||
import MHCore | ||
|
||
public protocol BookCategoryStorage: Sendable { | ||
func create(with category: BookCategoryDTO) async -> Result<Void, MHDataError> | ||
func fetch() async -> Result<[BookCategoryDTO], MHDataError> | ||
func update(oldName: String, with category: BookCategoryDTO) async -> Result<Void, MHDataError> | ||
func delete(with categoryName: String) async -> Result<Void, MHDataError> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import MHFoundation | ||
import MHCore | ||
import CoreData | ||
|
||
public final class CoreDataBookCategoryStorage { | ||
private let coreDataStorage: CoreDataStorage | ||
|
||
public init(coreDataStorage: CoreDataStorage) { | ||
self.coreDataStorage = coreDataStorage | ||
} | ||
|
||
private func performDatabaseTask<T>( | ||
_ task: @escaping (NSManagedObjectContext) throws -> T | ||
) async -> Result<T, MHDataError> { | ||
let context = coreDataStorage.persistentContainer.viewContext | ||
do { | ||
return try await context.perform { | ||
do { | ||
return .success(try task(context)) | ||
} catch let error as MHDataError { | ||
MHLogger.debug("Core Data 에러: \(error.description)") | ||
throw error | ||
} catch { | ||
MHLogger.debug("알 수 없는 Core Data 에러: \(error.localizedDescription)") | ||
throw error | ||
} | ||
} | ||
} catch let error as MHDataError { | ||
return .failure(error) | ||
} catch { | ||
return .failure(MHDataError.generalFailure) | ||
} | ||
} | ||
} | ||
|
||
extension CoreDataBookCategoryStorage: BookCategoryStorage { | ||
public func create(with category: BookCategoryDTO) async -> Result<Void, MHDataError> { | ||
return await performDatabaseTask { context in | ||
guard let entity = NSEntityDescription.entity(forEntityName: "BookCategoryEntity", in: context) else { | ||
throw MHDataError.noSuchEntity(key: "BookCategoryEntity") | ||
} | ||
let bookCategory = NSManagedObject(entity: entity, insertInto: context) | ||
bookCategory.setValue(category.order, forKey: "order") | ||
bookCategory.setValue(category.name, forKey: "name") | ||
try context.save() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: CoreDataStorage를 보면 실은 saveContext라는 메서드가 있어서.. try를 쓰지 않고도 저장할 수 있긴한데�이것도 괜찮아 보이네용.. saveContext 메서드는 지울까엽?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 옷 그러네요..! |
||
} | ||
} | ||
|
||
public func fetch() async -> Result<[BookCategoryDTO], MHDataError> { | ||
return await performDatabaseTask { [weak self] context in | ||
let request = BookCategoryEntity.fetchRequest() | ||
let bookCategoryEntities = try context.fetch(request) | ||
return bookCategoryEntities.compactMap { self?.coreBookCategoryToDTO($0) } | ||
} | ||
} | ||
|
||
public func update(oldName: String, with category: BookCategoryDTO) async -> Result<Void, MHDataError> { | ||
return await performDatabaseTask { context in | ||
let request = BookCategoryEntity.fetchRequest() | ||
if let entity = try context.fetch(request).first(where: { $0.name == oldName }) { | ||
entity.setValue(category.name, forKey: "name") | ||
entity.setValue(category.order, forKey: "order") | ||
try context.save() | ||
} | ||
} | ||
} | ||
|
||
public func delete(with categoryName: String) async -> Result<Void, MHDataError> { | ||
return await performDatabaseTask { context in | ||
let request = BookCategoryEntity.fetchRequest() | ||
if let entity = try context.fetch(request).first(where: { $0.name == categoryName }) { | ||
context.delete(entity) | ||
try context.save() | ||
} | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Mapper | ||
extension CoreDataBookCategoryStorage { | ||
func coreBookCategoryToDTO(_ bookCategory: BookCategoryEntity) -> BookCategoryDTO? { | ||
guard let name = bookCategory.name else { return nil } | ||
let order = Int(bookCategory.order) | ||
|
||
return BookCategoryDTO( | ||
order: order, | ||
name: name | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,17 @@ | ||
import CoreData | ||
import MHCore | ||
|
||
class CoreDataStorage { | ||
/// Core Data 스택을 구성하는 기본 클래스이며, 다른 코어데이터 구현체의 프로퍼티로 사용됩니다. | ||
/// 또한, 테스트 환경에서 `MockCoreDataStorage`를 상속받아 확장할 수 있도록 설계되었습니다. | ||
/// | ||
/// 이 클래스를 프로토콜로 작성하지 않은 이유는, | ||
/// 테스트 코드에서도 `MemorialHouseModel`이라는 Core Data 모델을 동일하게 사용하기 위해서입니다. | ||
/// 이를 통해 테스트 환경에서도 실제 DB 모델 구조를 유지하며 간단히 확장할 수 있습니다. | ||
/// | ||
/// - 주요 특징: | ||
/// - `NSPersistentContainer`를 활용해 Core Data 스택을 구성합니다. | ||
/// - `saveContext` 메서드를 통해 변경된 컨텍스트를 저장합니다. | ||
public class CoreDataStorage: @unchecked Sendable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 public final class CoreDataStorage: Sendable {
static let modelName: String = "MemorialHouseModel"
nonisolated(unsafe) static let memorialHouseModel: NSManagedObjectModel = {
guard let modelURL = Bundle(for: CoreDataStorage.self).url(
forResource: CoreDataStorage.modelName,
withExtension: "momd"
) else {
fatalError("Error loading model from bundle")
}
return NSManagedObjectModel(contentsOf: modelURL)!
}()
let persistentContainer: NSPersistentContainer
public init() {
let container = NSPersistentContainer(
name: CoreDataStorage.modelName,
managedObjectModel: Self.memorialHouseModel
)
container.loadPersistentStores { _, error in
guard let error else { return }
MHLogger.error("\(#function): PersistentContainer 호출에 실패; \(error.localizedDescription)")
}
self.persistentContainer = container
}
...
} 요렇게 하셔도 에러 뜨시나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lazy 사용하지 않는 방식인 거죵 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 문제없이 잘 돌아가네요, 저도 lazy 방식보다 정현님 말씀대로 하는게 좋은 거 같습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 참 테스트코드에서 |
||
static let modelName: String = "MemorialHouseModel" | ||
|
||
nonisolated(unsafe) static let memorialHouseModel: NSManagedObjectModel = { | ||
|
@@ -15,7 +25,7 @@ | |
}() | ||
|
||
lazy var persistentContainer: NSPersistentContainer = { | ||
let container = NSPersistentContainer(name: CoreDataStorage.modelName) | ||
let container = NSPersistentContainer(name: CoreDataStorage.modelName, managedObjectModel: Self.memorialHouseModel) | ||
Check warning on line 28 in MemorialHouse/MHData/MHData/LocalStorage/CoreData/CoreDataStorage.swift GitHub Actions / SwiftLint
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CoreData: error: Failed to load model named "우리팀모델.xcdatamodeld" |
||
container.loadPersistentStores { _, error in | ||
guard let error else { return } | ||
MHLogger.error("\(#function): PersistentContainer 호출에 실패; \(error.localizedDescription)") | ||
|
@@ -24,7 +34,7 @@ | |
return container | ||
}() | ||
|
||
init() { } | ||
public init() { } | ||
|
||
func saveContext() async { | ||
let context = persistentContainer.viewContext | ||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코어데이터 CRUD 로직이 겹치길래 얘로 묶어줬는데, 다른 분들 코드에 적용해보면 어떨까요 ??
중복되는 코드 길이를 줄일 수 있을 거 같습니당
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋아욤!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구뜨!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ERROR를 처리할 때 Result의 failure와 throw를 나누신 의도가 궁금합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yuncheol-AHN
내부 디버깅이랑 호출하는 곳에서의 에러 처리를 동시에 해주려고 그랬습니다 !!
crud 메소드에서 발생한 throw를 해당 메소드 catch에서 잡고 디버그 로그로 출력하고 이걸 Result로 반환합니다 !