Development with Guice,
                 Jersey and AppEngine
Ignacio Coloma

      icoloma@gmail.com – Google Technology User Group




             @nachocoloma
Hey!

Let's try something different
LAMP

Linux, Apache, MySQL, Perl/PHP
...or, in Java parlance...
AppEngine-based architecture
Who am I?

Nacho Coloma
CTO at Extrema Sistemas
Extrema Sistemas is the SpringSource and Javaspecialist
  partner @Spain
Which means...

We know a bit about development with the typical
                enterprise-y stack
Let's get indie!




               src: http://www.sportcartoons.co.uk/cartoon_vw_van.html
AppEngine platform

A set of services independent from the development
                        language
        Currently supported: Python, Java, Go
The AppEngine SDK

      An Eclipse plugin
A set of command-line tools
A glorified Jetty-on-steroids
AppEngine services
     Datastore
     Web
     Memcache
     Mail


                 src: http://www.gasgoo.com/
Other services
App Identity, Blobstore,Google Cloud Storage,
  Capabilities, Conversion, Channel, Images,
Multitenancy, OAuth, Prospective Search, Task
       Queues, URL Fetch, Users, XMPP
Configured using XML / YAML
       appengine-web.xml
      datastore-indexes.xml
             cron.xml
            queue.xml
             web.xml
(Almost) Everything is a
     web request
      Cron requests
         Queues
     Warm-up requests
Warm-up requests
30 seconds to start up
The triggering user will see this delay
Good enough for Python, not so much for Java
Save startup time

    Avoid heavy libraries
 Avoid classpath searches
Initialize lazy when possible
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
@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);
      }

}
REST resources
GET: read /users/{key}
POST: insert              /users
PUT: update /users/{key}
DELETE: remove /users/{key}
Special form pages

GET /users/create
GET /users/{key}/edit
Dependency Injection

Introducing Jersey configuration
Guice 3.0

   Started by Bob Lee
Used extensively @Google
  Configured using Java
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>
Servlet context listener
public class GuiceConfigListener extends com.google.inject.servlet.GuiceServletContextListener {

        @Override
        protected Injector getInjector() {
              return Guice.createInjector(
                    new MyWebModule(),
                    new MyServicesModule(),
                    new MyPersistenceModule()
              );
    }

}
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;

  }
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;

  }
Providers
public class MyServicesModule extends com.google.inject.AbstractModule {
      @Override
      protected void configure() {
            bind(User.class).toProvider(CurrentUserProvider.class);
      }
}

@RequestScoped
public 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);
      }

}
Web module
public 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
           };
     }

}
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) {
             …
       }

 }
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
        ) {
              …
        }

  }
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.
Introducing Base58

Take Base62 [a-zA-Z0-9]
Remove 0, O, caps 'i' and lowercase 'L'
Makes shorter URLs:
   9223372036854775807 = CFq8pKn6mQN
KeyParameter
public 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);
            }
      }

}
@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);
      }

}
Persistence module
public 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 };
     }

}
Persistent class
public class User {

      @Id
      private Key key;

      @Property(required=true)
      private String name;

      private String email;

      @Transient
      private String hashCode;

}
EntityManager
public 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);
      }

}
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);
}
Exceptions in Jersey

public class InsufficientPermissionsException extends WebApplicationException {

      public InsufficientPermissionsException() {
            super(Status.UNAUTHORIZED);
      }

}
More elaborate exceptions
public 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()
            );
      }

}
Exception mapper
public class MyWebModule extends ServletModule {

      protected void configureServlets() {
            bind(MyExceptionMapper.class);
      }

}

@Singleton
public 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();
      }

}
In the cloud

You pay for what you use
Communicate using JSON

      Less bandwidth
      Less server CPU
        Less latency
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();
 }
Use smaller entities

 Property names get stored
      with each entity
@Entity(“u”)
public class User {

      @Id
      @Property(“k”)
      private Key key;

      @Property(required=true, value=”n”)
      private String name;

      @Property(“e”)
      private String email;

}
Use fewer indexes

   Indexes take space
 Indexes penalize writes
Use unindexed properties
      public class User {

            @Property(required=true)
            private String oauthId;

            @Property(unindexed=true)
            private String accessToken;

            @Property(unindexed=true)
            private String analyticsID;

      }
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();
         }

   }
Cache everything
Reduces latency
Cheaper than using the datastore

                   @Cacheable(3600)
                   public class User {

                         …

                   }
Reduce the number of datastore
          operations

     entityManager.put(ImmutableList.of(currentUser, anotherUser, user3));
     Map<Key, Object> result = entityManager.get(userKey, showKey, user2Key);
src: http://blog.appenginefan.com/2010/04/patterns-of-doom.html
Conclusions

What did we learn?
AppEngine vs AWS
Infrastructure as a Service or Platform as a Service
Simpler to manage and much harder to develop
Lots of limitations apply: read the docs!
You'll get stronger from the
         experience
   Think about the limitations as:
  learn to design the Google way
Guice vs Spring

       Less features
       More flexible
Strong design skills required
Jersey vs <anything else>

Not the best option for old-style HTML applications
Perfect foundation for REST services
Use lots of javascript
SimpleDS vs JPA

JPA/JDO is not a good approach to the Datastore
Valid options are: Objectify / Twig / SimpleDS
Q?
A!
Thanks!
Demo available:
http://github.com/icoloma/simpleds-kickstart

      @nachocoloma

Codemotion appengine

  • 1.
    Development with Guice, Jersey and AppEngine Ignacio Coloma icoloma@gmail.com – Google Technology User Group @nachocoloma
  • 2.
  • 3.
  • 4.
    ...or, in Javaparlance...
  • 5.
  • 6.
    Who am I? NachoColoma CTO at Extrema Sistemas Extrema Sistemas is the SpringSource and Javaspecialist partner @Spain
  • 7.
    Which means... We knowa bit about development with the typical enterprise-y stack
  • 8.
    Let's get indie! src: http://www.sportcartoons.co.uk/cartoon_vw_van.html
  • 9.
    AppEngine platform A setof services independent from the development language Currently supported: Python, Java, Go
  • 10.
    The AppEngine SDK An Eclipse plugin A set of command-line tools A glorified Jetty-on-steroids
  • 11.
    AppEngine services Datastore Web Memcache Mail src: http://www.gasgoo.com/
  • 12.
    Other services App Identity,Blobstore,Google Cloud Storage, Capabilities, Conversion, Channel, Images, Multitenancy, OAuth, Prospective Search, Task Queues, URL Fetch, Users, XMPP
  • 13.
    Configured using XML/ YAML appengine-web.xml datastore-indexes.xml cron.xml queue.xml web.xml
  • 14.
    (Almost) Everything isa web request Cron requests Queues Warm-up requests
  • 15.
    Warm-up requests 30 secondsto start up The triggering user will see this delay Good enough for Python, not so much for Java
  • 16.
    Save startup time Avoid heavy libraries Avoid classpath searches Initialize lazy when possible
  • 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.
    @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.
    REST resources GET: read/users/{key} POST: insert /users PUT: update /users/{key} DELETE: remove /users/{key}
  • 20.
    Special form pages GET/users/create GET /users/{key}/edit
  • 21.
  • 22.
    Guice 3.0 Started by Bob Lee Used extensively @Google Configured using Java
  • 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.
    Servlet context listener publicclass GuiceConfigListener extends com.google.inject.servlet.GuiceServletContextListener { @Override protected Injector getInjector() { return Guice.createInjector( new MyWebModule(), new MyServicesModule(), new MyPersistenceModule() ); } }
  • 25.
    Services module: explicitbindings 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.
    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.
    Providers public class MyServicesModuleextends com.google.inject.AbstractModule { @Override protected void configure() { bind(User.class).toProvider(CurrentUserProvider.class); } } @RequestScoped public 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.
    Web module public classMyWebModule 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.
    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.
    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.
    Jersey parameters mustbe one of: Primitive type Has a constructor(String) Has a static valueOf(String) Be Collection<T>, where T satisfies one of the above.
  • 32.
    Introducing Base58 Take Base62[a-zA-Z0-9] Remove 0, O, caps 'i' and lowercase 'L' Makes shorter URLs: 9223372036854775807 = CFq8pKn6mQN
  • 33.
    KeyParameter public 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.
    @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.
    Persistence module public classMyPersistenceModule 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.
    Persistent class public classUser { @Id private Key key; @Property(required=true) private String name; private String email; @Transient private String hashCode; }
  • 37.
    EntityManager public class UsersServiceImplimplements 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.
    public User get(Keykey) { 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.
    Exceptions in Jersey publicclass InsufficientPermissionsException extends WebApplicationException { public InsufficientPermissionsException() { super(Status.UNAUTHORIZED); } }
  • 40.
    More elaborate exceptions publicclass 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.
    Exception mapper public classMyWebModule extends ServletModule { protected void configureServlets() { bind(MyExceptionMapper.class); } } @Singleton public 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.
    In the cloud Youpay for what you use
  • 43.
    Communicate using JSON Less bandwidth Less server CPU Less latency
  • 44.
    Receiving and returningJSON @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.
    Use smaller entities Property names get stored with each entity
  • 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.
    Use fewer indexes Indexes take space Indexes penalize writes
  • 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.
    Avoid composite indexesif possible public class UsersServiceImpl implements UsersService { public List<User> findByEmail(String email) { return entityManager.createQuery(User.class) .equal("email", email) .sortDesc("creationTimestamp") .asList(); } }
  • 50.
    Cache everything Reduces latency Cheaperthan using the datastore @Cacheable(3600) public class User { … }
  • 51.
    Reduce the numberof datastore operations entityManager.put(ImmutableList.of(currentUser, anotherUser, user3)); Map<Key, Object> result = entityManager.get(userKey, showKey, user2Key);
  • 52.
  • 53.
  • 54.
    AppEngine vs AWS Infrastructureas a Service or Platform as a Service Simpler to manage and much harder to develop Lots of limitations apply: read the docs!
  • 55.
    You'll get strongerfrom the experience Think about the limitations as: learn to design the Google way
  • 56.
    Guice vs Spring Less features More flexible Strong design skills required
  • 57.
    Jersey vs <anythingelse> Not the best option for old-style HTML applications Perfect foundation for REST services Use lots of javascript
  • 58.
    SimpleDS vs JPA JPA/JDOis not a good approach to the Datastore Valid options are: Objectify / Twig / SimpleDS
  • 59.
  • 60.