Wirebox 2.0 and Multiple Injectors?

Hi,

I’m looking for some guidance on how best to implement wiring up multiple model libraries using wirebox and using in coldbox (currently have this implemented in DI/1 and FW/1). I have three separate data access libraries broken out by a specific domain, they are Admin, Login and Agency.

Each library has a folder structure like this:

root
–beans
–plugins
–repositories
–services

I’m trying to figure out how this would be implemented in Wirebox/Coldbox. For example in my FW/1 app I have a ‘SecurityService’ cfc that needs to make calls using two different libraries, so my Init() method looks like this:

`


<cfset variables.UserInfoRepository = LoginBeanFactory.getBean(‘UserInfoRepository’)/>
<cfset variables.ClientRepository = AgencyBeanFactory.getBean(‘ClientRepository’)/>

`

How would I do something similar in Coldbox/Wirebox?

Thanks.
Brett

Hi Brett,
]
There are many ways to achieve this, but let’s cover the simplest and best ways.

To familiarize yourself with ColdBox, you may want to run through our getting started guide:
http://coldbox.ortusbooks.com/content/getting_started_guide/index.html

And here’s a quick reference card for WireBox:
https://github.com/ColdBox/cbox-refcards/raw/master/WireBox/WireBox-Refcard.pdf

By default, ColdBox (WireBox inside of ColdBox, that is) looks for models in a folder called “models”.

So if I have /models/foo.cfc I can ask WireBox for “foo”. If I have /models/folder1/folder2/foo.cfc then I can ask for “folder1.folder2.foo”. The models folder is called the default scan location.
If your CFC names are unique and you don’t want to specify the full component path, you can place the following line in your /config/WireBox.cfc’s configure() method:

mapDirectory( “models” )

This will scan the folder on start and map every model by the name of the CFC. Use an alias annotation in the CFC to add additional names to the CFCs.

So, on to your question about providing constructor injection. If the LoginBeanFactory and AgencyBeanFactory are already mapped in WireBox by that name, your code should actually work as is. The longhand way is to add an **"**inject" annotation to the argument like so, but required constructor param default to being injected and the mapping name defaults to the argument name:

or

I personally avoid constructor args because they’re annoying and add boilerplate to my code. A cleaner way is to use mixins by just adding properties to the top of your CFC like so:

component {
property name=“foo” inject=“foo”;
property name=“myBar” inject=“bar”;

function onDIComplete() {
foo.doSomething();
myBar.doSomething();
}
}

That will automatically create variables.foo and variables.myBar in your CFC on creation.

Now, I did notice that the two CFCs in your example are factories. That’s a very Java-esque thing to have, but when using ColdBox, WireBox IS your factory. Can you explain what your factories do? There might be a better way to handle them using WireBox’s many built-in features.

Now, to address your last bit-- you seemed to imply that you’ve gone to the trouble of breaking your site up into logical chunks or libraries. This sounds like it might be a great use for ColdBox modules. A module is a chunk of your application that can either stand alone, or just be separate for organizational reasons. Each module lives in a folder inside the /modules directory and has a simple ModuleConfig.cfc inside that describes it. WireBox is module-aware and modules have their own “models” convention.

So consider these two simple modules, that both have some CFCs inside:

/modules/security/models/user.cfc
/modules/security/models/userService.cfc

/modules/orders/models/Customer.cfc
/modules/orders/models/CustomerService.cfc

With zero configuration, we can ask WireBox for these like so:

getinstance( “UserService@security” )
getinstance( “CustomerService@orders” )

Or, we can inject the same CFCs like so:

component {
property name=“UserService” inject=“UserService@security”;
property name=“CustomerService” inject=“CustomerService@orders”;

}

Or, if you really want, you can get them in your constructor like so (script syntax):

component {

/**

  • @UserService.inject UserService@security
  • @CustomerService.inject CustomerService@orders
    */
    function init( UserService, CustomerService ) {

    }

}

This is just the tip of what modules can do for you. Hopefully this isn’t too much information for you. Digest it and come back with more questions. Also note, I’m on the CFML Slack chat if you want to pop in and question some questions in the #box-products channel.

http://cfml-slack.herokuapp.com/

Thanks!

~Brad

ColdBox Platform Evangelist
Ortus Solutions, Corp

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

Hi Brad,

Thanks for the quick reply. I should have mentioned that the Libraries are in an external location other than my Coldbox app. The idea is that these libraries would be used in other apps we have.

The ‘Factory’ is just a naming convention that DI/1 used. Basically, DI/1 allowed me to set up a Default Bean Factory (which would be model in the application) and then create child bean factories (for my libraries). My DI/1 config for this was

`

//Setup DSN Service for model layers
var AgencyDSNService = CreateObject(‘component’,‘model.AgencyDSN’).Init();
var AdminDSNService = CreateObject(‘component’,‘model.AdminDSN’).Init();
var LoginDSNService = CreateObject(‘component’,‘model.LoginDSN’).Init();
var S3AccountSettings = CreateObject(‘component’,‘model.S3AccountSettings’).Init();

this.mappings["/ModelLogin"] = application.settings.ModelLogin;
this.mappings["/ModelAgency"] = application.settings.ModelAgency;
this.mappings["/ModelAdmin"] = application.settings.ModelAdmin;

// setup ModelLogin bean factory
var loginBeanfactory = new org.acme.ioc(
folders="/ModelLogin",
config={
constants={
“rootPath” = “/ModelLogin”,
“DSNService” = LoginDSNService
},
singletonPattern = “(Service|Gateway)$”
}
);

setSubsystemBeanFactory(‘login’, loginBeanfactory); //Set up subsystem factory

// setup ModelAgency bean factory
var agencyBeanfactory = new org.acme.ioc(
folders="/ModelAgency",
config={
constants={
“rootPath” = “/ModelAgency”,
“DSNService” = AgencyDSNService,
“adminDSN” = “acmeadmins”,
“S3AccountSettings” = S3AccountSettings
},
singletonPattern = “(Service|Gateway|Repository|Plugins)$”
}
);
setSubsystemBeanFactory(‘agency’, agencyBeanfactory); //Set up subsystem factory

// setup ModelAdmin bean factory
var adminBeanfactory = new org.acme.ioc(
folders="/ModelAdmin",
config={
constants={
“rootPath” = “/ModelAdmin”,
“DSNService” = AdminDSNService
},
singletonPattern = “(Service|Gateway)$”
}
);
setSubsystemBeanFactory(‘admin’, adminBeanFactory); //Set up subsystem factory

// setup Parent bean factory
var defaultBeanfactory = new org.acme.ioc(
folders="/model",
config={
constants={
“AgencyBeanFactory”=agencyBeanFactory, //We are setting these constants so we can inject specific beanfactories into our local models
“AdminBeanFactory”=adminBeanFactory,
“LoginBeanFactory”=loginBeanFactory,
“fw”=this,
“logger”=application.logger,
“data_center”=application.settings.data_center,
“data_center_lookups”=application.settings.data_center_lookups,
“encryption_key”=“acme_encrypt”
},
singletonPattern = “(Service|Gateway)$”
}
);

setBeanFactory(defaultBeanfactory);
adminBeanFactory.setParent(getDefaultBeanFactory());
agencyBeanfactory.setParent(getDefaultBeanFactory());
loginBeanfactory.setParent(getDefaultBeanFactory());

`

I might be overthinking it. I could just set Wirebox to scan the external libraries?

Thanks.
Brett

Hi Brett, sorry for the delay in replying. All my mailing lists and Slack teams are ganging up on me to such every minute of spare time :slight_smile:

> I should have mentioned that the Libraries are in an external location other than my Coldbox app

No problem, that doesn’t change anything. WireBox can have multiple scan locations. Multiple folders can also be mapped via mapDirectory() as well. For what it’s worth, I still think modules are a great way to separate your application. You have two ways to handle external modules. The first is to use the external modules setting to point to a folder outside of your application that contains modules that need to be loaded:

http://coldbox.ortusbooks.com/content/modules/locations.html

The second method, which is more along the lines of how other programming languages like Node or Java would work, would be to package up your modules in separate repos so they can be tracked independently. Then, use CommandBox to build your app, specifying the external dependencies in your box.json. Since we haven’t released private ForgeBox packages, you’d probably want to use CommandBox’s ability to install packages from URLs, or local file/folders.

“dependencies”:{
“coldbox”:“folder:C:\packages\coldbox\4.1.0”,
“cbfeeds”:“file:C:\packages\zipped\cbfeeds\1.0.1\cbfeeds.zip”,
“cbi18n”:“1.0.2”,
“weather-lookup-by-ip”:“https://github.com/bdw429s/Weather-Lookup-By-IP/archive/master.zip
}

This would give you tons of options to really modularize your application and use a package manager workflow to build the app.

> DI/1 allowed me to set up a Default Bean Factory (which would be model in the application) and then create child bean factories (for my libraries).

Interesting. WireBox allows you to have multiple injectors where each injector has a parent, but honestly that doesn’t feel like a good fit here. That all sounds like a workaround for a framework that didn’t support a first-class modularity.

> My DI/1 config for this was

I’m only a little familiar with DI/1, but I’m curious why you’re manually creating CFCs such as “model.AgencyDSN”. When using an object factory you shouldn’t need to be manually creating CFCs any longer!

> I could just set Wirebox to scan the external libraries?

Yep, that’s always a super-simple option. The quickest way to just map all three packages and still keep them namespaces would just be these three lines of config:

mapDirectory( packagepath=“package1” namespace=“package1” );
mapDirectory( packagepath=“package2” namespace=“package2” );
mapDirectory( packagepath=“package3” namespace=“package3” );

Then just access stuff as “foo@package1”, “bar@package2”, and “baz@package3”. You’d basically be duplicating exactly what modules do.

Thanks!

~Brad

ColdBox Platform Evangelist
Ortus Solutions, Corp

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

Hi Brad,

Thats funny I just started looking at namespaces and it looks like they will do the trick, which is to allow me to explicitly reference what library I’m trying to pull the object from.

This might be a fool’s errand but my goal is to get these libraries to work in both DI/1 and Wirebox. These libraries are being used in a FW/1 app we have set up and we have no plans on moving that over to CB. We have a current Fusebox site that we are investigating moving to CB and it would be a gradual port. We already have written several apps in CB so we feel pretty comfortable in all 3 frameworks.

I’m going to have a bunch more questions, but this has got me over the hump.

Thanks.
Brett

Glad to hear you have a solution. I do think you should check out ColdBox’s modules though. It’s a trademark feature that really sets it apart from pretty much every CF framework that’s ever existed. That coupled with the package management functionality of CommandBox is the most modern thing CF has seen in a while.

Note that if you want CFCs to be a singleton when mapping via mapDirectory, you need to add the “singleton” annotation to those CFCs so WireBox can pick that up. Wanting to share the same models between frameworks is a cool idea, but remember there are some differences in the way that both work which will keep you from using all the sweet mixin injection stuff.

Thanks!

~Brad

ColdBox Platform Evangelist
Ortus Solutions, Corp

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

Brad,

Thanks for the tip on singletons.

The namespace thing worked, but I ran into something I didn’t like about it. The issue was this:

  1. I have following chunk of code in my wirebox configure() method to wire up the Admin library:
  2. mapDirectory(packagePath=’/ModelAdmin’,namespace=’@admin’,influence=influencer)
  3. I have a CFC in one of my libraries called ‘BrokerRepository’ which has an Init() argument of ‘DataManipulation’.
  4. I added an ‘inject’ attribute to this argument1. The ‘DataManipulation’ cfc is in a subfolder of the library called plugins
  5. I expected (incorrectly) that when I requested ‘BrokerRespository’ from wirebox in my handler that it would have found ‘DataManipulation’, but got an error about wirebox not being able to find it
  6. It turns out that I had to go the full distance with the inject attribute and specify ‘inject=“DataManipulation@admin”’ for wirebox to find the CFC
  7. That sucks…but I kind of get it. My main issue is if I had to register the namespace for the library as something different than ‘@admin’ in a different app, I’m in trouble. Also its just a lot of unnecessary ceremony. Within the library if I need to inject something that exists within the library I would like to just be able to add the ‘inject’ attribute and be done with it.
    Is there something I’m missing to avoid having to do ‘inject=“whatever@admin”’ within the library?

Also I’m wondering if there is something i can do like you showed in this post, Brad:

https://groups.google.com/forum/#!searchin/coldbox/wirebox$20parent/coldbox/mY5bEoYHZtE/XoHFvHTK-HIJ

You basically fired up two different injectors. I think the intent was completely different, but maybe there is some way to fire up multiple injectors and register them under different providers, namespaces or whatever so I can I do something like this in my handler:

`
function home(){
//This
getInstance(‘AdminBinder:BrokerRepository’);

//Or This
getInstance(‘BrokerRepository@AdminBinder’);

//Or ??
}
`

Thanks.

Brett

> It turns out that I had to go the full distance with the inject attribute and specify ‘inject=“DataManipulation@admin”’ for wirebox to find the CFC

That’s just the way mapping names work. The injector’s binder keeps track of all the mappings that are registered and they need unique names. When a CFC asks Wirebox for an object, no attention is paid to where the CFC lives. The object is simply looked up by its unique mapping name.

> My main issue is if I had to register the namespace for the library as something different than ‘@admin’ in a different app, I’m in trouble.

Why would you do that? When I package models up in a module, they are part of that module and will always be namespaced after it. If you want to have generic names that can apply anywhere, just ditch the namespace all together and use the CFC name by itself. But then you risk the model names conflicting with another module which is why we namespace them in the first place.

> Also I’m wondering if there is something i can do like you showed in this post, Brad:

Yes, you can do that but I think it’s overthinking the issue. Package your models in reusable modules which have built-in, consistent namespacing. Then include those modules as dependencies in every application that needs them.

getInstance(‘AdminBinder:BrokerRepository’);
getInstance(‘BrokerRepository@AdminBinder’);

It wouldn’t work like that-- getInstance is actually a method of the injector itself, so if you wanted to have multiple injectors, you would need to somehow keep track of them all and ask the appropriate injector for the CFC you want (kind of like what you’re doing with DI/1).

injector1.getInstance( ‘foo’ )
injector2.getInstance( ‘bar’ )

I think you’re just stuck on that approach because it’s what you did with FW/1, but I really think it was just a crutch for not having a proper method to bundle like functionality to “real” packages that your object factory understands. The irony here is the correct solution in my opinion is actually smpler and less code when you use ColdBox.

Thanks!

~Brad

ColdBox Platform Evangelist
Ortus Solutions, Corp

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

Hi Brad,

I’m back. Two things:

  1. What does packaging my model libraries as modules entail? Currently the directory structure is:
    [ModelAdmin]
    –beans
    –plugins
    –repositories
    –services
    [ModelAgency]
    –beans
    –plugins
    –repositories
    –services

Will I need to put them under the modules directory in the application I’m working on or can they remain the way they are? Am I correct in assuming I would need a ModuleConfig.cfc in each root folder? I’m just trying to figure out how much re-arranging would need to go into making this work.

Thanks.
Brett

If you follow the standard conventions, it would look like this;

/modules/
ModelAdmin/ModuleConfig.cfc

/modules/ModelAdmin/models/
beans/

/modules/
ModelAdmin/models/
plugins/
/modules/
ModelAdmin/models/
repositories/
/modules/
ModelAdmin/models/
services/

/modules/ModelAgency
/ModuleConfig.cfc

/modules/ModelAgency
/models/
beans/

/modules/ModelAgency
/models/
plugins/
/modules/ModelAgency
/models/
repositories/
/modules/ModelAgency
/models/
services/

The thing is that they don’t need to live in the modules directory in your source control repo. If they are stand-alone libraries that can be included in multiple projects, just add them as dependencies to your box.json file and let the “install” command build our your application.

Or if you don’t want to do that, register a folder outside your web root as an external modules location and they can be loaded from there where they can also be shared with other apps. As long as the models are in a “models” folder inside the module, they will get automatically picked up by WireBox.

Thanks!

~Brad

ColdBox Platform Evangelist
Ortus Solutions, Corp

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