5 Mins is the New 2 Weeks
When I first started my professional corporate development career it seemed that when I sat down with a project manager to go over my estimates, any sprint or milestone that was over two weeks was too long. For reasons, still unbeknownst to me, managers/executives thought that any small project shouldn’t take more than 2 weeks and large projects should only have milestones that didn’t exceed two weeks.
I have longed since moved on from places like that and refuse to be apart of the madness ever again
The previous articles I’ve written about PaaS/BaaS providers highlight that you can get up and running in as little as 5 mins. Most developers will think to themselves:
WOW!
I have this really great app idea and can’t wait to get started, but developing will take me weeks if not months. I’ll use provider ABC and be in immersed in my UITableViewControllers by minute 6.
This statement is pretty close to true. 5 mins has become the new standard for the maximum amount of time it should take to get “up and running” with a PaaS/BaaS.
The Other 11995 Minutes
One of the apps I released last year I spent ~200hrs coding (12,000 mins). Getting setup with one of the providers took the advertised 5 mins, but what about the other 11995 minutes…well that was different story. It wasn’t a complete disaster, but as I got deeper into project I hit the maxed out threshold of the features offered. Object relationships, querying, persistance etc. The support I got was extremely responsive. Their developers and I worked on various solutions to my issues, but they couldn’t change the nature of the platform. I had two choices:
- Create a hacky workaround
- Move off the service
Moving off service wasn’t a realistic option because of the time and effort. It wasn’t like I was using my own service layer classes or model objects. I was locked in to using their object-types. I ended up coming with a nasty hack to get part of what I needed done.
Considerations
Does the backend have the power/flexibility that allows you do the things that makes your app really cool? This is what deserves the considerations.
The Frontend Shouldn’t Drive Your Backend
Convienence is a funny thing. The instant gratification comes a price. Sometimes that price is worth paying, but there are no shortcuts with development. Sooner or later the “coding gods” must have their sacrifice. Having Login/SignupViewControllers is extremely convenient, but that shouldn’t be a deciding factor in selecting which BaaS provider that you go with. The amount of time saved by not having to write a login/signup viewcontroller is lost exponetially by having to write mounds and mounds of client-side code to manage your actual objects.
It Goes on the Server
WIth every product release cycle, it doesn’t matter the company, mobile devices get more RAM, CPU and disk space, but these resources are still limited and have to be shared across, not only your apps, but the system itself. There has been an unforunate trend that has been accepted as “best practice” in that the client (your phone) is executing tasks that should be done on the server.
Source: https://parse.com/tutorials/anypic#model
- (BOOL)shouldUploadImage:(UIImage *)anImage {
// Resize the image to be square (what is shown in the preview)
UIImage *resizedImage = [anImage resizedImageWithContentMode:UIViewContentModeScaleAspectFit
bounds:CGSizeMake(560.0f, 560.0f)
interpolationQuality:kCGInterpolationHigh];
// Create a thumbnail and add a corner radius for use in table views
UIImage *thumbnailImage = [anImage thumbnailImage:86.0f
transparentBorder:0.0f
cornerRadius:10.0f
interpolationQuality:kCGInterpolationDefault];
// Get an NSData representation of our images. We use JPEG for the larger image
// for better compression and PNG for the thumbnail to keep the corner radius transparency
NSData *imageData = UIImageJPEGRepresentation(resizedImage, 0.8f);
NSData *thumbnailImageData = UIImageJPEGRepresentation(thumbnailImage, 0.8f);
if (!imageData || !thumbnailImageData) {
return NO;
}
}
A common use case in illustrating this point is handling image resizing after a user takes a picture with the camera. Let’s take a look at how Parse handles this scenario
This is to improve performance in the activity feed table view where several thumbnail images are displayed simultaneously. As we’ll see in Section 3, the resizing is done on the client side.
While the amount of code written isn’t too much, the device resources that are utilized are done so unecessarily. Most image processing tasks are for the server. By deferring the image manipulation to server you allow yourself for greater flexibility in time to process, future enhancements/tweaks to the processing itself, and you have a much greater pool of resources at your disposal. Finally, there is the tanglible financial savings of not having repeated calls to the API itself.
Client code should be small, fast and focused on UI. Frequently the libraries required for this kind of operation can bloat your app ridiculous amounts.
Let’s look at how this is done with FatFractal:
Server Side Code
var ff = require('ffef/FatFractal');
var io = require ('io'); // standard CommonJS module
var fs = require ('fs'); // standard CommonJS module
var bin = require ('binary'); // standard CommonJS module
var hc = require ('ringo/httpclient'); // not standardised by CommonJS yet, hence ringo prefix. see http://ringojs.org
var Scalr = Packages.org.imgscalr.Scalr; // import the Scalr Java package
var ImageIO = Packages.javax.imageio.ImageIO; // import the imageIo Java packages
function WNTimelinePhoto() {
this.clazz = "WNTimelinePhoto";
this.countForUser = 0;
this.photoInstance = null;
return this;
}
function resizeImage(picBytes, width, height) {
var croppedBytes = null;
/**
* We need a BufferedImage for the Scalr processing
* There are ImageIO read methods for InputStream, File and URL. We've got a
* ByteArray. So let's create a ByteArrayInputStream.
*/
try {
var bais = new java.io.ByteArrayInputStream(picBytes);
var img = ImageIO.read(bais);
/**
* Crop the picture
*/
var cropped = Scalr.crop(img, width, height);
var baos = new java.io.ByteArrayOutputStream();
ImageIO.write (cropped, 'JPG', baos);
/**
* Get the bytes from the ByteArrayOutputStream
*/
croppedBytes = new bin.ByteArray(baos.toByteArray());
} catch (e) {}
return croppedBytes;
}
exports.resizeImages = function () {
var data = ff.getExtensionRequestData();
var r = ff.response();
var evnt = null;
var finalResponse = '';
var usr = null;
var photoInstance = null;
if (data.httpMethod.toLowerCase() !== 'post') {
r.responseCode = "405";
r.statusMessage = "You must use POST method";
r.mimeType = "application/json";
r.result = JSON.stringify("REJECTED");
return;
}
photoInstance = ff.getObjFromUri(data.httpContent.photoresource);
if (photoInstance === null) {
r.responseCode = "400";
r.statusMessage = "Photo Instance Error";
r.mimeType = "application/json";
r.result = JSON.stringify("The photo instance can't be null: (photo ffurl) " + photoInstance.ffUrl);
return;
}
/**
* Check and make sure the current user owns the image
*/
if (data.ffUser !== photoInstance.createdBy) {
r.responseCode = "403";
r.statusMessage = "Don't Think So";
r.mimeType = "application/json";
r.result = JSON.stringify("Not Authorized");
return;
}
originalImage = ff.getBlob("image", photoInstance);
if (originalImage === null) {
r.responseCode = "400";
r.statusMessage = "Image Request Failure";
r.mimeType = "application/json";
r.result = JSON.stringify("Image information can't be null: (photo ffurl) " + photoInstance.ffUrl);
return;
}
previewImage = resizeImage(originalImage, 280, 75);
if (previewImage === null) {
r.responseCode = "400";
r.statusMessage = "Preview Resize Failed";
r.mimeType = "application/json";
r.result = JSON.stringify("Preview information can't be null: (photo ffurl) " + photoInstance.ffUrl);
return;
}
thumbnailImage = resizeImage(originalImage, 100, 100);
if (thumbnailImage === null) {
r.responseCode = "400";
r.statusMessage = "Thumbnail Resize Failed";
r.mimeType = "application/json";
r.result = JSON.stringify("Thumbnail information can't be null: (photo ffurl) " + photoInstance.ffUrl);
return;
}
ff.saveBlob(photoInstance, 'previewImage', previewImage, 'image/jpeg');
ff.saveBlob(photoInstance, 'thumbnailImage', thumbnailImage, 'image/jpeg');
finalResponse = {"instance" : photoInstance, "raw" : data.ffUser};
r.responseCode = "200";
r.statusMessage = "Meta Information";
r.mimeType = "application/json";
r.result = JSON.stringify(finalResponse);
};
Client
I defined an POST event handler in my FatFractal FFDL file to be executed after the object is created
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[self dismissViewControllerAnimated:YES completion:nil];
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
UIImage *anImage = [info valueForKey:UIImagePickerControllerEditedImage];
if (anImage) {
NSData *imageData = UIImageJPEGRepresentation(anImage, 0.2);
WNPhoto *photo = [[WNPhoto alloc] init];
photo.event = self.currentEvent;
photo.image = imageData;
[self.ffMgr.ff createObj:photo
atUri:userPhotosUri
onComplete:^(NSError *err, id obj, NSHTTPURLResponse *httpResponse) {
if (err) {
WNLog(@"error saving: %@", err);
[ADVAlertView showStandardAlert:@"Error Posting Image"
withMessage:[err localizedDescription]];
} else {
[ADVAlertView showStandardAlert:@“Success”
withMessage:@“Image upload is complete”];
}
}];
} else {
[MBProgressHUD hideHUDForView:self.view animated:YES];
[ADVAlertView showStandardAlert:NSLocalizedString(@"Error Saving Image", nil)
withMessage:NSLocalizedString(@"There was an error saving your picture", nil)];
}
}
With FatFractal, I’m able to create as many sizes/formats as I want based upon my model without changing any client code. I’m able to set the permissions, specify the types of images I want (PNG, JPEG, etc) in a consistant manner that is scaled and in theory unlimited resources. I have ensured that my data is consistent and complete because it is handled on the backend.
Regardless of the client: desktop browser, mobile browser, Windows mobile, iOS or Android the client code should stay the same, take a picture and upload, and all get consistent image processing results. Often implementing such features is very different on the various devices, writing once saves time and probably a lot of headaches.
Do You Speak Other Languages (Idiomas, Sprog, Lingue)
With any new technology there is always a learning curve. What is best for the developer is when that technology provides you with lots of options. Most developers tend to work on more than one project at time and require more than one language for those projects. This becomes a real pain when you have to have separate accounts to manage from various BaaS providers (because most only offer one, maybe two languages) or worse you have to setup yourself different development enviornments that support each of the various languages. Why shouldn’t you be able to mix and match code from different languages using the same provider.
FatFractal supports all of this out of the box. No additional modules to request or add-on. They are available to through the engine itself. You can mix/match server-side js with Ruby or Rails or Servlets without the headaches of setting up gems, bundler or java class paths. If Ruby is the better choice for your “Movie” model event handlers and server-side js is better for the “Theater” model event handlers/extensions then you make the choice….not the backend provider. A few releases later if you want to write everything with servlets then go ahead. The backend is agnostic and your client code doesn’t change one bit.
FatFractal – Picking the Lock(in)
In my experiences and writing about FatFractal I have championed some of their unique features:
but the one that I haven’t talked about is the fact that you aren’t locked in so tight to their services that you can’t leave it or use it in pieces and parts. For BaaS to become mainstream, it is imperative that the players make it really easy to move to another service. Dalton Caldwell, from App.net, illustrates…though indirectly, what others have done wrong and what FatFractal has been doing from day one.
Imagine a world in which your social data (e.g. messages, photos, videos) was easier to work with. For instance, imagine you could try out a new photo sharing service without having to move all of your photos and social graph.
In this world, your photos are held in a data store controlled by you. If you want to try out a new service, you can seamlessly login and choose to give permission to that service, and the photos that you have granted access to would be immediately available.
This is one benefit of an “unbundled” social service. Unbundling gives the user power to pick the software that best suits their needs, rather than being forced to use the software made by the company that manages their data.
Source: http://blog.app.net/2013/01/28/announcing-the-app-net-file-api/
FatFractal doesn’t lock you into any particular client side technology for your models (HashMaps, Dictionaries, CoreData) nor server side language or technology. You can use their service as a full stack or you could use it, for example, as an extension to your Heroku app.
Using the image resizing example above, let’s assume you have an existing Rails app that you are happy with, but ImageMagick isn’t performing as well as you want and you want to offload the resizing functionality asynchronously, but be notified when it is complete. You could create a simple server extension with FatFractal and accomplish all of that without having to worry about an extened amount of time coding the service layer, security or scalling…no properitary libraries or syntax to write except for the serverside js FF library, but that is for fetching/saving collections from your backend. If you would rather write it in Ruby/Python/Java, etc. well that is your choice. It is “unbundled”.
Parse has done a great job with their 3rd party integrations, but developers rarely use only the functionality out of the box. They want to extend or write something custom. FatFractal is the only BaaS/PaaS that provides what you need to accomplish everything that you need. It is the balance between OOTB features while leveraging custom code, all the well, trying to not reinvent the wheel with every application.
FatFractal: providing an unbundle platform for the other 11995 minutes of development.