//
you're reading...

Programming

Future-Proof Your Data Persistence

SQLite or Firebase for backend?

Previously I’ve outlined various built-in options of persistence technologies available for applications in Apple’s platforms. In short you should use the technologies which comes with the platform, unless you have a really good reason to select a 3rd party solution.

Nevertheless things change. What may be the right technology today may no longer be appropriate tomorrow as your application grows and constraints changes. Consequently, as an architect you would need to engineer for solving today’s problems but design the system such that it can grow to face tomorrow’s challenges.

A standard technique to make sure that an application’s architecture can grow is encapsulation. A component of the software is separated from other components by means of interfaces. therefore if the former’s implementation need to change, the change does not impact others that uses it as long as the interface doesn’t change. Ideally the evolving component can be replaced with another without any change to other parts of the application.

In terms of data persistence technologies, the standard method of allowing implementation changes is the Data Access Object (DAO) design pattern.  The application’s data model is modeled as a set of protocols describing the nouns that the application handle. In turn the implementation for these are completely isolated from the rest of the application, allowing it to change without impacting the application as a whole.

Library Database

Implementing Data Access Object on an App

I’m going to show how you can implement the DAO pattern in an app for Apple’s operating systems – from watchOS, iOS, iPadOS, tvOS, to macOS. We’ll talk about the nuances of implementing this pattern on a Graphical User Interface (GUI) application and how it differs from a typical server-side implementation often described in other texts describing the pattern. In the later part of this post I’ve provided a working example of an iOS app implementing the design pattern that you can use, study, and extend.

The following is a class diagram how the DAO pattern can be implemented in a UIKit application. The diagram illustrates in an abstract way that how DAO classes are typically connected to the rest of the application.

Data Access Object in UIKit

As per the design pattern, there is a clear separation between interface and implementation. Furthermore clients to the persistence only talks to the interface and never exposed to the underlying implementation directly. This is called interface-based programming which is the key element which ensures that the data persistence component can have drop-in replacements. The ViewController and other user interface classes only knows about the DataAccessObject and DataObject protocols. It has no knowledge of the DAOImpl nor DataObjectImpl classes.

Between the ViewController and the Data Model Layer protocols you could have ViewModel objects.  They are loosely based from the Model-View-ViewModel (MVVM) architecture and maintains user-visible data of its corresponding view objects. Its use in the DAO pattern is totally optional — however I find ViewModel objects to be very useful in supporting UI state preservation and restoration.  Usually I make ViewModel objects to conform to NSCoding and support state preservation by just encoding the ViewModel objects into storage provided by the system. Then the reverse state restoration consists of decoding those ViewModel objects, setting them into the corresponding views, and link them back with the data persistence objects if required.  ViewModel is also a good home for formatting and transformation code which converts the internal representation of data into a form for the user. One example is formatting a Date object into its corresponding date string or vice-versa.

DAO Methods

The DAO interface contains methods to access the data. That is, “listFoo”, “insertFoo”, “retrieveFoo” and “updateFoo”. Often one DAO provides methods for one strong entity and maybe its sub-entities. However when the schema is small, the DAO interface may define access methods for all entities in the schema.

When implementing DAO for GUI applications, the data access methods should be asynchronous. That is, the user interface must not wait for data access operations to complete. This would be more apparent when the data is stored on a remote server — synchronous methods would block its callers and when called from the user interface layer it would hog the main thread causing the UI to appear frozen until the call returns. Hence making all data access methods asynchronous would abstract out the proximity of the data store as well as reducing the load on the main thread, making the UI more performant.

The following sequence diagram illustrates the difference between synchronous and asynchronous data access. When accessing data synchronously, the caller must wait for each call until it is completed. This may be fine for server-side software, but would make users unhappy if the GUI freezes every time a record needs to be updated – which in turn would be more true if record updates goes through the Internet.

Data Access Object sequence diagram

Being asynchronous, a GUI app’s DAO methods would likely take three parameters:

  • A query specification characterizing the data items to access as well as the amount.
  • The result handler to be called when the data items become available.
  • An optional completion handler which gets invoked at the end of the operation indicating success or failure.

Thus a typical DAO interface for a Swift GUI application would probably look like this:

public protocol ItemDAO {
    func listItems(
        query: DataQuery,
        resultHandler:  @escaping ([Item]) -> TransactionStatus,
        completionHandler: ((Error?) -> Void)?
    )
    func retrieveItem(
        itemID: Item.PrimaryKeyType,
        resultHandler:  @escaping(Item?) -> TransactionStatus,
        completionHandler:  ((Error?) -> Void)?
    )
    func insertItem(
        resultHandler:  @escaping(Item) -> TransactionStatus,
        completionHandler:  ((Error?) -> Void)?
    )
}

Details on the methods are as follows.

Query Specification

The query specification describes the items to retrieve from the database. This can be as simple as a primary key or as complex as as an expression resolving to a boolean value to be evaluated against every entity in the database. Furthermore this parameter can also contain — as a tuple or composite — any sorting/ordering specification limiting how many items to fetch. Paging can be done as well — as long as there is a stable order criteria — by filter expressions like “get y records starting at offset x”. 

This code snippet below shows an example of specifying such queries as a Swift structure:

struct DataQuery {
    // Evaluated on each data item, resulting `true` means include in the list.
    var filter: NSPredicate

    // Sorting critera
    var orderBy: [NSSortDescriptor]?

    // how many items to return at maximum.
    var top: Int?

    // how many items to skip from the first item, only valid when `orderBy` is present.
    var skip: Int?
}

That DataQuery structure can be used as the first parameter of a DAO method to encapsulate a query specification. It uses an NSPredicate object as the filter criteria to determine objects to select. The ordering of objects returned is defined by an array of NSSortDescriptor objects. Finally paging is done by varying the values of top and skip

Result Handler

The result handler is a callback to receive the query result. In turn the closure consumes the data retrieved and take further actions on it. Furthermore it may also modify the data items retrieved – either by changing the passed-in objects (works when the data records are expressed as Swift reference type) or returning the modifications.

The example below shows one way to code a result handler for an asynchronous DAO. Here, data are modified in-place since RecordType is a class. The closure returns a status value which tells the DAO instance whether to persist the changes back into the database or just discard them. When TransactionStatus is commit, then the DAO should persist changes to the data objects back into the database. However when TransactionStatus is rollback then any changes to the data objects are ignored.

enum TransactionStatus {
    case commit
    case rollback
}

let resultHandler = {
    (records: [RecordType]) -> TransactionStatus in
    //...
    return .commit
}

Completion Handler

Last but not least the completion handler gets called at the very end of the data access cycle. Having a completion handler is more useful for data modification operations in which the closure gets informed whether the update was successfully persisted or had failed, hence often is an optional parameter to the DAO method.  Usually the closure only takes one optional Error object — which would have a value if the operation was successful or had failed. Nevertheless having a completion handler for query operations may also be useful in cases of errors fetching the data — usually network issues when the data is located remotely.

let completionHandler = {
    (error: Error?) -> Void in
    //...
}

Sample Implementation of Data Access Object on iOS

I’ve written a sample iOS app to show how the DAO pattern can be implemented. This is a bare-bones to-do application that uses Core Data as its backing store. However the app is designed in such a way that the Core Data implementation can be replaced with just about any other persistence technology — even calling a remote backend over the network.

The app is structured where the user interface code is linked into the main application binary whereas the persistence layer is split into two frameworks. Splitting is done purely for the sake of demonstration and not a requirement of the DAO pattern. However on macOS that allows dynamically loadable frameworks, this split enables a plug-in architecture. The persistence implementation can be provided as a separate framework bundle delivered separately from the main application (i.e. as a separate download or a 3rd party implementation). Then the main application can dynamically load this framework and use it as if delivered in the original application bundle.

Data Access Object Demo App

These are the primary targets of the application:

  • DataAccessObject-Demo – the main application binary, containing the user interface code as well as the application delegate.
  • ReminderDataModel – contains just protocols defining the interface to the persistence layer.
  • CoreDataModelImp – implements the protocols in ReminderDataModel providing the actual persistence functionality.

The DAO interface in the ReminderDataModel framework defines the entire data model of the application without providing any implementation. The framework defines the DAO itself, the data object which is a reminder, as well as ancillary types as necessary. The DAO has three method declarations:

  • listAllReminderItems – retrieve all reminders.
  • retrieveReminderItem – get a reminder item based from its identifier and optionally updates it.
  • insertReminderItem – create a new “blank” reminder item and optionally modifies it.

The following is an excerpt of the ReminderDataModel framework:

public protocol ReminderDAO {
    func listAllReminderItems(
        resultHandler:  @escaping ([ReminderItem]) -> TransactionStatus,
        completionHandler: ((Error?) -> Void)?
    )
    func retrieveReminderItem(
        reminderID: UUID,
        resultHandler:  @escaping(ReminderItem?) -> TransactionStatus,
        completionHandler:  ((Error?) -> Void)?
    )
    func insertReminderItem(
        resultHandler:  @escaping(ReminderItem) -> TransactionStatus,
        completionHandler:  ((Error?) -> Void)?
    )
}

public protocol ReminderItem : class {
    var title: String? { get set }
    var completedTimestamp: Date? { get set }
    var reminderID: UUID? { get set }
}


The view controller only sees the ReminderDAO protocol which is injected by the app delegate. It uses this protocol to retrieve data, populate view model objects, as well as creating new reminder records. Similarly the reminder only knows about the ReminderItem interface, used to populate the former using data from the latter. Only the app delegate is aware of the concrete implementation of ReminderDAO.

Last but not least, the ReminderItemViewModel objects are the ones that is used to initialize the table view cells containing reminder items. Besides keeping data obtained from ReminderItem, the class also formats date values into user-visible date strings.

You can download the sources of the app from my Github account. This was developed as a Swift 5.0 app on Xcode 10.2.1.

Challenge Yourself

Now that you have a sample DAO implementation, why don’t you challenge yourself and extend this sample app to further your learning. Here are some ideas for you to get started:

  • Implement UI State Preservation and Restoration – make the view model classes confirms to NSCoding and hook it up with the state preservation/restoration API.
  • Support CloudKit — create a new data persistence layer which saves the data on CloudKit and let the user switch between the Core Data and CloudKit persistence.

Until next time. Please subscribe to my list to get the next article delivered into your mailbox.



Do you enjoy this post? Enter your e-mail address below to receive articles like this one in your mailbox.
* indicates required

Discussion

No comments yet.

Leave a Reply

Free Updates!

Learn how to grow your indie business while keeping your day job.

Categories

Archives

Keep updated!

Don't miss out on new articles!