You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cocoon.apache.org by Reinhard Poetz <re...@apache.org> on 2005/04/01 08:25:46 UTC

Re: [RT] composition vs. inheritance in blocks

Daniel Fagerstrom wrote:
> Reinhard Poetz wrote:
> <snip/>
> 
>> As promised, I've tried to come up with some pseudo-code that shows 
>> how "single-inheritance + composition" looks like in a particular 
>> example (portal + skinning).
> 
> 
> That is excelent.
> 
>>                                           - o -
>>
>> We have following block interfaces:
>> ===================================
>>  - http://cocoon.apache.org/interfaces/portal/1.0
>>  - http://cocoon.apache.org/interfaces/portal-skin/1.0
>>  - http://mycompany.com/interfaces/skin/1.0
>>
>>
>> Here the block implementations:
>> ===============================
>>
>> --------------------------------------------------------------------------- 
>>
>> "Portal"
>> --------------------------------------------------------------------------- 
>>
>> BLOCK.XML
>> <block xmlns="http://apache.org/cocoon/blocks/cob/1.0"
>>  id="http://cocoon.apache.org/blocks/portal/1.0.0">
>>   <name>portal</name>
>>   <requirements>
>>     <requires 
>> interface="http://cocoon.apache.org/interface/portal-skin/1.0"
>>      name="portal"
>>      default="http://cocoon.apache.org/blocks/portal-skin/1.0.0"/>
>>   </requirements>
>>   <implements>
>>     <interface id="http://cocoon.apache.org/interfaces/portal/1.0"/>
>>   </implements>
>> </block>
>>
>> --------------------------------------------------------------------------- 
>>
>> "Portal-Skin"
>> --------------------------------------------------------------------------- 
>>
>> BLOCK.XML   <block xmlns="http://apache.org/cocoon/blocks/cob/1.0"
>>  id="http://cocoon.apache.org/blocks/portal-skin/1.0.0">
>>   <name>portal-skin</name>
>>   <implements>
>>     <interface id="http://cocoon.apache.org/interfaces/portal-skin/1.0"/>
>>   </implements>
>> </block>
>>
>> SITEMAP.XMAP
>> <map:match pattern="*.css">
>>   <map:read src="styles/css/{1}.css"/>
>> </map:match>
>> <map:match pattern="styles/portal-page.xsl">
>>   <map:read src="styles/portal-page.xsl"/>
>> </map:match>
>>
>>
>> --------------------------------------------------------------------------- 
>>
>> "MyPortal"
>> --------------------------------------------------------------------------- 
>>
> 
> 
> Is MyPortal an actual application or is it a block that you are going to 
> use for building applications? Let's call them application block and 
> reusable block respectively. We must take application blocks into 
> account to get the whole picture, so I assume that MyPortal is an 
> application block that will contain real user profiles etc. Seeing that 
> it neither implements nor extends but rather use the portal block 
> enforces that view.
> 
>> BLOCK.XML   <block xmlns="http://apache.org/cocoon/blocks/cob/1.0"
>>  id="http://mycompany.com/blocks/my-Portal/1.0.0">
>>   <name>MyPortal</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/portal/1.0"
>>       name="portal"
>>       default="http://cocoon.apache.org/blocks/portal/1.0.0"/>
>>   </requirements>
>> </block>
>>
>> SITEMAP.XMAP
>> <map:match pattern="portal">
>>   <map:act type="portal:auth-protect">
>>     <map:parameter name="handler" value="portal-handler"/>
>>     <map:parameter name="application" value="portal"/>
>>
>>     <map:generate type="portal:portal"/>
>>     <map:transform src="blocks://skin/styles/portal-page.xsl">
>>       <map:parameter name="user" value="{ID}"/>
>>     </map:transform>
>>     <map:transform type="core:cinclude"/>
>>     <map:transform type="portal:portal-coplet"/>
>>     <map:transform type="portal:portal-new-eventlink"/>
>>     <map:transform type="core:encodeURL"/>
>>     <map:serialize type="portal:html-include"/>
>>   </map:act>
>> </map:match>
> 
> 
> Shouldn't this sitemap be part of Portal rather than MyPortal, AFAICS it 
> is a "blockified" version of the "portal" rule from the demo portal, so 
> there seem no to be no reason to reimplement it in MyBlock.
> 
>> (Note: Most of the used components come from the portal block, the 
>> rest from Cocon core; the stylesheet is provided by the "skin" block.)
>>
>> --------------------------------------------------------------------------- 
>>
>> "MySkin"
>> --------------------------------------------------------------------------- 
>>
>> BLOCK.XML
>> <block xmlns="http://apache.org/cocoon/blocks/cob/1.0"
>>  id="http://mycompany.com/blocks/my-Portal/1.0.0">
>>   <name>MySkin</name>
>>   <implements>
>>     <interface id="http://cocoon.apache.org/interfaces/portal/1.0"/>
>>   </implements>
>>   <extends>http://cocoon.apache.org/blocks/portal-skin/1.0.0</extends>
>> </block>
> 
> 
> 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.

> 
> <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.

>                                  --- 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>


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".
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.

                                        - 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?

-- 
Reinhard Pötz           Independent Consultant, Trainer & (IT)-Coach 

{Software Engineering, Open Source, Web Applications, Apache Cocoon}

                                        web(log): http://www.poetz.cc
--------------------------------------------------------------------


Re: [RT] composition vs. inheritance in blocks

Posted by Daniel Fagerstrom <da...@nada.kth.se>.
Reinhard Poetz wrote:

> Daniel Fagerstrom wrote:
>
>> I have given some examples of how a user could use some adapted form 
>> of MI to build applications based on a number of blocks, so I'm 
>> interested in your view about how to build blocks based applications.
>
>
> I can't add much to what Stefano already has said:
>
> "You install "Linotype2", is a real block and it requires "Linotype 
> Skin", "JCR Repository", "RDF TripleStore". The block manager goes to 
> the block library on cocoon.apache.org and finds a list of possible 
> blocks that implement those interfaces and that are known to be 
> compatible with the version of the block you are now installing."
>
>
> Except that not the BlocksManager but the BlockDeployer will install 
> blocks, that's the vision:
>
>  - reusability
>    --> skin can be used by more than one block)
>    --> develop functionality *once* and reuse it in many applications
>  - versioning
>  - development will become much easier because you can wire locally
>    available development blocks
>    e.g. I want to use the latest cForms block in *my* project I simply
>    point in the wiring.xml of *my* project to this block. There are
>    no difficult deployment cycles!
>
>
> And quoting Stefano a second time: "what is the best way to use those 
> features in order to achieve what I need". I don't know what will be 
> the best way to use all those features. When we have a working 
> prototype we will learn a lot and best practices will evolve.
>
Ok, so lets get to a working prototype. Still if we don't spend some 
thought on relevant use cases that comes up, the working prototype might 
be less relevant than it could be.

/Daniel


Re: [RT] composition vs. inheritance in blocks

Posted by Reinhard Poetz <re...@apache.org>.
Daniel Fagerstrom wrote:

> I have given some examples of how a user could use some adapted form of 
> MI to build applications based on a number of blocks, so I'm interested 
> in your view about how to build blocks based applications.

I can't add much to what Stefano already has said:

"You install "Linotype2", is a real block and it requires "Linotype Skin", "JCR 
Repository", "RDF TripleStore". The block manager goes to the block library on 
cocoon.apache.org and finds a list of possible blocks that implement those 
interfaces and that are known to be compatible with the version of the block you 
are now installing."


Except that not the BlocksManager but the BlockDeployer will install blocks, 
that's the vision:

  - reusability
    --> skin can be used by more than one block)
    --> develop functionality *once* and reuse it in many applications
  - versioning
  - development will become much easier because you can wire locally
    available development blocks
    e.g. I want to use the latest cForms block in *my* project I simply
    point in the wiring.xml of *my* project to this block. There are
    no difficult deployment cycles!


And quoting Stefano a second time: "what is the best way to use those features 
in order to achieve what I need". I don't know what will be the best way to use 
all those features. When we have a working prototype we will learn a lot and 
best practices will evolve.

-- 
Reinhard Pötz           Independent Consultant, Trainer & (IT)-Coach 

{Software Engineering, Open Source, Web Applications, Apache Cocoon}

                                        web(log): http://www.poetz.cc
--------------------------------------------------------------------


Re: [RT] composition vs. inheritance in blocks

Posted by Daniel Fagerstrom <da...@nada.kth.se>.
Reinhard Poetz wrote:
> Daniel Fagerstrom wrote:
>> Reinhard Poetz wrote:

>>> 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. 
> 
> 
> No, you don't. The block "portal" only contains pipelines calls which 
> the block "profile" provides in its sitemap:
> 
> Portal block
> ------------
> - requires "MyProfile" that implements "profile"
> 
> <profiles>
>  <copletbasedata-load
>   uri="blocks:profile:/load-global-profile?profile=copletbasedata"/>
>  <copletdata-global-load
>   uri="blocks:profile:/load-global-profile?profile=copletdata"/>
>   ..
> </profiles>
> 
> (After reading Stefano's mail I found a mistake in my examples: The 
> block name is a sub-protocol and not part of the path.)
> 
> You can have one block that provides default pipelines, let's call it 
> "profile-default":
> 
> "profile-default"
> -----------------
> - implements "profile"
> 
> <map:match pattern="load-global-profile">
>  ...
> </map:match>
> 
> 
> If you need special behaviour in your _custom_ profile block, you extend 
> the "profile-default" block and override _only_ those parts that you 
> want to see changed. In your custom portal block ("MyPortal") you change 
> the requirement from "profile-default" to "MyProfile" and you're done.
> 
> "MyProfile"
> -----------
> - extends "profile-default"
> 
> 
>> 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.
> 
> 
> IIUC your point is that how your application works is spread over to 
> many blocks. Maybe you're right but I don't think so, at least the 
> examples we talked about here are overly complex using

?

Ok, accepting that we get a number of small blocks, I still think that 
you miss things in your example by not using polymorphism.

Whithout knowing much about the portal, it seem to me that it would more 
natural to let the Profile interface be about an actual profile rather 
than about a number of pipelines that you could use for building a 
profile. A DefaultProfile implementation could (and probably should) 
have a number of pipelines that makes default configuration content 
avaliable and overidable and that are used for building the profile. But 
if we want to actually build easy reusable components the interface 
should not be that fat and specific.

If the DefaultProfile is built this way, really polymorphic (single) 
inheritance will reduce the amount of copying, compared to pure composition.

>>> 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.
> 
> 
> Yes, if something isn't designed for reusability, then you have to copy 
> it. If you design for reusablity, you don't have to copy services.

Aggregated services requires real polymorphism, if you don't want to 
copy the sitemap rule or code that does the actual composition.

<snip/>

> I think we should stop the discussion at this point for now (I don't 
> think that anybody can follow it anymore). IIUC everything you proposed 
> can be added in a backwards-compatible way to the existing proposal. But 
> I'm confident we don't need it ;-)

Yes, we obviously not getting anywher, so lets move on and let us design 
really polymorphic blocks with sigle inheritance.

>> Also, I'm intersted in your view about how to actually use the blocks 
>> in an application.

> I can think of many scenarios where blocks make sense. We already talked 
> abut them in this discussion (skinning, profiles, easy deployment, easy 
> development, ...). Does this answer your question or is it too general.

I wouldn't have parcipated in this discussion if I didn't found blocks 
usefull ;)

My question is a serious and IMO important one. If a user is going to 
build an own portal for his/her company and is going to base this on the 
portal block, how will he/she do this. What will the main sitemap look 
like, the deployment configuration etc. In the same way that we needed 
to write example block.xml and block sitemaps, to get better 
understanding of blocks, I think we need to take a look on a "real" 
usage of a block, to get the design as usable as possible.

I have given some examples of how a user could use some adapted form of 
MI to build applications based on a number of blocks, so I'm interested 
in your view about how to build blocks based applications.

Is the question clearer now?

/Daniel

Re: [RT] composition vs. inheritance in blocks

Posted by Reinhard Poetz <re...@apache.org>.
Daniel Fagerstrom wrote:

> Reinhard Poetz wrote:
 >
>> 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. 

No, you don't. The block "portal" only contains pipelines calls which the block 
"profile" provides in its sitemap:

Portal block
------------
- requires "MyProfile" that implements "profile"

<profiles>
  <copletbasedata-load
   uri="blocks:profile:/load-global-profile?profile=copletbasedata"/>
  <copletdata-global-load
   uri="blocks:profile:/load-global-profile?profile=copletdata"/>
   ..
</profiles>

(After reading Stefano's mail I found a mistake in my examples: The block name 
is a sub-protocol and not part of the path.)

You can have one block that provides default pipelines, let's call it 
"profile-default":

"profile-default"
-----------------
- implements "profile"

<map:match pattern="load-global-profile">
  ...
</map:match>


If you need special behaviour in your _custom_ profile block, you extend the 
"profile-default" block and override _only_ those parts that you want to see 
changed. In your custom portal block ("MyPortal") you change the requirement 
from "profile-default" to "MyProfile" and you're done.

"MyProfile"
-----------
- extends "profile-default"


> 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.

IIUC your point is that how your application works is spread over to many 
blocks. Maybe you're right but I don't think so, at least the examples we talked 
about here are overly complex using


>> 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.

Yes, if something isn't designed for reusability, then you have to copy it. If 
you design for reusablity, you don't have to copy services.

> 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.

The same you reach if everything that should be extensible in Forrest, is moved 
into a "default-block". This can be overridden by a project specific block.

> 
>> 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/.

Sorry, my usage of the blocks protocal was wrong in my former mails. I'm with 
Stefano here that the block is a sub-protocol and additionally we have a "super" 
sub-protocol referencing the parent block.

> 
>>                                        - 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. 

I think we should stop the discussion at this point for now (I don't think that 
anybody can follow it anymore). IIUC everything you proposed can be added in a 
backwards-compatible way to the existing proposal. But I'm confident we don't 
need it ;-)

> Also, I'm intersted in your view 
> about how to actually use the blocks in an application.

I can think of many scenarios where blocks make sense. We already talked abut 
them in this discussion (skinning, profiles, easy deployment, easy development, 
...). Does this answer your question or is it too general.

-- 
Reinhard Pötz           Independent Consultant, Trainer & (IT)-Coach 

{Software Engineering, Open Source, Web Applications, Apache Cocoon}

                                        web(log): http://www.poetz.cc
--------------------------------------------------------------------


Re: [RT] composition vs. inheritance in blocks

Posted by Daniel Fagerstrom <da...@nada.kth.se>.
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