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.

Orbitz and Spring Webflow Case Study

4,611 views

Published on

In this session, Michael Alford and Mark Meeker will describe the major business goals that drove the development of Orbitz Worldwide’s next generation online travel commerce platform, and how those goals were met with Spring and other technologies.

Last summer, Orbitz Worldwide released a new generation of its global technology platform with the goals of internationalization, white-label capability, and faster, streamlined development. Michael and Mark will describe the key challenges of this technology project and how those challenges were addressed, including the good, bad, and ugly of the Spring Framework and Spring Web Flow.

Published in: Technology
  • Be the first to comment

Orbitz and Spring Webflow Case Study

  1. 1. Case Study: The Next Generation Online Travel Platform Michael Alford, Mark Meeker, and Alex Antonov
  2. 2. Agenda • Project context • Technology architecture • Building UI forms • Interesting Flows • Security and SSL • Progressive Enhancement • Composite View Pattern • Componentization • URLs and REST • State management and HTTP browser caching headers • Internationalization • Performance and concurrency • Summary • Q&A
  3. 3. Project Context
  4. 4. Tech History
  5. 5. 3 years of Spring Framework usage
  6. 6. Spring MVC experiments
  7. 7. Travel Commerce Platform
  8. 8. Increase developer productivity
  9. 9. Technology Architecture
  10. 10. Direct Connections Booking Services Distributed Business Travel Business Services Session Cache Thin Presentation Layer Distributed Web (Tomcat, Spring, SWF) Session Cache
  11. 11. Deals Shop Book Care (REST) (REST) (not REST) (mixed)
  12. 12. Spring and the UI Layer
  13. 13. Dear Java Developers, Whatever you do, don’t choose a web framework that generates its own XHTML (or worse, non- validating @#$% HTML that we wouldn’t be caught dead near). Your friends, The UI Team
  14. 14. <spring:bind /> <spring:bind path=”mymodel.myinput”> <input name=”${status.expression}” value=”${status.value}” /> </spring:bind>
  15. 15. Form Custom Tags • Started pre- spring-form.tld • Full control of mark-up • Abstracts concept of binding • Limit attributes available to be set • type, maxlength, class, autocomplete • Extended scope to include <label />
  16. 16. Form Custom Tags • Additional values for inputs • label-key, label-state, required, read-only • Build in hook for JavaScript • Turn-off running against live model • Augment error handling
  17. 17. Error Display • Custom Error custom tag • Formats errors with correct styles • Support for error tag debugging • Error highlighting built into form inputs
  18. 18. Interesting Flows • Leverage framework for page navigation • Large number of special conditions in booking path • Informational pop-up flows need data from flow, but don’t affect it • “Helper” flows for RESTful Ajax calls • Requirement for a “wait page”
  19. 19. Security and SSL • Browser security alerts are bad for business! • HTTP vs. HTTPS based on URL pattern • Spring Security channel filter to enforce SSL • Custom link tag insures correct protocol • Single source of truth
  20. 20. Flow listener with flow annotations to indicate flow’s security level <action-state id=“purchase”> <attribute name=“permissions” value=“USER”/> … </action-state>
  21. 21. http://blaugh.com/2006/08/21/ajax-makes-everything-better/
  22. 22. Universality • Serve pages that will work on any and every device • Fundamental basis for the web • Goes hand-in-hand with Accessibility
  23. 23. Graded Browser Support http://developer.yahoo.com/yui/articles/gbs
  24. 24. Progressive Enhancement
  25. 25. Plain Old Semantic HTML
  26. 26. Progressive Enhancement HTML CSS JS Content Presentation Behavior Frills” s it Up” it Sing” “ No “D res “M ake
  27. 27. Progressive Enhancement • Separation of Layers ( HTML / CSS / JS ) • Phased development • Easier to define view model and flows • And the “good stuff” too!
  28. 28. Progressive Enhancement 1 2 3 4 HTML     CSS     JS    
  29. 29. 1 HTML  CSS  JS 
  30. 30. <img src=”/map/image” /> <ul> <li><a href=”/map/move?dir=n”>North</a></li> <li><a href=”/map/move?dir=e”>East</a></li> <li><a href=”/map/move?dir=s”>South</a></li> <li><a href=”/map/move?dir=w”>West</a></li> ... </ul> <ul> <li><a href=”/map/zoom?level=1”>Zoom Level 1</a></li> <li><a href=”/map/zoom?level=2”>Zoom Level 2</a></li> <li><a href=”/map/zoom?level=3”>Zoom Level 3</a></li> ... </ul>
  31. 31. 2 HTML  CSS  JS 
  32. 32. 3 HTML  CSS  JS 
  33. 33. 4 HTML  CSS  JS 
  34. 34. Select from known list: Free form input:
  35. 35. 2 HTML  CSS  JS  4 HTML  CSS  JS 
  36. 36. 2 HTML  CSS  JS  4 HTML  CSS  JS 
  37. 37. XHR
  38. 38. Hijax • Term coined by Jeremy Keith • Bulletproof Ajax (http://bulletproofajax.com/) • Pull in portion of page via Ajax when XHR is supported • Re-use same portion when a full page refresh is required • Requires UI Componentization
  39. 39. Header Module 1 Rail Module 2 Footer
  40. 40. Header Login Module Module 1 JS-Enabled Rail XHR Module 2 noscript Footer Login page Module transition
  41. 41. JS-Enabled XHR no-script page transition
  42. 42. 4 HTML  CSS  JS 
  43. 43. Composite View Pattern • Separates “layout” of page from content • Allows to plug in different modules into page • Used in Apache Tiles • Leverage in-house framework • Try and gain as much re-use of JSP code
  44. 44. Componentization
  45. 45. Webapp Multi-Model • Webflow definition • Reusable components • Form
  46. 46. Reusable Component • View Module (JSP) • Model • Validator [optional] • Executor (Component Action) [optional] • Tests
  47. 47. Model = POJO
  48. 48. public class ShipperSelection implements Serializable { private List<ShipperOption> shipperOptions; private ShipperOption shipperOption; private COUNTRY country; public COUNTRY getCountry() { return country; } public void setCountry(COUNTRY country) { this.country = country; } public ShipperOption getShipperOption() { return shipperOption; } public void setShipperOption(ShipperOption shipperOption) { this.shipperOption = shipperOption; } public List<ShipperOption> getShipperOptions() { return shipperOptions; } public void setShipperOptions(List<ShipperOption> shipperOptions) { this.shipperOptions = shipperOptions; } public void addShipperOption(ShipperOption shipperOption) { this.shipperOptions.add(shipperOption); } }
  49. 49. /** * @spring.bean id=quot;shipperSelectionExecutorquot; * */ public class ShipperSelectionExecutor { private static PRODUCT_TYPE[] productTypes = {PRODUCT_TYPE.AIR}; public PRODUCT_TYPE[] getApplicableProductTypes() { return productTypes; } public boolean supports(Class clazz) { return ClassUtils.isAssignable(ShipperSelection.class, clazz); } public void setupBookComponent(ShipperSelection shipperSelection, BookState newParam) { List<ShipperOption> shipperOptions = getShipperOptions(); shipperSelection.setShipperOptions(shipperOptions); } private List<ShipperOption> getShipperOptions() { … return shipperOptions; } }
  50. 50. /** * @spring.bean id=quot;shipperSelectionValidatorquot; * */ public class ShipperSelectionValidator implements Validator { private List<COUNTRY> restrictedCountries; public ShipperSelectionValidator(List<COUNTRY> restrictedCountries) { this.restrictedCountries = restrictedCountries; } public boolean supports(Class clazz) { return ClassUtils.isAssignable(ShipperSelection.class, clazz); } public void validate(Object obj, Errors errors) { ShipperSelection c = (ShipperSelection) obj; if (restrictedCountries.contains(c.getCountry()) { errors.rejectValue(quot;countryquot;, quot;1535quot;); } } }
  51. 51. <bean id=quot;shipperSelectionExecutorquot; class=quot;com.orbitz.wl.web.book.air.action.ShipperSelectionExecutorquot; /> <bean id=quot;shipperSelectionValidatorquot; class=quot;com.orbitz.wl.web.book.air.validator.ShipperSelectionValidatorquot; /> <bean id=quot;shipperSelectionComponentquot; class=quot;com.orbitz.webframework.component.ComponentFactoryBeanquot;> <property name=quot;singletonquot; value=quot;falsequot; /> <property name=quot;idquot; value=quot;WL_UBP310.110quot; /> <property name=quot;namequot; value=quot;shipperSelectionquot; /> <property name=quot;modelClassquot; value=quot;com.orbitz.wl.web.book.air.model.ShipperSelectionquot; /> <property name=quot;executorquot; ref=quot;shipperSelectionExecutorquot;/> <property name=”validator” ref=”shipperSelectionValidator” /> </bean>
  52. 52. /** * @spring.bean id=quot;travelerInfoActionquot; * @spring.property name=quot;formObjectNamequot; value=quot;travelerInfoFormquot; * @spring.property name=quot;formObjectClassquot; *value=quot;com.orbitz.webframework.component.Formquot; * @spring.property name=quot;formObjectScopequot; value=quot;FLOWquot; * @spring.property name=quot;validatorquot; ref=quot;formValidatorquot; * @spring.property name=quot;propertyEditorRegistratorsquot; ref=quot;propertyEditorRegistratorsquot; */ public class TravelerInfoAction extends Action { private static final String SHIPPER_SELECTION = quot;shipperSelectionquot;; private static final String TRAVELERS_INPUT = quot;travelersInputquot;; @Override protected String[] getComponentIds() { return new String[] { SHIPPER_SELECTION + quot;Componentquot;, TRAVELERS_INPUT + quot;Componentquot;,}; } @Override public Event setupComponents(RequestContext context) { ShipperSelectionExecutor flightTicketExecutor = getComponentExecutor(context, SHIPPER_SELECTION); flightTicketExecutor.setupComponent(shipperSelection.getModel(), getBookState(context)); return success(); } public Event postProcess(RequestContext context) { TravelersInput travelersInput = getComponentModel(context, TRAVELERS_INPUT); TravelersInputExecutor whosTravelingExecutor = getComponentExecutor(context, TRAVELERS_INPUT); whosTravelingExecutor.executePostProcess(travelersInput, getBookState(context)); return success(); } }
  53. 53. URLs and REST
  54. 54. Fundamental nature of the web
  55. 55. RESTful URL represents all the state necessary to reference a particular resource
  56. 56. long-lived backwards compatible
  57. 57. benefits?
  58. 58. usability
  59. 59. agility and runtime isolation
  60. 60. Cache: performance and hardware savings
  61. 61. exponential growth of network effects
  62. 62. diffusion of innovation
  63. 63. search engine optimization
  64. 64. implementation?
  65. 65. Spring Webflow versus Spring MVC
  66. 66. one framework to rule them all
  67. 67. Session scope obfuscated public interface Action { public Event execute(RequestContext context) throws Exception; } public interface RequestContext { public MutableAttributeMap getRequestScope(); public MutableAttributeMap getFlashScope(); public MutableAttributeMap getFlowScope(); public MutableAttributeMap getConversationScope(); … } requestContext.getExternalContext().getSessionMap()
  68. 68. RESTful stateless flows
  69. 69. Deals Shop Book Care (REST) (REST) (not REST) (mixed)
  70. 70. <flow …> <attribute name=”stateless” value=”true”/> … </flow>
  71. 71. no view state no pause no continuation enforce with customization at XMLFlowBuilder level & made easier by SWF-310
  72. 72. separate model from the URL
  73. 73. http://www.ebookers.com/shop/home? type=air&ar.type=roundTrip&ar.rt.leaveSlice.orig.key=L HR&ar.rt.leaveSlice.dest.key=FLL&ar.rt.leaveSlice.date =12%2F12%2F07&ar.rt.leaveSlice.time=Anytime&ar.rt.retu rnSlice.date=15%2F12%2F07&ar.rt.returnSlice.time=Anyti me&ar.rt.numAdult=1&ar.rt.numSenior=0&ar.rt.numChild=0 &ar.rt.child%5B0%5D=&ar.rt.child%5B1%5D=&ar.rt.child %5B2%5D=&ar.rt.child%5B3%5D=&ar.rt.child %5B4%5D=&ar.rt.child%5B5%5D=&ar.rt.child %5B6%5D=&ar.rt.child%5B7%5D=&search=Search+Flights Internet Explorer’s URL limit: 2083 characters source: Source: http://support.microsoft.com/kb/208427
  74. 74. input mapper
  75. 75. AttributeMapper config <flow …> … <subflow-state id=”executeSearch” flow=”search”/> <attribute-mapper> <input-mapper> <input-attribute name=”dealsModel.searchAttr1”/> <input-attribute name=”dealsModel.searchAttr2”/> </input-mapper> </attribute-mapper> <transition on=”someOutcome” to=”someView”/> </subflow-state> </flow>
  76. 76. SWF-297 will also allow the input-mapper to also record validation error messages, providing a better alternative to the FormAction generally used for this purpose now.
  77. 77. re-use flows as subflows without changing the URL
  78. 78. State Management
  79. 79. infinite memory
  80. 80. used smallest scope possible
  81. 81. avoided session and conversation usage
  82. 82. request scope not for redirect-after-post
  83. 83. flow and flash scopes
  84. 84. continuation limits <flow:executor id=quot;flowExecutorquot; registry-ref=quot;flowRegistryquot;> <flow:repository type=quot;continuationquot; max-conversations=quot;1quot; max-continuations=quot;30quot;/> </flow:executor>
  85. 85. stateless flows, continuations, and form value history
  86. 86. no-cache no-store
  87. 87. Spring source when cacheSeconds == 0 /** * Prevent the response from being cached. * See www.mnot.net.cache docs. */ protected final void preventCaching(HttpServletResponse response) { response.setHeader(HEADER_PRAGMA, quot;No-cachequot;); if (this.useExpiresHeader) { // HTTP 1.0 header response.setDateHeader(HEADER_EXPIRES, 1L); } if (this.useCacheControlHeader) { // HTTP 1.1 header: quot;no-cachequot; is the standard value, // quot;no-storequot; is necessary to prevent caching on FireFox response.setHeader(HEADER_CACHE_CONTROL, quot;no-cachequot;); response.addHeader(HEADER_CACHE_CONTROL, quot;no-storequot;); } }
  88. 88. workaround #1 <bean name=quot;dealsFlowControllerquot; class=quot;org.springframework.webflow.…quot;> … <property name=quot;cacheSecondsquot; value=quot;1quot;/> </bean>
  89. 89. workaround #2 <bean name=quot;dealsFlowControllerquot; class=quot;org.springframework.webflow.…quot;> … <property name=“useCacheControlHeaderquot; value=“falsequot;/> <property name=quot;cacheSecondsquot; value=“0quot;/> </bean>
  90. 90. Internationalization
  91. 91. third-party libraries
  92. 92. Joda-time (what JDK Date/Calendar/DateFormat should be)
  93. 93. International Components for Unicode (ICU)
  94. 94. JScience’s Units JSR-275
  95. 95. immutable / thread-safe domain models & formatters
  96. 96. point-of-sale and locale parameterization
  97. 97. URL pattern POS
  98. 98. POS defaultLocale
  99. 99. POS config <pos> <posCode>EBCH</posCode> <description>Ebookers Switzerland Point of Sale</description> … <supportedLocales> <defaultLocale>de_CH</defaultLocale> <locale>de_CH</locale> <locale>fr_CH</locale> … </supportedLocales> … </pos>
  100. 100. locale selection
  101. 101. <property name=quot;interceptorsquot;> <list> … <bean id=quot;localeChangeInterceptorquot; class=“ localeChangeInterceptorquot;> org.springframework.web.i18n. <property name=quot;paramNamequot;> <value>locale</value> </property> </bean> … </list> <property>
  102. 102. locale persistence <bean id=quot;localeResolverquot; class=quot;org.springframework.web.servlet.i18n.cookieLocaleResolverquot;/> OR <bean id=quot;localeResolverquot; class=quot;org.springframework.web.servlet.i18n.sessionLocaleResolverquot;/>
  103. 103. <spring:bind /> versus <format />
  104. 104. register PropertyEditors editors.put(Currency.class, new CurrencyEditor()); editors.put(Scalar.class, new ScalarEditor()); editors.put(DateTime.class, new JodaDateTimeEditor(true)); editors.put(YearMonthDay.class, new JodaYearMonthDayEditor(true)); editors.put(LocalDate.class, new JodaLocalDateEditor(true)); …
  105. 105. format tags for data types format:dateTime format:distance format:distanceVector format:interval format:money format:number format:period format:address format:creditCard format:emailAddress format:location format:name format:phoneNumber
  106. 106. content access
  107. 107. MessageSource <bean id=quot;messageSource“ class=quot;com.orbitz.webframework.ContentMessageSourcequot;> <constructor-arg> <ref bean=quot;contentMessageConfigquot;/> </constructor-arg> <property name=quot;useCodeAsDefaultMessagequot; value=quot;truequot;/> </bean>
  108. 108. content access • <format:message /> • supports tokens of custom type
  109. 109. Spring’s dependency on JDK’s MessageFormat
  110. 110. open-source
  111. 111. performance and concurrency
  112. 112. good performance from Spring & Spring Web Flow
  113. 113. lock contention quot;http-8585-Processor39quot; daemon prio=1 tid=0x081634c8 nid=0x65b7 waiting for moni tor entry [0x9d41a000..0x9d41e0b0] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistr y.getSingleton(DefaultSingletonBeanRegistry.java:114) - waiting to lock <0xb0d85828> (a java.util.LinkedHashMap) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean (AbstractBeanFactory.java:187) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean (AbstractBeanFactory.java:156)
  114. 114. cached results of Spring’s getBeansOfType (circa March 2007 – Juergen says it may be fixed in Spring v2.0.5)
  115. 115. SWF, registerCustomEditors, PropertyEditor issues
  116. 116. Other lock contentions • JDK RMIClassLoaderSpi • JDK java.security.* • Tomcat • 3000 tag invocations in search results page view • Log4j • ICU
  117. 117. ConcurrentHashMap JDK 1.5 Emory backport for JDK 1.4
  118. 118. concurrency is hard (see Rob Harrop’s “Practical Enterprise Concurrency” presentation)
  119. 119. Scala: JVM language Erlang-style message passing
  120. 120. Summary • Project context • Technology architecture • Building UI forms • Managing Flows • Security and SSL • Progressive Enhancement • Composite View Pattern • Componentization • URLs and REST • State management and HTTP browser caching headers • Internationalization • Performance and concurrency • Summary • Q&A
  121. 121. Slides & Contact Slides: http://markmeeker.com/events/tse2007 Emails: Michael Alford - malford@orbitz.com Mark Meeker - mmeeker@orbitz.com We are Hiring: http://www.orbitz.com/startmycareer

×