Grails: Patterns &
Practices
Paul Bowler
Senior Consultant, OpenCredo
Who are you?
Coding Dojo
• Agile!
• Teams of 2-4 people
• 1 sprint = 20 minutes
• Domain-Drive Design (I’m the Product Owner)
• Test-Dr...
?
5 ‘Maturity’ Levels
?
?
?
?
Domain-Driven Design
• “Domain-driven design (DDD) is an approach to developing
software for complex needs by deeply conne...
User Story 1
“As a pomodoro fan, I would like to be able to add
tasks to a uniquely named activity inventory, so that I
ca...
Useful Commands
• grails create-app pomodoro
• grails create-domain-class <domain>
• grails generate-all <domain>
• grails...
Considerations
• Associations
• One-to-One
• One-to-Many
• Many-to-Many
• Constraints &Validation
• Time-Stamping?
• Defau...
You did create some
tests first, right?
Implementation
class Task {
Inventory inventory
String description
static belongsTo = [Inventory]
static constraints = {
d...
Domain Tests
class InventoryTests extends GrailsUnitTestCase {
void testConstraints() {
def existingInventory = new Invent...
Gotcha!
• Potential performance issue with mapped
collections:
• Adding to the Set requires loading all
instances from the...
Implementation (2)
class Task {
Inventory inventory
String description
static constraints = {
description(nullable: false,...
Side-effects?
• Different syntax for adding Tasks
• No cascading deletes
• Custom finder required to find all Tasks in
an In...
User Story 2
“As a pomodoro fan, I would like to be able move tasks
onto a ‘To Do Today’ sheet, so that I can see work to ...
Considerations
• Does the ‘Today’ list share any common
attributes with the Inventory?
• How about a more intuitive URL sc...
?
Level 1
?
?
?
Views
Level 1 -Views
Controller
Model
PageView PageViewPageView
Model Model
Level 1 ‘Smells’
• Logic built into pages:
• Overuse of Request Parameters
• If-Then tags
• Inline groovy using ${...}
• P...
?
Level 2
?
?
Controllers
Views
Level 1-2 Refactoring
• Move logic out of pages into controllers
• Reduce pages into fragments
• Use layouts to construct ...
User Story 3
“As a pomodoro fan, I would like to have an optimised
workflow for US2, so that I can save time and reduce
inp...
Considerations
• Web Flow plugin?
• Command Objects?
• What changes need to be made to domain
classes?
Web Flow
class InventoryController {
…
def inventoryFlow = {
showInventory {
on("done").to "saveInventory"
on("continue")....
User Story 4
“As a pomodoro fan, I would like to be able update the
number of iterations I’ve completed on each task in my...
Considerations
• Can we do this without page refreshes?
• How can we test this?
• Domain changes?
Level 2 - Controllers
Controller
Domain
Controller Controller
Domain Domain Domain
Layout
Fragments Fragments
Layout
Fragm...
Level 2 ‘Smells’
• Large, complex Controllers
• Different scenarios driven by ‘If/Then’ logic
• Content negotiation increa...
?
Level 3 - Services
?
Services
Controllers
Views
Level 2-3 Refactoring
• Move domain transaction logic out of
controllers into services
• Controllers should be ‘glue’ that...
User Story 5
“As a pomodoro partner, I would like a simple REST
API over your daily task view, so I can integrate your
dat...
Considerations
• Don’t clutter your Controllers!
• REST-ful URLs
• Content negotiation?
• Custom XML/JSON formats?
RESTful URL Mappings
static mappings = {
"/task/$id?"(resource:"task")
}
static mappings = {
"/task/$id"(controller:"task"...
Content Negotiation
class InventoryController {
def inventory
def list = {
this.inventory = Inventory.list()
withFormat {
...
Custom Formats?
def listAsXML = {
def inventory = Inventory.get(params.id)
def tasks = inventory.tasks
render(contentType:...
User Story 6
“As a pomodoro fan, I’d like to be able to add
unplanned and urgent tasks to the bottom of my daily
list, so ...
Level 3 - Services
Controller
Domain
Controller Controller
Domain Domain Domain
Services
Layout
Fragments Fragments
Layout...
Level 3 ‘Smells’
• Large, complex Services
• Services acting as proxies for domain
behaviour
• ‘Cut-and-paste’ methods
?
Level 4 - Libraries
Libraries
Services
Controllers
Views
Level 3-4 Refactoring
• Move common code out of services into
POGO’s (or POJO’s)
• Enrich our domain model to simplify ser...
User Story 7
“As a pomodoro fan, I would like to be able to search
for tasks on my inventory through a simple interface, s...
Level 4 - Libraries
Controller
Domain
Controller Controller
Domain Domain Domain
Services
Libraries Libraries
Layout
Fragm...
Level 4 ‘Smells’
• Large, monolithic application
• Increased cognitive overhead
• New starters struggle
• Components ‘cut ...
Plugins
Level 5 - Plugins
Libraries
Services
Controllers
Views
Level 4-5 Refactoring
• Componentise the application into plugins
• Construct applications by combining plugins
• Could yo...
User Story 8
“As a pomodoro fan, I would like a simplified version of
my Inventory, so I can view it on my iPhone.”
Useful Commands
• grails create-plugin <plugin>
• grails package-plugin
• grails install-plugin /path/to/plugin/grails-
ex...
Layouts and Fragments
<g:include action="show" id="1" />
<g:include action="show" id="${currentTask.id}" />
<g:include con...
Layout Options
• In your views:
<meta name="layout" content="main"></meta>
• In your controller:
static layout = 'task'
st...
Level 5 - Plugins
Controller
Domain
Controller
Domain Domain
Plugins
Page
View
Page
View
Controller
Services
Libraries / P...
Plugins
Libraries
Services
Controllers
The Full Picture
Views
Phew!
Well Done.
Grails patterns and practices
Upcoming SlideShare
Loading in …5
×

Grails patterns and practices

991 views

Published on

Slides from Grails coding Dodo (OpenCredo 2011)

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
991
On SlideShare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
23
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Grails patterns and practices

  1. 1. Grails: Patterns & Practices Paul Bowler Senior Consultant, OpenCredo
  2. 2. Who are you?
  3. 3. Coding Dojo • Agile! • Teams of 2-4 people • 1 sprint = 20 minutes • Domain-Drive Design (I’m the Product Owner) • Test-Driven Development (Maybe!) • Yes, you can use the user guide and internet! • User demo at the end of each sprint • Discussion + Refactoring • Prize for best app!
  4. 4. ? 5 ‘Maturity’ Levels ? ? ? ?
  5. 5. Domain-Driven Design • “Domain-driven design (DDD) is an approach to developing software for complex needs by deeply connecting the implementation to an evolving model of the core business concepts.” • The premise of domain-driven design is the following: • Placing the project's primary focus on the core domain and domain logic • Basing complex designs on a model • Initiating a creative collaboration between technical and domain experts to iteratively cut ever closer to the conceptual heart of the problem.
  6. 6. User Story 1 “As a pomodoro fan, I would like to be able to add tasks to a uniquely named activity inventory, so that I can see what work I need to complete over the next few weeks.”
  7. 7. Useful Commands • grails create-app pomodoro • grails create-domain-class <domain> • grails generate-all <domain> • grails generate-controller <domain> and add ‘static scaffold = true’ to controller
  8. 8. Considerations • Associations • One-to-One • One-to-Many • Many-to-Many • Constraints &Validation • Time-Stamping? • Default values (and field values inViews?)
  9. 9. You did create some tests first, right?
  10. 10. Implementation class Task { Inventory inventory String description static belongsTo = [Inventory] static constraints = { description(nullable: false, blank: false) } } class Inventory { String name static hasMany = [tasks: Task] static constraints = { name(nullable: false, blank: false, unique: true) } }
  11. 11. Domain Tests class InventoryTests extends GrailsUnitTestCase { void testConstraints() { def existingInventory = new Inventory(name: "Paul’s Inventory") mockForConstraintsTests(Inventory, [ existingInventory ]) ! ! // Validation should fail if both properties are null. ! ! def inventory = new Inventory() ! ! assertFalse inventory.validate() ! ! assertEquals "nullable", inventory.errors["description"] ! ! // So let's demonstrate the unique constraint. ! ! inventory = new Inventory(name: "Paul’s Inventory") ! ! assertFalse inventory.validate() ! ! assertEquals "unique", inventory.errors["name"] ! ! // Validation should pass! ! ! inventory = new Inventory(name: "John’s Inventory") ! ! assertTrue inventory.validate() ! } }
  12. 12. Gotcha! • Potential performance issue with mapped collections: • Adding to the Set requires loading all instances from the database to ensure uniqueness • Likewise for mapped List • Works fine in development, but what if you have 1,000,000+ rows?
  13. 13. Implementation (2) class Task { Inventory inventory String description static constraints = { description(nullable: false, blank: false) } } class Inventory { String name }
  14. 14. Side-effects? • Different syntax for adding Tasks • No cascading deletes • Custom finder required to find all Tasks in an Inventory • Scaffolding breaks!
  15. 15. User Story 2 “As a pomodoro fan, I would like to be able move tasks onto a ‘To Do Today’ sheet, so that I can see work to be completed today and view my work history.”
  16. 16. Considerations • Does the ‘Today’ list share any common attributes with the Inventory? • How about a more intuitive URL scheme?
  17. 17. ? Level 1 ? ? ? Views
  18. 18. Level 1 -Views Controller Model PageView PageViewPageView Model Model
  19. 19. Level 1 ‘Smells’ • Logic built into pages: • Overuse of Request Parameters • If-Then tags • Inline groovy using ${...} • Poor use of layouts • Little use of tags • Domain classes as simple ‘active records’ • Page-based information architecture
  20. 20. ? Level 2 ? ? Controllers Views
  21. 21. Level 1-2 Refactoring • Move logic out of pages into controllers • Reduce pages into fragments • Use layouts to construct device or stakeholder- centric views from pages and fragments • Use available tag libraries • Create your own tag libraries! • Stylesheets rule - minimise markup
  22. 22. User Story 3 “As a pomodoro fan, I would like to have an optimised workflow for US2, so that I can save time and reduce input mistakes.”
  23. 23. Considerations • Web Flow plugin? • Command Objects? • What changes need to be made to domain classes?
  24. 24. Web Flow class InventoryController { … def inventoryFlow = { showInventory { on("done").to "saveInventory" on("continue").to "addTask" } … addTask { redirect(controller:"task", action:"create") } saveInventory() } } <g:form action="inventory"> <g:submitButton name="continue" value="Add Another"></g:submitButton> <g:submitButton name="done" value="I’m Done"></g:submitButton> </g:form>
  25. 25. User Story 4 “As a pomodoro fan, I would like to be able update the number of iterations I’ve completed on each task in my ‘To Do Today’ list, so that I can keep track of my progress and improve my future estimates.”
  26. 26. Considerations • Can we do this without page refreshes? • How can we test this? • Domain changes?
  27. 27. Level 2 - Controllers Controller Domain Controller Controller Domain Domain Domain Layout Fragments Fragments Layout Fragments Fragments Layout Fragments Fragments
  28. 28. Level 2 ‘Smells’ • Large, complex Controllers • Different scenarios driven by ‘If/Then’ logic • Content negotiation increases complexity further • Many similar controller methods (not DRY!) • Poorly handled Transactions
  29. 29. ? Level 3 - Services ? Services Controllers Views
  30. 30. Level 2-3 Refactoring • Move domain transaction logic out of controllers into services • Controllers should be ‘glue’ that binds business services to UI • Service methods should reflect business scenarios • Make use of transactional capability of services
  31. 31. User Story 5 “As a pomodoro partner, I would like a simple REST API over your daily task view, so I can integrate your data into my application.”
  32. 32. Considerations • Don’t clutter your Controllers! • REST-ful URLs • Content negotiation? • Custom XML/JSON formats?
  33. 33. RESTful URL Mappings static mappings = { "/task/$id?"(resource:"task") } static mappings = { "/task/$id"(controller:"task") { action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"] } } static mappings = { "/task/$id"(controller:"task", parseRequest:true) { action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"] } }
  34. 34. Content Negotiation class InventoryController { def inventory def list = { this.inventory = Inventory.list() withFormat { html inventoryList:inventory json { render inventory as JSON } xml { render inventory as XML } } } }
  35. 35. Custom Formats? def listAsXML = { def inventory = Inventory.get(params.id) def tasks = inventory.tasks render(contentType:"text/xml") { inventory(name:inventory.name) { tasks { for(t in tasks) { task(title:t.title) ! } }! } } }
  36. 36. User Story 6 “As a pomodoro fan, I’d like to be able to add unplanned and urgent tasks to the bottom of my daily list, so that I can track and manage interruptions.”
  37. 37. Level 3 - Services Controller Domain Controller Controller Domain Domain Domain Services Layout Fragments Fragments Layout Fragments Fragments Layout Fragments Fragments
  38. 38. Level 3 ‘Smells’ • Large, complex Services • Services acting as proxies for domain behaviour • ‘Cut-and-paste’ methods
  39. 39. ? Level 4 - Libraries Libraries Services Controllers Views
  40. 40. Level 3-4 Refactoring • Move common code out of services into POGO’s (or POJO’s) • Enrich our domain model to simplify services: • Named Queries • Derived Properties • Criteria: Conjunctions, Disjunctions, Projections, Restrictions
  41. 41. User Story 7 “As a pomodoro fan, I would like to be able to search for tasks on my inventory through a simple interface, so I can find and modify them easily.”
  42. 42. Level 4 - Libraries Controller Domain Controller Controller Domain Domain Domain Services Libraries Libraries Layout Fragments Fragments Layout Fragments Fragments Layout Fragments Fragments
  43. 43. Level 4 ‘Smells’ • Large, monolithic application • Increased cognitive overhead • New starters struggle • Components ‘cut and pasted’ into similar projects
  44. 44. Plugins Level 5 - Plugins Libraries Services Controllers Views
  45. 45. Level 4-5 Refactoring • Componentise the application into plugins • Construct applications by combining plugins • Could your application itself be constructed as a plugin for an organisation’s product suite? • Writing plugins that modify the Grails/Spring context is beyond the scope of this workshop!
  46. 46. User Story 8 “As a pomodoro fan, I would like a simplified version of my Inventory, so I can view it on my iPhone.”
  47. 47. Useful Commands • grails create-plugin <plugin> • grails package-plugin • grails install-plugin /path/to/plugin/grails- example-0.1.zip
  48. 48. Layouts and Fragments <g:include action="show" id="1" /> <g:include action="show" id="${currentTask.id}" /> <g:include controller="task" /> <g:include controller="task" action="list" /> <g:include action="list" params="[sort:'title', order:'asc'] /> <html> <head> <title><g:layoutTitle default="An example decorator" /></title> <g:layoutHead /> </head> <body> <div class="menu"><!--my common menu goes here--></menu> <div class="body"> <g:layoutBody /> </div> </div> </body> </html>
  49. 49. Layout Options • In your views: <meta name="layout" content="main"></meta> • In your controller: static layout = 'task' static layout = 'custom/task' • By Convention: grails-app/views/layouts/task.gsp grails-app/views/layouts/task/list.gsp • Inline: <g:applyLayout name="myLayout" template="taskTemplate" collection="${tasks}" /> <g:applyLayout name="myLayout" url="http://www.google.com" /> <g:applyLayout name="myLayout">The content to apply a layout to</g:applyLayout>
  50. 50. Level 5 - Plugins Controller Domain Controller Domain Domain Plugins Page View Page View Controller Services Libraries / Plugins Domain Domain Layout Fragments Fragments Layout Fragments Fragments Services Libraries Libraries
  51. 51. Plugins Libraries Services Controllers The Full Picture Views
  52. 52. Phew! Well Done.

×