Date post: | 08-Jan-2017 |
Category: |
Documents |
Upload: | edwin-van-nes |
View: | 863 times |
Download: | 0 times |
Grails Code from the Trenches
Collec3ons.shuffle(dayToDayWork)
Edwin van Nes 1989 dBase / Clipper / FoxPro 1995 Borland Delphi 2000 Java 2, Enterprise Edi3on 2005 PHP, Typo3, Joomla!, … 2011 Groovy & Grails
Direc3ons
• Weapons of Choice • AbstractDomain • Heavy-‐Duty Scaffolding • Mul3-‐lingualiza3on • Types & Categories • Connec3ng an API
Weapons of Choice Specifica3on Google Docs, FogBugz, Balsamiq Mockups, PlantUML, yEd, …
Design similar stuff
Construc3on Grails (dûh!), IntelliJ IDEA, mySQL, SVN -‐> GIT, …
Tes3ng JUnit, Spock, Kiln, …
Deployment Jenkins, Gradle, OSX, Ubuntu, MS Windows, …
Project Skype, Google Hangout, IRIS, …
Weapons of Choice Specifica3on Google Docs, FogBugz, Balsamiq Mockups, yEd, …
Design similar stuff
Construc3on Grails (dûh!), IntelliJ IDEA, mySQL, SVN -‐> GIT, …
Tes3ng JUnit, Spock, Kiln, …
Deployment Jenkins, OSX, Ubuntu, MS Windows, …
Project Skype, Google Hangout, IRIS, …
Weapons of Choice Specifica3on Google Docs, FogBugz, Balsamiq Mockups, yEd, …
Design similar stuff
Construc3on Grails (dûh!), IntelliJ IDEA, mySQL, SVN -‐> GIT, …
Tes3ng JUnit, Spock, …
Deployment Jenkins, Gradle, OSX, Ubuntu, MS Windows, …
Project Skype, Google Hangout, IRIS, …
Weapons of Choice Specifica3on Google Docs, FogBugz, Balsamiq Mockups, yEd, …
Design similar stuff
Construc3on Grails (dûh!), IntelliJ IDEA, mySQL, SVN -‐> GIT, …
Tes3ng JUnit, Spock, Kiln, …
Deployment Jenkins, OSX, Ubuntu, MS Windows, …
Project Skype, Google Hangout, IRIS, …
Weapons of Choice Specifica3on Google Docs, FogBugz, Balsamiq Mockups, yEd, …
Design similar stuff
Construc3on Grails (dûh!), IntelliJ IDEA, mySQL, SVN -‐> GIT, …
Tes3ng JUnit, Spock, …
Deployment Jenkins, Gradle, OSX, Ubuntu, MS Windows, …
Project Skype, Google Hangout, IRIS, …
Weapons of Choice Specifica3on Google Docs, FogBugz, Balsamiq Mockups, yEd, …
Design similar stuff
Construc3on Grails (dûh!), IntelliJ IDEA, mySQL, SVN -‐> GIT, …
Tes3ng JUnit, Spock, …
Deployment Jenkins, OSX, Ubuntu, MS Windows, …
Project Skype, Google Hangout, IRIS, …
Direc3ons
• Weapons of Choice • AbstractDomain • Heavy-‐Duty Scaffolding • Mul3-‐lingualiza3on • Types & Categories • Connec3ng an API • Error Abstrac3on in a Plugin
WhatWeDoMost*
Services Frontend Portal
Backend Portal Database Our Clients
Consumers (their clients)
* overly simplified for educa3onal purposes
GR8Tunes
Used here as an example for a typical Backend portal
GR8Tunes
Used here as an example for a typical Backend portal
AbstractDomain
• Not too Groovy probably • Add some standard akributes – Some useful 3mestamps – ‘recentlyUpdated’ boolean for easy highligh3ng
– Each record gets a ‘remarks’ akribute
AbstractDomain.Status
AbstractDomain.Status
Hibernate filters Plugin • defaultFilter in Backend
– Status in [Status.ACTIVE, Status.INACTIVE]
• defaultFilter in Frontend – Status == Status.ACTIVE
AbstractDomain.Status
Hibernate filters • defaultFilter in Backend
– Status in [Status.ACTIVE, Status.INACTIVE]
• defaultFilter in Frontend – Status == Status.ACTIVE
AbstractDomain.Status class DefaultFilters { def sessionFactory def filters = { all(controller:'*', action:'*') { before = { def session = sessionFactory.currentSession if (params.filter_status) {
session.enableFilter('statusFilter').setParameter('status',params.filter_status) } else {
session.enableFilter('defaultFilter') } } } } }
AbstractDomain.Status class DefaultFilters { def sessionFactory def filters = { all(controller:'*', action:'*') { before = { def session = sessionFactory.currentSession if (params.filter_status) {
session.enableFilter('statusFilter').setParameter('status',params.filter_status) } else {
session.enableFilter('defaultFilter') } } } } }
AbstractDomain.Status class DefaultFilters { def sessionFactory def filters = { all(controller:'*', action:'*') { before = { def session = sessionFactory.currentSession if (params.filter_status) {
session.enableFilter('statusFilter').setParameter('status',params.filter_status) } else {
session.enableFilter('defaultFilter') } } } } }
AbstractDomain.change
• Holds a record of type AbstractDomain
• Like a “muta3on-‐record” • i.e. future change to the object • example: division of responsibility (amongst users)
AbstractDomain.change
id version change .tle year
1 5 7 The Bright Side of the Moon 1973
id version change .tle year
7 5 null The Dark Side of the Moon 1973
Should verify (op3mis3c)
Hibernate filter: exclude records that are “change records”
Direc3ons
• Weapons of Choice • AbstractDomain • Heavy-‐Duty Scaffolding • Mul3-‐lingualiza3on • Types & Categories • Connec3ng an API • Error Abstrac3on in a Plugin
Heavy-‐Duty Scaffolding
• Substan3al 3me spent on tailor-‐made scaffolding • Controllers are always scaffolded – Only specific code for a specific domain is coded
• Views are scaffolded as a start – Much more likely to manually adjust – But certainly not for each and every Domain Class
• Domain Classes can contain seungs to influence rendering during scaffolding
Scaffolded Controllers
• All Controllers exists as files but are 100% scaffolded unless … class AlbumController {
static scaffold = true }
Scaffolded Controllers
• All Controllers exists as files but are 100% scaffolded unless … class AlbumController {
static scaffold = true def list (Integer max) { // different implementation of Album.list }
}
SimpleFlow = true • Simple states – List – Edit
• Mul3ple ac3ons – Create – Cancel – Save – SaveAndClose – SaveAndCreate
SimpleFlow = false • More states
– List – Create – Edit – Show
• Same set of ac3ons – Create – Cancel – Save – SaveAndClose – SaveAndCreate
Scaffolded Views
• Views are most likely to be changed – Order of fields from Domain Class constraints
static constraints = { title (blank:false) artist (blank:false) year (display:true)
change (display:false) }
Scaffolded Views
• Views are most likely to be changed – Order of fields from Domain Class constraints
static constraints = { title (blank:false) artist (blank:false) year (display:true)
change (display:false) }
Scaffolded Views • Render more artefacts
_form.gsp = fields used in create / edit _table.gsp = <table> used in list for reuse _panel.gsp = simple <div> for reuse list.csv.gsp = used for formatting exports
Scaffolded Views • Render more artefacts
_form.gsp = fields used in create / edit _table.gsp = <table> used in list for reuse _panel.gsp = simple <div> for reuse list.csv.gsp = used for formatting exports
<%@ page import="gr8tunes.Album" %> <g:if test="${albumInstance instanceof Album}">
<div class="domain-‐panel domain-‐album"> <h4><small><g:message code="album.title.label" />:</small></h4>
<p> <g:if test="${!controllerName.equals('album')}"> <g:link controller=”album" action="show" id="${albumInstance.id}">
<g:fieldValue bean="${albumInstance}" field="year"/> <g:fieldValue bean="${albumInstance}" field="title"/>
</g:link> </g:if> <g:else> <g:fieldValue bean="${albumInstance}" field="year"/> <g:fieldValue bean="${albumInstance}" field="title"/> </g:else> </p> </div>
</g:if>
_panel.gsp
Scaffolded Views • Render more artefacts
_form.gsp = fields used in create / edit _table.gsp = <table> used in list for reuse _panel.gsp = simple <div> for reuse list.csv.gsp = used for formatting exports
Scaffolded Views • Render more artefacts
_form.gsp = fields used in create / edit _table.gsp = <table> used in list for reuse _panel.gsp = simple <div> for reuse list.csv.gsp = used for formatting exports
Scaffolded Views • Render more artefacts
_form.gsp = fields used in create / edit _table.gsp = <table> used in list for reuse _panel.gsp = simple <div> for reuse list.csv.gsp = used for formatting exports
Scaffolding seungs in Domain Classes class Item extends AbstractDomain {
... static scaffolding = [ simpleFlow: true, inFilter: ['status', 'itemType', 'itemCategory'], inSearch: ['title', 'description'], inSort: ['title', 'itemType', 'artist'] ] ...
}
Scaffolding seungs in Domain Classes class Item extends AbstractDomain {
... static scaffolding = [ simpleFlow: true, inFilter: ['status', 'itemType', 'itemCategory'], inSearch: ['title', 'description'], inSort: ['title', 'itemType', 'artist'] ] ...
}
Scaffolding seungs in Domain Classes class Item extends AbstractDomain {
... static scaffolding = [ simpleFlow: true, inFilter: ['status', 'itemType', 'itemCategory'], inSearch: ['title', 'description'], inSort: ['title', 'itemType', 'artist'] ] ...
}
Direc3ons
• Weapons of Choice • AbstractDomain • Heavy-‐Duty Scaffolding • Mul3-‐lingualiza3on • Types & Categories • Connec3ng an API • Error Abstrac3on in a Plugin
Mul3-‐lingualiza3on • Transla3on of program code – aka i18n; that’s in Grails right? – .proper3es file per ‘group of (Domain) classes’
• album.proper3es • messages.proper3es • abstractDomain.proper3es • enums.proper3es
• Transla3on of data (records) – That’s what we refer to as “m17n”
Mul3-‐lingualiza3on • Transla3on of program code – aka i18n; that’s in Grails – .proper3es file per “group of Domain classes”
• album.proper3es • messages.proper3es • abstractDomain.proper3es • enums.proper3es
• Transla3on of objects (records) – That’s what we refer to as “m17n”
Mul3-‐lingualiza3on
• System default Language reflects the language of all record in the database
• Data is op3onally “overlayed” with an M17n record
• By conven3on – i.e. if the domain-‐name ends in “M17n” – Scaffolding renders a different controller – Scaffolding renders a different view
AbstractDomain.change
id .tle year image ar.st_id descrip.on
3 Licensed to Ill 1986 /img/4529ad62}.png 5 The first rap rock LP to top the Billboard album chart
id album_id language_id .tle descrip.on
1 3 7 Licensed to Ill Es ist das erste reine Hip-‐Hop-‐Album, das Platz 1 in den US-‐amerikanischen Billboard-‐Charts erreichte.
2 3 4 Licensed to Ill La canzone raggiunse la seuma posizione nella speciale classifica del ”Billboard Hot 100"
Album
AlbumM17n
Language.isSystem == false
Rela3ons are not overlay / translated (could be a limita3on)
Not overlayed
Not overlayed
m17n service Class Album extends AbstractDomain {
def m17nService
private String title ...
public String getTitle() { m17nService.getProperty(this, ‘title’) }
}
m17n service Class Album extends AbstractDomain {
def m17nService
private String title ...
public String getTitle() { m17nService.getProperty(this, ‘title’) }
}
The m17nService can figure out: • If an overlay Class exists (by conven3on) • What the system language is … Language.findByIsSystem(true) • What the requested Language is (from Session) • … or return the un-‐translated … this[title]
NB. Session does not necessarily contain browser locale, users can override this
Direc3ons
• Weapons of Choice • AbstractDomain • Heavy-‐Duty Scaffolding • Mul3-‐lingualiza3on • Types & Categories • Connec3ng an API
Types & Categories • Types – Hardcoded – Used by program-‐code(business-‐logic, rendering etc.)
• Categories – Added wherever they could be somewhat useful to a par3cular user
– Used in the UI for quick filters – Used in the UI for color-‐coding
Types & Categories
• Types – Hardcoded – Use by program-‐code (business-‐logic, rendering etc.)
• Categories – Added wherever they could be somewhat useful to a par3cular user
– Used in the UI for quick filters – Used in the UI for color-‐coding
Types & Categories
• Types – Hardcoded – Use by program-‐code (business-‐logic, rendering etc.)
• Categories – Added wherever they could be somewhat useful to a par3cular user
– Used in the UI for quick filters – Used in the UI for color-‐coding
An overview … just for the fun of it
Direc3ons
• Weapons of Choice • AbstractDomain • Heavy-‐Duty Scaffolding • Mul3-‐lingualiza3on • Types & Categories • Connec3ng an API
Now something completely different
Connector for API -‐> Grails
• Example of calling the Flickr REST API • Explain my approach to API wrapping
Abstrac3on of an API call
1. Validate the parameters needed for the par3cular call
2. Well eh, do the actual API call 3. Process the response or handle any errors
Abstrac3on of an API call
1. Validate the parameters needed for the par3cular call
2. Well eh, do the actual API call 3. Process the response or handle any errors
void apiCall(method, params) { if (validator(params)) { try { def rsp = doApiCall(method,params) return process(rsp) } catch (Exception ex) { ... } } else { // todo: raise validation exception } }
Params
Validator
Processor
API
Abstrac3on of an API call // abstracting the whole call handling with closures implemented in individual classes private def apiCall(FlickrApiMethod apiImplementation, def apiModel = {} ) { def params = apiImplementation.paramsClosure def validator = apiImplementation.validatorClosure def processor = apiImplementation.processorClosure if (validator(apiModel())) { try { def rsp = doApiCall(apiImplementation.apiMethod, params(apiModel())) return processor(rsp,apiModel()) } catch (FlickrException ex) { FlickrExceptionHandler.handleApiCallException(ex) } } // TODO: raise validation exception return apiModel() }
Implementa3on class photosGetInfo implements FlickrApiMethod{
// API METHOD static final String apiMethod = 'flickr.photos.getInfo' // VALIDATOR Closure validatorClosure = { FlickrPhoto photo -‐> if (!photo || !photo?.id) { return [validated:false, message:"Required value FlickrPhoto.id missing”] } return [validated:true] } // PARAMS Closure paramsClosure = { FlickrPhoto photo -‐> [photo_id:photo?.id, secret:(photo?.secret ?:'')] } ...
Implementa3on class photosGetInfo implements FlickrApiMethod{
...
// PROCESSOR Closure processorClosure = { GPathResult response, FlickrPhoto photo -‐> photo.isPublic = (response.photo.visibility.@ispublic?.toString() == '1') photo.secret = [email protected]() photo.serverId = [email protected]() photo.title = response.photo.title.toString() ...
return photo } ...
Implementa3on class photosGetInfo implements FlickrApiMethod{
... // ERROR CODES Closure errorsClosure = { GPathResult response -‐> if ([email protected]() == 1) { return null } // photo not found // recoverable errors if ([100,105,116].contains([email protected]())) { return new FlickrServiceApiException(response) } else { return new FlickrServiceSyntaxException(response) } }
}
We we’re at
• We’ve got a generic implementa3on of calls • A Class that implements a par3cular call • The params back-‐and-‐forth are Groovy • Now 3e it all together in a Grails-‐like way
Service.getPhotoById(..) class flickrService {
… // abstracting call handling implemented in individual classes private def apiCall(def apiImplementation, def apiModel = {}) { … } // public service methods public FlickrPhoto getPhotoById(Long id) { return apiCall( new photosGetInfo(), {new FlickrPhoto(id:id)} ) as FlickrPhoto }
Well, that’s a Wrap
• Weapons of Choice • AbstractDomain • Heavy-‐Duty Scaffolding • Mul3-‐lingualiza3on • Types & Categories • Connec3ng an API
Well, that’s a Wrap
• Weapons of Choice • AbstractDomain • Heavy-‐Duty Scaffolding • Mul3-‐lingualiza3on • Types & Categories • Connec3ng an API
[email protected] linkedin.com/in/edwinvannes twiker.com/edwinvannes