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
6. What we‘ll look at
Quick, what is Seaside?
Parallels between Fat client development in
VAST and Seaside Web development
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
9. Quick, what is
Seaside?
A very short
introduction to the
Seaside Web
Application Framework
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
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> </tr>
<tr>
<td></td><td></td><td><strong>$5.50</strong></
td></tr>
</table>
</div>
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
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. Components vs.
Visual Parts
Both can be composed to complex
components with subcomponents
Controls are event driven
Callbacks to perform Smalltalk methods
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. 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:
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. 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 ?
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. 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. 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. 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
56. A typical Fat Client
in VA Smalltalk
typically your
Workflow Controller
framework
57. A typical Fat Client
in VA Smalltalk
typically your
Workflow Controller
framework
View
Controller(s) typically
a visual part /
Composition
Editor
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. A typical Fat Client
in VA Smalltalk
typically your
Workflow Controller
framework
View View ...
Controller(s) Controller(s) typically
a visual part /
Composition
Editor
68. 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
69. 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
70. 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].
72. From Fat Client to
Seaside Application
What you have to
look at
and change in an
existing application
73. From Fat Client to
Seaside Application
Persistency
What you have to
look at
and change in an
existing application
74. From Fat Client to
Seaside Application
Persistency
Back-end access
What you have to
look at
and change in an
existing application
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
76. 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
77. 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
78. 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
79. 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
80. 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
81. Persistency
on a Web Server
Connection Pooling
Parallel Transactions
Isolation on Image Level
Multithreading
All supported by current Frameworks
38
82. 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
83. 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
84. 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
85. Other Topics
Access Control
Many fat client apps use DB
password of current user
User management and access
control needs to be implemented
42
86. 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
87. Other Topics
Production
Logging
Error reporting
Health checks
Performance measurement and
reporting
44
88. Other Topics
Availability
Scheduled Downtimes for
Maintenance Tasks
DB Reorganization
Installing Fixpacks
New releases
Database Migrations
Most important for Internet Apps
45
89. 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
90. 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
91. 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