I was dealing with a strange bug using my QueryToArrayOfStructsConverter in a new project. The code was super simple — just a select * from table as an example for a test I was writing. I expected to get back my array of structs, but I instead got this mysterious error:

Exception mixing in AOP aspect for (TestDAO)

I decided to take the time to dig in to the AOP portion of ColdBox to try to (1) learn about how AOP is implemented in ColdBox and (2) find my problem here.

Side Note:

Whenever I dig in to these packages and frameworks, I’m assuming I’m going to find that I was doing something wrong.
It’s more time consuming than finding an answer in the documentation, but also more enlightening about how the tool I’m using works.

AOP is a fascinating pattern. I was interested to find that ColdBox accomplishes the pattern by writing a dyanmic function to a file and then including that file in the in-memory class using a <cfinclude />. Very smart.

I mention the <cfinclude /> because it was here that I was getting the error. You see, ColdBox is just catching any error and reporting it back as the error message above. By commenting out the try-catch block, we see a different and, in this case, more useful error message:

Looks like Lucee is using find as a function name under the hood. The method I was trying to join to was also called find. Easy enough fix — I renamed the method to findAll.

There’s probably other reserved words, as it were, that you can’t join an aspect to, but I don’t have a list. What I do have now is a good guess at the problem when I see this kind of error message and a better understanding of how ColdBox puts together AOP.

I was excited for the conference, but it exceeded my expectations. Especially when compared with some other conferences I’ve attended recently, this conference has had a huge impact on my skills and career.

I think that’s partially because it reminded me how much I love programming. There are a lot of different kinds of projects back at work, and not all of them programming ones. This conference reminded me that I really like the programming ones.

I think it’s partially because the conference was well organized. The conference area was nice. Wifi worked well. The sessions were very informative. The food was excellent. This was probably my favorite Vegas conference location yet.

But mostly, I think it’s because of the community. I decided that I was going to be very prolific on Twitter. (Really prolific. Over 100 of my 500+ tweets happened in the last two days!) That led me to interact with a lot of smart people. The CFML community is very friendly and helpful. I already knew this from the CFML Slack (which you definitely should be on), but it was nice to see in yet another venue. I was able to meet some people in person that I had only interacted with online. Their interaction helped build my confidence — confidence to write more and even submit a couple proposals to dev.Objective() 2016.

I think a key step in keeping a language and community alive is to participate. Participate on Slack and on Twitter. Help out where you can. Submit bug reports and pull requests. No community will die when we’re participating.

This conference was absolutely a success. I can’t wait to come back next year for CFSummit 2016.

There, I said it. I don’t like to look foolish or stupid or, really, less than perfect.

And it’s held me back. Especially from participating more in conferences.

I’ve wanted to submit a proposal to a conference for a while, but I was waiting around until I had a talk that was (1) completed, (2) awesome, (3) completely unique, and (4) wanted by the community.

But something clicked last night.

This is a proposal.

And being a proposal, I don’t need my talk to be (1) completed. I just need the outline.

The conference organizers will decide if my talk is (3) unique enough and (4) wanted by the community. And if it isn’t, that doesn’t mean the talk is bad, necessarily; just not the right time and place.

Basically, I was taking on myself the judgement that I should leave up to conference organizers.

So I just submitted a proposal to dev.Objective() 2016. I’m excited. I have no idea if it will be accepted, but I’m glad I finally submitting a proposal.

Now, all I need to focus on and all I should focus on is (2) make the talk awesome.

This would be an awesome addition. Arrays of structs have better collection methods with each(), map(), reduce(), etc. I’ve been told arrays of structs are faster. They definitely interact with Javascript better on the frontend.

In the meantime, let’s solve this using Aspect Oriented Programming (AOP).

What is Aspect Oriented Programming (AOP)?

As with most programming topics, there is a bit of jargon that comes with it. Don’t let this jargon scare you away. A list of definitions is available on the WireBox site, but we’re going to try and define it very simply here.

What is an aspect?

One of the dictionary definitions of “aspect” is a “part; feature; phase”. In AOP, an aspect is similar — it is a feature of your application. What makes an aspect different from any other object is that an aspect can spans multiple objects. It is more concerned with its job or feature than what it is. So while your normal beans model real world “things” like BankAccount, User, or even Transaction, an aspect will model a feature or job with names like MethodLogger, DBTransaction, or (in our case) QueryToArrayOfStructsConverter.

When would I use this?

If you find yourself writing the same code or method over and over (especially in different components), an aspect might be the way to go.
One other place an aspect shines is adding a feature to existing code without touching the existing code.

Examples can include:

Logging every method that is called

Wrapping all query methods in a transaction

Implementing a security system around certain handler methods

Transforming a result in some way <– this is our example

Note: Whenever you are injecting code in to your components, you risk running in to tough to debug problems. Use your best judgement.

Our Setup

I’m going to show this using ColdBox and WireBox. (I’m sure you can do the same thing with FW/1 and AOP/1).
You can recreate the same skeleton by running coldbox create app query-converter-aspect in CommandBox.

Next, we’ll hijack the index method of the Main handler.

We’re injecting the DAO directly just for this example. When we hit this route we will dump out the data that we get back from the database.

Here’s a look at the DAO.

Very simple. Just querying a test database I set up for this example. Note that we are returning a query.

That’s all of the setup. Let’s move on to the AOP portion of this example.

Building the Aspect

Aspects need to implement coldbox.system.aop.MethodInterceptor (or wirebox.system.aop.MethodInterceptor for standalone). There is only one required method, invokeMethod.

Gotcha!

When implementing the interface, don’t forget to include all of the method definition, including the output=false metadata.

Here is QueryToArrayOfStructsConverter:

Let’s deconstruct this example:

Line 7: This is our implementation of the required method from coldbox.system.aop.MethodInterceptor.

Line 8: By calling invocation.proceed(), we call the matched method. It will return any value it would normally return. In our case, it should return our query object we defined in TestDAO.cfc (once we match it in our WireBox.cfc later).

Line 9: We transform the query in to an array of structs and return it.

Our aspect is ready to go. Now we need to tell WireBox when to inject it.

Registering the Aspect

Gotcha!

Don’t forget to activate the AOP listener! If you can’t figure out why your aspect isn’t running, even after setting your matcher to .any(), you probably forget to add the coldbox.system.aop.Mixer to your listeners array in WireBox.cfc.

You can register aspects in WireBox.cfc, the same place you register your other bindings. Here are the bindings I added:

Now, this is overkill. I probably should pick just one bindAspect, but I wanted to show options. Let’s go over this in detail:

Line 18: First, we bind our aspect to a named key.

By Filename

Line 22: This binding matches components that end in “DAO” for every method and runs our aspect. This is the binding that will match our original TestDAO.cfc file.

By Method Annotation

Line 29: This binding matches all components for methods that have the annotation convertToArrayOfStructs and runs our aspect.

The annotatedWith will match both inline annotations and doc block annotations. Here are those examples:

By Component Annotation

Line 36: This binding matches all components that have the annotation convertToArrayOfStructs for all methods and runs our aspect.

The annotatedWith will match both inline annotations and doc block annotations. Here are those examples:

Gotcha!

You may think you can bind your aspects on returns('query'), but then you have to return a query. Instead, use a different AOP matcher and don’t type hint the methods you want to transform in this way.

Note: this may be a bug. I haven’t really dived in to this use case yet.

Wrap Up

Running this code before, we saw this query:

Running the code with our aspect in place, we see this array:

Now, thanks to AOP, we have an easy way to bring back our queries as arrays of structs every time.

To learn more you can refer to the WireBox docs here or the AOP/1 docs here.

Bonus! — ColdBox Module

This kind of use case bundles real well into a module.

There’s a little magic in this one to activate the AOP listener if it hasn’t been activated and then add the aspects in automatically. Since it is automatic, I’ve chosen to go with component and method annotation bindings only.

The Problem

Don’t use columnlist if you need to worry about the column order. Use the query’s metadata.

- adam_cameron

I was caught off guard by this statement because I had some tests regarding query column ordering in one of my open projects that were passing. Checking the tests, I was worried because the columns were already in alphabetical order. I changed the order of the columns in the query, but the test still passed.

The Solution

I figured this might be an engine-specific bug. TryCF lets me test this on multiple environments without setting them each up on my system.

Looks like ACF decides that sorting your column names is helpful, while Lucee respects the order you provided. If your code needs to be cross-platform, you can, like Adam Cameron suggested, use the query metadata. Here’s an example:

You could use ArrayMap (or, even better, getMetadata(data).map(...)) if you are on Lucee or ACF 11, but this example was about better cross platform compatibility.

The Wrap Up

Going forward, I look forward to the day when CommandBox has built in servers for ACF. It will make testing cross-platform libraries a lot easier. Until then, a good CI strategy (like cfml-ci) will have to do.