codemotion appengine
TRANSCRIPT
Diapositiva 1
Development with Guice,
Jersey and AppEngine
Ignacio Coloma
[email protected] 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
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 {
@Injectprivate 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
com.acme.config.GuiceConfigListener
GuiceFilter com.google.inject.servlet.GuiceFilter GuiceFilter /*
Servlet context listener
public class GuiceConfigListener extends com.google.inject.servlet.GuiceServletContextListener {
@Overrideprotected Injector getInjector() {return Guice.createInjector(new MyWebModule(),new MyServicesModule(),new MyPersistenceModule()); }
}
Services module: explicit bindings
public class MyServicesModule extends com.google.inject.AbstractModule {
@Overrideprotected void configure() {bind(FooBar.class);bind(FooBar.class).to(FooBarImpl.class);bind(MemcacheService.class).toInstance(MemcacheServiceFactory.getMemcacheService());}
}
public class Users {
@Injectprivate FooBar foobar;
@Injectprivate 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 {
@Injectprivate UsersService usersService;
}
Providers
public class MyServicesModule extends com.google.inject.AbstractModule {@Overrideprotected void configure() {bind(User.class).toProvider(CurrentUserProvider.class);}}
@RequestScopedpublic class CurrentUserProvider implements com.google.inject.Provider {
@Injectprivate HttpServletRequest request;
@Injectprivate EntityManager entityManager;
@Override @RequestScopedpublic 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 {
@Overrideprotected void configureServlets() {
// bind resourcesfor (Class resourceClass : getResources()) {bind(resourceClass);}
// jersey customizationserve("/*").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 {
@Injectprivate User currentUser;
@Injectprivate 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, 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 URLthrow new WebApplicationException(e, Status.NOT_FOUND);}}
}
@Path("/users")public class Users {
@Injectprivate 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 {
@Overrideprotected void configure() {this.withPersistentClasses(getPersistentClasses());super.configure();}
private Class[] getPersistentClasses() {return new Class[] { User.class, Vote.class };}
}
Persistent class
public class User {
@Idprivate Key key;
@Property(required=true)private String name;
private String email;
@Transientprivate String hashCode;
}
EntityManager
public class UsersServiceImpl implements UsersService {
@Injectprivate User currentUser;
@Injectprivate 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 findByEmail(String email) {return entityManager.createQuery(User.class).equal("email", email).asList();}
public CursorList 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);}
}
@Singletonpublic class MyExceptionMapper implements ExceptionMapper {
@Overridepublic 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 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 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
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!
@nachocoloma
Demo available:
http://github.com/icoloma/simpleds-kickstart
02/05/12
AUTORE
Nome speaker
Mail speaker company or community
02/05/12
AUTORE