HTML RIAs with Grails
Brian Kotek
Overview
Who am I?

 Brian Kotek
 Booz Allen Hamilton

 Code junkie for 15 years
 Twitter: @brian428
 compiledammit.com
What Shall We Talk About?

 Client Language Options (plugins)
 UI Frameworks

 Data Format and Structure
 Hibernate Is...
What is a RIA?
 Not just a bit of AJAX
 Not page-based
 Desktop-like functionality
 Stateful client
 Strong client-si...
User Expectations over Time
Client Language Options
Languages: JavaScript
Languages: CoffeeScript
Languages: TypeScript
Languages: Dart
Language Tools
Command Line

 Node
 Cake

 Shell script / batch
 Built-in watch args for compilers
IDE Options

 IDEA External Tools

 IDEA File Watchers
 Eclipse Builders
Grails Plugins

 Coffeescript-compiler

 Typescript
 Dart?
Client UI Libraries
Piecemeal Options

 jQuery UI (+Extensions)

 Bootstrap (+Extensions)
Unified Options

 Dojo

 Kendo UI
 Ext JS / Sencha Touch
Java Options

 GWT
 SmartClient

 ZK
 Vaadin
Choosing a UI Library

 Free vs. Paid
 JavaScript-based vs. Java (+XML)

 Complexity of UI
 Web vs. Mobile vs. Native ...
Data Formats
JSON

 render users as JSON

 // Done!
XML

 render users as XML

 // Done!
SOAP

 CXF Plugin

 Axis Plugin
 Metro Plugin
Advice and Common Pitfalls
JSON Structure
Request Structure: Params

 GET
 user/list?departmentId=5
 params.departmentId (or Command object)

 POST
 new User( ...
Request Structure: Paging

 start=0&limit=100

 page=1
Request Structure: Sorting

 sort=lastpost&dir=desc
 sort=[{"field":"lastpost","dir":"desc"},
{"field":"threadId","dir":...
Request Structure: Grouping

 group=userId&dir=desc
 group=[{"field":"threadId","dir":"desc"},
{"field":"userId","dir":"...
Request Structure: Filtering

filter=[
{"field":"id", "data":{"type":"numeric",
"comparison":"lt", "value":10}},
{"field":...
Response Structure

 Content negotiation (withFormat)
 Based on Mime type or Accept HTTP header
Response Structure

def list() {
...
withFormat {
html bookList: books // render GSP
js { ... } // build JSON response
xml...
Response Structure
[
{"id":1, "firstName":"Al",
"lastName":"Smith"},

{"id":2, "firstName":"Bill",
"lastName":"Lumberg"}
]
Response Structure
{

"success":true,
"data":[
{"id":1, "firstName":"Al", "lastName":"Smith"},

{"id":2, "firstName":"Bill...
Response Structure

{
"success":true,

"data":[...],
"totalcount":1234
}
Response Structure: Errors
{
"success":false,
"data":[],
"errors": [
"User Name must be unique.",
"User Name must be at le...
Response Structure: Errors
{
"success":false, "data":[],
"errors": [{
"field":"userName",
"messages": [
"User Name must be...
Takeaways

 Use a standard format

 Plan Ahead
 Consider less-than-obvious cases
 Pick a format and stick with it
Domain Model Concerns
Associations

 Omit them
 Send them all (deep conversion)

 Send just the ID (old default converter)
 Partial data (si...
Serialization and Lazy Loading

 Serializes everything
 Indiscriminate

 Inadvertently send large result data
 DTOs to...
Data Transfer Objects

 Customized snapshot of object state
 No behavior

 Can be a realized class
 Can be a map

 Ma...
Option 1: Manually Building DTOs

 Build Map
 Recurse associations

 Total control
 Verbose
Option 2: GSON Plugin

 users as GSON

 new User( request.GSON )
 user.properties = request.GSON
Option 3: Custom JSON Marshaller

JSON.registerObjectMarshaller( User ) { User user ->
Map dto = [:]
// ...build DTO versi...
Option 4: Domain Metaclass Methods

 Explicitly control what is serialized

 (Inspired by Foxgem)
customers.each { thisCustomer ->
// Omit orders and wishlists associations
dto = thisCustomer.asDto(
except: [ "orders", "...
grailsApplication.domainClasses.each{ domainClass ->
domainClass.metaClass.asDto = { filter ->
def dto = [ id : null ]
if(...
Option 5: Assembler Pattern

 user = assembler.assemble( params )

 assembler.disassemble( user ) as JSON
Option 6: Dozer

 Automatic mapping by name
 Highly customizable

 More complex
 DTO plugin
Choosing an Approach

 Plan for this on SOME level
 Level of control

 Level of encapsulation
 Complexity of solution
Testing Your JSON API

 Especially critical for separate UI and server devs
 Can use controller unit tests

 Manual con...
Concurrency
Request Overlap

1. Request 1 (list Users)
2. Request 2 (list Users with filter)

3. Request 2 result
4. Request 1 result
Option 1: UI Locking

 Disable controls

 Mask UI
Option 2: Synchronous Calls

 “Single-thread” calls

 Async=false
Option 3: Abort and Replace

 Track last request

 Abort last and execute new
 Reads only?
Data Synchronization
Option 1: Always Reload
Option 2: User-Initiated Reload
Option 3: Client Polling (Comet)

 Simple JavaScript setInterval()

 XMLHttpRequest long polling
Option 4: Server Push

 Websockets
 events-push plugin

 Can instruct client to refresh
 Can push updated data for con...
Miscellaneous Advice
Client-side MVC

 Client logic can be extensive

 Avoid spaghetti code
 Architect as thoroughly as server-side
Client-side Service Classes

 Encapsulate service logic

 Isolate data conversions
 Built to handle asynchronous proces...
Promises

 Represents a future value
 Provides a stable API

 Allows resolve(), reject(), progress(), etc.
 Allows cha...
Mock JSON

 Simple config value to switch

 Client work can proceed without remote services
 Sanity test for proposed J...
Dynamic Tomcat Contexts
// In _Events.groovy:
eventConfigureTomcat = { tomcat ->
def contextRoot = "/myRoot"
File contextP...
Service API Granularity

vs
Make it Client-Agnostic
Client Testing
Client Tests are Critical

 Runtime errors only

 RIAs have extensive logic
 Match server testing zeal
Common Testing Options

 Jasmine
 Mocha

 Chai
 Sinon.js
 Many more
WebDriver

 Integration tests vs. unit tests
 Not granular

 Tests wide swathes of client code
 Can run with Grails vi...
WAR Deployment
Excluding Files
// BuildConfig.groovy:
grails.war.copyToWebApp = { args ->
Environment.executeForCurrentEnvironment {
prod...
Removing Files
// _Events.groovy (invoked just before WAR is zipped)
eventWarStart = { event ->
Environment.executeForCurr...
Cleaning Up
// _Events.groovy (invoked after WAR is built)
eventWarEnd = { event ->
Environment.executeForCurrentEnvironme...
Conclusion
Recap

 Languages and UI Options
 Data exchange

 Hibernate issues
 General RIA challenges
 Building and Testing
Questions?
Thanks!
Brian Kotek - @brian428
Grails-Powered HTML RIAs
Grails-Powered HTML RIAs
Grails-Powered HTML RIAs
Upcoming SlideShare
Loading in …5
×

Grails-Powered HTML RIAs

1,368 views

Published on

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.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,368
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
6
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Grails-Powered HTML RIAs

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

×