Intent

This pattern's goal is to have a very loose coupling between cocoon services and the data provided by the generator.

Motivation

When developing web applications, we usually quickly prototype a solution for one context. Often, requirements evolve and multi context support is needed.

This pattern provides a systematic approach to make services multi context aware, with an elaborated fallback mechanism to manage specific, then common, then default (for example) configuration.

Moreover, all the information where mapping from name services to services' paths are centralized in one location only. Things are easy to update and maintain.

Architecture

http://www.bluexml.org/static/images/loose-coupling-dataaccess-dp.gif

Implementation

Tight coupling

It is a very simple pipeline.

http://www.bluexml.org/static/images/tight-coupling-dataaccess-dp.gif

    <map:match pattern="hello.html">
        <map:generate src="docs/hello.xml"/>
        <map:transform src="stylesheets/convert2html.xsl"/>
        <map:serialize type="xml"/>
    </map:match>

This web app works well, but if your boss suddenly tells you : "Well, things are changing quite a bit. The customer wants one web app to serve multiple hosts. And the 'hello' may change according the user's role."

Of course, there are a lot of services beside the hello one with the same kind of requirement. You have to update every service. So, after a quick brainstorming, you suggest to create a context directory under docs one. And you immediately go back to work and do it :

    <map:match pattern="hello.html">
        <map:generate src="docs/{request:serverName}/hello-{request-param:role}.xml"/>
        <map:transform src="stylesheets/convert2html.xsl"/>
        <map:serialize type="xml"/>
    </map:match>

At this point, you think it may be a good idea to centralize data for each context in one location only. You immediately go back to work and create the following :

    <map:match pattern="hello.html">
        <map:generate src="context://customer/{request:serverName}/hello-{request-param:role}.xml"/>
        <map:transform src="stylesheets/convert2html.xsl"/>
        <map:serialize type="xml"/>
    </map:match>

But you have to update every service again.

After a few cycles like this one, you're a little fed up with all this mess, and the always evolving requirements. So, you decide to be agile and build a medium coupling solution :

Medium coupling solution

http://www.bluexml.org/static/images/medium-coupling-dataaccess-dp.gif

    <map:match pattern="getData-*">
        <map:generate src="context://customer/{request:serverName}/hello-{request-param:role}.xml"/>
        <map:serialize type="xml"/>
    </map:match>

    <map:match pattern="hello.html">
        <map:generate src="cocoon:/getData-hello"/>
        <map:transform src="stylesheets/convert2html.xsl"/>
        <map:serialize type="xml"/>
    </map:match>

In this solution, you only to update the getData-* pipeline to reflect new requirements. But you have to do that for all your applications (all your sitemaps).

Loose coupling solution

[ATTACH]

    <map:match pattern="getData-*">
        <map:generate src="cocoon://locator/getData-({request:serverName})-{1}"/>
        <map:serialize type="xml"/>
    </map:match>

    <map:match pattern="hello.html">
        <map:generate src="cocoon:/getData-hello"/>
        <map:transform src="stylesheets/convert2html.xsl"/>
        <map:serialize type="xml"/>
    </map:match>

And you have in the locator directory at the root of your cocoon app the following :

    <!--|
        | {1} : context (for example serverName, but it could be application name)
        | {2} : criteria
        |-->
    <map:match pattern="getData-(*)-*">
        <map:select type="resource-exists">
              <map:when test="{config:/project-root}/{1}/author/{2}.xml">
                  <map:generate src="cocoon://project/{1}/author/{2}.xml"/>
              </map:when>
              <map:otherwise test="{config:/project-root}/{1}/anonymous/{2}.xml">
                  <map:generate src="cocoon://project/{1}/anonymous/{2}.xml"/>
              </map:otherwise>
        </map:select>
        <map:serialize type="xml"/>                    
    </map:match> 

You can improve the fallback mechanism by adding new situations for anonymous user for example :

              <map:when test="{config:/project-root}/{1}/reader/{2}.xml">
                  <map:generate src="cocoon://project/{1}/reader/{2}.xml"/>
              </map:when>

Now, to solve new requirements, you just need to update the locator sitemap.

If your boss tells you the customer wants the information in a database. Don't worry, just update your locator sitemap (but you will need in this case to update the resource-exists action too).

More information

In Forrest we have a Locationmap. This is an Input Module that allows you to implement this pattern. For more information see Locationmap Documentation and the Locationmap Code. It is intended that this will be turned inot a Cocoon Block as soon as someone gets the time (hint hint).

This web page is available in html format on http://www.bluexml.org. You may have help on cocoon or bluexml mailing lists.

JCKermagoret

DesignPattern/Locator (last edited 2009-09-20 23:42:48 by localhost)