86
HTML RIAs with Grails Brian Kotek

Grails-Powered HTML RIAs

Embed Size (px)

DESCRIPTION

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.

Citation preview

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