Your SlideShare is downloading. ×
0
PLAY VS. GRAILSSMACKDOWNJAMES WARD AND MATT RAIBLEandChangelogMarch 24, 2013: Updated and for .June 24, 2012: .June 19, 20...
WHY A SMACKDOWN?Play 2 and Grails 2 are often hyped as the most productiveJVM Web Frameworks.* We wanted to know how they ...
HAPPY TRAILS REQUIREMENTSServer-side TemplatesPlay 2 with JavaForm ValidationData PaginationAuthenticationScheduled JobsAt...
OUR SCHEDULEWeek 1 - Data Model DefinitionWeek 2 - Data Layer & URL DesignWeek 3 - Controllers & AuthWeek 4 - ViewsWeek 5 ...
INTRO TO PLAY 2
“Play is based on a lightweight, stateless, web-friendly architecture and features predictableand minimal resource consump...
MY TOP 10 FAVORITE FEATURES1. Simple2. URL Routing3. Class Reloading4. Share-Nothing5. Java & Scala Support6. Great Testin...
INTRO TO GRAILS 2
“ Powered by Spring, Grails outperforms thecompetition. Dynamic, agile web developmentwithout compromises. ”
MY TOP 10 FAVORITE FEATURES1. Documentation2. Clean URLs3. GORM4. IntelliJ IDEA Support5. Zero Turnaround6. Excellent Test...
OUR SETUPIntelliJ IDEA for DevelopmentGitHub for Source ControlCloudBees for Continuous IntegrationHeroku for ProductionLa...
CODE WALK THROUGHWe developed the same app, in similar ways, so lets look atthe different layers.↓DatabaseURL MappingModel...
DATABASE - GRAILSHibernate is the default persistence providerCreate models, Hibernate creates the schema for yougrails-ap...
DATABASE - PLAYEBean is the default persistence provider in Java projectsEvolutions can be auto-appliedInitial evolution s...
DATABASE - PLAYUsing Heroku Postgres in production rocks!Postgres 9.1DataclipsFork & FollowMulti-Ingres
URL MAPPING - GRAILSgrails-app/conf/UrlMappings.groovyclass UrlMappings {static mappings = {"/$controller/$action?/$id?" {...
URL MAPPING - PLAYconf/routesGET / controllers.ApplicationController.index()GET /signup controllers.ApplicationController....
MODELS - GRAILSAll properties are persisted by defaultConstraintsMappings with hasMany and belongsToOverride methods for l...
MODELS - PLAYEBean + JPA AnnotationsDeclarative Validations (JSR 303)Query DSLLazy Loading (except in Scala Templates)app/...
CONTROLLERS - GRAILSgrails-app/controllers/happytrails/HomeController.groovypackage happytrailsimport org.grails.comments....
CONTROLLERS - PLAYStateless - Composable - InterceptableClean connection between URLs and response codeapp/controllers/Rou...
VIEWS - GRAILSGroovy Server Pages, like JSPsGSP TagsLayouts and TemplatesOptimized Resourcesgrails-app/views/region/show.g...
VIEWS - PLAYScala TemplatesComposableCompiledapp/views/region.scala.html@(region: Region, sort: String)@headContent = {<li...
VALIDATION - GRAILSgrails-app/controllers/happytrails/RouteController.groovydef save() {def routeInstance = new Route(para...
VALIDATION - PLAYapp/controllers/RouteController.javapublic static Result signup() {Form<User> signupForm = form(User.clas...
IDE SUPPORT - GRAILSIntelliJ Rocks!
IDE SUPPORT - PLAY$ play idea$ play eclipsifyJava!!!Debugging Support via Remote DebuggerLimited Testing within IDE
JOB - GRAILSgrails-app/jobs/happytrails/DailyRegionDigestEmailJob.groovypackage happytrailsimport org.grails.comments.Comm...
JOB - PLAYPlain old `static void main`Independent of web appapp/jobs/DailyRegionDigestEmailJob.javapublic class DailyRegio...
FEED - GRAILS1. grails install-plugin feeds2. Add feed() method to controllergrails-app/controllers/happytrails/RegionCont...
FEED - PLAYNo direct RSS/Atom supportDependency: "rome" % "rome" % "1.0"app/jobs/DailyRegionDigestEmailJob.javaRegion regi...
EMAIL - GRAILS* powered by the (built-in)grails-app/jobs/happytrails/DailyRegionDigestEmailJob.groovyprintln "Sending dige...
EMAIL - PLAYSendGrid Heroku Add-onDependency:"com.typesafe" %% "play-plugins-mailer" % "2.0.2"app/jobs/DailyRegionDigestEm...
PHOTO UPLOAD - GRAILS
PHOTO UPLOAD - PLAYAmazon S3 for Persistent File Storageapp/models/S3Photo.javaPutObjectRequest putObjectRequest = new Put...
TESTING - GRAILSUnit Tests with @TestFor and @MockTest URL Mappings with UrlMappingsUnitTestMixinIntegration Testing with ...
TESTING - PLAYStandard JUnitUnit Tests & Functional TestsFakeApplication, FakeRequest, inMemoryDatabaseTest: Controllers, ...
DEMO DATA - GRAILSgrails-app/conf/BootStrap.groovyimport happytrails.Userimport happytrails.Regionimport happytrails.Route...
DEMO DATA - PLAYapp/Global.javapublic void onStart(Application application) {//Ebean.getServer(null).getAdminLogging().set...
CONFIGURATION - GRAILSgrails-app/conf/Config.groovygrails.app.context = "/"grails.project.groupId = appName // change this...
CONFIGURATION - PLAYBased on the TypeSafe Config LibraryOverride config with Java Properties:-Dfoo=barEnvironment Variable...
AUTHENTICATION - GRAILSSpring Security UI Plugin, “I love you!”grails-app/conf/Config.groovygrails.mail.default.from = "Bi...
AUTHENTICATION - PLAYUses cookies to remain statelessapp/controllers/Secured.javapublic class Secured extends Security.Aut...
APPLICATION COMPARISONYSlowPageSpeedLines of CodeLoad TestingWhich Loads Faster?Security Testing
YSLOW
PAGESPEED
LINES OF CODE
LOAD TESTING WITH BROWSERMOB
Bike (Grails) Hike (Play)LOAD TESTING - 1 DYNO
LOAD TESTING - 1 DYNO
Bike (Grails) Hike (Play)LOAD TESTING - 5 DYNOS
LOAD TESTING - 5 DYNOS
LOAD TESTING - 5 DYNOSGrailsPlay Framework0 700 1,400 2,100 2,800Requests / Second
LOAD TESTING - 5 DYNOS + 100 USERS
WHICH LOADS FASTER?
WHICH ACTUALLY LOADS FASTER?
PEN TESTING WITH OWASP ZAP
GRAILS 2 VS. PLAY 2JobsLinkedIn SkillsGoogle TrendsIndeedMailing List TrafficBooks on Amazon2012 ReleasesStack OverflowHac...
JOBSMarch 19, 2013GrailsPlay FrameworkSpring MVC0 400 800 1,200 1,600DiceMonsterIndeed
LINKEDIN SKILLSMarch 19, 2013# People0 4,000 8,000 12,000 16,000GrailsPlay FrameworkSpring MVC
GOOGLE TRENDSGrails Play
INDEED JOB TRENDS
USER MAILING LIST TRAFFICJune 2012GrailsPlay Framework800 1,600 2,400 3,200 4,000MarchAprilMay
USER MAILING LIST TRAFFICMarch 2013GrailsPlay Framework800 1,100 1,400 1,700 2,000DecemberJanuaryFebruary
BOOKS ON AMAZONMarch 2013GrailsPlay Framework38
2013 RELEASESGrailsPlay Framework1 1
2012 RELEASESGrailsPlay Framework69
STACKOVERFLOW QUESTIONSgrailsplayframework395010710
HACKER NEWSGrails 2.0 releasedPlay Framework 2.0 Final released6195
CONCLUSIONS: CODEFrom a code perspective, very similarframeworks.Code authoring good in both.Grails Plugin Ecosystem is ex...
CONCLUSIONS: STATISTICAL ANALYSISGrails has better support for FEO (YSlow,PageSpeed)Grails has less LOC! (4 more files, bu...
CONCLUSIONS: ECOSYSTEM ANALYSIS"Play" is difficult to search for.Grails is more mature.Play has momentum issues.LinkedIn: ...
QUESTIONS?Source:Branches: grails2, play2_javaPresentation*: master/presoContact Us:* Presentation created with , and .htt...
ACTION!Learn something new*!* Or prove that were wrong...
Play vs Grails Smackdown - Devoxx France 2013
Play vs Grails Smackdown - Devoxx France 2013
Play vs Grails Smackdown - Devoxx France 2013
Play vs Grails Smackdown - Devoxx France 2013
Play vs Grails Smackdown - Devoxx France 2013
Play vs Grails Smackdown - Devoxx France 2013
Upcoming SlideShare
Loading in...5
×

Play vs Grails Smackdown - Devoxx France 2013

5,664

Published on

The Play vs. Grails Smackdown. A comparison done by James Ward and Matt Raible. Includes detailed analysis from building the same webapp with these two popular JVM Web Frameworks.

See blog post about this presentation at http://raibledesigns.com/rd/entry/devoxx_france_a_great_conference.

Published in: Technology

Transcript of "Play vs Grails Smackdown - Devoxx France 2013"

  1. 1. PLAY VS. GRAILSSMACKDOWNJAMES WARD AND MATT RAIBLEandChangelogMarch 24, 2013: Updated and for .June 24, 2012: .June 19, 2012: Published for .@_JamesWard @mraiblestatistics load tests Devoxx FrancePlay Performance FixÜberConf
  2. 2. WHY A SMACKDOWN?Play 2 and Grails 2 are often hyped as the most productiveJVM Web Frameworks.* We wanted to know how they enhanced the Developer Experience (DX).
  3. 3. HAPPY TRAILS REQUIREMENTSServer-side TemplatesPlay 2 with JavaForm ValidationData PaginationAuthenticationScheduled JobsAtom / RSSEmail NotificationsUnit / Integration TestsLoad TestsPerformance TestsStretch Goals: Search, Photo Upload to S3
  4. 4. OUR SCHEDULEWeek 1 - Data Model DefinitionWeek 2 - Data Layer & URL DesignWeek 3 - Controllers & AuthWeek 4 - ViewsWeek 5 - Misc Polish
  5. 5. INTRO TO PLAY 2
  6. 6. “Play is based on a lightweight, stateless, web-friendly architecture and features predictableand minimal resource consumption (CPU,memory, threads) for highly-scalableapplications - thanks to its reactive model,based on Iteratee IO.”
  7. 7. MY TOP 10 FAVORITE FEATURES1. Simple2. URL Routing3. Class Reloading4. Share-Nothing5. Java & Scala Support6. Great Testing Support7. JPA/EBean Support8. NIO Server (Netty)9. Asset Compiler10. Instant Deployment on Heroku
  8. 8. INTRO TO GRAILS 2
  9. 9. “ Powered by Spring, Grails outperforms thecompetition. Dynamic, agile web developmentwithout compromises. ”
  10. 10. MY TOP 10 FAVORITE FEATURES1. Documentation2. Clean URLs3. GORM4. IntelliJ IDEA Support5. Zero Turnaround6. Excellent Testing Support7. Groovy8. GSPs9. Resource Optimizer10. Instant Deployment on Heroku
  11. 11. OUR SETUPIntelliJ IDEA for DevelopmentGitHub for Source ControlCloudBees for Continuous IntegrationHeroku for ProductionLater added: QA Person and BrowserMob
  12. 12. CODE WALK THROUGHWe developed the same app, in similar ways, so lets look atthe different layers.↓DatabaseURL MappingModelsControllersViewsValidationIDE SupportJobFeedEmailPhoto UploadTestingDemo DataConfigurationAuthentication
  13. 13. DATABASE - GRAILSHibernate is the default persistence providerCreate models, Hibernate creates the schema for yougrails-app/conf/DataSource.groovyenvironments {development {dataSource {dbCreate = "create-drop" // one of create, create-drop, update, validate, url = "jdbc:postgresql://localhost:5432/happytrails"}}
  14. 14. DATABASE - PLAYEBean is the default persistence provider in Java projectsEvolutions can be auto-appliedInitial evolution sql is auto-createdSubsequent changes must be versionedAuto-created schema file is database dependentPlay 2 supports multiple datasources (Play 1 does not)conf/evolutions/default/2.sql# --- !UpsALTER TABLE account ADD is_admin boolean;UPDATE account SET is_admin = FALSE;# --- !DownsALTER TABLE account DROP is_admin;
  15. 15. DATABASE - PLAYUsing Heroku Postgres in production rocks!Postgres 9.1DataclipsFork & FollowMulti-Ingres
  16. 16. URL MAPPING - GRAILSgrails-app/conf/UrlMappings.groovyclass UrlMappings {static mappings = {"/$controller/$action?/$id?" {constraints {// apply constraints here}}"/"(controller: "home", action: "index")"/login"(controller: "login", action: "auth")"/login/authfail"(controller: "login", action: "authfail")"/login/denied"(controller: "login", action: "denied")"/logout"(controller: "logout")"/signup"(controller: "register")"/feed/$region"(controller: "region", action: "feed")"/register/register"(controller: "register", action: "register")"/register/resetPassword"(controller: "register", action: "resetPassword")"/register/verifyRegistration"(controller: "register", acti
  17. 17. URL MAPPING - PLAYconf/routesGET / controllers.ApplicationController.index()GET /signup controllers.ApplicationController.signupForm()POST /signup controllers.ApplicationController.signup()GET /login controllers.ApplicationController.loginForm()POST /login controllers.ApplicationController.login()GET /logout controllers.ApplicationController.logout()GET /addregion controllers.RegionController.addRegion()POST /addregion controllers.RegionController.saveRegion()GET /:region/feed controllers.RegionController.getRegionFeed(region)GET /:region/subscribe controllers.RegionController.subscribe(region)GET /:region/unsubscribe controllers.RegionController.unsubscribe(region)GET /:region/addroute controllers.RegionController.addRoute(region)POST /:region/addroute controllers.RegionController.saveRoute(region)GET /:region/delete controllers.RegionController.deleteRegion(region)GET /:region/:route/rate controllers.RouteController.saveRating(region, route, rating: java.lang.Integer)POST /:region/:route/comment controllers.RouteController.saveComment(region, route)GET /:region/:route/delete controllers.RouteController.deleteRoute(region, route)GET /:region/:route controllers.RouteController.getRouteHtml(region, route)GET /:region controllers.RegionController.getRegionHtml(region, sort ?= "name")
  18. 18. MODELS - GRAILSAll properties are persisted by defaultConstraintsMappings with hasMany and belongsToOverride methods for lifecycle eventsgrails-app/domain/happytrails/Region.groovypackage happytrailsclass Region {static charactersNumbersAndSpaces = /[a-zA-Z0-9 ]+/static searchable = truestatic constraints = {name blank: false, unique: true, matches: charactersNumbersAndSpacesseoName nullable: trueroutes cascade:"all-delete-orphan"}static hasMany = [ routes:Route ]
  19. 19. MODELS - PLAYEBean + JPA AnnotationsDeclarative Validations (JSR 303)Query DSLLazy Loading (except in Scala Templates)app/models/Direction.java@Entitypublic class Direction extends Model {@Idpublic Long id;@Column(nullable = false)@Constraints.Requiredpublic Integer stepNumber;@Column(length = 1024, nullable = false)@Constraints.MaxLength(1024)@Constraints.Requiredpublic String instruction;
  20. 20. CONTROLLERS - GRAILSgrails-app/controllers/happytrails/HomeController.groovypackage happytrailsimport org.grails.comments.Commentclass HomeController {def index() {params.max = Math.min(params.max ? params.int(max) : 10,100)[regions: Region.list(params), total: Region.count(),comments: Comment.list(max: 10, sort: dateCreated, order: desc)]}}
  21. 21. CONTROLLERS - PLAYStateless - Composable - InterceptableClean connection between URLs and response codeapp/controllers/RouteController.java@With(CurrentUser.class)public class RouteController extends Controller {@Security.Authenticated(Secured.class)public static Result saveRating(String urlFriendlyRegionName, String urlFriendlyRouteName, Integer rating) {User user = CurrentUser.get();Route route = getRoute(urlFriendlyRegionName, urlFriendlyRouteName);if ((route == null) || (user == null)) {return badRequest("User or Route not found");}if (rating != null) {Rating existingRating = Rating.findByUserAndRoute(user, route);if (existingRating != null) {existingRating.value = rating;existingRating.save();}else {Rating newRating = new Rating(user, route, rating);newRating.save();}}return redirect(routes.RouteController.getRouteHtml(urlFriendlyRegionName, urlFriendlyRouteName));}
  22. 22. VIEWS - GRAILSGroovy Server Pages, like JSPsGSP TagsLayouts and TemplatesOptimized Resourcesgrails-app/views/region/show.gsp<%@ page import="happytrails.Region" %><!doctype html><html><head><meta name="layout" content="main"><g:set var="entityName" value="${message(code: region.label,default: Region)}"/><title><g:message code="default.show.label" args="[entityName]"/></title><link rel="alternate" type="application/atom+xml" title="${regionInstance.name} Updates"href="${createLink(controller: region, action: feed,params: [region: regionInstance.seoName])}"/></head>
  23. 23. VIEWS - PLAYScala TemplatesComposableCompiledapp/views/region.scala.html@(region: Region, sort: String)@headContent = {<link rel="alternate" type="application/rss+xml" title="@region.getName RSS Feed" href="@routes.RegionController.getRegionFeed(region.getUrlFriendlyName)" />}@breadcrumbs = {<div class="nav-collapse"><ul class="nav"><li><a href="@routes.ApplicationController.index()">Hike</a></li><li class="active"><a href="@routes.RegionController.getRegionHtml(region.getUrlFriendlyName)">@region.getName</a></li></ul></div>
  24. 24. VALIDATION - GRAILSgrails-app/controllers/happytrails/RouteController.groovydef save() {def routeInstance = new Route(params)if (!routeInstance.save(flush: true)) {render(view: "create", model: [routeInstance: routeInstance])return}flash.message = message(code: default.created.message, args: [message(code: route.label, default: Route), routeInstance.name])redirect(action: "show", id: routeInstance.id)}grails-app/views/route/create.gsp<g:hasErrors bean="${routeInstance}"><div class="alert alert-error" role="alert"><g:eachError bean="${routeInstance}" var="error"><div <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></div></g:eachError></div></g:hasErrors>
  25. 25. VALIDATION - PLAYapp/controllers/RouteController.javapublic static Result signup() {Form<User> signupForm = form(User.class).bindFromRequest();if (signupForm.hasErrors()) {return badRequest(views.html.signupForm.render(signupForm));}app/views/signupForm.scala.html@if(signupForm.hasGlobalErrors) {<p class="error alert alert-error">@signupForm.globalError.message</p>}@helper.form(action = routes.ApplicationController.signup(), class -> "form-horizontal") {@helper.inputText(signupForm("fullName"), _label -> "Full Name")@helper.inputText(signupForm("emailAddress"), _label -> "Email Address")@helper.inputPassword(signupForm("password"), _label -> "Password")<div class="controls"><input type="submit" class="btn btn-primary" value="Create Account"/></div>}
  26. 26. IDE SUPPORT - GRAILSIntelliJ Rocks!
  27. 27. IDE SUPPORT - PLAY$ play idea$ play eclipsifyJava!!!Debugging Support via Remote DebuggerLimited Testing within IDE
  28. 28. JOB - GRAILSgrails-app/jobs/happytrails/DailyRegionDigestEmailJob.groovypackage happytrailsimport org.grails.comments.Commentclass DailyRegionDigestEmailJob {def mailServicestatic triggers = {//simple repeatInterval: 5000l // execute job once in 5 secondscron name:cronTrigger, startDelay:10000, cronExpression:0 0 7 ? * MON-FRI // 7AM Mon-Fri}def execute() {List<RegionUserDigest> digests = getRegionUserDigests()for (digest in digests) {
  29. 29. JOB - PLAYPlain old `static void main`Independent of web appapp/jobs/DailyRegionDigestEmailJob.javapublic class DailyRegionDigestEmailJob {public static void main(String[] args) {Application application = new Application(new File(args[0]), DailyRegionDigestEmailJob.class.getClassLoader(), null, Mode.Prod());Play.start(application);List<RegionUserDigest> regionUserDigests = getRegionUserDigests();
  30. 30. FEED - GRAILS1. grails install-plugin feeds2. Add feed() method to controllergrails-app/controllers/happytrails/RegionController.groovydef feed = {def region = Region.findBySeoName(params.region)if (!region) {response.status = 404return}render(feedType: "atom") {title = "Happy Trails Feed for " + region.namelink = createLink(absolute: true, controller: region, action: feed, params: [region, region.seoName])description = "New Routes and Reviews for " + region.nameregion.routes.each() { route ->entry(route.name) {link = createLink(absolute: true, controller: route, action: show, id: route.id)
  31. 31. FEED - PLAYNo direct RSS/Atom supportDependency: "rome" % "rome" % "1.0"app/jobs/DailyRegionDigestEmailJob.javaRegion region = Region.findByUrlFriendlyName(urlFriendlyRegionName);SyndFeed feed = new SyndFeedImpl();feed.setFeedType("rss_2.0");feed.setTitle("Uber Tracks - " + region.getName());feed.setLink("http://hike.ubertracks.com"); // todo: externalize URLfeed.setDescription("Updates for Hike Uber Tracks - " + region.getName());List entries = new ArrayList();for (Route route : region.routes) {SyndEntry entry = new SyndEntryImpl();
  32. 32. EMAIL - GRAILS* powered by the (built-in)grails-app/jobs/happytrails/DailyRegionDigestEmailJob.groovyprintln "Sending digest email to " + digest.user.usernamemailService.sendMail {to digest.getUser().usernamesubject "Updates from Ã​ber Tracks " + digest.regionsbody message}mail plugin
  33. 33. EMAIL - PLAYSendGrid Heroku Add-onDependency:"com.typesafe" %% "play-plugins-mailer" % "2.0.2"app/jobs/DailyRegionDigestEmailJob.javaMailerAPI mail = play.Play.application().plugin(MailerPlugin.class).email();mail.setSubject("Uber Tracks Region Updates");mail.addRecipient(regionUserDigest.user.getEmailAddress());mail.addFrom("noreply@ubertracks.com");mail.send(emailContent);conf/prod.confsmtp.host=smtp.sendgrid.netsmtp.port=587smtp.ssl=truesmtp.user=${SENDGRID_USERNAME}smtp.password=${SENDGRID_PASSWORD}
  34. 34. PHOTO UPLOAD - GRAILS
  35. 35. PHOTO UPLOAD - PLAYAmazon S3 for Persistent File Storageapp/models/S3Photo.javaPutObjectRequest putObjectRequest = new PutObjectRequest(bucket, key,inputStream, objectMetadata);putObjectRequest.withCannedAcl(CannedAccessControlList.PublicRead);if (S3Blob.amazonS3 == null) {Logger.error("Cloud not save Photo because amazonS3 was null");}else {S3Blob.amazonS3.putObject(putObjectRequest);}app/controllers/RegionController.javaHttp.MultipartFormData.FilePart photoFilePart = request().body().asMultipartFormData().getFile("photo");
  36. 36. TESTING - GRAILSUnit Tests with @TestFor and @MockTest URL Mappings with UrlMappingsUnitTestMixinIntegration Testing with GroovyTestCaseFunctional Testing with Geb* Grails plugins often arent in test scope.test/unit/happytrails/RouteTests.groovypackage happytrailsimport grails.test.mixin.*@TestFor(Route)class RouteTests {void testConstraints() {def region = new Region(name: "Colorado")def whiteRanch = new Route(name: "White Ranch", distance: 12.0, location: "Golden, CO", region: region)mockForConstraintsTests(Route, [whiteRanch])// validation should fail if required properties are null
  37. 37. TESTING - PLAYStandard JUnitUnit Tests & Functional TestsFakeApplication, FakeRequest, inMemoryDatabaseTest: Controllers, Views, Routing, Real Server, Browsertest/ApplicationControllerTest.java@Testpublic void index() {running(fakeApplication(inMemoryDatabase()), new Runnable() {public void run() {DemoData.loadDemoData();Result result = callAction(routes.ref.ApplicationController.index());assertThat(status(result)).isEqualTo(OK);assertThat(contentAsString(result)).contains(DemoData.CRESTED_BUTTE_COLORADO_REGION);assertThat(contentAsString(result)).contains("<li>");}});}
  38. 38. DEMO DATA - GRAILSgrails-app/conf/BootStrap.groovyimport happytrails.Userimport happytrails.Regionimport happytrails.Routeimport happytrails.RegionSubscriptionimport javax.servlet.http.HttpServletRequestclass BootStrap {def init = { servletContext ->HttpServletRequest.metaClass.isXhr = {->XMLHttpRequest == delegate.getHeader(X-Requested-With)}if (!User.count()) {User user = new User(username: "mraible@gmail.com", password: "happyhour",
  39. 39. DEMO DATA - PLAYapp/Global.javapublic void onStart(Application application) {//Ebean.getServer(null).getAdminLogging().setDebugGeneratedSql(true);S3Blob.initialize(application);// load the demo data in dev mode if no other data existsif (Play.isDev() && (User.find.all().size() == 0)) {DemoData.loadDemoData();}super.onStart(application);}
  40. 40. CONFIGURATION - GRAILSgrails-app/conf/Config.groovygrails.app.context = "/"grails.project.groupId = appName // change this to alter the default package name and Maven publishing destinationgrails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request formatgrails.mime.use.accept.header = falsegrails.mime.types = [html: [text/html, application/xhtml+xml],xml: [text/xml, application/xml],text: text/plain,js: text/javascript,rss: application/rss+xml,atom: application/atom+xml,css: text/css,csv: text/csv,all: */*,json: [application/json, text/json],form: application/x-www-form-urlencoded,
  41. 41. CONFIGURATION - PLAYBased on the TypeSafe Config LibraryOverride config with Java Properties:-Dfoo=barEnvironment Variable substitutionRun with different config files:-Dconfig.file=conf/prod.confconf/prod.confinclude "application.conf"application.secret=${APPLICATION_SECRET}db.default.driver=org.postgresql.Driverdb.default.url=${DATABASE_URL}applyEvolutions.default=true
  42. 42. AUTHENTICATION - GRAILSSpring Security UI Plugin, “I love you!”grails-app/conf/Config.groovygrails.mail.default.from = "Bike Ã​ber Tracks <bike@ubertracks.com>"grails.plugins.springsecurity.ui.register.emailFrom = grails.mail.default.fromgrails.plugins.springsecurity.ui.register.emailSubject = Welcome to Ã​ber Tracks!grails.plugins.springsecurity.ui.forgotPassword.emailFrom = grails.mail.default.fromgrails.plugins.springsecurity.ui.forgotPassword.emailSubject = Password Resetgrails.plugins.springsecurity.controllerAnnotations.staticRules = [/user/**: [ROLE_ADMIN],/role/**: [ROLE_ADMIN],/registrationCode/**: [ROLE_ADMIN],/securityInfo/**: [ROLE_ADMIN]]
  43. 43. AUTHENTICATION - PLAYUses cookies to remain statelessapp/controllers/Secured.javapublic class Secured extends Security.Authenticator {@Overridepublic String getUsername(Context ctx) {// todo: need to make sure the user is valid, not just the tokenreturn ctx.session().get("token");}app/controllers/RegionController.java@Security.Authenticated(Secured.class)public static Result addRegion() {return ok(views.html.regionForm.render(form(Region.class)));}
  44. 44. APPLICATION COMPARISONYSlowPageSpeedLines of CodeLoad TestingWhich Loads Faster?Security Testing
  45. 45. YSLOW
  46. 46. PAGESPEED
  47. 47. LINES OF CODE
  48. 48. LOAD TESTING WITH BROWSERMOB
  49. 49. Bike (Grails) Hike (Play)LOAD TESTING - 1 DYNO
  50. 50. LOAD TESTING - 1 DYNO
  51. 51. Bike (Grails) Hike (Play)LOAD TESTING - 5 DYNOS
  52. 52. LOAD TESTING - 5 DYNOS
  53. 53. LOAD TESTING - 5 DYNOSGrailsPlay Framework0 700 1,400 2,100 2,800Requests / Second
  54. 54. LOAD TESTING - 5 DYNOS + 100 USERS
  55. 55. WHICH LOADS FASTER?
  56. 56. WHICH ACTUALLY LOADS FASTER?
  57. 57. PEN TESTING WITH OWASP ZAP
  58. 58. GRAILS 2 VS. PLAY 2JobsLinkedIn SkillsGoogle TrendsIndeedMailing List TrafficBooks on Amazon2012 ReleasesStack OverflowHacker News
  59. 59. JOBSMarch 19, 2013GrailsPlay FrameworkSpring MVC0 400 800 1,200 1,600DiceMonsterIndeed
  60. 60. LINKEDIN SKILLSMarch 19, 2013# People0 4,000 8,000 12,000 16,000GrailsPlay FrameworkSpring MVC
  61. 61. GOOGLE TRENDSGrails Play
  62. 62. INDEED JOB TRENDS
  63. 63. USER MAILING LIST TRAFFICJune 2012GrailsPlay Framework800 1,600 2,400 3,200 4,000MarchAprilMay
  64. 64. USER MAILING LIST TRAFFICMarch 2013GrailsPlay Framework800 1,100 1,400 1,700 2,000DecemberJanuaryFebruary
  65. 65. BOOKS ON AMAZONMarch 2013GrailsPlay Framework38
  66. 66. 2013 RELEASESGrailsPlay Framework1 1
  67. 67. 2012 RELEASESGrailsPlay Framework69
  68. 68. STACKOVERFLOW QUESTIONSgrailsplayframework395010710
  69. 69. HACKER NEWSGrails 2.0 releasedPlay Framework 2.0 Final released6195
  70. 70. CONCLUSIONS: CODEFrom a code perspective, very similarframeworks.Code authoring good in both.Grails Plugin Ecosystem is excellent.TDD-Style Development easy with both.Type-safety in Play 2 was really useful, especiallyroutes and upgrades.
  71. 71. CONCLUSIONS: STATISTICAL ANALYSISGrails has better support for FEO (YSlow,PageSpeed)Grails has less LOC! (4 more files, but 20% lesscode)Apache Bench with 10K requests (2 Dynos):Requests per second: {Play: 2227,Grails: 1053}Caching significantly helps!
  72. 72. CONCLUSIONS: ECOSYSTEM ANALYSIS"Play" is difficult to search for.Grails is more mature.Play has momentum issues.LinkedIn: more people know Grails than SpringMVC.Play has 3x user mailing list traffic.We had similar experiences with documentationand questions.Outdated documentation is a problem for both.Play has way more hype!
  73. 73. QUESTIONS?Source:Branches: grails2, play2_javaPresentation*: master/presoContact Us:* Presentation created with , and .http://ubertracks.comhttps://github.com/jamesward/happytrailsjamesward.comraibledesigns.comReveal.js Google Charts GitHub Files
  74. 74. ACTION!Learn something new*!* Or prove that were wrong...
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×