[coldbox-3.6] .asParent(), .asSingleton() issue

Took us a while to figure this one out. Not sure if it’s a bug, but I don’t care much this behavior if it isn’t.

We had this config:

map(‘AbstractService’)
.to(‘com.services.baseService’)
.property(name=‘ServiceFactory’, ref="ServiceFactory’);
map(‘MyService’)
.parent(‘AbstractService’)
.asSingleton();

However, on multiple requests, grabbing “MyService” fired the preconstructor each time (shown with console dumps). We had to change it to this:

map(‘AbstractService’)
.to(‘com.services.baseService’)
.asSingleton() // MOVED TO HERE
.property(name=‘ServiceFactory’, ref="ServiceFactory’);
map(‘MyService’)
.parent(‘AbstractService’);

My conclusion is that you cannot define the caching properties of an object unless you specify it on the parent mapping. So if I now want a service that is NOT a singleton, I have to create both AbstractService and AbstractServiceSingleton parent mappings.

Is this a bug?

little off topic.

will you be mapping more than one service to the parent service?

if not, why not just extend the base class in the cfc then create the mapping to that class in the config?

and my 2 cents.

i see ‘MyService’ as just a soft reference to the parent ‘AbstractService’.

Since ‘AbstractService’ is the actual instance, it should be the one that is cached as a singleton.

Additional info:

i see ‘MyService’ as just a soft reference to the parent ‘AbstractService’.

No, it’s not that, at all. The way I understand it, the “parent pattern” is so you don’t have to repeat yourself.

Since ‘AbstractService’ is the actual instance, it should be the one that is cached as a singleton.

No, AbstractService should NEVER be instantiated. It’s just a “design”.

Let’s use a more thorough example:
map(‘AbstractService’).to(‘some.cfc.never.instantiated’)

.initArg(name=‘arg1’, value=‘value1’)

.initArg(name=‘arg2’, value=‘value2’)

.initArg(name=‘arg3’, value=‘value3’)

.property(name=‘prop1’, value=‘prop1’)

.property(name=‘prop2’, value=‘prop2’)

.property(name=‘prop3’, value=‘prop3’)

Now, I have 27 services I want to do just like this. Most would be singletons, some would not.

map(‘ServiceA’).to(‘com.serviceA’).parent(‘AbstractService’).asSingleton()

map(‘ServiceB’).to(‘com.serviceB’).parent(‘AbstractService’).asSingleton()

map(‘ServiceC’).to(‘com.serviceC’).parent(‘AbstractService’); /// NOT a singleton

map(‘ServiceD’).to(‘com.serviceD’).parent(‘AbstractService’); /// NOT a singleton

This doesn’t work. NONE of them end up being a singleton, because (if it’s a bug?) I didn’t put .asSingleton() on the parent definition.
I would have to duplicate the existing ‘AbstractService’ definition and add:

map(‘AbstractServiceSingleton’).to(‘some.cfc.never.instantiated’)
.asSingleton()
.initArg(name=‘arg1’, value=‘value1’)

.and.so.on

In my opinion, you should be able to add the .asSingleton() or not to the parent definition. If you do not, but you provide it on the “child” definitions, it should read it there. In fact, if there were a feature of .asTransient(), I would expect it to override the .asSingleton() on the parent definition. At the minimum, I would expect that if you did not define the caching strategy on the parent, that it would certainly pick it up from the child definition.

i see. i would find the definition of the parent() method in the docs and see if that answers your question. it might be following the design pattern.

Yah, well, it doesn’t indicate, hence my posting in this Group:
http://wiki.coldbox.org/wiki/WireBox.cfm#Parent_Object_Definitions

No, AbstractService should NEVER be instantiated. It’s just a “design”.

to me, that code says that cfc is instantiated as a transient since there is no caching.

why not just extend that base class in serviceA, serviceB, etc… ?

that way, you can explicitly set each service to either transient or singleton.

I think the issue is in the processMemento() method in mapping.cfc. It takes another mapping’s instance scope (the memento) and merges it on top of itself. The singleton property would get processed here:

instance[key] = arguments.memento[key];

There is no check to see if the property exists in the current mapping, everything is just wholesale overwritten with the incoming mapping. Well, that’s not entirely true. Things like DI properties, and DI method arguments and such actually have an exists check, but regular component annotations such as “singleton” are just copied over. I’d suggest putting a ticket. It seems like if there’s a conflict of annotations between a concrete and abstract class, the concrete class should win. Luis will need to confirm though and we’ll need to consider backwards compatibility in case someone’s application was depending on this behavior for some reason.

Thanks!

~Brad

ColdBox Platform Evangelist
Ortus Solutions, Corp

E-mail: brad@coldbox.org
ColdBox Platform: http://www.coldbox.org
Blog: http://www.codersrevolution.com