codemotion appengine

Download Codemotion appengine

If you can't read please download the document

Upload: ignacio-coloma

Post on 16-Apr-2017

5.304 views

Category:

Technology


1 download

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

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 {

@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