Transitive Closure in PostgreSQL
At Remind we operate one of the largest communication tools for education in the United States and Canada. We have...
One of those most common and important things mobile applications do today is communicate with an API. In this blog post, we’ll illustrate some of the design choices we made with our network stack, which is built on top of RestKit, by walking through how to implement a new API endpoint.
We recently added the ability to select an icon for your Remind class that will appear in different spots in the UI.
It was a fairly simple integration, so we’ll be walking through the components of this feature with the following steps:
###Core Data Setup
We use Core Data at Remind to persist the data retrieved from the API. For all of its faults (pun intended!), we rely heavily on Core Data to sync data updates across our application and provide a reliable read-only view of our application when the user is offline. The nitty-gritty of inserting, updating, and deleting entities across multiple contexts is handled under the hood by RestKit, so our application code can focus mainly on business logic if we just make the right calls into RestKit.
The first thing we have to do for this endpoint integration is prepare Core Data for our new RDAvatar
entities. This is as simple as adding an entity to our .xcdatamodel
with the appropriate properties. In this case our new entity has a remoteId
and a fileUrlString
. (We use SDWebImage to download and cache remote images)
Thanks to the magic of mogenerator, at this point we can simply define an associated RDAvatar
class with our new entity and hit build, and mogenerator will create a convenient dedicated NSManagedObject
subclass to use throughout the codebase. If you’re using Core Data for your application and haven’t heard of mogenerator, I highly suggest you look at that project.
###The Network Request
Now that we have an RDAvatar
entity in our object graph, we need to retrieve data from our API to populate them in our local database. There are many of ways to construct network requests; at Remind, we’ve gone the route of defining a class for every request. One reason I like this is that it allows us to parameterize each request in a type-safe manner instead of relying on the consumer to provide the right keys and object types to put into an NSDictionary
for parameters. Our next step, then, is to define a new RDAvatarListRequest
class that will inherit from our RDAPIRequest
base class. RDAPIRequest
serves as our bucket-o’-data object that tells the core of our network stack how to make our request.
For our new request, the only parameter is a query string that will filter the list of avatars to only those relevant to the provided search terms.
@interface RDAvatarList : RDAPIRequest
- (instancetype)initWithQuery:(NSString *)query;
@end
The implementation of this class consists of overriding a small set of methods for the basic information about the request, such as the path, method, and parameters.
@implementation RDAvatarListRequest
- (instancetype)init
{
return [self initWithQuery:nil];
}
- (instancetype)initWithQuery:(NSString *)query
{
self = [super initWithMethod:RDAPIRequestMethodGet];
if (self)
{
_query = [query copy];
}
return self;
}
- (NSString *)requestPath
{
return @"groups/avatars";
}
- (NSString *)requestParameters
{
if ([self.query length] > 0)
{
return @{ @"q" : self.query };
}
return nil;
}
@end
That’s really all there is to it. Sitting below this level of abstraction is a set of classes that attempts to insulate the rest of the code from knowing anything about RestKit.
RDAPICommunicator
constructs the correct method call into RestKit based on the parameters of the RDAPIRequest
instance, then repackages the response data in an RDAPIResponse
instance that is passed back up through the stack.RDAPIRequestDescriptorFactory
constructs and registers the appropriate RKRequestDescriptor
and RKResponseDescriptor
with RestKit for each RDAPIRequest
RDAPIDispatcher
is the public interface to the rest of the application, which consists entirely of dispatchRequest:completion:
.The details and subtleties of the wrapper described above may be the subject of a future blog post, but that is the general structure of how we’ve quarantined RestKit as much as possible.
###JSON -> RDAvatar
JSON Response:
[
{
"id" : 123,
"file_url" : "some_url.png"
},
{
"id" : 124,
"file_url" : "some_other_url.png"
},
...
]
Now that we’ve defined how to request the data, we need to then translate the API response into our RDAvatar
Core Data entities. This is a service that RestKit provides pretty powerfully, and we’ve built a small layer on top to make these incremental changes easier. Many of the RestKit tutorials I found when it was first integrated had one setup method that contained the mapping information for every request the app was going to make. (Naturally, all of these examples only contained a couple of endpoints.) With a large and diverse API, this pattern quickly breaks down. Instead, we’ve distributed the mapping information across many “mapper” classes. These classes conform to the RDAPIResponseMappingProvider
protocol, requiring that any implementers are able to construct an RKMapping
, which the RDAPIRequestDescriptorFactory
mentioned above uses when it registers response descriptors with RestKit. This avoids a monolithic registration method, facilitates code reuse when multiple endpoints return the same kind of object, and has the added benefit of lazily registering descriptors as they’re needed.
In this case, we have a very simple mapping to construct:
@interface RDAvatarMapper : NSObject<RDAPIResponseMappingProvider>
@end
@implementation RDAvatarMapper
- (RKMapping *)rkResponseMapping:(RKObjectManager *)objectManager
{
// 1
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:[RDAvatar entityName]
inManagedObjectStore:objectManager.managedObjectStore];
// 2
[mapping addAttributeMappingsFromDictionary:@{
@"id" : @"remoteId",
@"file_url" : @"fileUrlString"
}];
// 3
mapping.identificationAttributes = @[ @"remoteId" ];
return mapping;
}
@end
There are only a few things going on here:
RKEntityMapping
, which tells RestKit that this is a Core Data entity (as opposed to a regular NSObject
), and it’s for our RDAvatar
entity. (entityName
is a function generated by mogenerator)id
from the response is mapped to remoteId
on our RDAvatar
entity. Likewise, file_url
is mapped to fileUrlString
.remoteId
matches one we already have in Core Data, we won’t create a new entity - we’ll just update the existing one.(Bonus: These mappers are also used to handle events from our Pusher integration.)
The only other thing we still need to do is associate our request with this mapping provider; for that, we override another RDAPIRequest
method.
@implementation RDAvatarListRequest
- (id<RDAPIRequestMappingProvider>)mappingProvider
{
return [RDAvatarMapper new];
}
@end
We make similar adjustments to our existing RDGroupMapper
, which now contains an embedded avatar object in the JSON response, and we’re done mapping the API response.
Now our application code can create a new RDAvatarListRequest
instance and send it off to our API, and we can populate our UI with the avatars that just landed in Core Data.
###What to Test
One of the harder parts of testing an application is figuring out what components to test and at what granularity; it’s important to figure out how you’ll determine that things are working the way you expect. In the case of our API code, “correct” is determined by the state of Core Data after the request completes. After we make our request, we just make sure that the correct entities have been created, updated, or destroyed in Core Data.
The first version of our network stack basically had two classes: RDAPIRequest
and RDAPIDispatcher
, meaning that RDAPIRequest
did basically everything. As such, the first version of our tests essentially ran full integration tests against stubbed data. (Stubbing our network responses with OHHTTPStubs is another topic worthy of its own blog post). With the necessary utilities for sending an asynchronous request and waiting in a unit test, we would have tests like this:
- (void)testRequestCreatesAvatar
{
[self.avatarStub registerStub];
[self sendRequestExpectingSuccess:self.avatarRequest];
RDVerifyEntityCount(RDAvatar, 1);
}
This seemed fine at the time, but it soon became a little heavy. As is the nature with integration tests, there were many points of failure that affected the whole suite of tests at once without pointing to what the real issue was. After restructuring our code into the classes described above, we’ve been able to unit test each component of our network stack in isolation, so that in cases like this RDAvatar
addition, all we really need to test is the mapper we’ve implemented.
@interface UTAvatarMapperTests : UTAPITestCase
@property (nonatomic, strong) RDAvatarMapper *subject;
@end
@implementation UTAvatarMapperTests
// These tests will fail if someone renames the entity's properties without also updating the mapper
- (void)testAvatarIdIsMapped
{
RDAvatar *result = [[self performMapping:self.subject forStub:self.stub] firstObject];
XCTAssertEqualObjects(result.remoteId, @(self.stub.remoteId));
}
- (void)testFileUrlIsMapped
{
RDAvatar *result = [[self performMapping:self.subject forStub:self.stub] firstObject];
XCTAssertEqualObjects(result.fileUrlString, self.stub.fileUrlString);
}
// This test fails if we remove the identificationAttributes from our mapper
- (void)testExistingAvatarsAreUpdated
{
[self.singleStub generateAvatar];
RDVerifyEntityCount(RDAvatar, 1);
[self performMapping:self.subject forStub:self.stub];
RDVerifyEntityCount(RDAvatar, 1);
}
@end
(Here, performMapping:stub:
calls directly into RestKit’s object mapping service, bypassing any network request attempt.)
With this setup, we’ve essentially eliminated the networking aspect of our implementation, which is fine because the networking layer has its own suite of unit tests. These tests simply feed the expected response data through the RestKit mapping service and make sure Core Data is in the state we expect. It gets a little more complicated as relationships are added between entities and our API response contains nested objects, but we’re still able to test the behavior at this fairly modular level.
###Profit
In my opinion, the biggest win of our current network implementation is its approachability. With a few simple overrides and configuration, you can get a new endpoint up and running behind an expressive interface very quickly and with high confidence. Building up a modular wrapper on top of RestKit also grants us the opportunity to replace it with something else in the future without a total rewrite. Later, we may dive into the details of that wrapper or explore our framework for stubbing our API responses for testing, but for now, you have a good look at the tip of our networking iceberg.