Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. - Patterns of Enterprise Application Architecture
어플리케이션 로직과 데이터베이스 사이의 레이어를 두는 패턴이다.
애플리케이션의 나머지 부분에서 데이터 저장 로직을 분리한다.
장점
제 앱에 Realm을 도입하면서 적용해 봤습니다.
최종본은 아닌데 이 글도 초안 적어둔지 좀 되서.. 매끄럽지 못합니다.
protocol Repository {
associatedtype EntityObject: Entity
func getAll(where predicate: NSPredicate?) -> [EntityObject]
func insert(item: EntityObject) throws
func update(item: EntityObject) throws
func delete(item: EntityObject) throws
}
extension Repository {
func getAll() -> [EntityObject] {
return getAll(where: nil)
}
}
디비 쿼리를 하는 동한 수행하는 CRUD 기능을 포함하는 레포지토리 프로토콜.
public protocol Entity {
associatedtype StoreType: Storable
func toStorable() -> StoreType
}
public protocol Storable {
associatedtype EntityObject: Entity
var model: EntityObject { get }
var id: Int { get }
}
디비에 저장해야하는 모든 모델은 Entity프로토콜을 구현해야함.
실제 데이터를 디비에 저장하기 위해 Storable
프로토콜을 사용해야 함.
이 프로토콜은 모델의 데이터베이스 객체를 나타내는 클래스에 의해 구현됨. Storable 프로토콜에는 두 가지 속성이 있음.
model
저장된 객체를 실제 모델로 변환하는 역할.id
– unique identifierclass Game {
var cost: Int
var targetCount: Int
var playerCount: Int
init(cost: Int, targetCount: Int, playerCount: Int) {
self.cost = cost
self.targetCount = targetCount
self.playerCount = playerCount
}
}
extension Game: Entity {
private var storableGame: StorableGame {
let realmGame = StorableGame()
realmGame.id = realmGame.autoIncrementKey()
realmGame.cost = cost
realmGame.targetCount = targetCount
return realmGame
}
func toStorable() -> StorableGame {
return storableGame
}
}
class StorableGame: Object, Storable {
@objc dynamic var id = 0
@objc dynamic var cost: Int = 0
@objc dynamic var targetCount: Int = 0
@objc dynamic var playerCount: Int = 0
override static func primaryKey() -> String? {
return "id"
}
func autoIncrementKey() -> Int {
let realm = try! Realm()
return (realm.objects(StorableGame.self).max(ofProperty: "id") as Int? ?? 0) + 1
}
var model: GameVO {
get {
return Game(cost: cost, targetCount: targetCount, playerCount: playerCount)
}
}
}
모델 객체인 Game 은 Entity를 채택하고 구현만 하면 됨.
class AnyRepository<RepositoryObject>: Repository
where RepositoryObject: Entity,
RepositoryObject.StoreType: Object {
typealias RealmObject = RepositoryObject.StoreType
private let realm: Realm
init() {
realm = try! Realm()
}
func getAll(where predicate: NSPredicate?) -> [RepositoryObject] {
var objects = realm.objects(RealmObject.self)
if let predicate = predicate {
objects = objects.filter(predicate)
}
print(objects.compactMap { $0 })
return objects.compactMap{ ($0).model as? RepositoryObject }
}
func insert(item: RepositoryObject) throws {
try realm.write {
realm.add(item.toStorable())
}
}
func update(item: RepositoryObject) throws {
try delete(item: item)
try insert(item: item)
}
func delete(item: RepositoryObject) throws {
try realm.write {
let predicate = NSPredicate(format: "id == %@", item.toStorable().id)
if let productToDelete = realm.objects(RealmObject.self)
.filter(predicate).first {
realm.delete(productToDelete)
}
}
}
}
제네릭한 레포지토리 구현체.
새로운 모델 Entity가 생길때 편함.
repository : DAO 역할
UI - viewmodel(현재 Game) - model - storable(DTO) - repository(DAO) - DB(현재 Realm)
이런식으로 레이어가 나뉨.
예전엔 레이어가 많으면 불편하다 생각했는데 생각이 좀 바뀐것 같음.
물론 보일러 플레이트 같이 느껴질 때도 있지만.. 그건 다른 방법으로 해결 할 수 있어 보임.
1달 전에 반쯤 정리한 글을 지금 포스팅 하는데.. 참 그지같다.
글을 완결하는 습관은 들여야 할듯.
레일즈, 루비도 쓰던게 있는데..
id대신 uuid 를 사용 하면 될 것 같다.
피부에 와닿았던 디자인 패턴 중 한가지 인듯.
카카오페이에서 랜덤 사다리 정산 서비스가 이번에 나와서
랜덤빵 접어야할듯..
새 앱 들어가야지.