Download pdf - Grails-Powered HTML RIAs

Transcript
Page 1: Grails-Powered HTML RIAs

HTML RIAs with Grails

Brian Kotek

Page 2: Grails-Powered HTML RIAs

Overview

Page 3: Grails-Powered HTML RIAs

Who am I?

Brian Kotek

Booz Allen Hamilton

Code junkie for 15 years

Twitter: @brian428

compiledammit.com

Page 4: Grails-Powered HTML RIAs

What Shall We Talk About?

Client Language Options (plugins)

UI Frameworks

Data Format and Structure

Hibernate Issues

Rich Client Issues and Concerns

Page 5: Grails-Powered HTML RIAs

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

Page 6: Grails-Powered HTML RIAs
Page 7: Grails-Powered HTML RIAs
Page 8: Grails-Powered HTML RIAs
Page 9: Grails-Powered HTML RIAs

User Expectations over Time

Page 10: Grails-Powered HTML RIAs

Client Language Options

Page 11: Grails-Powered HTML RIAs

Languages: JavaScript

Page 12: Grails-Powered HTML RIAs

Languages: CoffeeScript

Page 13: Grails-Powered HTML RIAs

Languages: TypeScript

Page 14: Grails-Powered HTML RIAs

Languages: Dart

Page 15: Grails-Powered HTML RIAs

Language Tools

Page 16: Grails-Powered HTML RIAs

Command Line

Node

Cake

Shell script / batch

Built-in watch args for compilers

Page 17: Grails-Powered HTML RIAs

IDE Options

IDEA External Tools

IDEA File Watchers

Eclipse Builders

Page 18: Grails-Powered HTML RIAs

Grails Plugins

Coffeescript-compiler

Typescript

Dart?

Page 19: Grails-Powered HTML RIAs

Client UI Libraries

Page 20: Grails-Powered HTML RIAs

Piecemeal Options

jQuery UI (+Extensions)

Bootstrap (+Extensions)

Page 21: Grails-Powered HTML RIAs

Unified Options

Dojo

Kendo UI

Ext JS / Sencha Touch

Page 22: Grails-Powered HTML RIAs

Java Options

GWT

SmartClient

ZK

Vaadin

Page 23: Grails-Powered HTML RIAs

Choosing a UI Library

Free vs. Paid

JavaScript-based vs. Java (+XML)

Complexity of UI

Web vs. Mobile vs. Native App

Page 24: Grails-Powered HTML RIAs

Data Formats

Page 25: Grails-Powered HTML RIAs

JSON

render users as JSON

// Done!

Page 26: Grails-Powered HTML RIAs

XML

render users as XML

// Done!

Page 27: Grails-Powered HTML RIAs

SOAP

CXF Plugin

Axis Plugin

Metro Plugin

Page 28: Grails-Powered HTML RIAs

Advice and Common Pitfalls

Page 29: Grails-Powered HTML RIAs

JSON Structure

Page 30: Grails-Powered HTML RIAs

Request Structure: Params

GET

user/list?departmentId=5

params.departmentId (or Command object)

POST

new User( params )

user.properties = params

bindData( user, params )

Page 31: Grails-Powered HTML RIAs

Request Structure: Paging

start=0&limit=100

page=1

Page 32: Grails-Powered HTML RIAs

Request Structure: Sorting

sort=lastpost&dir=desc

sort=[{"field":"lastpost","dir":"desc"},

{"field":"threadId","dir":"desc"}]

Page 33: Grails-Powered HTML RIAs

Request Structure: Grouping

group=userId&dir=desc

group=[{"field":"threadId","dir":"desc"},

{"field":"userId","dir":"desc"}]

Page 34: Grails-Powered HTML RIAs

Request Structure: Filtering

filter=[

{"field":"id", "data":{"type":"numeric",

"comparison":"lt", "value":10}},

{"field":"price", "data":{"type":"numeric",

"comparison":"lt", "value":50}}

]

Page 35: Grails-Powered HTML RIAs

Response Structure

Content negotiation (withFormat)

Based on Mime type or Accept HTTP header

Page 36: Grails-Powered HTML RIAs

Response Structure

def list() {

...

withFormat {

html bookList: books // render GSP

js { ... } // build JSON response

xml { ... } // build XML response

}

}

Page 37: Grails-Powered HTML RIAs

Response Structure

[

{"id":1, "firstName":"Al",

"lastName":"Smith"},

{"id":2, "firstName":"Bill",

"lastName":"Lumberg"}

]

Page 38: Grails-Powered HTML RIAs

Response Structure

{

"success":true,

"data":[

{"id":1, "firstName":"Al", "lastName":"Smith"},

{"id":2, "firstName":"Bill",

"lastName":"Lumberg"}

]

}

Page 39: Grails-Powered HTML RIAs

Response Structure

{

"success":true,

"data":[...],

"totalcount":1234

}

Page 40: Grails-Powered HTML RIAs

Response Structure: Errors

{

"success":false,

"data":[],

"errors": [

"User Name must be unique.",

"User Name must be at least 5 characters."

]

}

Page 41: Grails-Powered HTML RIAs

Response Structure: Errors

{

"success":false, "data":[],

"errors": [{

"field":"userName",

"messages": [

"User Name must be unique.",

"User Name must be at least 5 characters."

]

}]

}

Page 42: Grails-Powered HTML RIAs

Takeaways

Use a standard format

Plan Ahead

Consider less-than-obvious cases

Pick a format and stick with it

Page 43: Grails-Powered HTML RIAs

Domain Model Concerns

Page 44: Grails-Powered HTML RIAs

Associations

Omit them

Send them all (deep conversion)

Send just the ID (old default converter)

Partial data (situation-dependent)

Page 45: Grails-Powered HTML RIAs

Serialization and Lazy Loading

Serializes everything

Indiscriminate

Inadvertently send large result data

DTOs to the rescue

Page 46: Grails-Powered HTML RIAs

Data Transfer Objects

Customized snapshot of object state

No behavior

Can be a realized class

Can be a map

Maps are usually sufficient

Page 47: Grails-Powered HTML RIAs

Option 1: Manually Building DTOs

Build Map

Recurse associations

Total control

Verbose

Page 48: Grails-Powered HTML RIAs

Option 2: GSON Plugin

users as GSON

new User( request.GSON )

user.properties = request.GSON

Page 49: Grails-Powered HTML RIAs

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

Page 50: Grails-Powered HTML RIAs

Option 4: Domain Metaclass Methods

Explicitly control what is serialized

(Inspired by Foxgem)

Page 51: Grails-Powered HTML RIAs

customers.each { thisCustomer ->

// Omit orders and wishlists associations

dto = thisCustomer.asDto(

except: [ "orders", "wishlists" ]

)

result.push( dto )

}

render result as JSON

Page 52: Grails-Powered HTML RIAs

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

}

}

Page 53: Grails-Powered HTML RIAs

Option 5: Assembler Pattern

user = assembler.assemble( params )

assembler.disassemble( user ) as JSON

Page 54: Grails-Powered HTML RIAs

Option 6: Dozer

Automatic mapping by name

Highly customizable

More complex

DTO plugin

Page 55: Grails-Powered HTML RIAs

Choosing an Approach

Plan for this on SOME level

Level of control

Level of encapsulation

Complexity of solution

Page 56: Grails-Powered HTML RIAs

Testing Your JSON API

Especially critical for separate UI and server devs

Can use controller unit tests

Manual confirmation

Compare to baseline

Page 57: Grails-Powered HTML RIAs

Concurrency

Page 58: Grails-Powered HTML RIAs

Request Overlap

1. Request 1 (list Users)

2. Request 2 (list Users with filter)

3. Request 2 result

4. Request 1 result

Page 59: Grails-Powered HTML RIAs

Option 1: UI Locking

Disable controls

Mask UI

Page 60: Grails-Powered HTML RIAs

Option 2: Synchronous Calls

“Single-thread” calls

Async=false

Page 61: Grails-Powered HTML RIAs

Option 3: Abort and Replace

Track last request

Abort last and execute new

Reads only?

Page 62: Grails-Powered HTML RIAs

Data Synchronization

Page 63: Grails-Powered HTML RIAs

Option 1: Always Reload

Page 64: Grails-Powered HTML RIAs

Option 2: User-Initiated Reload

Page 65: Grails-Powered HTML RIAs

Option 3: Client Polling (Comet)

Simple JavaScript setInterval()

XMLHttpRequest long polling

Page 66: Grails-Powered HTML RIAs

Option 4: Server Push

Websockets

events-push plugin

Can instruct client to refresh

Can push updated data for consumption

Page 67: Grails-Powered HTML RIAs

Miscellaneous Advice

Page 68: Grails-Powered HTML RIAs

Client-side MVC

Client logic can be extensive

Avoid spaghetti code

Architect as thoroughly as server-side

Page 69: Grails-Powered HTML RIAs

Client-side Service Classes

Encapsulate service logic

Isolate data conversions

Built to handle asynchronous processing

Page 70: Grails-Powered HTML RIAs

Promises

Represents a future value

Provides a stable API

Allows resolve(), reject(), progress(), etc.

Allows chaining, map, reduce, etc.

Page 71: Grails-Powered HTML RIAs

Mock JSON

Simple config value to switch

Client work can proceed without remote services

Sanity test for proposed JSON structures

Page 72: Grails-Powered HTML RIAs

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 )

}

}

Page 73: Grails-Powered HTML RIAs

Service API Granularity

vs

Page 74: Grails-Powered HTML RIAs

Make it Client-Agnostic

Page 75: Grails-Powered HTML RIAs

Client Testing

Page 76: Grails-Powered HTML RIAs

Client Tests are Critical

Runtime errors only

RIAs have extensive logic

Match server testing zeal

Page 77: Grails-Powered HTML RIAs

Common Testing Options

Jasmine

Mocha

Chai

Sinon.js

Many more

Page 78: Grails-Powered HTML RIAs

WebDriver

Integration tests vs. unit tests

Not granular

Tests wide swathes of client code

Can run with Grails via webdriver plugin

Page 79: Grails-Powered HTML RIAs

WAR Deployment

Page 80: Grails-Powered HTML RIAs

Excluding Files

// BuildConfig.groovy:

grails.war.copyToWebApp = { args ->

Environment.executeForCurrentEnvironment {

production {

fileset( dir:"web-app" ) {

exclude( name: "js/test/**" )

exclude( name: "mockdata/**" )

}

}

}

}

Page 81: Grails-Powered HTML RIAs

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

)

}

}

}

Page 82: Grails-Powered HTML RIAs

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

)

}

}

}

Page 83: Grails-Powered HTML RIAs

Conclusion

Page 84: Grails-Powered HTML RIAs

Recap

Languages and UI Options

Data exchange

Hibernate issues

General RIA challenges

Building and Testing

Page 85: Grails-Powered HTML RIAs

Questions?

Page 86: Grails-Powered HTML RIAs

Thanks!

Brian Kotek - @brian428