Using the AWS Persistence Framework for Core Data

This article discusses a new beta framework to link Apple's Core Data to Amazon DynamoDB.


Submitted By: BobK@AWS
AWS Products Used: AWS SDK for iOS, Amazon DynamoDB
Created On: May 23, 2012


Version 2 of the AWS Mobile SDK
  • This article and sample apply to Version 1 of the AWS Mobile SDK. If you are building new apps, we recommend you use Version 2. For details, please visit the AWS Mobile SDK page.
  • This content is being maintained for historical reference.

This article discusses a new framework that works with the AWS SDK for iOS to allow apps to use Amazon DynamoDB as a persistent back-end for the Apple Core Data Framework. The article assumes familiarity with both technologies. If you haven't done so already, please review the User Preferences sample app to familiarize yourself with using Amazon DynamoDB with the AWS SDK for iOS. You should also review Apples's Core Data Introduction.

The Apple Core Data Framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence. You can read more about Core Data in the Apple documentation here

Amazon DynamoDB is a fast, highly scalable, highly available, cost-effective, non-relational database service. Amazon DynamoDB removes traditional scalability limitations on data storage while maintaining low latency and predictable performance.

Overview

This sample app is based on Apple's example iOS Core Data app, Locations. Our version adds another entity to capture checkins at a particular location and adds a relationship between the two entities. Finally, we use the AWS Persistence Framework for Core Data to allow our app to save the entities to Amazon DynamoDB.

Entity and Relationship Model

Core Data - Model
Where the Locations example from Apple has only a single entity, our version expands the model to have two entities, Location and Checkin, with a 1-to-many relationship between them.
Note: The AWS Persistence Framework for Core Data has been tested with 1-to-1 and 1-to-many relationships.

Core Data - Location
Core Data - Checkin
For each object, we defined the property types to correspond to an appropriate Core Data type.
Note: The AWS Persistence Framework for Core Data supports String, Numeric (Integer 16/32/64, Decimal, Double, and Float), Date, and Boolean types. For properties that are hash keys, only String and Numeric types are supported. Primitives are not supported as entity properties.

Configuring the AWS Persistence Framework for Core Data

Given this entity and relationship model, you need to configure the app to use the AWS Persistence Framework for Core Data.

The AppDelegate also implements the AWSPersistenceDynamoDBIncrementalStoreDelegate protocol, which requires you to implement a single selector:

- (AmazonCredentials *)credentials
{
    return [AmazonClientManager credentials];
}   

Note: This selector is called every time the framework requires a connection to Amazon DynamoDB; it should return valid credentials. The supplied sample uses TVM to gather these credentials.

The next step is to register that you want to use Amazon DynamoDB as your back-end store. This happens in your AppDelegate as part of the persistentStoreCoordinator selector.

[NSPersistentStoreCoordinator registerStoreClass:[AWSPersistenceDynamoDBIncrementalStore class] forStoreType:AWSPersistenceDynamoDBIncrementalStoreType];
    
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];    

Next, you need to create a number of mappings to allow the framework to understand how to handle your managed objects. These include:

  • Hash keys for each table in Amazon DynamoDB that is being used to represent an entity.
    NSDictionary *hashKeys = [NSDictionary dictionaryWithObjectsAndKeys:
                              @"locationId", @"Location", 
                              @"checkinId", @"Checkin", nil];
  • Attribute to use in each table to represent the version of the object being stored.
    NSDictionary *versions = [NSDictionary dictionaryWithObjectsAndKeys:
                              @"version", @"Location", 
                              @"version", @"Checkin", nil];
  • (Optionally) Table name to use for entities. By default, the entity name is used.
    NSDictionary *tableMapper = [NSDictionary dictionaryWithObjectsAndKeys:
                                 @"AWS-Locations", @"Location",
                                 @"AWS-Checkins", @"Checkin", nil];

With the mappings created, you next create a larger options dictionary and include the delegate for your object manager, which is set to be the AppDelegate itself.

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                         hashKeys, AWSPersistenceDynamoDBHashKey, 
                         versions, AWSPersistenceDynamoDBVersionKey,
                         self, AWSPersistenceDynamoDBDelegate,
                         tableMapper, AWSPersistenceDynamoDBTableMapper, nil];    

Finally, you pass the options dictionary to the persistence store coordinator and check for errors.

if(![persistentStoreCoordinator addPersistentStoreWithType:AWSPersistenceDynamoDBIncrementalStoreType 
                                             configuration:nil 
                                                       URL:nil 
                                                   options:options 
                                                     error:&error])
{
    // Handle the error.
    NSLog(@"error: %@", error);
}    

Fetching Managed Objects

When fetching objects in Core Data, you create an instance of the NSFetchRequest class and specify the type of entity you are fetching:

NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Location" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];    

While it is not shown in our sample app, you can also specify an NSPredicate to your NSFetchRequest to restrict the entities returned. In the initial beta release of the AWS Persistence Framework for Core Data, NSPredicate is supported only for hash key properties.
Note: Fetching without a predicate will execute as many scan operations as necessary to load the entire contents of the underlying Amazon DynamoDB table.

Once you've created your fetch request, you ask your managed object context to run it and return the results. You can then work with the returned array as you normally would.

NSArray *fetchedResults = [managedObjectContext executeFetchRequest:request error:&error];
NSMutableArray *mutableFetchResults = [fetchedResults mutableCopy];

Creating/Saving/Deleting Managed Objects

When creating new objects in Core Data, you must request they be created from the managed object context. After the object has been created, you can interact with it normally, setting properties on the object.

Checkin *checkin = (Checkin *)[NSEntityDescription insertNewObjectForEntityForName:@"Checkin" inManagedObjectContext:managedObjectContext];
checkin.checkinId = [Utilities getUUID];
checkin.checkinTime = [NSDate date];
checkin.comment = @"First Checkin";    

Making changes to managed objects does not cause them to be persisted until the application makes a call to explicitly save the changes.
Note: This saves any and all changes to managed objects created or modified during this session or since the previous save.

NSError *error;
if (![managedObjectContext save:&error])
{
    // Handle the error.
    NSLog(@"error: %@", error);
}    

The app can also choose to undo changes.
Note: It is possible to undo persisted changes made during the current session, but you'll need to save the undo.

[[[self managedObjectContext] undoManager] undo];    

Deleting objects also requires notifying the managed object context. You cannot remove objects from the back-end store until you've saved the changes.

[managedObjectContext deleteObject:locToDelete];    

Conflict Management

When using the AWS Persistence Framework for Core Data on multiple threads or devices, you may encounter a situation where you need to resolve inconsistencies between data on Amazon DynamoDB tables and your local copy in memory. When the conflicts are detected, NSManagedObjectContext returns an NSError object and your change will not be persisted. You can change this behavior by setting a merge policy to your NSManagedObjectContext:

managedObjectContext.mergePolicy = [[[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyStoreTrumpMergePolicyType] autorelease];

There are five NSMergePolicyTypes available: NSErrorMergePolicyType (default), NSMergeByPropertyStoreTrumpMergePolicyType, NSMergeByPropertyObjectTrumpMergePolicyType, NSOverwriteMergePolicyType, and NSRollbackMergePolicyType. The AWS Persistence Framework for Core Data can handle all five merge policy types. For more information about NSMergePolicy, please read NSMergePolicy Class Reference.

Because NSManagedObject instances returned by Core Data are lazily loaded, it is possible that when you access an attribute of an object, the actual row in the Amazon DynamoDB table has been already deleted. In this situation, the AWS Persistence Framework for Core Data will return an empty NSManagedObject (the accessor will return nil) and sends out AWSPersistenceDynamoDBObjectDeletedNotification. You can register to observe AWSPersistenceDynamoDBObjectDeletedNotification by calling the following method:

[[NSNotificationCenter defaultCenter] addObserver:self
	selector:@selector(mergeDeletedObject:)
	name:AWSPersistenceDynamoDBObjectDeletedNotification
	object:nil];

When you receive the notification, you should remove any invalid objects from local cache and refresh the NSManagedObject instance as necessary. The notification object has a userInfo dictionary, which you can use to identify the deleted object, containing:

  • Hash key of the deleted object with AWSPersistenceDynamoDBObjectDeletedNotificationHashKey key
  • Entity name of the deleted object with AWSPersistenceDynamoDBObjectDeletedNotificationEntityName key
  • NSManagedObjectID of the deleted object with AWSPersistenceDynamoDBObjectDeletedNotificationObjectID key

The following line shows how to refresh an NSManagedObject. Please note that refreshing an NSManagedObject will send a getItem request to the Amazon DynamoDB.

[managedObjectContext refreshObject:managedObject mergeChanges:YES];

Under heavily concurrent environments, periodically calling the refreshObject method may reduce inconsistent states. Also, calling the refreshObject method before saving the change will minimize inconsistent states to be persisted to the Amazon DynamoDB tables.

Limitations

Although the AWS Persistence Framework for Core Data was developed to meet the widest use case possible, it does have certain limitations.

  • iOS versions prior to 5.0 are not supported by the AWS Persistence Framework for Core Data.
  • You cannot modify the hash key attribute once the object has been persisted to the Amazon DynamoDB table. Please delete the object and insert a new object instead of changing the hash key. When hash key attributes are modified, the save operation will fail and return an NSError object.
  • Primitives are not supported as properties for entities. When generating your NSManagedObject subclasses, make sure this option is not enabled.
  • For properties that are mapped to hash keys for the entities, only string and numeric (Integer 16/32/64, Decimal, Double, and Float) types are supported.
  • For all other properties, it is possible to use Date and Boolean types as well as the already mentioned string and numeric types.
  • Only 1-to-1 and 1-to-many relationships between entities are supported.
  • The framework handles creating, modifying, and deleting rows in the underlying Amazon DynamoDB tables, but it does not manage the tables themselves. You must create tables using primitives provided by the AWS SDK for iOS or via the AWS Management Console.
  • When fetching objects without a predicate, the framework will potentially do multiple scan operations against Amazon DynamoDB, which will use large amounts of your provisioned read throughput. Using a predicate when fetching uses a get request against Amazon DynamoDB, which uses only the throughput necessary to load the single object.
  • With the current version of the framework, NSPredicates are limited to only the properties that are mapped to hash keys.
  • Save operations are not guaranteed to be transactional. You should check the errors that Core Data returns and formulate your handling strategies assuming partial data might have been persisted to DynamoDB tables.
  • When doing fetches of all objects in a table, the AWS Persistence Framework for Core Data cannot guarantee that recent objects inserts and deletes will be reflected due to inconsistent read status.
  • The Persistence Framework does not support sorting because the Amazon DynamoDB scan operation does not sort the result. However, Apple's NSManagementObjectContext Class Reference states that "if you fetch some objects, work with them, and then execute a new fetch that includes a superset of those objects, you do not get new instances or update data for the existing objects-you get the existing objects with their current in-memory state." In this situation, Core Data may sort the in-memory objects based on the sort descriptors provided. However, we don't recommend relying on this behavior; instead, you should sort the results in memory when the objects need to be sorted.
  • With the current version of the framework, NSPredicates are limited to only the properties that are mapped to hash keys. Also, when using the framework, your query can specify only one hashKey and only one possible value for that hashKey. Some examples are shown below:
    "hashKey1=value1" supported, one hashKey and one value
    "hashKey1 = value1 OR hashkey1 = value2" not supported, one haskKey, but two values
    "hashKey1 IN (value1, value2)" not supported, one haskKey, but two values

Conclusion and Additional Resources

The code in this article demonstrates how to use the AWS Persistence Framework for Core Data to create a simplified object model backed by a scalable cloud database. We are offering the AWS Persistence Framework for Core Data as beta software that you can use in addition to the AWS SDK for iOS. You can download the SDK and review the documentation here.

For more information about using AWS credentials with mobile apps, see the following article:

Want to learn more?

To learn about mobile development best practices, follow our AWS Mobile Development Blog. You can also ask questions or post comments in the Mobile Development Forum about this or any other topic related to mobile development with AWS.