64
Grails Code from the Trenches Collec3ons.shuffle(dayToDayWork)

2013 gr8 conf_grails_code_from_the_trenches

Embed Size (px)

Citation preview

Page 1: 2013 gr8 conf_grails_code_from_the_trenches

Grails  Code  from  the  Trenches  

Collec3ons.shuffle(dayToDayWork)  

Page 2: 2013 gr8 conf_grails_code_from_the_trenches

Edwin  van  Nes    1989  dBase  /  Clipper  /  FoxPro  1995    Borland  Delphi  2000  Java  2,  Enterprise  Edi3on  2005  PHP,  Typo3,  Joomla!,  …  2011  Groovy  &  Grails  

Page 3: 2013 gr8 conf_grails_code_from_the_trenches

Direc3ons  

•  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API      

Page 4: 2013 gr8 conf_grails_code_from_the_trenches

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,  …  

Page 5: 2013 gr8 conf_grails_code_from_the_trenches

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,  …  

Page 6: 2013 gr8 conf_grails_code_from_the_trenches

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,  …  

Page 7: 2013 gr8 conf_grails_code_from_the_trenches

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,  …  

Page 8: 2013 gr8 conf_grails_code_from_the_trenches

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,  …  

Page 9: 2013 gr8 conf_grails_code_from_the_trenches

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,  …  

Page 10: 2013 gr8 conf_grails_code_from_the_trenches

Direc3ons  

•  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  •  Error  Abstrac3on  in  a  Plugin  

Page 11: 2013 gr8 conf_grails_code_from_the_trenches

WhatWeDoMost*  

Services   Frontend  Portal  

Backend  Portal  Database   Our  Clients  

Consumers  (their  clients)  

*  overly  simplified  for  educa3onal  purposes  

Page 12: 2013 gr8 conf_grails_code_from_the_trenches

GR8Tunes  

Used  here  as  an  example  for  a  typical  Backend  portal  

Page 13: 2013 gr8 conf_grails_code_from_the_trenches

GR8Tunes  

Used  here  as  an  example  for  a  typical  Backend  portal  

Page 14: 2013 gr8 conf_grails_code_from_the_trenches

AbstractDomain  

•  Not  too  Groovy  probably  •  Add  some  standard  akributes  – Some  useful  3mestamps  –  ‘recentlyUpdated’  boolean  for  easy  highligh3ng  

– Each  record  gets  a  ‘remarks’  akribute  

Page 15: 2013 gr8 conf_grails_code_from_the_trenches

AbstractDomain.Status  

Page 16: 2013 gr8 conf_grails_code_from_the_trenches

AbstractDomain.Status  

Hibernate  filters  Plugin  •  defaultFilter  in  Backend  

–  Status  in  [Status.ACTIVE,  Status.INACTIVE]  

•  defaultFilter  in  Frontend  –  Status  ==  Status.ACTIVE  

Page 17: 2013 gr8 conf_grails_code_from_the_trenches

AbstractDomain.Status  

Hibernate  filters  •  defaultFilter  in  Backend  

–  Status  in  [Status.ACTIVE,  Status.INACTIVE]  

•  defaultFilter  in  Frontend  –  Status  ==  Status.ACTIVE  

Page 18: 2013 gr8 conf_grails_code_from_the_trenches

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')                                    }                          }                  }          }  }    

Page 19: 2013 gr8 conf_grails_code_from_the_trenches

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')                                    }                          }                  }          }  }    

Page 20: 2013 gr8 conf_grails_code_from_the_trenches

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')                                    }                          }                  }          }  }    

Page 21: 2013 gr8 conf_grails_code_from_the_trenches

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)    

Page 22: 2013 gr8 conf_grails_code_from_the_trenches

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”  

Page 23: 2013 gr8 conf_grails_code_from_the_trenches

Direc3ons  

•  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  •  Error  Abstrac3on  in  a  Plugin  

Page 24: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 25: 2013 gr8 conf_grails_code_from_the_trenches

Scaffolded  Controllers  

•  All  Controllers  exists  as  files  but  are  100%  scaffolded  unless  …    class  AlbumController  {  

 static  scaffold  =  true  }  

Page 26: 2013 gr8 conf_grails_code_from_the_trenches

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    }  

}  

Page 27: 2013 gr8 conf_grails_code_from_the_trenches

SimpleFlow  =  true  •  Simple  states  –  List  –  Edit  

•  Mul3ple  ac3ons  –  Create  –  Cancel  –  Save  –  SaveAndClose  –  SaveAndCreate  

Page 28: 2013 gr8 conf_grails_code_from_the_trenches

SimpleFlow  =  false  •  More  states  

–  List  –  Create  –  Edit  –  Show  

•  Same  set  of  ac3ons  –  Create  –  Cancel  –  Save  –  SaveAndClose  –  SaveAndCreate  

Page 29: 2013 gr8 conf_grails_code_from_the_trenches

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)  }  

Page 30: 2013 gr8 conf_grails_code_from_the_trenches

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)  }  

Page 31: 2013 gr8 conf_grails_code_from_the_trenches

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 32: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 33: 2013 gr8 conf_grails_code_from_the_trenches

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 34: 2013 gr8 conf_grails_code_from_the_trenches

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 35: 2013 gr8 conf_grails_code_from_the_trenches

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 36: 2013 gr8 conf_grails_code_from_the_trenches

Scaffolding  seungs  in  Domain  Classes  class  Item  extends  AbstractDomain  {  

 ...    static  scaffolding  =  [      simpleFlow:  true,      inFilter:      ['status',  'itemType',  'itemCategory'],      inSearch:      ['title',  'description'],      inSort:          ['title',  'itemType',  'artist']    ]    ...  

}  

Page 37: 2013 gr8 conf_grails_code_from_the_trenches

Scaffolding  seungs  in  Domain  Classes  class  Item  extends  AbstractDomain  {  

 ...    static  scaffolding  =  [      simpleFlow:  true,      inFilter:      ['status',  'itemType',  'itemCategory'],      inSearch:      ['title',  'description'],      inSort:          ['title',  'itemType',  'artist']    ]    ...  

}  

Page 38: 2013 gr8 conf_grails_code_from_the_trenches

Scaffolding  seungs  in  Domain  Classes  class  Item  extends  AbstractDomain  {  

 ...    static  scaffolding  =  [      simpleFlow:  true,      inFilter:      ['status',  'itemType',  'itemCategory'],      inSearch:      ['title',  'description'],      inSort:          ['title',  'itemType',  'artist']    ]    ...  

}  

Page 39: 2013 gr8 conf_grails_code_from_the_trenches

Direc3ons  

•  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  •  Error  Abstrac3on  in  a  Plugin    

Page 40: 2013 gr8 conf_grails_code_from_the_trenches

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”  

Page 41: 2013 gr8 conf_grails_code_from_the_trenches

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”  

Page 42: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 43: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 44: 2013 gr8 conf_grails_code_from_the_trenches

m17n  service  Class  Album  extends  AbstractDomain  {  

 def  m17nService    

 private  String  title    ...  

   public  String  getTitle()  {      m17nService.getProperty(this,  ‘title’)    }  

}    

Page 45: 2013 gr8 conf_grails_code_from_the_trenches

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    

Page 46: 2013 gr8 conf_grails_code_from_the_trenches

Direc3ons  

•  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  

Page 47: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 48: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 49: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 50: 2013 gr8 conf_grails_code_from_the_trenches

An  overview  …  just  for  the  fun  of  it  

Page 51: 2013 gr8 conf_grails_code_from_the_trenches

Direc3ons  

•  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  

Page 52: 2013 gr8 conf_grails_code_from_the_trenches

Now  something  completely  different  

Page 53: 2013 gr8 conf_grails_code_from_the_trenches

Connector  for  API  -­‐>  Grails  

•  Example  of  calling  the  Flickr  REST  API  •  Explain  my  approach  to  API  wrapping  

Page 54: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 55: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 56: 2013 gr8 conf_grails_code_from_the_trenches

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()  }      

Page 57: 2013 gr8 conf_grails_code_from_the_trenches

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  ?:'')]      }          ...  

Page 58: 2013 gr8 conf_grails_code_from_the_trenches

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            }      ...    

Page 59: 2013 gr8 conf_grails_code_from_the_trenches

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)        }      }    

}      

Page 60: 2013 gr8 conf_grails_code_from_the_trenches

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  

Page 61: 2013 gr8 conf_grails_code_from_the_trenches

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      }  

Page 62: 2013 gr8 conf_grails_code_from_the_trenches

Well,  that’s  a  Wrap  

•  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  

Page 63: 2013 gr8 conf_grails_code_from_the_trenches

Well,  that’s  a  Wrap  

•  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  

Page 64: 2013 gr8 conf_grails_code_from_the_trenches

 [email protected]    linkedin.com/in/edwinvannes    twiker.com/edwinvannes