Codemotion appengine

5,331 views

Published on

The easy way to develop Java applications has always been the standard stack (Spring, JEE, SQL) that confirms the LAMP equivalent in Java-speak. This presentation compares this model with a real use case based on Guice, Jersey and AppEngine.

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
5,331
On SlideShare
0
From Embeds
0
Number of Embeds
2,361
Actions
Shares
0
Downloads
50
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Codemotion appengine

  1. 1. Development with Guice, Jersey and AppEngineIgnacio Coloma icoloma@gmail.com – Google Technology User Group @nachocoloma
  2. 2. Hey!Lets try something different
  3. 3. LAMPLinux, Apache, MySQL, Perl/PHP
  4. 4. ...or, in Java parlance...
  5. 5. AppEngine-based architecture
  6. 6. Who am I?Nacho ColomaCTO at Extrema SistemasExtrema Sistemas is the SpringSource and Javaspecialist partner @Spain
  7. 7. Which means...We know a bit about development with the typical enterprise-y stack
  8. 8. Lets get indie! src: http://www.sportcartoons.co.uk/cartoon_vw_van.html
  9. 9. AppEngine platformA set of services independent from the development language Currently supported: Python, Java, Go
  10. 10. The AppEngine SDK An Eclipse pluginA set of command-line toolsA glorified Jetty-on-steroids
  11. 11. AppEngine services Datastore Web Memcache Mail src: http://www.gasgoo.com/
  12. 12. Other servicesApp Identity, Blobstore,Google Cloud Storage, Capabilities, Conversion, Channel, Images,Multitenancy, OAuth, Prospective Search, Task Queues, URL Fetch, Users, XMPP
  13. 13. Configured using XML / YAML appengine-web.xml datastore-indexes.xml cron.xml queue.xml web.xml
  14. 14. (Almost) Everything is a web request Cron requests Queues Warm-up requests
  15. 15. Warm-up requests30 seconds to start upThe triggering user will see this delayGood enough for Python, not so much for Java
  16. 16. Save startup time Avoid heavy libraries Avoid classpath searchesInitialize lazy when possible
  17. 17. Jersey REST interfaces Simple as Mecanismo del Botijo Not many fancy features Not that well documented with Guice src: http://www.terra.org/articulos/art01863.html
  18. 18. @Path("/users")public class Users { @Inject private UsersService usersService; /** * HTML response */ @GET @Path("{key}") public Viewable view(@PathParam("key") KeyParameter k) { Key key = k.getValue(User.class); User user = usersService.get(key); return new Viewable("/users/view.jsp", user); } /** * JSON request/response */ @PUT @Path("{key}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public User put(User user) { return usersService.put(user); }}
  19. 19. REST resourcesGET: read /users/{key}POST: insert /usersPUT: update /users/{key}DELETE: remove /users/{key}
  20. 20. Special form pagesGET /users/createGET /users/{key}/edit
  21. 21. Dependency InjectionIntroducing Jersey configuration
  22. 22. Guice 3.0 Started by Bob LeeUsed extensively @Google Configured using Java
  23. 23. web.xml<listener> <listener-class>com.acme.config.GuiceConfigListener</listener-class></listener><filter> <filter-name>GuiceFilter</filter-name> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class></filter><filter-mapping> <filter-name>GuiceFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>
  24. 24. Servlet context listenerpublic class GuiceConfigListener extends com.google.inject.servlet.GuiceServletContextListener { @Override protected Injector getInjector() { return Guice.createInjector( new MyWebModule(), new MyServicesModule(), new MyPersistenceModule() ); }}
  25. 25. Services module: explicit bindings public class MyServicesModule extends com.google.inject.AbstractModule { @Override protected void configure() { bind(FooBar.class); bind(FooBar.class).to(FooBarImpl.class); bind(MemcacheService.class).toInstance(MemcacheServiceFactory.getMemcacheService()); } } public class Users { @Inject private FooBar foobar; @Inject private MemcacheService memcacheService; }
  26. 26. Just-in-time bindings @ImplementedBy(UsersServiceImpl.class) public interface UsersService { public User put(User user); public User get(Key key); } public class Users { @Inject private UsersService usersService; }
  27. 27. Providerspublic class MyServicesModule extends com.google.inject.AbstractModule { @Override protected void configure() { bind(User.class).toProvider(CurrentUserProvider.class); }}@RequestScopedpublic class CurrentUserProvider implements com.google.inject.Provider<User> { @Inject private HttpServletRequest request; @Inject private EntityManager entityManager; @Override @RequestScoped public User get() { Key userKey = CookieUtils.getUserFromCookie(request); if (userKey == null) { return User.ANONYMOUS; } return (User) entityManager.get(userKey); }}
  28. 28. Web modulepublic class MyWebModule extends com.google.inject.servlet.ServletModule { @Override protected void configureServlets() { // bind resources for (Class resourceClass : getResources()) { bind(resourceClass); } // jersey customization serve("/*").with(GuiceContainer.class, ImmutableMap.of( "com.sun.jersey.config.property.JSPTemplatesBasePath", "/WEB-INF/jsp", JSONConfiguration.FEATURE_POJO_MAPPING, "true" // use Jackson to serialize )); } private Class[] getResources(boolean development) { return new Class[] { Users.class, Shows.class, Help.class }; }}
  29. 29. Resource class @Path("/users") public class Users { @Inject private User currentUser; @Inject private UsersService usersService; @GET @Path("{key}") public Viewable view(@PathParam("key") KeyParameter k) { … } }
  30. 30. Parameters in Jersey @Path("/users") public class Users { // e.g.: /users/{key}?foo=123 @GET @Path("{key}") public Viewable view( @PathParam("key") String key, @QueryParam("foo") String foo ) { … } // e.g.: form pointing at /users/create @POST @Path("create") public Viewable put( @FormParam("foo") String foo, @InjectParam HttpServletRequest request, @InjectParam UsersService usersService ) { … } }
  31. 31. Jersey parameters must be one of: Primitive type Has a constructor(String) Has a static valueOf(String) Be Collection<T>, where T satisfies one of the above.
  32. 32. Introducing Base58Take Base62 [a-zA-Z0-9]Remove 0, O, caps i and lowercase LMakes shorter URLs: 9223372036854775807 = CFq8pKn6mQN
  33. 33. KeyParameterpublic class KeyParameter { private String serializedValue; public KeyParameter(String serializedValue) { this.serializedValue = serializedValue; } public Key getValue(Class<?> persistentClass) { try { long id = Base58.alphaToNumber(serializedValue); return KeyFactory2.createKey(persistentClass, id); } catch (InvalidBase58Exception e) { // malformed URL throw new WebApplicationException(e, Status.NOT_FOUND); } }}
  34. 34. @Path("/users")public class Users { @Inject private UsersService usersService; @GET @Path("{key}") public Viewable view(@PathParam("key") KeyParameter k) { Key key = k.getValue(User.class); User user = usersService.get(key); return new Viewable("/users/view.jsp", user); }}
  35. 35. Persistence modulepublic class MyPersistenceModule extends org.simpleds.guice.SimpledsModule { @Override protected void configure() { this.withPersistentClasses(getPersistentClasses()); super.configure(); } private Class[] getPersistentClasses() { return new Class[] { User.class, Vote.class }; }}
  36. 36. Persistent classpublic class User { @Id private Key key; @Property(required=true) private String name; private String email; @Transient private String hashCode;}
  37. 37. EntityManagerpublic class UsersServiceImpl implements UsersService { @Inject private User currentUser; @Inject private EntityManager entityManager; public User put(User user) { if ( !currentUser.isApplicationAdmin() && !currentUser.getKey().equals(user.getKey()) ) { throw new WebApplicationException(Status.UNAUTHORIZED); } return entityManager.put(user); }}
  38. 38. public User get(Key key) { return entityManager.get(key);}public User findByEmail(String email) { return entityManager.createQuery(User.class) .equal("email", email) .asSingleResult();}public List<User> findByEmail(String email) { return entityManager.createQuery(User.class) .equal("email", email) .asList();}public CursorList<User> findAll(Cursor cursor) { return entityManager.createQuery(User.class) .withCursor(cursor) .asCursorList(20);}
  39. 39. Exceptions in Jerseypublic class InsufficientPermissionsException extends WebApplicationException { public InsufficientPermissionsException() { super(Status.UNAUTHORIZED); }}
  40. 40. More elaborate exceptionspublic class AuthenticationRequiredException extends WebApplicationException { public AuthenticationRequiredException(String redirectAfterLoginUrl) { super( Response.status(Response.Status.UNAUTHORIZED) .entity(new Viewable("/login/login.jsp", null)) .cookie(new NewCookie("ral", redirectAfterLoginUrl,"/", null, null, 600, false)) .build() ); }}
  41. 41. Exception mapperpublic class MyWebModule extends ServletModule { protected void configureServlets() { bind(MyExceptionMapper.class); }}@Singletonpublic class MyExceptionMapper implements ExceptionMapper<Throwable> { @Override public Response toResponse(Throwable root) { Throwable exception = root; if (exception instanceof WebApplicationException) { return ((WebApplicationException)exception).getResponse(); } if (exception instanceof EntityNotFoundException) { return Response.status(404).build(); } return Response.status(500).build(); }}
  42. 42. In the cloudYou pay for what you use
  43. 43. Communicate using JSON Less bandwidth Less server CPU Less latency
  44. 44. Receiving and returning JSON @GET @Path("{key}") @Produces(MediaType.APPLICATION_JSON) public User get(@PathParam(“key”) KeyParameter k) { Key key = k.getValue(User.class); return usersService.get(key); } @PUT @Path("{key}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response put(User user) { usersService.put(user); return Response.seeOther(new URI(user.getLink())).build(); }
  45. 45. Use smaller entities Property names get stored with each entity
  46. 46. @Entity(“u”)public class User { @Id @Property(“k”) private Key key; @Property(required=true, value=”n”) private String name; @Property(“e”) private String email;}
  47. 47. Use fewer indexes Indexes take space Indexes penalize writes
  48. 48. Use unindexed properties public class User { @Property(required=true) private String oauthId; @Property(unindexed=true) private String accessToken; @Property(unindexed=true) private String analyticsID; }
  49. 49. Avoid composite indexes if possible public class UsersServiceImpl implements UsersService { public List<User> findByEmail(String email) { return entityManager.createQuery(User.class) .equal("email", email) .sortDesc("creationTimestamp") .asList(); } }
  50. 50. Cache everythingReduces latencyCheaper than using the datastore @Cacheable(3600) public class User { … }
  51. 51. Reduce the number of datastore operations entityManager.put(ImmutableList.of(currentUser, anotherUser, user3)); Map<Key, Object> result = entityManager.get(userKey, showKey, user2Key);
  52. 52. src: http://blog.appenginefan.com/2010/04/patterns-of-doom.html
  53. 53. ConclusionsWhat did we learn?
  54. 54. AppEngine vs AWSInfrastructure as a Service or Platform as a ServiceSimpler to manage and much harder to developLots of limitations apply: read the docs!
  55. 55. Youll get stronger from the experience Think about the limitations as: learn to design the Google way
  56. 56. Guice vs Spring Less features More flexibleStrong design skills required
  57. 57. Jersey vs <anything else>Not the best option for old-style HTML applicationsPerfect foundation for REST servicesUse lots of javascript
  58. 58. SimpleDS vs JPAJPA/JDO is not a good approach to the DatastoreValid options are: Objectify / Twig / SimpleDS
  59. 59. Q?A!
  60. 60. Thanks!Demo available:http://github.com/icoloma/simpleds-kickstart @nachocoloma

×