Lightweight Persistence layer with Realm (RealmSwift Part 5)

https://realm.io/docs/swift/latest/

In this blog we will look into the lightweight persistence layer with realm and this is not the final and can improve. This is the persistent layer I recently followed in an application. Download the complete project with title “DB_Layer_Architecture_Realm”. In this project I tried to create a loosely coupled persistence layer using realm.

As shown below we created a DataManager protocol that has a basic interface for CRUD operation task and can conform by any persistent framework like CoreData / Realm . This protocol can also have saveInBackground methods which can perform CRUD operation on background thread and we can provide common interface for batches insertion as well. There is one thing to note

  1. CRUD tasks perform operation on persistence specific object since we are creating generic protocol we need to program to an interface instead of concrete implementation that’s why we created a Storable protocol and persistence framework object should implement it as shown below Realm Object implemented it

To sort the fetch result common interface is created for this purpose since different persistence framework provides different interface for sorting

As shown in Figure 1 we created a class RealmDataManager. It has a reference of realm database instance which will be injected by the client. For Core Data the class should be CoreDataManager that will have a reference to Core Data stack.

Figure 1

As shown in Figure 2 we created RealmDataManager extension that is conforming to DataManager protocol and implemented Realm specific CRUD operation. If you don’t understand this code please look this blog. This class responsibilities include

  1. Threading logic specific to realm
  2. Working on Realm specific object
  3. Write Transaction logic specific to realm
  4. Batched Logic specific to Realm
Figure 2

As discussed in Realm Custom Configuration part we can configure realm with different configuration. So RealmProvider acts as a builder to build a realm instance required by RealmDataManager class

Figure 3

As shown in Figure 4.1 , 4.2 and 4.3 we created three models for demo purpose. These models are specific to realm which has persistence and many other features under the hood.

Figure 4.1
Figure 4.2
Figure 4.3

If you used MVVM architecture, you probably use these models in View Model layer to drive the UI or Interactor and Presenter in VIPER architecture. Using Database specific model in your business layer has some pros and cons

Pros

  1. Fast access no explicit mapping required
  2. Access to persistence specific model features which includes
  3. Undo and redo (Core data )
  4. Access to Live Results (Realm)
  5. Built-in object graph features

Cons

  1. Business layer tightly coupled with your persistence specific model
  2. Nearly impossible to change the persistence framework
  3. Added additional responsibility to business layer to take care of threading in models
  4. Faulted data crashes (Core Data)
  5. Business layer dealing with heavy models
  6. Business layer is doing data layer work
  7. Heavy Models (Models only be Class)

As shown in Figure 5.1 to 5.3 we created simple Swift plain object replica of Persistence specific models. They’re simple objects with little to no logic in them. They contain all the data needed by the view model to drive the UI and they’re not tied to any single context or thread; they’re simple and clear to work with. There is one thing to note

  1. If you want PassportDTO to have a reference of UserDTO same as in Realm specific model you have to use class instead of struct which should be weak. In most of the case you don’t need it since persistence framework requires this for the consistency of the object graph

Now ViewModel business logics will based on these models so in future if you want to switch new persistence framework application approx 70% code will not change .

In one of the application we used Core Data that directly used by ViewModel to drive the UI and we have some logics where we have to delete the objects after getting fresh data from server and we have lots of random crashes since objects that was used by Viewmodel became faulted and by doing this approach we we reduces random crashes which increases our crash free users report on fabric.

Figure 5.1
Figure 5.2
Figure 5.3

Above strategy has some pros and cons as well.

Pros

  1. Business layer is working on Lightweight model
  2. You can use use Struct if you don’t need inverse relationship
  3. Value type also have its advantages
  4. Lightweight model driving the UI
  5. No faulted data
  6. No threading Issues in business layer
  7. Fewer bugs
  8. This scales well for large or multiple teams as developers can specialize in areas where they work best, in the UI or in the back end
  9. No more complex models

Cons

  1. Manually manage object graph
  2. Don’t have Persistence specific features
  3. Duplication of models
  4. Slightly impact on performance due to translation between mnodels

Since persistence framework have to work on its persistence specific model we need to map from plain swift object to persistence specific model and vice versa as shown in Figure 6.1 to 6.3 we created a Realm mapper from plain swift object to Realm object and vice versa which slightly have performance impact. If you want to change new persistence framework you have to do completely new mapping for all your models.

Figure 6.1
Figure 6.2
Figure 6.3

As shown in Figure 7 I created a MappableProtocol to provide a common interface for mapping between plain swift object to persistence specific object and vice versa and it is loosely coupled to any persistence specific framework logic

Figure 7

Now we are done with the data layer we need to create some central point to query to data layer without dependent on any persistence framework since we program to an interface if we injected CoreDataManger it will call it’s CRUD operation and if we inject MockDataManager for testing it will call it’s CRUD operation. This class has no effect if we change the persistence framework

Figure 8

As shown in Figure 9 we created a UserRepository subclass of BaseRepository . This class responsibilities includes

  1. Mapping from plain swift object to persistent specific object and vise versa. Since we created a common interface irrespective of persistence specific logic we don’t need to change the implementation of these methods
  2. Since we need to do some concrete implementation we have to provide Persistence specific model for the generic placeholder type when subclassing from Base Repository.
  3. We did error handling in User Repository since there might be some logic that depends on database error
Figure 9

As shown in Figure 10 we are consuming User Repository by injecting RealmDataManager if not provided and dbManager type is protocol to follow dependency Inversion principle and it will make unit testing easy.

Figure 10

Unit Testing

As shown in Figure 11 we created in memory realm for testing purpose and injected in User repository . This is very simple example and if there is no need for mocking you don’t need to do. If you know more about unit testing please see this blog

Figure 11

Summary

Our objective was to create a very loose coupled Data persistence layer using realm and which requires less code changes when your persistence framework change .

Useful links

https://academy.realm.io/posts/isolating-your-data-layer/

Senior iOS Engineer | HungerStation | Delivery Hero