Building an SDK: Part I

Our team has spent the last few weeks patiently planning and discussing how we plan to create our first public iOS SDK integrating our newly released Marqeta Payment APIs & Marqeta Developer Portal. The availability of this new SDK will enable developers all over the world to be able to tap into our network and process payments via our new APIs.

The new APIs have been written from the ground up to be fast, lightweight, yet robust SDK for accessing Marqeta services within your iOS apps, including users, cards, balances, payments, transactions, and metadata. The SDK is currently in the early alpha stages and will be published once we're closer to launch—until then, I'll be writing about our experiences while we code.

Top 5 Reasons to Have an Mobile SDK (from Red Foundry):

  • Close more sales. The biggest reason to create a mobile SDK is to help sales and business development staffs close key deals faster. The SDK do this by helping move the process of integrating services along more quickly.

  • Speed up deployment. Time is a critical cost factor because the integration services are performed by mobile engineers with a highly valued skill sets. Having an SDK can help them simplify their projects and enable integration of APIs that require complex use cases that are complemented by standard on-client processing for mobile devices. A SDK greatly simplifies this integration effort.

  • Increase security. Security may be a critical issue. In the case of large ad networks, SDKs are often required in order to reduce programmatic fraud. SDKs can be used to encrypt portions of the user interface (UI) to secure data quality. Mobile payments processing requires PCI compliance and some platforms may have specific requirements for storing passwords, etc. A SDK can help provide that needed security.

  • Reduce bad code & ensure best practices. Developers are rarely perfect. Even the best can make mistakes. Errors in how data is passed to back end services can cause critical issues and delays that impact the entire business. Inefficient code by a single developer can take down services for across the enterprise. The SDK can go a long way to reducing bad or inefficient code and its impact on critical systems. The right SDK can further insure the right business rules and practices are in place across their entire publisher network.

  • Control over your brand. Control over your brand with the UI of the publisher’s app may be critical to your business. Developers aren’t designers; an SDK will allow you to lock down critical portions of the interface while retaining analytics needed to see how the users interact with your service within the publisher’s application.

With these overwhelmingly obvious justifications for having a mobile SDK, we figured it would be nice to start as quickly as possible. My teammate and I deemed it best to start with the models (at a very low level) and move up from there. which brings me to our first contested topic in our planning process—the singleton pattern.

Part I: Singleton Addiction

Singletons are generally considered to be elements of bad design, however they are a fundamental part of Cocoa. I'll get to that in a bit. Developers view singletons negatively for a myriad of reasons, but mainly because they are often overly utilized or simply misused altogether. Most custom singletons in iOS development are essentially just fancy wrappers around global variables and due to the mutable state of the singleton, globals such as these should be minimized in your code.

+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static MyClass sharedInstance = nil;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

Nevertheless, singletons are heavily used in Apple's own SDKs. In fact, Apple's Developer Library considers the singleton one of the Cocoa Core Competencies. Our team even uses model-factory or model-resource singletons throughout our own applications. But the first major question that has come to mind when building this SDK is if the singleton pattern is the correct way to go for this project.

To make these examples easier to understand for any developers out there, I'll only show snippets on what our user models look like.

Before our SDK, we had our user model split into two classes:

  • MQUser contained properties and initializers for a user object.
  • MQUserManager contained functions and handled API requests for a user object.

This is how these classes were laid out:

// MQUser.h
@interface MQUser : NSObject

/* ... */

+ (instancetype)initWithJSONData:(NSDictionary *)data;

@end
// MQUserManager.h
typedef void (^MQUserManagerFailureBlock)(NSError *error, NSArray *errors);  
typedef void (^MQUserManagerSuccessBlock)();

@interface MQPUserManager : NSObject

/* ... */

- (void)loginUserWithUserInfo:(NSDictionary *)userInfo
                 successBlock:(MQUserManagerSuccessBlock)successBlock
                 failureBlock:(MQUserManagerFailureBlock)failureBlock;

- (void)registerUserWithUserInfo:(NSDictionary *)userInfo
                    successBlock:(MQUserManagerSuccessBlock)successBlock
                    failureBlock:(MQUserManagerFailureBlock)failureBlock;

- (void)replaceUserWithUserInfo:(NSDictionary *)userInfo
                        forKeys:(NSArray *)keys
                   successBlock:(MQUserManagerSuccessBlock)successBlock
                   failureBlock:(MQUserManagerFailureBlock)failureBlock;

- (void)updateUserWithUserInfo:(NSDictionary *)userInfo
                       forKeys:(NSArray *)keys
                  successBlock:(MQUserManagerSuccessBlock)successBlock
                  failureBlock:(MQUserManagerFailureBlock)failureBlock;

- (void)resetPasswordWithUserInfo:(NSDictionary *)userInfo
                     successBlock:(void(^)(NSString *token))successBlock
                     failureBlock:(MQUserManagerFailureBlock)failureBlock;

+ (instancetype)sharedManager;

As you can see, MQUser was a typical model object, while MQUserManager was a singleton which acted as a utility class for MQUser. Over time, I have grown away from this pattern and have tried to decouple the functions from a global class. Certain issues have come to mind, what happens when:

  • there are multiple users authenticated at once?
  • there are changes to the API?
  • there are issues with having mutable states?

After a bit of research, I have proposed a slightly different approach to the same solution. This design, however, removes the hidden dependencies of a singleton utility class and allows the user object itself to handle all of its methods within its own scope. How's that for abstraction?

Also, I've decided to add a useful singleton to MQUser which references the current authenticated user within the application. This user can be accessed globally and would allow all views to be notified and retrieve any state changes as soon as they happen. How's that for decoupling?

Below is an example of what I've come up with:

// MQUser.h
typedef void (^MQUserCompletionBlock)(MQUser *user, NSError *error);  
typedef void (^MQUserTokenCompletionBlock)(NSString *token, NSError *error);

@interface MQUser : MQObject // NSObject subclass with default properties (e.g., createdAt, updatedAt, etc.).

/* ... */

// GET user object, sets +currentUser to returned user, if authenticated.
+ (void)loginWithUsername:(NSString *)username
                 password:(NSString *)password
               completion:(MQUserCompletionBlock)completion;

// Sets +currentUser to nil.
+ (void)logOut;

// POST user object, sets +currentUser to returned user, if authenticated.
+ (void)signUpWithCompletion:(MQUserCompletionBlock)completion;

// PATCH/PUT user object with modified properties.
// Should throw exception if user is invalid or not authenticated.
+ (void)save;

// GET resetPasswordToken if email is valid.
+ (void)requestPasswordResetForEmail:(NSString *)email
                          completion:(MQPUserTokenCompletionBlock)completion;

// Helper method for creating a user with default property values.
+ (instancetype)user;

// Helper method for creating a user with given property values from dictionary.
+ (instancetype)userWithDictionary:(NSDictionary *)dictionary;

// Reference to the current user authenticated within the application.
// Any changes to the authenticated user will also be reflected here globally.
+ (instancetype)currentUser;

@end

I believe bringing everything down to the model level will make things a lot easier for external developers. They wouldn't have to learn our API request/response formatting to use our SDK, but could also customize their own applications to use the REST API instead of our given methods if they choose to. Of course this puts more work on our plate as a team to create a top of the line codebase, but shouldn't that be what every team is aiming for anyway?

Please feel free to leave any comments or shoot me a tweet/email if you want to discuss this in more detail. Part II coming soon...

Comments