> I've just started playing with DataFaucet, and wondered if there were
> any existing examples / recomendation re integration of DataFaucet
> with ColdSpring managed services.
I don't have any examples handy, but I certainly understand the reasons
for it. :) I should probably add something about it to the documentation
- which is really designed mostly to get people started quickly.
> For most of the key functional areas of the application, I have a
> singleton Service object, which is managed through ColdSpring.
>
> If for instance I have a site service, and I want to get the site
> "Record Bean" for an individual site, I would call SiteService.getSite
> (siteID);
>
> Ideally I would like to continue having my service be responsible for
> providing data to the rest of the application, but looking at the
> examples, I can't see a way of doing this other than having large
> numbers of references to request.DataFaucet.getDatasource(); within my
> service objects - which feels like bad coupling...
No you don't have to. The advantage of doing it the simple way that's
described in the documentation is that it's easy to get started. In
practice however I use an IoC factory as well.
> Ideally I'd like to be able to inject "DataFaucet" (or perhaps
> DataFaucet.getDatasource() ) into each service using ColdSpring - but
> I'm struggling to see the best way of doing this.
>
> Does this make sense?
Yep, that's primarily the way I write my components. If you check out
the documentation for the ActiveRecord object you should see somewhere
in that page that it describes a setDatasource() method, which is useful
if you choose to instantiate your active record outside of the
datasource object (as opposed to using datasource.getActiveRecord()). So
in my case I tend to override the init() method on the active record and
then have my IoC Factory autowire it with constructor-based injection.
So my AR CFC looks like this:
<cfcomponent extends="datafaucet.system.activerecord">
... cfproperty tags go here ...
<cfset setTable("MyTable") />
<cffunction name="init" access="public" output="false">
<cfargument name="datasource" type="any" required="true" />
... any additional arguments ...
<cfset setDatasource(arguments.datasource) />
... do anything else it needs ...
<cfreturn this />
</cffunction>
... more custom functions ...
</cfcomponent>
So if you're wanting to use an AR with a service CFC singleton that can
cache them (this is done in the Members onTap plugin for the onTap
framework as well), then you should be able to inject your datasource
into the service singleton and then create your AR objects with the
loaded datasource.
Here are the caveats. ;)
The system does actually need a request.DataFaucet component. Part of
the reason for this lies in the use of cftransactions for isolation and
rollbacks (because they're not allowed to be nested in CF8, so you have
to manage it per request), although it's also necessary for reasons of
debugging as well.
So in your framework or your application, you still need to configure
the DF framework both onApplicationStart and then again onRequestStart.
You can however choose to do this via ColdSpring if you like. If you
open the bootstrap.cfm in the datafaucet directory, you should see that
it just instantiates the CFCs it needs for the application and then for
the request. So you should be able to migrate those same object
instantiations to your ColdSpring config file. Just make sure that the
respective application and request objects get where they need to be and
that the request.DataFaucet object (boostrap.cfc) is a transient and not
a singleton. Making that one a singleton will prevent it doing its job
with regard to handling transactions.
Let me know how it works for you. :)
ike
--
[ ike ] founder - DataFaucet ORM
phone: 781.769.0723
Right the object you want to inject is going to be a singleton as
opposed to the transient request.datafaucet object (the bootstrap). You
really shouldn't inject the bootstrap unless you're injecting it into
another transient object. If you were to inject it into your service
object, it actually wouldn't destroy the object at the end of the
request (and leave you with a null-pointer). Instead it would hold on to
that object as long as your service object lives -- and then you'd have
a sort of wierd scope-mismatch because you'd have the one injected and
then a 2nd one for the request, but they wouldn't be the same object on
subsequent requests.
So let me back up a little and see if I can clarify this a little.
You mentioned creating a DataFaucet Factory in one of the previous
messages that just contains the stuff that's in the custom tag for
handling the onApplicationStart and onRequestStart configs. That'll work
fine for your work flow. You can create the DF Factory as a singleton
and then in onRequestStart you can simply set the bootstrap for the
request as in:
<cfset request.datafaucet =
ColdSpring.getBean("DataFaucetFactory").getBootstrap() />
In the onApplicationStart you would be doing something similar, where
you create your ColdSpring instance just prior to and then getting the
DFFactory bean and using it to populate the necessary
application.datafaucet objects (primarily the source manager) and
server.datafaucet (for caching and metadata).
Once that's out of the way then you have various options regarding how
you want to populate your AR objects. You could use CS to autowire them
the way I do with my IoC factory using constructor injection, but as you
mentioned that may not be the best case scenario.
I actually don't typically use CS as my IoC factory, I usually use a
simplified IoC factory that's less comprehensive than LightWire or CS
because I don't find that I generally need all those features. So my
simplified factory helps me to reduce dependencies and keep things
simple.
In your case, given your description of services, I had imagined that
you wouldn't be creating the AR transients using CS but rather that your
service object would be creating them. So I imagined CS -> Service -> AR
Transient.
So to get each of these objects loaded, you would say:
step 1. CS -> inject datasource object into -> Service
step 2. Service -> inject datasource object into -> AR
The way DataFaucet is set up by default for the sake of simplicity is to
provide the Datasource object from request.DataFaucet.getDatasource() as
you mentioned in the first message. Now to avoid calling out to the
request scope within your CS factory to get the datasource, you can use
CS to create your datasource object and then attach it to
application.DataFaucet.Sources if needed.
I'm not certain but I think you can bypass the sources singleton
(manager) if you want since you're using CS. The sources object doesn't
really do much other than create datasource objects and hold them in
cache to be singletons like CS does anyway. But when I design frameworks
my general design path is a) avoid dependencies on external frameworks b)
provide the ability to integrate with external frameworks. And so as in
DataFaucet you see that there are some extra objects and methods to
allow you to use it independently.
> I'm sure I've missed something really simple here, and am just making
> things hard work!
No worries. I need to document alternative configuration options, so
it's really more about the documentation being incomplete than it is
about your overlooking anything. :) I'll try and get to adding some info
about using ColdSpring in the near future. If you'd like to help out,
you can create a page about it on the wiki at
http://datafaucet.wikispaces.com once you've got your CS setup
configured. ;)
ike
p.s. Regarding the contact form on RIAForge - a lot of email to me from
RIAForge bounces. I honestly don't know why. I remember emailing ray to
ask if there was a white list he could put me on... but maybe that's a
false memory and I didn't actually get around to doing it. I'll have to
go look.
Good to know. :)
> If I was to call the bootstrap (possibly with the existing custom tag)
> prior to initiating coldspring, and then retrieved the "primary"
> datasource from application.datafaucet.sources to inject into my
> services (and subsequentally call the bootstrap again within
> onrequeststart), would that cover all bases?
Yep, that will work too. :) And will be a bit simpler than instantiating
everything yourself. Some folks are just really fanatical about not
using the custom tag. The reason eludes me in particular, but I tend to
kind of expect people to want everything to start from CS. But if you're
fine with using CS to fetch the datasource from app scope for injection,
that probably represents the least amount of additional coding.
Thanks Dan
Good deal. :)
> This is what seems to be happening...
>
> - If this is the first time this tag has been called in the current
> request, then create a new instance of bootstrap.cfc, passing in the
> application scope.
Yep.
> - If reinit = true, then call bootstrap.reset, which in turn will
> create a new instance of datamanager.cfc and place it in server
> scope, and will create a new instance of sources.cfc and place
> it in application.DataFaucet.sources.
Yep.
> - For all requests, call bootstrap.open, passing in datasource
> specific parameters
> - This will get an instance of datasource.cfc for the primary
> datasource from the datasources object
> (application.DataFaucet.sources) -
> this datasource object will be created by sources.cfc if it
> doesn't already exist.
> - The init method will be called on the returned datasource
> instance, which will set / overwrite the properties for that
> datasource.
>
> Does this sound right?
Almost. :)
In theory that should only occur when the datasource settings are
provided in the attributes of the custom tag. It's possible I may have
made a logical error in the tag, in which case, if it is calling the
init every time, then it would be overwriting it... but unless the
datasource attribute were provided, I would think it would be
overwriting it with an empty string, which would cause the queries to
fail on subsequent requests.
You certainly could provide the datasource attributes in both the
onApplicationStart and the onRequestStart and it would behave the way
you described, just overwriting the settings on each request, so it
wouldn't really hurt anything in particular (other than the extra time
to run the init), but that's not the intent.
Typically:
onApplicationStart - this initializes the primary db
<cfmodule template="/datafaucet/open.cfm" datasource="xxx"
[username="xxx" password="xxx" server="xxx"] />
onRequestStart - this initializes the request object
<cfmodule template="/datafaucet/open.cfm" />
> The thing that confused me for a while, is that bootstrap.open()
> calls ds.init() on every request - leading me to assume that a new
> instance of the datasource object was being created on every request -
> which I can see looking further doesn't appear to be the case...
Right it should only create it if it doesn't already exist.
> Going back on what I said in the last email, I am thinking that I
> will use CS to initiate DataFaucet to start with - as I will need to
> use multiple data sources within this application, and it doesn't seem
> that bootstrap.cfc supports this method of operation...
Well it's not really bootstrap in particular that handles multiple
datasources. The bootstrap has a few extra "helper" functions that are
designed just to get people started quickly in typical cases (single db).
In a multi-db environment, then just to make sure all your db's are
created and ready when the application starts, you would want to do the
typical onApplicationStart portion and then in addition simply add your
extra datasources to the application.DataFaucet.Sources manager object.
The main or "default" datasource is named "primary" relative to the
application (this is not the same as your DSN by the way), and then
additional datasources as you might need them can be named however you
like. You can for that matter even have the primary database stored with
more than one name if that would be helpful (it exists as both "primary"
and as "mycompanyname").
> That being said, am I correct in thinking that the 2 things that
> require a transient object, are transaction handling and logging - and
> for that that we need a transient instance of bootstrap.cfc?
Almost. The 2 are transaction handling and debugging at this point.
There aren't any built-in logging features at least yet, and I would
guess that those would be handled via cflock. But the debugging features
are two-fold.
1. You have the ability to set the debug attribute on an individual
sql-statement object if you just want to debug an individual query,
although that's not a huge help in most cases because you can just dump
the query and see the SQL that way.
2. If you have a block of code where you want to see the debug output
for n number of queries (not necessarily knowing how many will execute),
then you can call request.DataFaucet.TRON() which switches on debugging
for the entire request until you then call request.DataFaucet.TROFF() to
disable it. That lets you see the sql for all queries between tron and
troff in the debugging output at the bottom of the page (although they
will all have the same query name).
> If so, then using CS factories to initiate each of my datasources
> when CS is loaded (from onApplicationStart normally) using similar
> logic to that used in bootstrap.cfc, and then using the open.cfm
> custom tag to load bootstap.cfc called from onRequestStart seems to
> make sense.
Okay. So just to make sure I'm still on the same page, your plan is:
onApplicationStart:
-- custom code to build my datasource objects ---
onRequestStart:
<cfcomponent template="/datafaucet/open.cfm" />
> I'm still not certain I understand why I might want to change the
> datasource properties at any other point than when the app loads or is
> reinitiated as seems to be possible using open.cfm - I would have
> thought that once the properties for my primary datasource are set
> onApplicationStart , the only time they should be allowed to change is
> if the app is reinitated (at which point I manually call the
> onApplicationStart method anyway...)
Yeah, it's not supposed to be doing that. It's possible it might, but
that would generally be due to a coding error either on my part in the
open.cfm tag or in the tag implementation. Maybe they should have been
separate tags to avoid this confusion. ;)
> Let me know if you think I'm on the right track!
Yeah, you seem to have grokked most of it. :)
> Many thanks,
Many welcomes. :)
Awesome! Thanks for the help. :)