Grails-Powered HTML RIAs
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Grails-Powered HTML RIAs

on

  • 1,189 views

Speaker: Brian Kotek ...

Speaker: Brian Kotek
Page-based web applications were once the norm, but times have changed. Users increasingly expect rich, desktop-like experiences from their browser-based applications. They demand apps that adhere to common standards, without the need for special plugins. Finally, they want to use them on any device, from smartphone to flat-screen.
It's a daunting task, but help is on the way. Grails provides an awesome foundation for HTML-based Rich Internet Applications. In this session, we'll see how Grails can make HTML RIAs a snap.
JavaScript is the language of the web, but it has its warts. Grails plugins allow us to easily leverage intermediary languages like CoffeeScript or TypeScript to help create large-scale client-side code. Libraries like ExtJS or Dojo offer expansive UI toolkits that hook perfectly into Grails REST APIs. JSON and GSON are excellent data exchange formats, as long as you properly plan out their structure. And GORM is incredibly powerful, but without careful thought you can inadvertently send huge amounts of unnecessary data to the client.
We'll dig into each of these areas, and more. Come see just how much power and productivity Grails brings to the HTML RIA table.

Statistics

Views

Total Views
1,189
Views on SlideShare
1,189
Embed Views
0

Actions

Likes
0
Downloads
4
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Grails-Powered HTML RIAs Presentation Transcript

  • 1. HTML RIAs with Grails Brian Kotek
  • 2. Overview
  • 3. Who am I?  Brian Kotek  Booz Allen Hamilton  Code junkie for 15 years  Twitter: @brian428  compiledammit.com
  • 4. What Shall We Talk About?  Client Language Options (plugins)  UI Frameworks  Data Format and Structure  Hibernate Issues  Rich Client Issues and Concerns
  • 5. What is a RIA?  Not just a bit of AJAX  Not page-based  Desktop-like functionality  Stateful client  Strong client-side model  Significant business logic
  • 6. User Expectations over Time
  • 7. Client Language Options
  • 8. Languages: JavaScript
  • 9. Languages: CoffeeScript
  • 10. Languages: TypeScript
  • 11. Languages: Dart
  • 12. Language Tools
  • 13. Command Line  Node  Cake  Shell script / batch  Built-in watch args for compilers
  • 14. IDE Options  IDEA External Tools  IDEA File Watchers  Eclipse Builders
  • 15. Grails Plugins  Coffeescript-compiler  Typescript  Dart?
  • 16. Client UI Libraries
  • 17. Piecemeal Options  jQuery UI (+Extensions)  Bootstrap (+Extensions)
  • 18. Unified Options  Dojo  Kendo UI  Ext JS / Sencha Touch
  • 19. Java Options  GWT  SmartClient  ZK  Vaadin
  • 20. Choosing a UI Library  Free vs. Paid  JavaScript-based vs. Java (+XML)  Complexity of UI  Web vs. Mobile vs. Native App
  • 21. Data Formats
  • 22. JSON  render users as JSON  // Done!
  • 23. XML  render users as XML  // Done!
  • 24. SOAP  CXF Plugin  Axis Plugin  Metro Plugin
  • 25. Advice and Common Pitfalls
  • 26. JSON Structure
  • 27. Request Structure: Params  GET  user/list?departmentId=5  params.departmentId (or Command object)  POST  new User( params )  user.properties = params  bindData( user, params )
  • 28. Request Structure: Paging  start=0&limit=100  page=1
  • 29. Request Structure: Sorting  sort=lastpost&dir=desc  sort=[{"field":"lastpost","dir":"desc"}, {"field":"threadId","dir":"desc"}]
  • 30. Request Structure: Grouping  group=userId&dir=desc  group=[{"field":"threadId","dir":"desc"}, {"field":"userId","dir":"desc"}]
  • 31. Request Structure: Filtering filter=[ {"field":"id", "data":{"type":"numeric", "comparison":"lt", "value":10}}, {"field":"price", "data":{"type":"numeric", "comparison":"lt", "value":50}} ]
  • 32. Response Structure  Content negotiation (withFormat)  Based on Mime type or Accept HTTP header
  • 33. Response Structure def list() { ... withFormat { html bookList: books // render GSP js { ... } // build JSON response xml { ... } // build XML response } }
  • 34. Response Structure [ {"id":1, "firstName":"Al", "lastName":"Smith"}, {"id":2, "firstName":"Bill", "lastName":"Lumberg"} ]
  • 35. Response Structure { "success":true, "data":[ {"id":1, "firstName":"Al", "lastName":"Smith"}, {"id":2, "firstName":"Bill", "lastName":"Lumberg"} ] }
  • 36. Response Structure { "success":true, "data":[...], "totalcount":1234 }
  • 37. Response Structure: Errors { "success":false, "data":[], "errors": [ "User Name must be unique.", "User Name must be at least 5 characters." ] }
  • 38. Response Structure: Errors { "success":false, "data":[], "errors": [{ "field":"userName", "messages": [ "User Name must be unique.", "User Name must be at least 5 characters." ] }] }
  • 39. Takeaways  Use a standard format  Plan Ahead  Consider less-than-obvious cases  Pick a format and stick with it
  • 40. Domain Model Concerns
  • 41. Associations  Omit them  Send them all (deep conversion)  Send just the ID (old default converter)  Partial data (situation-dependent)
  • 42. Serialization and Lazy Loading  Serializes everything  Indiscriminate  Inadvertently send large result data  DTOs to the rescue
  • 43. Data Transfer Objects  Customized snapshot of object state  No behavior  Can be a realized class  Can be a map  Maps are usually sufficient
  • 44. Option 1: Manually Building DTOs  Build Map  Recurse associations  Total control  Verbose
  • 45. Option 2: GSON Plugin  users as GSON  new User( request.GSON )  user.properties = request.GSON
  • 46. Option 3: Custom JSON Marshaller JSON.registerObjectMarshaller( User ) { User user -> Map dto = [:] // ...build DTO version as a Map... return dto } // To use in Controller: render userList as JSON
  • 47. Option 4: Domain Metaclass Methods  Explicitly control what is serialized  (Inspired by Foxgem)
  • 48. customers.each { thisCustomer -> // Omit orders and wishlists associations dto = thisCustomer.asDto( except: [ "orders", "wishlists" ] ) result.push( dto ) } render result as JSON
  • 49. grailsApplication.domainClasses.each{ domainClass -> domainClass.metaClass.asDto = { filter -> def dto = [ id : null ] if( filter."include" ) { filter."include".each{ dto[ it ] = delegate."${ it }" } } else if( filter."except" ) { def props= domainClass.persistentProperties.findAll { !( it.name in filter."except" ) } props.each{ dto[ it.name ] = delegate."${ it.name }" } } return dto } }
  • 50. Option 5: Assembler Pattern  user = assembler.assemble( params )  assembler.disassemble( user ) as JSON
  • 51. Option 6: Dozer  Automatic mapping by name  Highly customizable  More complex  DTO plugin
  • 52. Choosing an Approach  Plan for this on SOME level  Level of control  Level of encapsulation  Complexity of solution
  • 53. Testing Your JSON API  Especially critical for separate UI and server devs  Can use controller unit tests  Manual confirmation  Compare to baseline
  • 54. Concurrency
  • 55. Request Overlap 1. Request 1 (list Users) 2. Request 2 (list Users with filter) 3. Request 2 result 4. Request 1 result
  • 56. Option 1: UI Locking  Disable controls  Mask UI
  • 57. Option 2: Synchronous Calls  “Single-thread” calls  Async=false
  • 58. Option 3: Abort and Replace  Track last request  Abort last and execute new  Reads only?
  • 59. Data Synchronization
  • 60. Option 1: Always Reload
  • 61. Option 2: User-Initiated Reload
  • 62. Option 3: Client Polling (Comet)  Simple JavaScript setInterval()  XMLHttpRequest long polling
  • 63. Option 4: Server Push  Websockets  events-push plugin  Can instruct client to refresh  Can push updated data for consumption
  • 64. Miscellaneous Advice
  • 65. Client-side MVC  Client logic can be extensive  Avoid spaghetti code  Architect as thoroughly as server-side
  • 66. Client-side Service Classes  Encapsulate service logic  Isolate data conversions  Built to handle asynchronous processing
  • 67. Promises  Represents a future value  Provides a stable API  Allows resolve(), reject(), progress(), etc.  Allows chaining, map, reduce, etc.
  • 68. Mock JSON  Simple config value to switch  Client work can proceed without remote services  Sanity test for proposed JSON structures
  • 69. Dynamic Tomcat Contexts // In _Events.groovy: eventConfigureTomcat = { tomcat -> def contextRoot = "/myRoot" File contextPath = new File( "//some/file/path" ) if( contextPath.exists() ) { def context = tomcat.addWebapp( contextRoot, contextPath.getAbsolutePath() ) context.reloadable = true context.loader = new WebappLoader( tomcat.class.classLoader ) } }
  • 70. Service API Granularity vs
  • 71. Make it Client-Agnostic
  • 72. Client Testing
  • 73. Client Tests are Critical  Runtime errors only  RIAs have extensive logic  Match server testing zeal
  • 74. Common Testing Options  Jasmine  Mocha  Chai  Sinon.js  Many more
  • 75. WebDriver  Integration tests vs. unit tests  Not granular  Tests wide swathes of client code  Can run with Grails via webdriver plugin
  • 76. WAR Deployment
  • 77. Excluding Files // BuildConfig.groovy: grails.war.copyToWebApp = { args -> Environment.executeForCurrentEnvironment { production { fileset( dir:"web-app" ) { exclude( name: "js/test/**" ) exclude( name: "mockdata/**" ) } } } }
  • 78. Removing Files // _Events.groovy (invoked just before WAR is zipped) eventWarStart = { event -> Environment.executeForCurrentEnvironment { production { // Force CoffeeScript recompile and minification ant.delete( dir: "${basedir}/web-app/js/app", includeemptydirs: true ) } } }
  • 79. Cleaning Up // _Events.groovy (invoked after WAR is built) eventWarEnd = { event -> Environment.executeForCurrentEnvironment { production { // Delete minified JS so next dev startup recompiles ant.delete( dir: "${basedir}/web-app/js/app", includeemptydirs: true ) } } }
  • 80. Conclusion
  • 81. Recap  Languages and UI Options  Data exchange  Hibernate issues  General RIA challenges  Building and Testing
  • 82. Questions?
  • 83. Thanks! Brian Kotek - @brian428