Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

2013 gr8 conf_grails_code_from_the_trenches

904 views

Published on

  • Be the first to comment

  • Be the first to like this

2013 gr8 conf_grails_code_from_the_trenches

  1. 1. Grails  Code  from  the  Trenches  Collec3ons.shuffle(dayToDayWork)  
  2. 2. Edwin  van  Nes    1989  dBase  /  Clipper  /  FoxPro  1995    Borland  Delphi  2000  Java  2,  Enterprise  Edi3on  2005  PHP,  Typo3,  Joomla!,  …  2011  Groovy  &  Grails  
  3. 3. Direc3ons  •  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API    
  4. 4. 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,  …  
  5. 5. 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,  …  
  6. 6. 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,  …  
  7. 7. 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,  …  
  8. 8. 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,  …  
  9. 9. 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,  …  
  10. 10. Direc3ons  •  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  •  Error  Abstrac3on  in  a  Plugin  
  11. 11. WhatWeDoMost*  Services  Frontend  Portal  Backend  Portal  Database   Our  Clients  Consumers  (their  clients)  *  overly  simplified  for  educa3onal  purposes  
  12. 12. GR8Tunes  Used  here  as  an  example  for  a  typical  Backend  portal  
  13. 13. GR8Tunes  Used  here  as  an  example  for  a  typical  Backend  portal  
  14. 14. AbstractDomain  •  Not  too  Groovy  probably  •  Add  some  standard  akributes  – Some  useful  3mestamps  – ‘recentlyUpdated’  boolean  for  easy  highligh3ng  – Each  record  gets  a  ‘remarks’  akribute  
  15. 15. AbstractDomain.Status  
  16. 16. AbstractDomain.Status  Hibernate  filters  Plugin  •  defaultFilter  in  Backend  –  Status  in  [Status.ACTIVE,  Status.INACTIVE]  •  defaultFilter  in  Frontend  –  Status  ==  Status.ACTIVE  
  17. 17. AbstractDomain.Status  Hibernate  filters  •  defaultFilter  in  Backend  –  Status  in  [Status.ACTIVE,  Status.INACTIVE]  •  defaultFilter  in  Frontend  –  Status  ==  Status.ACTIVE  
  18. 18. 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)                                    }                          }                  }          }  }    
  19. 19. 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)                                    }                          }                  }          }  }    
  20. 20. 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)                                    }                          }                  }          }  }    
  21. 21. 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)    
  22. 22. 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”  
  23. 23. Direc3ons  •  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  •  Error  Abstrac3on  in  a  Plugin  
  24. 24. 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  
  25. 25. Scaffolded  Controllers  •  All  Controllers  exists  as  files  but  are  100%  scaffolded  unless  …    class  AlbumController  {    static  scaffold  =  true  }  
  26. 26. 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    }  }  
  27. 27. SimpleFlow  =  true  •  Simple  states  –  List  –  Edit  •  Mul3ple  ac3ons  –  Create  –  Cancel  –  Save  –  SaveAndClose  –  SaveAndCreate  
  28. 28. SimpleFlow  =  false  •  More  states  –  List  –  Create  –  Edit  –  Show  •  Same  set  of  ac3ons  –  Create  –  Cancel  –  Save  –  SaveAndClose  –  SaveAndCreate  
  29. 29. 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)  }  
  30. 30. 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)  }  
  31. 31. 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  
  32. 32. 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  
  33. 33. 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  
  34. 34. 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  
  35. 35. 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  
  36. 36. Scaffolding  seungs  in  Domain  Classes  class  Item  extends  AbstractDomain  {    ...    static  scaffolding  =  [      simpleFlow:  true,      inFilter:      [status,  itemType,  itemCategory],      inSearch:      [title,  description],      inSort:          [title,  itemType,  artist]    ]    ...  }  
  37. 37. Scaffolding  seungs  in  Domain  Classes  class  Item  extends  AbstractDomain  {    ...    static  scaffolding  =  [      simpleFlow:  true,      inFilter:      [status,  itemType,  itemCategory],      inSearch:      [title,  description],      inSort:          [title,  itemType,  artist]    ]    ...  }  
  38. 38. Scaffolding  seungs  in  Domain  Classes  class  Item  extends  AbstractDomain  {    ...    static  scaffolding  =  [      simpleFlow:  true,      inFilter:      [status,  itemType,  itemCategory],      inSearch:      [title,  description],      inSort:          [title,  itemType,  artist]    ]    ...  }  
  39. 39. Direc3ons  •  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  •  Error  Abstrac3on  in  a  Plugin    
  40. 40. 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”  
  41. 41. 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”  
  42. 42. 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  
  43. 43. 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  
  44. 44. m17n  service  Class  Album  extends  AbstractDomain  {    def  m17nService      private  String  title    ...      public  String  getTitle()  {      m17nService.getProperty(this,  ‘title’)    }  }    
  45. 45. 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    
  46. 46. Direc3ons  •  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  
  47. 47. 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  
  48. 48. 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  
  49. 49. 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  
  50. 50. An  overview  …  just  for  the  fun  of  it  
  51. 51. Direc3ons  •  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  
  52. 52. Now  something  completely  different  
  53. 53. Connector  for  API  -­‐>  Grails  •  Example  of  calling  the  Flickr  REST  API  •  Explain  my  approach  to  API  wrapping  
  54. 54. 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  
  55. 55. 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  
  56. 56. 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()  }      
  57. 57. 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  ?:)]      }          ...  
  58. 58. Implementa3on  class  photosGetInfo  implements  FlickrApiMethod{      ...      //    PROCESSOR      Closure  processorClosure  =  {  GPathResult  response,  FlickrPhoto  photo  -­‐>        photo.isPublic  =  (response.photo.visibility.@ispublic?.toString()  ==  1)        photo.secret  =  response.photo.@secret.toString()        photo.serverId  =  response.photo.@server.toInteger()        photo.title  =  response.photo.title.toString()        ...                      return  photo            }      ...    
  59. 59. Implementa3on  class  photosGetInfo  implements  FlickrApiMethod{      ...    //    ERROR  CODES      Closure  errorsClosure  =  {  GPathResult  response  -­‐>        if  (response.err.@code.toInteger()  ==  1)  {  return  null  }  //  photo  not  found        //  recoverable  errors        if  ([100,105,116].contains(response.err.@code.toInteger()))  {          return  new  FlickrServiceApiException(response)        }  else  {          return  new  FlickrServiceSyntaxException(response)        }      }    }      
  60. 60. 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  
  61. 61. 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      }  
  62. 62. Well,  that’s  a  Wrap  •  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  
  63. 63. Well,  that’s  a  Wrap  •  Weapons  of  Choice  •  AbstractDomain  •  Heavy-­‐Duty  Scaffolding  •  Mul3-­‐lingualiza3on  •  Types  &  Categories  •  Connec3ng  an  API  
  64. 64.  edwin@ihomer.nl    linkedin.com/in/edwinvannes    twiker.com/edwinvannes  

×