Skip to content

Static File Deployment Design Issues & Strawman

Dominic Chambers edited this page Jan 15, 2015 · 1 revision

Strawman

There are a number of deployment related use cases that need to be considered when designing a solution for flat-file export:

  • Deployment Properties: the ability to change some aspects of an app depending on where it is being deployed, like connecting to different RESTful servers in Dev, UAT & Prod.
  • Versioned Caching: the ability to indefinitely cache content until a new version of an app is released.
  • Locale Support: the ability to serve up different content bundles for different locales.

There are only 3 ways these kinds of use cases could be theoretically supported in a scalable way, by all current and future deployment plug-ins (e.g. flat-file, War, Apache, Heroku, etc):

  1. A lowest common denominator approach where we make flat-file deployment the primary target so that it will also work everywhere else too.
  2. Creation of a set of generalized declarative operations that all deployment plug-ins agree to support.
  3. Bake-in a fixed number of concepts that all deployment plug-ins agree to support.

On investigation, it has so far been impossible to identify a realistic set of generalized operations that can meet even the current use cases, let alone all future ones. Therefore, approaches #1 and #3 seem to be the only possibilities. Examples of what the solutions might look like for these use cases are as follows:

  • Deployment properties: use JNDI on JEE servers, or do build-time substitution otherwise.
  • Versioned caching: use a filter to set the caching headers on JEE servers, an '.htaccess' file on Apache servers, and live without strong caching in flat-file export.
  • Locale support: create locale specific versions of each page, and a forwarding page to route users to the correct locale (this is only one of the possible options here, the others are discussed below).

Within the existing version of BladeRunnerJS (only supporting deployment via Wars) we have only a single page per aspect that is dynamically localized on the server. We have identified a number of solutions that would allow localization switching to instead occur on the client:

  1. Write all of the includes dynamically using JavaScript.
  2. Write the entire page dynamically using JavaScript so that URLs containing locale tokens (e.g. '%locale%') could be swapped with the active locale.
  3. Create a dynamic <base/> tag that includes the active locale within it.
  4. Create multiple locale specific version of the page, and convert the main index page into a switcher that forwards to the locale specific version of the page.

The first option is messy anyway, but is made yet more difficult by our composable plug-in architecture, where content-plugin can be composed from the output of other content plug-ins, and where tag-handler plug-ins use content plug-ins to determine the requests that should be made to server.

The second solution has the problem that the main index page is now obfuscated, making understanding and change by developers much harder.

The base tag solution initially seems like a very elegant solution to the problem, but on closer inspection has a number of down-sides that must be considered:

  • It requires a locale specific version of each content bundle to be created, whether it's needed or not.
  • It requires a base tag to be dynamically written, which is risky, given all the problems we've had making just a regular base tag work in all browsers.
  • It prevents us from fixing the problem that i18n can't currently be done for the index page.
  • It requires the tag-handler and content-plugin interfaces to be changed.(ContentPlugin.getValidDev/ProdContentPaths(), ContentPlugin.writeContent() & TagHandlerPlugin.writeDev/ProdTagContent() will all need to change).
  • It requires mediated access to the base tag if more than one process needs to change it.

Therefore, my preference is for solution #4. The following is a proposal for a new URL structure that supports flat-file export as per solution #4 (this proposal addresses the future requirement of multiple pages per aspect too):

`/` [locale forwarding page]
`/<locale>` [default page for default aspect]
`/page/<page>/<locale>` [given page for default aspect]
`/v/<version>/<content-path>` [content bundle for default aspect]

`/<aspect>/` [locale forwarding page]
`/<aspect>/<locale>` [default page for given aspect]
`/<aspect>/page/<page>/<locale>` [given page for given aspect]
`/<aspect>/v/<version>/<content-path>` [content bundle for given aspect]

Re-questioning everything has made me wonder why we use a base tag even for versioning, since this can just as easily be solved by prefixing the content paths returned from ContentPlugin.getValidDev/ProdContentPaths() with bundle/<version>, and so I propose we make this change at the same time.

Within development, there will still be a requirement to have some development filters/servlets, even though the requests for the above URLs will be handled by the model, because:

  • We'll still need to forward requests for index pages to the correct page, so they can be handled by the JSP servlet, for example.
  • We'll still need to set strong caching headers for all responses generated by a content plug-in.
  • We'll still need to set the correct character encoding for all resources.

Other issues not currently addressed by this strawman:

  1. How do prevent creating resources we aren't going to need?
  2. What does a deployer plug-in look like, since much of the functionality will be common to all deployers, like the set of bundles at each URL?

Implementation Details

BRJSDevFilter (formerly BRJSFilter):

  • Redirect to BRJSServlet if ServerRequestHandler.canHandleLogicalRequest() returns true

BRJSDevServlet (formerly BRJSServlet):

  • Pass to ServerRequestHandler.handleLogicalRequest()

BRJSProdFilter:

  • */v/<version>/* (set Cache-Control header depending on whether it maches this path or not)
  • *.html (set Content-Type header to 'UTF-8')

ServerRequestHandler:

  • Has a ContentPathParser that recognizes the following paths:
    • [/<aspect>]/ [locale forwarding page]
    • [/<aspect>]/<locale>/ [aspect index page]
    • [/<aspect>]/v/<version>/<content-path> [aspect content bundle]
    • [/<aspect>]/workbench/<bladeset>/<blade>/ [locale forwarding page]
    • [/<aspect>]/workbench/<bladeset>/<blade>/<locale>/ [workbench index page]
    • [/<aspect>]/workbench/<bladeset>/<blade>/v/<version>/<content-path> [workbench content bundle]
  • Throws an exception if <aspect> is explicitly set to 'default' rather than just omitting it.
  • Uses a RequestDispatcher to serve page content or BundlableNode.handleLogicalRequest() to handle the bundle requests.

ContentRequestHandler (formerly LogicalRequestHandler):

  • Used by BundlableNode.handleLogicalRequest()

App.export():

  • Uses ServerRequestHandler.getContentPathParser() to ensure the exported app request paths are identical to what we handle in development.

Other Changes:

  • At the moment BRJSDevServlet uses the BladerunnerUri class to determine the scopePath, and App.getBundlableNode() to convert the scopePath part of the BladerunnerUri to a BundlableNode, but BladerunnerUri and App.getBundlableNode() should be deleted once we are dealing primarily with logical paths, rather than physical paths the server can understand.

Outstanding Questions:

  • Is it acceptable to have '/en' in the request path (e.g. http://www.acme.com/en/) even if there is only one locale?
  • Should the locale switching page be editable by the user, or held internally?
  • Will this affect the AppCache plug-in?
Clone this wiki locally