User.getUser(Long id) and User.getUsers(Long page, int cnt) methods retrieve data from the cache using different keys. So modifying and saving an object retrieved by getUser does not update the cached collection returned by getUsers, leading to inconsistencies. Play's caching at the model level does not consider relationships between entities.
35. Play’s App Looks Like ?conf/routesapp/App.javaGET /hello App.hellopublic class App extends Controller { public static void hello() { User user = User.findById(1L); render(user); }} views/App/hello.htmlHello World : ${user.name}http://localhost:9000/hello
36. And Play is ...A full stack framework (platform)
45. Can be packaged as a WAR and deployed to servlet containersPlay! A Glued Pure Web FrameworkShallow!Most libs are directly exposed to Play! & replaceableWhere Magic Happens
52. http://www.facebook.com/ photo.php? fbid=2121715487568& set=o.172881386106136& type=1&theaterPlay is not based on ServletMany 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);
66. Play's Architecture$ play new myprojmyprojappcontrollersmodelsconfapplication.confviewsmessageslibfooroutesmodulesbarpublicimagesjavascriptssrcstylesheets
71. Controllers and Redirectionspublic 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 }}
72. 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,Stringpasswd){..} public static void welcome() {...}}
73. Session : A Signed Cookiepublic static void login(String name , String passwd) { User u = ... if (...) { session.put("uid" , u.id); }}Only put index data to sessionNever store sensitive datasigned, uneditable!
74. Controller Revisitedpublic 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(...);
75. Controller and Viewcontrollers/App.javapublic static void showUser(Long uid) { User u = ... List<Car> cars = ... renderArgs.put("user",u); render(cars, company, job, ...); }public static void showCar(Long id) {...}renderArgs.put(“cars”,car);renderArgs.put(“company”,company);renderArgs.put(“job”,job);sameviews/App/showUser.htmlHello ${user.name} , these are your cars :#{list items:cars , as:'car'} #{a @showCar(car.id)} ${car.name} #{/a}#{/list}template tag@App.showCar(car.id)
76. How objects are passed to View !?controllers/App.javapublic static void showUser(Long uid) { renderArgs.put("user",u); render(cars , ...); }!!??views/App/showUser.html${user.name}#{list items:cars , as:'car'}Let's decompile it...
83. LocalvariablesNamesEnhancer, ...Impact ?Rapid development time !View Templateobject path${objectname}${object.property}implicit objects${params.userId}${session.userId}${request.userId}built-in tags#{if} ... #{/if} #{else}... #{/else}#{list items:users , as:'user'} ${user.name}#{/list}#{a @App.showUser(user.id)} show user #{/a}#{form @App.login() } ... #{/form}
84. Rich Domain Object Modelpackage 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 !
85. Rich Domain Object ModelBUT...The underlayer is hibernate & hibernate needs getter/setterAgain...Who modifies my model ?
86. Rich Domain Object ModelIn Fact... Your model still contains getter/setter , modified by Play's custom classloader & JDT & javassistUser.javapublic String getUsername() { return "overridden";}${user.username} will be ??
87. Rich Domain ObjectUser 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(...);
89. Validations : Controllerpublic 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();}
90. 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() ?
92. CacheConventional JavaEE's Waypublic 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();}
93. CachePlay's Way : Not In Favor of 2nd Levelpublic 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;}
94. 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);}
100. Cache object ids instead of objectspublic 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}
101. 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);
103. Module : CRUDpackage models;public class User extends Model { ... }package controllers;public class Users extends CRUD { ... }Cars , Photos , Logseven ...Boxs , Buss, Kisss
104. Module GAE + Module Sienapublic 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 !
137. Stateful (Session-aware)Conclusions : Use Play! If You...Have to prototype or build something quicklyDon’t want to buy high-priced Java application serversAre not so OO-purism, feel OK without interfacesMany Play’s “hook” are not enforced by abctract mathods or interfacesKnow JavaScript & other JS frameworksYou can build slick UIs without sluggish server-state implementation responses (Wicket/JSF...)Feel OK about object inconsistences in cacheWant to develop GAE appsYour team have a strong mendiatorBecause programming in play is too unrestrainedWant to learn Scala