cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Daniel Fagerstrom <dani...@nada.kth.se>
Subject Re: [RT] composition vs. inheritance in blocks
Date Fri, 01 Apr 2005 14:12:07 GMT
Reinhard Poetz wrote:

> Daniel Fagerstrom wrote:
>
>> Reinhard Poetz wrote:
>
<snip/>

>> What does extends actually buy us here, couldn't we just use 
>> "requrires" for the relation to portal-skin/1.0.0.
>
> IIUC using "extends" was the original idea here.

I know, but I don't see any reasons to take that as written in stone. 
Extends is an implementation aspect in the otherwise declarative 
block.xml and as we have discussed it is probably better to be more 
explicit in the sitemap about what and how a block exposes things. 
Thurthermore since Stefano gave his original design there have been a 
trend towards considering the sitemap as the central point for 
configuration, look at Sylvain's and Carsten's recent work e.g. So my 
question remains, what does "extends" buy us?

>> <requires block="http://cocoon.apache.org/blocks/portal-skin/1.0.0" 
>> name="skin"/>
>>
>> and be explicit about what we expose from portal-skin/1.0.0?
>>
>>> SITEMAP.XMAP
>>> <map:match pattern="one-special.css">
>>>   <map:read src="styles/css/one-special.css"/>
>>> </map:match>
>>
>>
>>
>> Being explicit means that we end the sitemap with:
>>
>> <map:mount uri-prefix="" src="blocks://skin"/>
>
> >
>
>>>                                           - o -
>>>
>>> The project that wants to use the Portal is in the block "MyPortal". 
>>> It needs several components from "Portal" (generator, several 
>>> transformers) and it needs a block that provides the skin, or more 
>>> precisly, it needs a block that implements 
>>> http://cocoon.apache.org/interfaces/portal-skin/1.0. This can either 
>>> be "portal-skin", the default skin, or "MySkin", that provides one 
>>> additional CSS. Everything else is taken from "portal-skin".
>>>
>>>
>>> From my POV this solution is very clear and comprehensible. The 
>>> aspects "portal functionality" and "skinning" are separated and the 
>>> used implementations can simply be replaced by other implementations 
>>> (shown by using the "mySkin" block).
>>>
>>>                                           - o -
>>>
>>> What does your solution that ueses multiple inheritance look like? 
>>> (If this is a bad example to show the advantages of MI feel free to 
>>> enhance it!)
>>
>>
>>
>> First I will not use the term MI as it doesn't describe what I want 
>> to achieve that well and as it also seem to stir all kinds of bad 
>> reactions that distracts us from the task at hand.
>>
>> So I agree with most of what you show in your example, it looks neat. 
>> What I lack from it is how to reuse the sitemap in the Portal block.
>>
>> I would have a sitemap similar to the one in the demo portal in the 
>> Portal block. But e.g. the profiles part in the portal-handler 
>> configuration would rather be:
>>
>> Portal Sitemap
>> --------------
>>
>> ...
>> <profiles>
>>  <copletbasedata-load 
>> uri="blocks:/load-global-profile?profile=copletbasedata"/>
>>  <copletdata-global-load 
>> uri="blocks:/load-global-profile?profile=copletdata"/>
>>  <copletdata-role-load 
>> uri="blocks:/load-role-profile?profile=copletdata"/>
>>  <copletdata-user-load 
>> uri="blocks:/load-user-profile?profile=copletdata"/>
>>  <copletinstancedata-global-load 
>> uri="blocks:/load-global-profile?profile=copletinstancedata"/>
>>  <copletinstancedata-role-load 
>> uri="blocks:/load-role-profile?profile=copletinstancedata"/>
>>  <copletinstancedata-user-load 
>> uri="blocks:/load-user-profile?profile=copletinstancedata"/>
>>  <copletinstancedata-user-save 
>> uri="blocks:/save-user-profile?profile=copletinstancedata"/>
>>  <layout-global-load uri="blocks:/load-global-profile?profile=layout"/>
>>  <layout-role-load uri="blocks:/load-role-profile?profile=layout"/>
>>  <layout-user-load uri="blocks:/load-user-profile?profile=layout"/>
>>  <layout-user-save uri="blocks:/save-user-profile?profile=layout"/>
>> </profiles>
>> ...
>>
>> Meaning that the different configuration pipelines are found through 
>> the blocks manager that would ask the extending block (recursively) 
>> for the configuration pipelines first, and if they not are found 
>> there, the own pipeline would be used.
>>
>> Then MyPortal could redefine some of the configuration pipelines and 
>> reuse the rest from Portal:
>>
>> MyPortal Sitemap
>> ----------------
>>
>> ...
>> <pipeline>
>>  <match pattern="load-user-profile">
>>    ...
>>  </match>
>>
>>  <mount uri-prefix="" src="blocks://portal"/>
>> </pipline>
>> ...
>>
>> Now this mechanism is more limited than real inheritance. map:mount 
>> become a two way contract where the mounting sitemap can be asked 
>> about services through the block manager, but it doesn't export the 
>> interface of the "extended" block. If we have something like the we 
>> probably should have some way to differ between mounts that allow the 
>> mounted block to ask and those who don't.
>
> (I think) I understand what you want. You called it an "application 
> block" which is the base for all your applications. This application 
> block (e.g. a company portal) provides services that can be used and 
> customized by other blocks.

No it's much simpler ;) I'm not talking about reuse I'm talking about 
*use*. To make blocks a success they must of course be easy to use in 
user applications. And to make shure that they are easy to use, we must 
of course discuss what it looks like when we really use them.

So my application block above is not at all intended for reuse, it is an 
actual application (actually nearly all of what I have discussed in this 
thread is about ease of use rather than reuse). If you don't like 
packaging your applications as blocks that's fine with me we can discuss 
that case instead.

The reason that I want to package my applications as (not intended for 
reuse) blocks is that it will solve a number of practical problems for 
us in a convenient way. Today I have a (large) number of applications 
running under the same Cocoon at my developer machine. When we have 
added some new functionality to a webapp I run a script that packages 
the particular webapp with dependencies and some deployment info and 
installs it on a production machine. When the customer is satisfied with 
the test release I update the production release in a similar way. 
Packaging my webapp as a block will make it able for me to achieve the 
same thing without much scripting at all as it takes care about 
dependencies, deploy time parameters (db connections e.g.) and packaging.

For the idea of creating a reusable "application block" it is in my 
experience not such a good idea. We started that way when we wanted to 
reuse a whole application, using the design pattern that I have tried to 
explain in this thread. We did it more because of time constraint than 
because we actually believed it to be a good idea. Since then we have 
factored out a numeber of "vertical" blocks from the original 
application that handles different concerns, and that makes reuse much 
more flexible.

>
>>                                  --- o0o ---
>>
>> Concerning the skin I find it somewhat burocratic to need to define a 
>> new block for beeing able to extend it but I'm ok with it for the 
>> time beeing, we will see when we start to use the things. What I 
>> would prefer would be to do something like:
>>
>> MyPortal Sitemap
>> ----------------
>>
>> ...
>> <pipeline>
>>  <match pattern="load-user-profile">
>>    ...
>>  </match>
>>
>>  <match pattern="skin/one-special.css">
>>    <read src="styles/css/one-special.css"/>
>>   </match>
>>
>>  <mount uri-prefix="skin" src="blocks://skin"/>
>>
>>  <mount uri-prefix="" src="blocks://portal"/>
>> </pipline>
>> ...
>>
>>                                  --- o0o ---
>>
>> So what do you think about this?
>
>
> I think I got the idea. Personally, I would solve this by composition; 
> the profiling is just another reference of the block. Of course, as 
> Stefano said, blocks have to be designed for this. If the application 
> block hasn't factored out the profile aspect, then you can't replace 
> implementation A by implementation B:
>
> --------------------------------------------------------------------------- 
>
> "Application block" (to be reused in many projects)
> --------------------------------------------------------------------------- 
>
> <block xmlns="http://apache.org/cocoon/blocks/cob/1.0"
>  id="http://mycompany.com/blocks/application-block/1.0.0">
>   <name>Application block</name>
>   <requirements>
>     <requires interface="http://mycompany.com/interfaces/skin/1.0"
>       name="skin"
>       default="http://mycompany.com/blocks/myskin/1.0.0"/>
>     <requires interface="http://cocoon.apache.org/interfaces/profile/1.0"
>       name="profile"
>       default="http://cocoon.apache.org/blocks/portal/1.0.0"/>
>     <requires interface="http://cocoon.apache.org/interfaces/portal/1.0"
>       name="portal"
>       default="http://cocoon.apache.org/blocks/portal/1.0.0"/>
>   </requirements>
> </block>

It would work, but to achieve what I did in a few sitemap lines (above) 
you have to create and deploy a special MyProfile block, and as I just 
wanted to override a few things in my customized profile I have to 
extend the original profile from the Portal block in some way. In the 
sitemap in MyProfile you have to write  a copy of the profiles section 
in the Portal block:

MyProfile sitemap
-----------------

...
<profiles>
 <copletbasedata-load 
uri="blocks:/profile/load-global-profile?profile=copletbasedata"/>
 <copletdata-global-load 
uri="blocks:/profile/load-global-profile?profile=copletdata"/>
 <copletdata-role-load 
uri="blocks:/profile/load-role-profile?profile=copletdata"/>
 <copletdata-user-load 
uri="blocks:/profile/load-user-profile?profile=copletdata"/>
 <copletinstancedata-global-load 
uri="blocks:/profile/load-global-profile?profile=copletinstancedata"/>
 <copletinstancedata-role-load 
uri="blocks:/profile/load-role-profile?profile=copletinstancedata"/>
 <copletinstancedata-user-load 
uri="blocks:/profile/load-user-profile?profile=copletinstancedata"/>
 <copletinstancedata-user-save 
uri="blocks:/profile/save-user-profile?profile=copletinstancedata"/>
 <layout-global-load 
uri="blocks:/profile/load-global-profile?profile=layout"/>
 <layout-role-load uri="blocks:/profile/load-role-profile?profile=layout"/>
 <layout-user-load uri="blocks:/profile/load-user-profile?profile=layout"/>
 <layout-user-save uri="blocks:/profile/save-user-profile?profile=layout"/>
</profiles>
...
<pipeline>
  <match pattern="profile/load-user-profile">
   ...
</match>

Here the blocks protocol is supposed to first look in the own sitemap 
(and find "load-user-profile") before it asks the super block. As you 
can see you need a copy of the profiles section of the sitemap booth in 
Portal and in MyProfile. And you define and deploy small blocks for each 
aspect you want to override in the original block. The behaviour of an 
actual application will be spread out in all these small extended 
blocks. I found it clumsy, complicated and burocratic compared to the 
simple sitemap based extension mechanism I used in my example.

> What I do like is <mount uri-prefix="" src="blocks://portal"/> which 
> makes it explicit what a block exports. I'm not sure about why this 
> has to be a "two way contract".

It must be a two way contract, as "extend and overide" is a two way 
contract. If you have it one way you end up having to copy everything 
that is an agregate of functionality as in the profile example above.

For another example of two way communication beween a default sitemap 
and an extending one you can look at Forrest which make good use of 
allowing user extensions 
(http://forrest.apache.org/docs/dev/sitemap-ref.html, 
http://svn.apache.org/viewcvs.cgi/forrest/trunk/main/webapp/sitemap.xmap?view=markup 
e.g.).

Here is an example:

    <map:resource name="skinit">
        <map:select type="exists">
          <map:when test="{project:skins-dir}{forrest:skin}/xslt/html/{type}.xsl">
            <map:transform src="{project:skins-dir}{forrest:skin}/xslt/html/{type}.xsl">
              <map:parameter name="notoc" value="{notoc}"/>
              <!-- For backwards-compat with 0.2 - 0.4 skins -->
              <map:parameter name="isfaq" value="{notoc}"/>
              <map:parameter name="nopdf" value="{nopdf}"/>
              <map:parameter name="path" value="{path}"/>
              <map:parameter name="config-file" value="{project:skinconf}"/>
            </map:transform>
          </map:when>
          <map:otherwise>
            <map:transform src="{forrest:context}/skins/{forrest:skin}/xslt/html/{type}.xsl">
              <map:parameter name="notoc" value="{notoc}"/>
              <!-- For backwards-compat with 0.2 - 0.4 skins -->
              <map:parameter name="isfaq" value="{notoc}"/>
              <map:parameter name="nopdf" value="{nopdf}"/>
              <map:parameter name="path" value="{path}"/>
              <map:parameter name="config-file" value="{project:skinconf}"/>
            </map:transform>
          </map:otherwise>
        </map:select>
      <map:serialize/>
    </map:resource>

if the style sheet exists in the extending directory given by 
"project:skins-dir" it is used otherwise the default variant from 
"forrest:context" is used. As you see it is a two way communication 
there Forrest need explicit info about the loocation of the extending 
application at deploy time. With the mechanism I propose we would get:

Forrest block sitemap
---------------------

    <map:resource name="skinit">
      <map:transform src="blocks:/skins/{forrest:skin}/xslt/html/{type}.xsl">
      <map:parameter name="notoc" value="{notoc}"/>
      <!-- For backwards-compat with 0.2 - 0.4 skins -->
      <map:parameter name="isfaq" value="{notoc}"/>
      <map:parameter name="nopdf" value="{nopdf}"/>
      <map:parameter name="path" value="{path}"/>
      <map:parameter name="config-file" value="{project:skinconf}"/>
      </map:transform>
      <map:serialize/>
    </map:resource>

instead and could use Forrest from our own app with:

Own documentation pipeline
--------------------------

<pipeline>
  <match pattern="skins/common/xslt/html/document2html.xsl">
    ...
  </match>

  <mount uri-prefix="" src="blocks://forrest"/>
</pipeline>

where mount:/ gives polymorphic lookup. The advantage, except for being 
terser, with this solution compared to the one Forrest use today for the 
block context is that the Forrest block doesn't need to know any 
explicit paths to the extending application wich is especially important 
if you want to use the same block in several contexts.

> If you write <copletdata-role-load 
> uri="blocks:/load-role-profile?profile=copletdata"/> then it means 
> that you make  it explicit what can be overriden and what not.
> I would use <copletdata-role-load 
> uri="blocks:/profile/load-role-profile?profile=copletdata"/> which 
> requires another reference. Using the proposed <mount>-mechanism, you 
> can reuse the "portal"-pipeline.

I'm not certain that I follow you. I would (possibly) use:

  blocks://profile/load-profile

for refering to another block, and:

  blocks:/load-profile

for refering to the same block possibly with sub protocols:

  blocks:polymorph:

if we want to be explicit about when something can be looked up from an 
extending sitemap

  blocks:super:

for refereing to the extended block.

A problem with blocks://profile, rather than blocks:profile// is if one 
want to access the "root" block sitemap from a sub sitemap, but that 
could maybe be done with blocks://this/.

>                                        - o -
>
> I think for now we shoudn't support these two-way contracts but favour 
> composition by references (incl. <map:mount uri="[another-block]"/>). 
> If this gets too bureaucratic, we can still think about alternatives.
>
> WDYT?

I think that it becomes to bureaucratic. But you maybe have some better 
solutions to the issues I adress above. Also, I'm intersted in your view 
about how to actually use the blocks in an application.

/Daniel


Mime
View raw message