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.

Building Seaside Applications in VA Smalltalk

3,987 views

Published on

Short intro to Seaside Web Programming in Smalltalk.
Thoughts and experiences on turning existing Smalltalk applications into a Web Application Server using Seaside. Seaside makes Web programming very much like writing a modal fat/rich client application.
This presentation was given at the VA Smalltalk Forum Europe 2008.
For more information visit http://www.objektfabrik.de

Published in: Technology
  • Be the first to comment

Building Seaside Applications in VA Smalltalk

  1. 1. Seaside Build ing Web App licat VA S ions i n mall talk Vers ion 8 © Joachim Tuchel, www.objektfabrik.de
  2. 2. This is not...
  3. 3. This is not... A Seaside Tutorial
  4. 4. What we‘ll look at
  5. 5. What we‘ll look at Quick, what is Seaside?
  6. 6. What we‘ll look at Quick, what is Seaside? Parallels between Fat client development in VAST and Seaside Web development
  7. 7. What we‘ll look at Quick, what is Seaside? Parallels between Fat client development in VAST and Seaside Web development Turning a fat client into a Seaside Web application
  8. 8. Quick, what is Seaside?
  9. 9. Quick, what is Seaside? A very short introduction to the Seaside Web Application Framework
  10. 10. Overview Open Source Started by Avi Bryant in 2001 Currently maintained by Lukas Renggli, Adrian Lienhardt and others In productive use since 2002 Available for Squeak,VisualWorks, Gemstone, Dolphin and soon VA Smalltalk
  11. 11. In Production DabbleDB (by Avi Reserve Travel (Hotel Bryant‘s company) Booking engine) CMSBox Run Basic www.cmsbox.ch auctomatic.com Seaside.st (Homepage whooka.com (outdoor of Seaside) sports) In-house applications many more...
  12. 12. Seaside is different Components vs. html-pages You never see http requests or responses (unless you want to) No templating or mixing code and design (like jsp‘s) Share as much as possible Components hold application state
  13. 13. Pure Smalltalk Components defined in Smalltalk Control Flow in Smalltalk No XML configuration, no state machine Plain Smalltalk code Debugging in Smalltalk ! You can read all code and learn from it
  14. 14. Components Subclasses of WAComponent Hold state render themselves: #renderContentOn: Can have subcomponents (#children) Reusable (same page and other pages) Pages are composed of components
  15. 15. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  16. 16. Rendering a Component html is the canvas we‘re painting on WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  17. 17. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  18. 18. Rendering a Component rendering can include application logic WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  19. 19. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  20. 20. Rendering a Component a DIV tag for WAStoreCartView>>renderContentOn: html applying CSS cart hasItems ifFalse: [^ self]. formatting html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  21. 21. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  22. 22. Rendering a Component this is a brush to WAStoreCartView>>renderContentOn: html paint on the canvas cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  23. 23. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  24. 24. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. with: is always the html div call on a brush last id: and writes xhtml 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  25. 25. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  26. 26. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; blocks for nesting with: [ certain brushes/tags html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  27. 27. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  28. 28. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ cart is an inst var html small: [ html strong: 'Your cart:' ]. of the component html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  29. 29. Rendering a Component WAStoreCartView>>renderContentOn: html cart hasItems ifFalse: [^ self]. html div id: 'cart'; with: [ html small: [ html strong: 'Your cart:' ]. html table: [ cart countsAndItems do: [:assoc | self renderRowForCount: assoc key of: assoc value on: html ]. html tableRow: [ html space]. html tableRow: [ html tableData: ''. html tableData: ''. html tableData: [ html strong: cart totalPrice printStringAsCents ] ] ] ]
  30. 30. Rendering and Callbacks WAStoreCartView>>renderRowForCount: aNumber of: anItem on: html | countString | countString := (aNumber = 1) ifTrue: [''] ifFalse: ['(', aNumber displayString, ') ']. html tableRow: [ html tableData: [ html anchor callback: [ cart remove: anItem ]; with: '-']. html tableData: countString, anItem title. html tableData: (aNumber * anItem price) printStringAsCents ]
  31. 31. Rendering and Callbacks WAStoreCartView>>renderRowForCount: aNumber of: anItem on: html | countString | countString := (aNumber = 1) ifTrue: [''] the anchor brush ifFalse: ['(', aNumber displayString, ') ']. html tableRow: [ a link draws html tableData: [ html anchor callback: [ cart remove: anItem ]; with: '-']. html tableData: countString, anItem title. html tableData: (aNumber * anItem price) printStringAsCents ]
  32. 32. Rendering and Callbacks WAStoreCartView>>renderRowForCount: aNumber of: anItem on: html | countString | countString := (aNumber = 1) ifTrue: [''] ifFalse: ['(', aNumber displayString, ') ']. html tableRow: [ html tableData: [ html anchor callback: [ cart remove: anItem ]; with: '-']. html tableData: countString, anItem title. html tableData: (aNumber * anItem price) printStringAsCents ]
  33. 33. Rendering and Callbacks WAStoreCartView>>renderRowForCount: aNumber of: anItem on: html | countString | countString := (aNumber = 1) this block will be ifTrue: [''] evaluated when user ifFalse: ['(', aNumber displayString,the link ']. clicks ') html tableRow: [ html tableData: [ html anchor callback: [ cart remove: anItem ]; with: '-']. html tableData: countString, anItem title. html tableData: (aNumber * anItem price) printStringAsCents ]
  34. 34. Rendering and Callbacks WAStoreCartView>>renderRowForCount: aNumber of: anItem on: html | countString | countString := (aNumber = 1) ifTrue: [''] ifFalse: ['(', aNumber displayString, ') ']. html tableRow: [ html tableData: [ html anchor callback: [ cart remove: anItem ]; with: '-']. html tableData: countString, anItem title. html tableData: (aNumber * anItem price) printStringAsCents ]
  35. 35. Rendering and Callbacks f! o WAStoreCartView>>renderRowForCount: aNumber of: anItem g tS on: html ns | countString | ie countString := (aNumber = 1) su ifTrue: [''] r ifFalse: ['(', aNumber displayString, ') ']. aq html tableRow: [ html tableData: [ Pe html anchor callback: [ cart remove: anItem ]; with: '-']. oR html tableData: countString, anItem title. NP html tableData: (aNumber * anItem price) printStringAsCents ] T T H
  36. 36. Rendering Result - the page
  37. 37. Rendering Result - the page this is the subcomponent we just rendered
  38. 38. Rendering Result - the page
  39. 39. Rendering Result - html <!DOCTYPE html PUBLIC quot;-//W3C//DTD XHTML 1.0 Strict...> <html xmlns=...> ... <div id=quot;cartquot;> <small><strong>Your cart:</strong><small> <table> <tr> <td><a href=quot;http://localhost:...quot;>-</a></ td><td>California Roll</td><td>$2.50</td></tr> <tr> <td><a href=quot;http://localhost:...quot;>-</a></ td><td>Akagai</td><td>$3.00</td></tr> <tr>&nbsp;</tr> <tr> <td></td><td></td><td><strong>$5.50</strong></ td></tr> </table> </div>
  40. 40. CSS for the Design Components generate valid XHTML only CSS files for Designing the XHTML output separation between code and design code is written by developer, css is written by web designer Served from within Image or from external web sever
  41. 41. Seaside development for VA Smalltalkers
  42. 42. Seaside development for VA Smalltalkers lels aral P een etw b side Sea ent lopm eve d and ient Cl Rich ent opm alk vel de allt A Sm in V
  43. 43. Components vs. Visual Parts Both can be composed to complex components with subcomponents Controls are event driven Callbacks to perform Smalltalk methods
  44. 44. Composed Components vs. Subparts Component renderContentOn: renderContentOn: html Your personal todomatic Page ... html render: mySubComponent1. html render: mySubComponent2. ... Save Logoff Subcomonent Subcomponent renderContentOn: renderContentOn: Checkbox Array: Option 1 ! Option 3 ! Option 5 Your rating: ! Option 2 Option 4 Multi-select: Full List My Items Item 1 none avarage: Item 2 Item 3 Item 4 Favourite: City, State, Postal - Select One - City Postal State Code:
  45. 45. Composed Components vs. Subparts don‘t call renderContentOn: of child components! Seaside calls it! Component renderContentOn: renderContentOn: html Your personal todomatic Page ... html render: mySubComponent1. html render: mySubComponent2. ... Save Logoff Subcomonent Subcomponent renderContentOn: renderContentOn: Checkbox Array: Option 1 ! Option 3 ! Option 5 Your rating: ! Option 2 Option 4 Multi-select: Full List My Items Item 1 none avarage: Item 2 Item 3 Item 4 Favourite: City, State, Postal - Select One - City Postal State Code:
  46. 46. Event-to-Action connections abtBuildInternals ... refreshButton abtWhenPrimitive: #clicked perform: ( conn22 := AbtEventToActionConnection new source: refreshButton; eventName: #clicked; actionProvider: applicationModel variableFeatureName: #refreshTrafficMessages featureSelector: #IS_refreshTrafficMessages).
  47. 47. Action and Value Callbacks renderContentOn: html ... html submitButton callback: [self applicationModel refreshTrafficMessages]; with: ‘Refresh‘. ... html label: ‘Please enter a name:‘. html textInput callback: [:txt| self name: txt]; value: self name. “or in short form“ html textInput on: #name of: self.
  48. 48. Differences Feedback cycle: Fat client: instant feedback Web: On Submit (AJAX / Scriptaculous for immediate feedback) Seaside has no GUI painter out of the box projects going on useful in HTML/CSS ?
  49. 49. Control Flow
  50. 50. Tasks Subclasses of WATask Control flow single method #go Can call Tasks and Components
  51. 51. Call and Answer Components and Tasks can #call: and #answer: Caller gets replaced by callee / delegate Control is delegated to callee #answer:returns a result to caller and returns control to caller
  52. 52. Call and Answer (2) Component 1 handleSomeCallbackWith: param ... Component 2 result := self call: ((Component2 new) renderContentOn: html someInstVar: param; html form: [ yourself). html text: someInstVar result ifTrue: [self saveData] printString. ifFalse: [self discardAll]. html submitButton callback: [self answer: true] text: ‚Okay, save this‘].
  53. 53. Call and Answer (2) Component 2 gets rendered in place of Component Component 1 1 and takes control of request/ response processing handleSomeCallbackWith: param ... Component 2 result := self call: ((Component2 new) renderContentOn: html someInstVar: param; html form: [ yourself). html text: someInstVar result ifTrue: [self saveData] printString. ifFalse: [self discardAll]. html submitButton callback: [self answer: true] text: ‚Okay, save this‘].
  54. 54. Call and Answer (2) Component 2 gets rendered in place of Component Component 1 1 and takes control of request/ response processing handleSomeCallbackWith: param ... Component 2 result := self call: ((Component2 new) renderContentOn: html someInstVar: param; html form: [ yourself). html text: someInstVar result ifTrue: [self saveData] printString. ifFalse: [self discardAll]. html submitButton callback: [self answer: true] text: ‚Okay, save this‘]. Component 1 gets back control as soon as Comp.2 was submitted and can use the answer to continue
  55. 55. A typical Fat Client in VA Smalltalk
  56. 56. A typical Fat Client in VA Smalltalk typically your Workflow Controller framework
  57. 57. A typical Fat Client in VA Smalltalk typically your Workflow Controller framework View Controller(s) typically a visual part / Composition Editor
  58. 58. A typical Fat Client in VA Smalltalk typically your Workflow Controller framework View View Controller(s) Controller(s) typically a visual part / Composition Editor
  59. 59. A typical Fat Client in VA Smalltalk typically your Workflow Controller framework View View ... Controller(s) Controller(s) typically a visual part / Composition Editor
  60. 60. A typical Seaside Application
  61. 61. A typical Seaside Application call: WATask answer:
  62. 62. A typical Seaside Application call: WATask answer: rendering Component request handling call/answer rendered by Web browser
  63. 63. A typical Seaside Application call: WATask answer: rendering Component Component request handling call/answer rendered by Web browser
  64. 64. A typical Seaside Application call: WATask answer: rendering ... Component Component request handling call/answer rendered by Web browser
  65. 65. ga tin ion ri at W lic ga p Ap itin ide like wr as Se ion h cat uc li m pp ry ve IA is GU al od M
  66. 66. Seaside and Rich Internet Applications
  67. 67. Ajax Most popular RIA technology Combination of Server Side-Application Code JavaScript in the Browser XML HTTP Request as transport protocol for portions of a page
  68. 68. JavaScript Libraries Numerous available Most Popular (and one of the most mature / complete) Prototype Script.aculo.us Full access to CSS styles, DOM objects XmlHttpRequest Browser-neutral
  69. 69. Seaside and Scriptaculous Seaside wrappers Prototype / Scriptaculous Developer writes Smalltalk code JavaScript ‚rendered‘ in Smalltalk JavaScript is embedded in HTML Page html updater id: ‚myelement‘ callback: [:html| self renderNewStuffOn: html].
  70. 70. Demo
  71. 71. From Fat Client to Seaside Application What you have to look at and change in an existing application
  72. 72. From Fat Client to Seaside Application Persistency What you have to look at and change in an existing application
  73. 73. From Fat Client to Seaside Application Persistency Back-end access What you have to look at and change in an existing application
  74. 74. From Fat Client to Seaside Application Persistency Back-end access What you have to look at GUI Interaction and change in an existing application
  75. 75. From Fat Client to Seaside Application Persistency Back-end access What you have to look at GUI Interaction and change in an existing application Miscellaneous
  76. 76. Comparing the two architectures Database Database fat client Persistency Persistency server Persistency Business logic Business logic Persistency Business logic Presentation Presentation Business logic Presentation Presentation browser Presentation Presentation Presentation 34
  77. 77. Changing the Architecture Easier when layers are clearly structured MVC Pattern Business logic should be fully reusable Persistency Persistency Business logic Business logic Transition Controller Presentation Presentation Presentation View 35
  78. 78. Persistency on a Fat Client 1 or only a few ID lastname firstname 1 Henderson Joe active transactions 2 Levinson Mark 3 McLachlan Sarah per client Database Each client holds its own copy 1 Concurrency & Joe 1 Henderson Joe Isolation on Henderson 1 Joe Client 1 Database level Henderson Client 1 Client 1 36
  79. 79. Persistency on a Web Server Many active transactions ID lastname firstname 1 Henderson Joe 2 Levinson Mark 3 McLachlan Sarah Server holds 1 copy per session Database Concurrency in 1 Image or DB 1 1 Joe Joe Joe Henderson Henderson Henderson Isolation on Web Server Server / Session todomatic.com todomatic.com todomatic.com level Details of Joe Henderson Details of Joe Henderson Details of Joe Henderson 37
  80. 80. Persistency on a Web Server Connection Pooling Parallel Transactions Isolation on Image Level Multithreading All supported by current Frameworks 38
  81. 81. Accessing Backend Systems CICS, Host-programs etc. Requests may not block the server Multithreading Last Resort: Replace existing library with TCP/IP based communications 39
  82. 82. Another Interaction model HTML is different than a local GUI Server Round-Trip vs. instant Event handling Validation Error Reporting HTML knows no Datatypes, only text 40
  83. 83. Possible Solutions JavaScript-based Ajax-calls for server-side Validation in the validation Browser Faster Slower, many requests Server-side validation validation only necessary implemented on the server Type info needs to be Type info only on the sent to the browser server 41
  84. 84. Other Topics Access Control Many fat client apps use DB password of current user User management and access control needs to be implemented 42
  85. 85. Other Topics Performance Server Smalltalk and Seaside can handle several hundred requests per second on a normal PC Overall performance depends on application code more than SST/ Seaside 43
  86. 86. Other Topics Production Logging Error reporting Health checks Performance measurement and reporting 44
  87. 87. Other Topics Availability Scheduled Downtimes for Maintenance Tasks DB Reorganization Installing Fixpacks New releases Database Migrations Most important for Internet Apps 45
  88. 88. Deployment scenario Seaside Seaside Seaside Seaside Image Image Image Image Sticky Sessions Static Files HTTP Server with Load Balancing Images (e.g. Apache with mod_proxy_balancer or mod_rewrite) CSS Media... 46
  89. 89. How to start a conversion project? Build a prototype (3-4 developers) Choose a few dialogs of your app Some easier ones to start with ca. 2 complex ones to see if possible Address architectural risks ~2 months to make a decision
  90. 90. Advantages of a conversion project? Reuse existing Smalltalk Know-How Reuse existing business code Adopt corporate Design Standards seamless visual integration Keep the Pace of Smalltalk Make Teams‘ strengths visible
  91. 91. Questions? Contact details: objektfabrik Joachim Tuchel Fliederweg 1 71640 Ludwigsburg, Germany email: jtuchel@objektfabrik.de http://www.objektfabrik.de
  92. 92. u o y k n ! a g h in T n te ! s e li id r s o a f e S y jo n E

×