35. Play’s App Looks Like ? conf/routes app/App.java GET /hello App.hello public class App extends Controller { public static void hello() { User user = User.findById(1L); render(user); } } views/App/hello.html Hello World : ${user.name} http://localhost:9000/hello
68. Play's Architecture controllers app MyController.java public static void index() public static void show() models views MyController index.html foo show.html bar
69. conf/routes HTTP method URI Pattern Action 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}
71. 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 } }
72. Controller Interceptions public 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 Cookie public static void login(String name , String passwd) { User u = ... if (...) { session.put("uid" , u.id); } } Only put index data to session Never store sensitive data signed, uneditable!
74. 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.Controller throw new RenderJson(jsonString); throw new RenderXml(xml); throw new RenderTemplate(...);
75. Controller and View controllers/App.java 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) {...} renderArgs.put(“cars”,car); renderArgs.put(“company”,company); renderArgs.put(“job”,job); same views/App/showUser.html Hello ${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.java public 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...
84. Rich Domain Object Model package models; @javax.persistence.Entity public 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 Model BUT... The underlayer is hibernate & hibernate needs getter/setter Again... Who modifies my model ?
86. Rich Domain Object Model In Fact... Your model still contains getter/setter , modified by Play's custom classloader & JDT & javassist User.java public String getUsername() { return "overridden"; } ${user.username} will be ??
87. 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(...);
89. 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(); }
90. Validation : Model public 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. Cache Conventional 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(); }
93. 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; }
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); }
95. Conventional JavaEE's Way User u1 = userDao.getUsers(1,10).get(0); User u2 = userDao.get(1L); assertTrue(u1.equals(u2)); // PASSED u2.modifySomething(...); userDao.save(u2); User u3 = userDao.getUsers(1,10).get(0); assertTrue(u3.equals(u2)); // PASSED Play's Way User u1 = User.getUsers(1,10).get(0); User u2 = User.getUser(1L); assertTrue(u1.equals(u2)); // PASSED u2.modifySomething(...); u2.save(); User u3 = User.getUsers(1,10).get(0); assertTrue(u3.equals(u2)); // FAILED!
96. Cache Problem : Reason users_1_10 cache key : TIME u u u u u u u u u u u1 userId_1 cache key : u modified / updated u2 users_1_10 cache key : ? u u u u u u u u u u3
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 : CRUD package models; public class User extends Model { ... } package controllers; public class Users extends CRUD { ... } Cars , Photos , Logs even ... Boxs , Buss, Kisss
104. 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 !
112. Issue : High Availability <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> Apache Web Server localhost:9002 localhost:9003 Same directory structures & application.secret , only different http.port