How do you do background processing in an AppEngine environment? The AppEngine experimental Task Queues API provides a way using Web hooks, but it’s a bit of a pain because you have to package each background task as a servlet.
Comes Vince Bonfanti of New Atlanta to the rescue with a Java implementation of Nick Johnson’s original Deferred.defer implementation in Python. Now queuing a task for background processing is as simple as creating a task and calling Deferred.defer(task). Here’s an example task to send an email to the application administrator:
package com.roa.server.service.task; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Properties; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.servlet.ServletException; import com.newatlanta.appengine.taskqueue.Deferred; public class AdminEmailTask implements Deferred.Deferrable { private String msgBody; private String msgSubject; public AdminEmailTask(String msgSubject, String msgBody) { this.msgSubject = msgSubject; this.msgBody = msgBody; } @Override public void doTask() throws ServletException, IOException { Properties props = new Properties(); Session session = Session.getDefaultInstance(props, null); try { Message msg = new MimeMessage(session); msg.setFrom(new InternetAddress("admin@example.com", "admin")); msg.addRecipient(Message.RecipientType.TO, new InternetAddress( "admin@example.com", "admin")); msg.setSubject(msgSubject); msg.setText(msgBody); Transport.send(msg); } catch (AddressException e) { throw new ServletException(e); } catch (MessagingException e) { throw new ServletException(e); } catch (UnsupportedEncodingException e) { throw new ServletException(e); } } }
And here’s an example of queuing the task inside a gwt-dispatch ActionHandler:
package com.roa.server.handler.admin; import java.io.IOException; import net.customware.gwt.dispatch.server.ActionHandler; import net.customware.gwt.dispatch.server.ExecutionContext; import net.customware.gwt.dispatch.shared.ActionException; import com.google.inject.Inject; import com.newatlanta.appengine.taskqueue.Deferred; import com.roa.admin.shared.rpc.AdminEmailAction; import com.roa.admin.shared.rpc.AdminEmailResult; import com.roa.server.service.task.AdminEmailTask; import com.turbomanage.gwt.server.PMF; public class AdminEmailHandler implements ActionHandler<AdminEmailAction, AdminEmailResult> { @Inject private PMF pmf; @Override public AdminEmailResult execute(AdminEmailAction action, ExecutionContext ctx) throws ActionException { AdminEmailTask adminEmailTask = new AdminEmailTask(action.getEmailSubject(), action .getEmailBody()); try { Deferred.defer(adminEmailTask); } catch (IOException e) { throw new ActionException(e); } return new AdminEmailResult(); } @Override public Class<AdminEmailAction> getActionType() { return AdminEmailAction.class; } @Override public void rollback(AdminEmailAction arg0, AdminEmailResult arg1, ExecutionContext arg2) throws ActionException { // TODO Auto-generated method stub } }
You might ask why you’d use a deferred task like this to send an email? In this example, there really is no reason to, but imagine you were sending 1000 emails at once. In order to guarantee you won’t exceed AppEngine’s free quota of 8 recipients / minute, you could set up your deferred queue in queue.xml with that rate and then call Deferred.defer() for each recipient rather than calling the mail API directly 1000 times in a loop. That way, the AppEngine task queuing facility throttles your mail API calls.
Here’s a link to the whole discussion thread and Vince’s code.
If you’d like to see this added to the Task Queues API, please star this issue.