Upload
smallufo-huang
View
19.197
Download
0
Embed Size (px)
DESCRIPTION
Play! Framework for JavaEE Developers Presented in JavaTWO Taiwan @ Jul 29,2011
Citation preview
JavaTWO 專業技術大會
Play!Framework for JavaEE Developers
Who am I
Exp : JSP/Servlet , Tapestry , Spring/Hibernate/JavaEE , Wicket , Android , Grails , Play! Framework
http://gplus.to/smallufohttp://twitter.com/[email protected]
Web Framework Experiences• JSP/Servlet
o Too old , cumbersome• Tapestry (old experience, T5 is very good)
o Complicated , evolution/deprecation too fast• Wicket
o Too elaborate , fail to see the wood for the treeso May lead to over-engineered architecture for OO-
purism Spend too much time refactoring
o Maybe web component reuse is just a myth• Grails (old experience, without Groovy++)
o Slowo Too many inconsistent DSLs (URL-mapping , DB ,
logging...)o Not Java , IDE unfriendly
Conventional JavaEE Stack
Conventional JavaEE Stack• Want standard page ?
o Let's define JSP/JSTL (JSR-53)o And let Apache (or others) to implement
Jakata Taglibs• Want web components ?
o Let's define Java Server Faces (JSR-127)o And let communities to implement MyFaces,
PrimeFaces, ICEfaces...• Want restful ?
o Let's define JAX-RS (JSR-311)o And let communities to implement CXF, Jersey,
RESTEasy, Wink...
Conventional JavaEE Stack• Lots of ...
o Specso Implementationso Configurations
• Tedious , Error-proneo Unless you use a full-fledged JavaEE
Server (Glassfish or WebLogic ...)• Layered
o Defined by Standards/Specso Assembled by Interfaces
OVER ARCHITECTED
Play’s App Looks Like ?
views/App/hello.html
Hello World : ${user.name}
conf/routes
GET /hello App.hello
http://localhost:9000/hello
app/App.javapublic class App extends Controller { public static void hello() { User user = User.findById(1L); render(user); }}
And Play is ...
• A full stack framework (platform)• Totally independent of JavaEE environment• RESTful• No session• Stateless• Pure server side• Similar to Rails / Django• Rich domain model
o Unlike JavaEE's anemic domain model
Full Stack Framework?
• Bundles compiler , embedded server , hibernate , logger , test runner , email , groovy template engine, scala...
• Can be packaged as a WAR and deployed to servlet containers
Play! A Glued Pure Web Framework
Where Magic Happens
Shallow!
Most libs are directly exposed to Play! & replaceable
Play! URLs are RESTful & SEO-friendly
• /car.jsp?id=1• /listcars.jsp?
page=1&count=10• http://www.facebook.com/
photo.php? fbid=2121715487568& set=o.172881386106136& type=1&theater
• /car/1• /listcar/page1/count10• /listcar/page1?count=10• /listcar/page/1/count/10
UGLY GRACEFUL
Play is not based on Servlet
Many java server side frameworks (Spring MVC / Struts / Wicket / JSF / Tapestry ...) are based on servlets, but Play! is not!
You cannot do such things :
• session.setAttribute("user" , user); • Use HttpSessionListener to count sessions (online
users)• ServletFilter• Servlet-related securities
o @ServletSecurityo <auth-constraint /> , <security-role />
Play! is Stateless
• Play! shares nothing between each request!
• The most important feature you have to keep in mind
• No session!• Session is the source of all problems of
the JavaEE platform!o Session replication o Sticky session
• Play uses (delegates to) memcached to eliminates such headaches
Play's Informative Error Page
Play's Domain Objects Are Rich*Spring promoted JavaEE Play
User extends Object
UserDao.java• save(User u)
UserDaoImpl implements UserDao• void setEntityManager(...)• save(User u)
Client.java@Inject UserDao userDaoUser u = new User(...)userDao.save(u)
User u = new User(...)u.save()
User extends Model
VS*JPA advocates eliminating the DAO layer
Play's Architecture
myproj
app
conf
lib
modules
public
application.conf
messages
routes
controllers
models
foo
views
src
images
javascripts
stylesheets
bar
$ play new myproj
conf/application.confapplication.name=myappapplication.mode=devapplication.secret=OOXXOOXXOOXXOOXXOOXXhttp.port=9000java.source=1.6
db.url=jdbc:mysql:...db.driver=com.mysql.jdbc.Driverdb.user=userdb.pass=pass
hibernate.use_sql_comments=truehibernate.format_sql = true
XForwardedSupport=127.0.0.1
Play's Architecture
app controllers
models
foo
views
bar
MyController.java
MyController
public static void index()public static void show()
index.html
show.html
conf/routes
GET / App.index
POST /login App.login
GET /tag/{name} App.tag(name)
GET /list/{page} App.list(page)GET /list/{<[0-9]+>page} App.list(page)
GET /list/{page}/{count} App.list(page,count)
GET /public/ staticDir:public* /{controller}/{action} {controller}.{action}
HTTP method
URI Pattern Action
Play's Request Life Cycle
Controllers and Redirections
public class App extends play.mvc.Controller {
public static void index() { render(); // views/App/index.html contains a login form }
public static void login(String name,String passwd) { if(...) // success welcome(); else // failed index(); }
public static void welcome() { render(); // renders views/App/welcome.html }}
Controller Interceptionspublic class App extends play.mvc.Controller {
@play.mvc.Before(unless={“index”, “login”}) public static void intercept() { if (session.get("uid")==null) index(); }
public static void index() {...}
public static void login(String name,String passwd){..}
public static void welcome() {...}}
Session : A Signed Cookiepublic static void login(String name , String passwd) { User u = ... if (...) { session.put("uid" , u.id); }}
signed, uneditable!
Only put index data to sessionNever store sensitive data
Controller Revisited
public static void show(Long uid, String type) { User u = ... if (type.equals("json") { renderJSON(u); // provided by GSON } else if (type.equals("xml") { renderXml(u); // provided by XStream } render(u); }
Why not continue rendering?
play.mvc.Controllerthrow new RenderJson(jsonString);throw new RenderXml(xml);throw new RenderTemplate(...);
Controller and View
public static void showUser(Long uid) { User u = ... List<Car> cars = ... renderArgs.put("user",u); render(cars, company, job, ...); }public static void showCar(Long id) {...}
Hello ${user.name} , these are your cars :
#{list items:cars , as:'car'} #{a @showCar(car.id)} ${car.name} #{/a}#{/list}
views/App/showUser.html
controllers/App.java
@App.showCar(car.id)
template tag
renderArgs.put(“cars”,car);renderArgs.put(“company”,company);renderArgs.put(“job”,job);
same
How objects are passed to View !?
public static void showUser(Long uid) { renderArgs.put("user",u); render(cars , ...); }
${user.name}#{list items:cars , as:'car'}
!!??
Let's decompile it...
controllers/App.java
views/App/showUser.html
public static void showUser(Long uid){ play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.enter(); play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.addVariable("uid", uid); if(!play.classloading.enhancers.ControllersEnhancer.ControllerInstrumentation.isActionCallAllowed()) { Controller.redirect("controllers.App.showUser", new Object[] { uid }); } else { play.classloading.enhancers.ControllersEnhancer.ControllerInstrumentation.stopActionCall(); User u = (User)User.findById(uid); play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.addVariable("u", u); List cars = Car.all().fetch(); play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.addVariable("cars", cars); Object obj = null; play.mvc.Scope.RenderArgs renderargs = null; renderargs = (play.mvc.Scope.RenderArgs)Java.invokeStatic(Desc.getType("Lplay/mvc/Scope$RenderArgs;"), "current"); renderargs.put("user", u); render(new Object[] { cars }); } break MISSING_BLOCK_LABEL_120; Exception exception; exception; Object obj1 = null; play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.exit(); throw exception; Object obj2 = null; play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.exit(); return;}
Who Modifies My Code !?
Who ?• Play's custom classloader & JDT & javassist
When ?• DEV mode : When you modify the code and reload the page• PROD mode : App's start time
How ?• At compile time , some classes are enhanced by
play.classloading.enhancers.* (powered by javassist)• ContinuationEnhancer• ControllerEnhancer• PropertiesEnhancer• LocalvariablesNamesEnhancer, ...
Impact ?• Rapid development time !
View Template
${objectname}${object.property}
object path
implicit objects${params.userId}${session.userId}${request.userId}
#{if} ... #{/if} #{else}... #{/else}#{list items:users , as:'user'} ${user.name}#{/list}#{a @App.showUser(user.id)} show user #{/a}#{form @App.login() } ... #{/form}
built-in tags
Rich Domain Object Model
package models;
@javax.persistence.Entitypublic class User extends play.db.jpa.Model { public String username; public String password;}
Support JPA's annotations : @Column , @ManyToOne , @OneToMany ...
NO more getters & setters... Great !
Rich Domain Object Model
BUT...The underlayer is hibernate & hibernate needs getter/setter
Again...Who modifies my model ?
Rich Domain Object Model
In Fact... Your model still contains getter/setter , modified by Play's custom classloader & JDT & javassist
User.javapublic String getUsername() { return "overridden";}
${user.username} will be ??
Rich Domain Object
User u = User.findById(1);
User u = User.find("byUsernameAndPassword", username , password).first();
User u = User.find("select u from User u where u.username = :username and u.password = :password") .bind("username",username) .bind("password",password) .first();
List<User> users = User.all().fetch();
User.em().createQuery(...);
Validations : Controller#{form @App.login()} <p> username <input type=“text” name=“username” value=“${flash.username}” /> <span class=“error”>#{error 'username'/}</span> </p> <p> password <input type=“password” name=“password” value=“${flash.password}” /> <span class=“error”>#{error 'password'/}</span> </p> <input type="submit" value=“Login" /> <span class=“error”>#{error ‘other'/}</span>#{/form}
username
password
Login
請輸入帳號
請輸入密碼
帳號或密碼輸入錯誤
Validations : Controller
public static void login( @Required(message ="請輸入帳號 ") String username, @Required(message ="請輸入密碼 ") String password) { User user = User.login(username , password); if (validation.hasErrors()){ params.flash(); // add parameters to flash scope validation.keep(); // keeps the errors flash.error(validation.errors().get(0).toString()); render(“pleaseLogin.html”); } flash.success(“welcome : “ + user.username); render();}
Validation : Modelpublic class User extends Model { @Required @MinSize(6) public username; @Required @MinSize(6) public password; public static User login(String username ,
String password) { Validation validation = Validation.current(); User user = ... validation.isTrue(user!=null) .key(“other”).message(“帳號或密碼輸入錯誤” ); return user;}}
Will @MinSize affect login() ?
Validations
• @Required , @Min , @Max , @MinSize , @MaxSize , @Range , @Email , @URL ...
• Custom validation annotation– extends AbstractAnnotationCheck & implements
isSatisfied()
• i18n messages– /conf/messages
CacheConventional JavaEE's Way
public User getUser(String name) { Session s = (Session)em.getDelegate(); Criteria c = s.createCriteria(User.class); c.add(Restrictions.eq("username",name); c.setMaxResults(1); c.setCacheable(true); if (c.uniqueResult() == null) return null; return (User) c.uniqueResult();}
Cache
Play's Way : Not In Favor of 2nd Level
public static User getUser(String name) { String key="username_"+name; User user = Cache.get(key,User.class); if (user != null) return user; user = User.find("byUsername",name).first(); Cache.set(key,user,"30mn"); return user;}
Cache - Problem !User.java { public static User getUser(Long id) { String key = "userId_"+id; ... }
public static List<User> getUsers(Long page, int cnt) { String key="users_"+page+"_"+cnt; ... }}
public interface UserDao.java { public User getUser(Long id); public List<User> getUsers(Long page, int cnt);}
Conventional JavaEE's WayUser u1 = userDao.getUsers(1,10).get(0);User u2 = userDao.get(1L);assertTrue(u1.equals(u2)); // PASSEDu2.modifySomething(...);userDao.save(u2);
User u3 = userDao.getUsers(1,10).get(0);assertTrue(u3.equals(u2)); // PASSED
Play's WayUser u1 = User.getUsers(1,10).get(0);User u2 = User.getUser(1L);assertTrue(u1.equals(u2)); // PASSEDu2.modifySomething(...);u2.save();
User u3 = User.getUsers(1,10).get(0);assertTrue(u3.equals(u2)); // FAILED!
Cache Problem : Reason users_1_10cache key :
u u u u u u u u u u
u1
userId_1cache key :
u
u2
modified / updated
users_1_10cache key :
? u u u u u u u u u
u3
TIME
Cache - Problem! How to Solve It ?• Ignore it , accept it• Never cache sensitive data• Two phase list retrieval
o Cache object ids instead of objects
public static List<User> getUsers(Long page, int cnt) { String key="users_"+page+"_"+cnt; List<Long> userIds = User.find("select u.id from User u) .fetch(page,cnt); Cache.set(key, userIds, "1mn"); // iterate each id in result and query cache or fetch}
Cache : Wait... I saw Model.em() ?
How about get underlaying Hibernate’s session and setCacheable(true) ?
Session s = (Session) User.em().getDelegate();Critieria c = s.createCriteria(...);c.add(... criterions ... );
c.setCacheable(true);
Play's Module System
Module : CRUD
package models;public class User extends Model { ... }
package controllers;public class Users extends CRUD { ... }
Cars , Photos , Logseven ...Boxs , Buss, Kisss
Module GAE + Module Siena
public class User extends siena.Model { public String uid; public static User getUser(String uid) { return User.all(User.class).filter("uid",uid).get(); } public static User getUsers(int page , int count) { return User .all(User.class) .fetch(count,(page-1)*count); }}
Don't forget war/WEB-INF/appengine-web.xml$ play gae:deploy
Done !
Play on GAE
• Simplest way to deploy Java Apps on GAE !• Shortest cold start time
o Less than 10 seconds• Cache is wrapped to GAE's memcache• Mail is wrapped to GAE's mail service
Issue : Portal-like Page
• Many blocks query DB in every page• Passing these query results in every
action is cumbersome• Solution : @Before and renderArgs.put()
@Beforestatic void addDefaults() { renderArgs.put("brands", Brand.all().fetch()); renderArgs.put("forums", Forum.getAll()); renderArgs.put("tags" , Tag.getAll());}
Issue : High Availability
localhost:9002 localhost:9003
Apache Web Server
<VirtualHost *:80> ServerName myapp.com <Location /balancer-manager> SetHandler balancer-manager Order Deny,Allow Deny from all Allow from .myapp.com </Location> <Proxy balancer://mycluster> BalancerMember http://localhost:9002 BalancerMember http://localhost:9003 status=+H </Proxy> <Proxy *> Order Allow,Deny Allow From All </Proxy> ProxyPreserveHost on ProxyPass /balancer-manager ! ProxyPass / balancer://mycluster/ ProxyPassReverse / http://localhost:9002/ ProxyPassReverse / http://localhost:9003/</VirtualHost>
Same directory structures & application.secret , only different http.port
Issue : Action Burst
login()logout()
register()myaccount()mybooks()
index()page()
search()showBook()listUsers()editUser()editBook()
controllers/Appcontrollers/Applogin()
logout()register()
myaccount()mybooks()
index()page()
search()showbook()
controllers/Admin
listUsers()editUser()editBook()
Issues : Validation Dilemma
Validation in controllers ?
validation in models ?
or hybrid ?
Issue : DI
• Dependency Injection is not so useful in Play’s Rich Domain Object environment
Case Study : VAGTW.COMVW/Audi Car Problem Stats
vagtw.com Data Model
VAGTW.com development
• Dev in one month (Since 2010/4/1)o Modified from Play's YABE sampleo 10 days learning Play! & modeling & codingo 20 days tuning HTML & CSS(3)
• 2010/5/1 Debuto Peak 4x users in 15 minso Indexed by Google in 2 dayso Indexed by Yahoo in 2 weekso Indexed by Bing after 2 months
Conclusions
IS ...• A Glued Pure Web
Frameworko Looseo Shallowo non-Standardized
• Page-based• Stateless
IS-NOT ...• A Java library• Full-Fledged JavaEE stack
o Standardo Stricto Defined-assembled by
interfaces• Component-based• Stateful (Session-aware)
Conclusions : Use Play! If You...• Have to prototype or build something quickly• Don’t want to buy high-priced Java application servers• Are not so OO-purism, feel OK without interfaces
– Many Play’s “hook” are not enforced by abctract mathods or interfaces
• Know JavaScript & other JS frameworks– You can build slick UIs without sluggish server-state
implementation responses (Wicket/JSF...)• Feel OK about object inconsistences in cache• Want to develop GAE apps• Your team have a strong mendiator
– Because programming in play is too unrestrained
• Want to learn Scala
回不去了小心