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

Slides from Grails coding Dodo (OpenCredo 2011)

  • Be the first to comment

  • Be the first to like this

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.

    Be the first to comment

    Login to see the comments

Slides from Grails coding Dodo (OpenCredo 2011)

Views

Total views

1,500

On Slideshare

0

From embeds

0

Number of embeds

8

Actions

Downloads

27

Shares

0

Comments

0

Likes

0

×