Search code examples
javagoogle-app-enginegoogle-cloud-datastoreblobstoregoogle-eclipse-plugin

Can I save images to Blobstore and save data to Datastore in one trip?


The argument has been made and settled on numerous occasions: Blobstore is better than DataStore for storing images. Now say I have an app similar to Instagram or Facebook or Yelp or any of those apps that are image intensive. In my particular case the ideal model would be

public class IdealPostModel{

  Integer userId;
  String synopsys;
  Blob image;
  ...//more data/fields about the post

}

But since I must use the BlobStore, my model does not have a blob but instead a URL or BlobKey. The heavy catch is that to send a post (i.e. save a post to server) the app must -- in that order --

  1. Send all the non-blob data to the server.
  2. Wait for server to respond with BlobstoreUtils.generateServingUrl(null) data
  3. Send the images to the BlobStore
  4. Have BlobStore send response to my server with the BlobKey or url of the image
  5. Store the BlobKey/url in my DataStore entity

That's a lot of handshakes!!!

Is there a way to send all the data to the server in step one: strings and image. And then from there have the server do everything else? Of course I am here hoping to reduce the amount of work. I image App Engine must be quite mature by now and there has to be a simpler way than my architecture.

Now of course I am here because I am experiencing situations where the data is saved but the BlobKey or URL is not being saved to the Entity. It happens about 10% of the time. Or maybe less, but it does feel like 10%. It's driving my users insane, which means it's driving me even more insane since I don't want to lose my users.

Ideally

  1. App sends everything to server in one call: image and metadata such as userId and synopsys
  2. Server somehow gets a blob key from Blobstore
  3. Server sends image to blobstore, to be stored at the provided blob key, and server sends other data to DataStore, also including the blob key in the datastore.

Update

    public static String generateServingUrl(String path) {
        BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
        return blobstoreService.createUploadUrl(null == path ? "/upload" : path);
    }

This is a snippet on my server.


Solution

  • You actually cand send everything in a single request.

    Bear in mind that when you send a blob using a URL gotten by calling blobstoreService.createUploadUrl("/yourservingurl") , GAE actually makes a first request to a different URL, stores the blob and then calls /yourservingurl passing you the blobkeys which you can retrive by doing:

    Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(req);
    List<BlobKey> blobKeys = blobs.get("myFile");
    

    So in effect all other form values will be lost after that first request, BUT if you can build that URL dynamically eg

    blobstoreService.createUploadUrl("/yourservingurl?param1=a&param2=b")
    

    then you can get back those parameters on your Servlet and persist everything (including the blobkey of the already stored blob) at once, making much fewer calls to the datastore.

    UPDATE: the steps would be

    1) Gather all parameters on the client side and create an upload URL with those params eg: blobstoreService.createUploadUrl("/yourservingurl?userId=989787") . GAE will generate a unique URL for that specific request.

    when you commit the form, GAE will persist the blobs and call /yourservingurl

    2) On the Servlet serving /yourservingurl you'll get all the blobkeys form files you uploaded by using : blobstoreService.getUploads(request) AND you can get the parameters you included with the standard request.getParameter("userId")

    3) Now in your servlet you have all the parameters (eg userId) you sent plus the blobkey, you can persist your Post object in one call.