I've had several conversations with people on various mailing lists about what is the best way to structure your CFCs so that they:
- can be typed via 'return' attributes of cffuncion of 'type' attributes of cfargument
- can be set in 'extends' attributes of cfcomponent
- are very portable – i.e. it is easy to use com.io.FileReader in one project, and you can pick it up and move it to another without having to do a global find and replace to change com.io to com.domain.io.
- are able to be used in different applications across a domain – i.e. if you have one application at /myapp1/ and another at /myapp2/ they can both have a com.myCFC that can be named the same, but do differnet things, and they won't intefere with one another.
- are easy to maintain.
Unfortunately, I don't believe that there is currently a BEST option out there to cater to all of these things, but I'm going to go through all the options I have found, and describe the pros and cons of each, and you can make your own decisions (For those of you who are on CFCDev, I'll be taking a lot of stuff out of what I wrote there).
Use a CF Mapping
In a lot of cases this can end up being the best option. It solves all your issues with typing (1, 2), however it does not make the code very portable, in that you may not ever have access to the cfadmin to create the mapping, and/or if the mapping needs to change, you have to edit your code to work with that, so you cannot simply just drag and drop.
To work around that problem, you could either (a) use the ServiceFactory to check if the mapping already exists, and if not create it in the appropriate place and/or (b) create a mapping that looks like "/com/mynamespace/mystuff/" that will nicely translate into a path like 'com.mynamespace.mystuff'. The advantages of (a) is that you can make a entry point to your CFC lib (most likely a Factory) setup the mapping as necessary, and therefore you don't need to go to the cfadmin, however, obviously it is a undocumented feature. The advantages of (b) is that you are almost totally guaranteed that someone hasn't already used that namespace (much like packaged in Java).
The cons of cfmapping (and the ServiceFactory workaround above) have to do with code portability and using the same CFCs across a domain (3,4). Since a mapping is server wide, if the applications have differing code, it's going to get very messy. You will end up having to set a unique mapping for that application, and therefore break all your return, extends and type attributes in your CFCs.
This would work very well for application specific code, but not code that you wanted to port from one place to another, such as a product or framework (I shudder at the thought of having different versions of framework on the same server that used a mapping…).
Put all your CFCs in one directory
Placing all your CFCs under a single directory solves most of the above problems, particularly the one with applications spread across a domain (4) and since all the CFCs can see each other typing is not a concern. The major issues here (I feel it's major) has to do with what happens when you start looking at systems with large amounts of CFCs. Having anywhere above 20 CFCs in one directory is going to start to get very hard to work with, and could cause some maintenance frustrations in the long run.
One of the nice things about packages is that they can be relatively self documenting, assuming they are named well, and can help break down an application into manageable 'chunks'.
This would be useful if you have a small amount of CFCs and/or having multiple CFCs across domains with the same name, but different functionality (4) is of utmost importance.
Use relative '/' rather than '.'
I can remember when I first found out about this technique – I thought it was going to be the absolute best thing ever. However, like all the rest it has its drawbacks. It handles issues of typing quite well (1,2), but does mean that you can end up with some very odd package design, and more often than not, most of your main CFCs in the same directory anyway. It is very portable (3), but only within a given OS, as '/' must be used on *nix, and '' must be used on Windows, which pretty much defeats the purpose of portability. Otherwise you have no problems with multiple apps across a domain (4) as all the mappings are relative.
This would be most useful when you know you aren't going to change OS, and being able to use code across a domain is very important. This couldn't be used for product development simply because of the OS issue (and it's an undocumented feature).
Don't use typing
This essentially boils down to not using inheritance (because you have to hard code it), and all type and return values are either 'any' or 'web-inf.cftags.component'. One could argue that since CF won't check things until runtime anyway, this won't make any in terms of how errors are fired on the system.
To help bring back some sort of typing (1,2) you could do some sort of testing to ensure that a CFC is the CFC you require (via getMetaData), or that it has the given method you wish to call. This can cause further overhead, especially considering that getMetaData is known to be pretty expensive. It will also take a little bit longer to develop as you have to write the extra code.
However – this code is very portable (3), and can be used across domains easily (4), but you do lose a lot of the self documentation that typing gives you, and could leave you wondering which CFC you were meant to pass in as an argument.
If you were writing a product to be used it a variety of places, this may be an appropriate place to use this method, but for application specific code, it is probably overkill.
Store them in a directory in the root of your domain
This seems to be the most 'rounded' of solutions for placing your CFCs. If you notice, Mach-ii, model-glue, Tartan etc all require you to include their install files in a directory in the root of your domain, and since we figure the people who wrote this stuff are a *little* bit clever ;o) so we can assume that there is a reason for this.
This solves issues with typing and portability (1,2,3) and is obviously easy to maintain (4). The issue comes from where you have multiple applications running across a domain (quite possibly a relatively low percentage of apps?) with different versions of CFCs required on them.
If application /app1/ is running v0.8 of their framework, and /app2/ is using v0.9 and they BOTH have to have their CFCs in the root of the domain – you are either going to run into a mess, or you will need to test app1 all over again to make sure nothing breaks with the new version.
This seems to be the best option for when you are developing a product (particularly frameworks) as it is the most portable version, and it is very unlikely you will not have access to the root of the domain. If you use this for application specific code is probably dependent on what is being developed on that server.
Of course there is nothing to stop you from chopping and changing different aspects of these approaches into whatever happens to work for you.
I think I managed to cover all the approaches that are possible – but if you have some other way, please let me (us?) know.