Monday, August 24, 2015

Rethinking CloudKit

Ok, when I'm wrong, I'm wrong and I will admit it.  My previous post on using CloudKit in my new app, was a bit naive so I am rethinking how I intend to use it.

I actually had it working with my top level object. But things went a bit awry when I started extending the design.

As described in my previous post the design supported a local store managed by a LocalDataManager class.  All view controllers used CRUD operations through the LocalDataManager.  The LocalDataManager, upon receiving a CRUD request, would pass the object to a RemoteDataManager class which would transform and update it in CloudKit.

The RemoteDataManager would then notify the LocalDataManager with the updated record after successfully making the change to the CloudKit store.

The LocalDataManager would then update it's local store appropriately and notify any views about the changes that just occurred.

Where this design has difficulty and needs some more thought is when there are related objects.

For example say we have a top level object called Category.  A Category can have many notes. Additionally when viewing a list of categories you want to know how many notes each one has.

Since CloudKit isn't a truly relational database you don't have access to the notes of a Category when you first load it.  In fact, using the recommended CloudKit design pattern, Category objects don't even know they have notes.  Instead, a Note object has a "back reference" to it's owning parent.

What this means is if you wanted to know how many notes each Category had in a list of categories you would need some way of knowing the note count without out actually loading the Note objects themselves.

For this example I will assume the desired solution is to store a count on the Category itself.  Note: I am ignoring other more traditional SQL counting solutions which would also work with the CloudKit store, but might require a separate network call.  

The reason I am ignoring this is that in my app the value stored in the parent object is not a count but more of a metadata type value which describes some attribute of the collection of dependent objects (such as an average over some field's value, etc.)  I am using the count value in this example only to keep it simple.

Ok, getting back to the example. Now let's say you want to store a new note.  So in my design you would need to store the Note then update the Category so it's count of notes is correct.

This causes two store operations to CloudKit both of which may fail.  So you now have a few cases to handle.
  1. Note stores successfully, but Category does not
  2. Note and Category store successfully
  3. Neither the Note or Category store successfully.
If you look at this pragmatically, only case 1 is of concern.  If case 2 happens you are good as everything saved correctly.  If case 3 happens you are still good as your persistent stores both locally and remotely have not been polluted.

But what do you do about case 1.  This is where I am stuck and I think my design begins to have problems.  Additionally, I was doing some reading and there are some metadata fields of a CKRecord that need to be managed properly on the local device as well, so that CloudKit knows what to do with the object when an update comes in.

So my current thinking is I need to use the CloudKit database as bunch of bags. Into each bag I store the type of object that bag is for.  Nothing more.  I then use the LocalDataManager to figure out other information.

For the relationship issue, I'll have a LocalDataManager for each object type.  It will represent the entire list of those types of objects.  So any time I need to get counts or special values I'll ask the LocalDataManager for that record type.

Going back to the Category/Note parent child example I used before.  I would have two LocalDataManagers (i.e. CategoryLocalDataManager and NoteLocalDataManager).  Anytime I need to get the number of notes for a Category I would ask the NoteLocalDataManager for the count of notes related to the specific Category I was interested in.

Would I persist the count on the Category in this design?  I'm not sure.  It seems like by doing that the Category object itself is polluted.  But depending on how the local store is implemented asking the NoteLocalDataManager may be expensive.  So I'm not sure how I will tackle this issue yet, although I am leaning toward the second idea.

Actually, upon further reflection I think it will be important to store the count value on the local object at least.  That way table views will be performant.

For the second issue, the one about the metadata that needs to be stored, the obvious answer is to persist it with the local store representation of the object.  In fact Apple even provides a method (encodeSystemFieldsWithEncoder) to do exactly that.

For this one, I think my first thing to do is understand exactly what this metadata is and how it will be used by CloudKit.  From there, maybe I can get a better grasp on when it is needed.

One other concern I have is the code for this implementation is starting to get complicated.  Maybe this is a complicated problem that requires a complicated solution.  But something doesn't feel right.  When I am first working on a solution and I see it becoming complicated, my gut instinct is it will be impossible to maintain so I prefer simpler solutions.  I think that will be another area I will investigate over the next few iterations.

To sum this up, obviously this idea still is not "cooked" enough to use in production.  I'll post more information as I continue to explore this topic in practice.

No comments:

Post a Comment