2013 gr8 conf_grails_code_from_the_trenches

Preview:

Citation preview

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  =  response.photo.@secret.toString()        photo.serverId  =  response.photo.@server.toInteger()        photo.title  =  response.photo.title.toString()        ...      

               return  photo            }      ...    

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

}      

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  

 edwin@ihomer.nl    linkedin.com/in/edwinvannes    twiker.com/edwinvannes