Adam Tuttle

In Response to CFHour #226

The latest episode of the CFHour podcast mentioned me in two consecutive segments, and in the latter they said that they fully expected me to respond... And who am I to let down a podcast to which I regularly listen?

First, they very kindly mention that I wrote a book! Thanks for that mention, guys. If you had reached out to let me know you were going to mention it, I would have been more than happy to offer a discount code for your listeners. Alas, I'll have to give that out here instead. CFHour listeners can save 10% by using this link.

The second segment was about my previous blog post, detailing the reasons that we gave up attempting to switch to Railo.

ORM Mapped Superclass entities named the same

As I mentioned in my previous post:

using MappedSuperClass with ORM entities that have the same name (e.g. User and base/User) — for reasons I don't feel like elaborating on but I assure you are completely legitimate — causes a Stack Overflow error

I guess Scott didn't accept my assurances of legitimacy. Not that I blame him, I'm always a skeptic, myself...

Scott: I guess I kind of understood, but I would never think to do that. Like, I would probably name the base user cfc, baseUser.cfc
Dave: Mmmhmm
Scott: I've used MappedSuperClasses a lot in the stuff that I do. As a matter of fact, all of my ORM entities extend a BaseEntity that has common methods that I want available to each one of the entities.
Dave: Yeah, most of mine do too.
Scott: I would just never think to name them the same name. Or more to the point, I wouldn't think to not name them base{Blah}, as part of the actual name itself—
Dave: Yeah
Scott: —rather than—
Dave: Uh-huh
Scott: —using a path to delineate them.

Ok guys, here goes. We've got lots of entities, and this system is only fractionally complete. We're sitting on around 50 entities right now and can easily see that expanding to be several hundred. In addition to that, this is for a resellable product, not a one-install bespoke system. So we're writing with customer-customizations in mind. Our customers are universities and colleges across the country (and maybe eventually the world?) and I'm sure you realize that they all have their own favorite way of storing data and their own favorite custom fields; so we're planning ahead to support that sort of thing.

Maybe User was a poor example. Universities tend to refer to their students, alumni, and sometimes even faculty and staff, as Constituents, so let's go with that. Uni A wants an isFaculty flag, an isAlumni flag, an isParent flag, and so-on (not a terrible idea, considering it's possible to be all 3 of those, and more)... Meanwhile Uni B might prefer to just have a type_code column with coded values to represent every possible case. Neither is objectively wrong, and it would be a crappy reason to lose a potential customer if we couldn't support their desired data formats.

So how did this drive the naming decision? Well, with potentially several hundred entities on our hands, our first goal was to set things up so that customizing the entities for one customer didn't make it difficult to maintain the source code (remember, this is all going into git) for other customers. So the strategy we decided on was to put the system properties in the MappedSuperClass orm/base/Constituent.cfc, and the custom properties into the child class orm/Constituent.cfc. Each customer will have their own branch of the repo, so the child class will stay empty in the master branch and the customer modifications to it will be done only in the customer's branch. This should (in theory!) practically eliminate merge conflicts (at least in the case of ORM entities), and keep each branch pretty clean.

We wanted to make maintenance as easy as possible, too. I suppose baseConstituent.cfc is just as easy to remember as base/Constituent.cfc — no argument there. But what happens when an entity legitimately needs to start with the word "Base?" Sure, we can have BaseBaseSalary.cfc (Yes, that's completely contrived: Why would you need a Base Salary entity? I've no idea!) but that just has a certain smell to it that I'm not fond of... like milk that hasn't quite expired yet, but still makes you think twice before drinking it. So instead we stuffed them in a base/ folder and called it a day.

As you've probably figured out, this was something that we were able to deal with relatively quickly: It wasn't "the last straw," so it had already been resolved by the time that last straw was reached. I do have to say, though, that I was very happy to revert the commit that made this work on Railo and go back to my base/Constituent.cfc strategy.

CFImport vs. App-Specific Mappings

I think we're mostly of the same mind on this, so just one quick note here: I think that the only way I'll consider this functionality "fixed" is when mappings are respected in all cases on all platforms. Anything short of that is a cop-out for what amounts to "academic" reasons. (If you're asking yourself what constitutes "academic" reasoning, think about your ipod playing the same song 5 times in a row in shuffle mode even though you'd hate that, because that's how random numbers work sometimes... Academic reasoning is not always the best.) And I don't think CF11 has, as you mentioned Scott, "fixed" (removed) the include-based workaround. I'm pretty sure it still works fine for me on CF11, though I did not revert the code changes I made to get this functionality running on Railo.

Metadata

Scott: This is the one that really had me kind of scratching my head

No worries, I'll explain!

Your description of the problem is dead-on: We've got functions with a definition like this:

function remove( rc ) iq:role="users:delete" {}

In fact, nearly every Controller method looks like that. It's the configuration necessary for our role-based security implementation. We combine this metadata with FW/1's before() lifecycle event to authorize (nearly) every pageview / form submission / etc a user makes in the application. This makes the code very terse, which is a particular passion of mine, but does come with its own challenges. For one, getting that metadata is on the "expensive" (in computational time) side. Again, we have to make use of a base class, but in this case it's just a generic baseController that all controllers extend:

component {

    function init(){
        var md = getMetadata( this );

        for ( var fn in md.functions ){
            if ( structKeyExists( fn, "IQ:ROLE" ) ){
                variables.permissions[ fn.name ] = fn['IQ:ROLE'];
            }
        }
    }

    function before( rc ){
        //if the requested action requires user to be logged in, verify that they are
        verifyLoggedIn( rc.action );

        //if user is logged in, check role-based security for the requested action
        verifyPermissions( framework.getItem() );
    }

    function verifyPermissions( 
        methodName
        ,failMessage = 'You do not have access to the requested functionality'
        ,jailEvent = 'main:jail/home'
    ){
        //action doesn't have role requirement
        if ( !structKeyExists( variables.permissions, methodName ) ){
            return true;
        }

        var requiredRole = variables.permissions[ arguments.methodName ];
        if ( session.user.isAssignedRole( trim(requiredRole) ) ){
            return true;
        }

        request.context.requiredRole = requiredRole;

        framework.redirect( arguments.jailEvent, "requiredRole" );
    }

}

When each controller is initialized (infrequently) it grabs a copy of its own metadata (see init()) and stores a copy of the iq:role metadata in the component instance (variables scope) for later reference. Then, during the before() lifecycle event, it is referenced to make sure the logged in user has access to the requested action, before the action happens.

Apologies, that was a rather long-winded way of getting to this next part: Why is the metadata named iq:role? Why not iq_role? The short answer is: Because when you can, it makes sense to... And you can on Adobe CF10+... So I did.

In a few more words: Namespacing. You never know what crazy feature the platform vendor will come out with next, right? I mean... CFClient, anyone? So by appending custom metadata to your functions, you run the risk that one day they will release a new version that you really want to use, but now they have revitalized <cflogin> to actually be relevant, and oh by the way, it uses a role metadata attribute on your functions. So you have to go change every function that uses your custom role metadata attribute if you're not lucky enough to have been using it "their way" before they were. By prepending some characters that are unique to your application, you reduce the risk of a collision later down the line. Just to be clear, the extra characters don't change the functionality in any way, they just reduce that risk of collision. That's what namespacing is for, but it still doesn't fully explain why ours uses a colon instead of an underscore or something else.

And the answer to that question is: because I said so! When you're in charge of decisions like that (and I am, right now), you sometimes get to pick things because you like the way they look. On Adobe ColdFusion, iq_role and iq:role are functionally identical, so I picked the one that I found more visually appealing. I can't really explain why I find the colon more pleasing to the eye, but I do. I probably saw it somewhere else (Java? Python? Who knows...) and it stuck with me. And it continues to serve us well on the ACF platform.

Taffy users are also probably familiar with it, as Taffy supports both taffy_uri and taffy:uri metadata attributes for specifying a resource's uri mapping (for both Railo and ACF8-9 support reasons).

So why doesn't Railo support it? Because they also support this: role:"foo". At some point in the past they chose to accept a colon as the delineator between the LHS and RHS of attributes, and as a consequence they can not support colons in the LHS. For them, it is like trying to write: iq=role="foo" — and I accept that. That is a particularly hard problem to solve, and I don't blame them for not considering it a high priority. I find it unfortunate that they dug that hole for themselves, but there's no use dwelling on the past: This is the world we live in, so this is what we have to deal with. Doing otherwise would be like fighting gravity.


Scott: You know, when people run into these problems and I don't, I can't help but think, "Are they doing something really weird, or am I doing something really weird?" Are they doing it wrong, or am I doing it wrong? And you know, when you're talking about me and Adam Tuttle, I would err on the side of me doing it wrong.

That's very kind of you to say so, Scott, but I think that this time it's very clearly my fault that it doesn't work on Railo... I didn't originally make the choice with platform agnosticism in mind. But would I go so far as to say it is "wrong"? I think not.


One last thing about that previous post ("We Tried (and Failed) to Switch to Railo")... While it was not meant to be putting down Railo in any way, I think it would be easy to see it in that light. I actually really think Railo is a great option; even more so if you get to start with a clean slate. But I just wanted to put out some information that showed a first-hand account that switching from one platform to the other is not all unicorns and rainbows. There is significant work involved. So when the fanboys spout off on mailing lists that you should just switch, you can rightfully start ignoring them. It's not that simple.

Scott had mentioned toward the end of that segment that he was impressed that these were the only issues that we ran into, and there is some truth to that statement, but probably not the kind he intended. It's more like the truth that your keys are always in the last place you look for them because you stop looking when you find them. We gave up once we ran into the metadata issue. It's possible that could have been the last thing; but it's equally possible that there could have been 30 other differences to deal with once the metadata problem was resolved.

In truth, we were working at a running pace. Steve was about to leave for a road trip and I was head-down on something unrelated, and while we were switching to a new AWS EC2 instance, we thought it would be a good opportunity to give Railo a try. We didn't really have the time to deal with a lot of issues that needed sorting out, so our threshold for conversion pain was pretty low. It ended up being about a 2 hour experiment that proved it wasn't going to be quick and painless to switch. Would we make these changes down the road and try again? We might... When we have time available to deal with it.

Published 2015-01-19 @ 09:40 in ColdFusion

We Tried (and Failed) to Switch to Railo

One of the things we wanted to do with our new product was to try running it on Railo. We've been developing it against Adobe ColdFusion 11 for a while now, but have wanted to try our code on Railo for a while. It would be nice if we could eliminate the license cost. We figured there would be a few things we would have to deal with, but we'd just fix those and move on. "How bad can it be?" we asked ourselves...

Pretty bad, apparently.

ORM Inconsistency

The first issue we ran into was that using MappedSuperClass with ORM entities that have the same name (e.g. User and base/User) — for reasons I don't feel like elaborating on but I assure you are completely legitimate — causes a Stack Overflow error.

So I renamed the base entities to xUser/etc, for what felt like ~200 entities (oh and all of the extends= referring to those base classes), but in reality was probably more like 50.

Class vs JAR

The next thing we found out was that Railo won't load .class files out of the classpath (as Adobe ColdFusion will), only .jar files. Fine, zip them up, rename to jar. A nuisance, but not the end of the world...

CFImport vs. App-specific Mappings

Having cleared those issues, we next found that Railo doesn't respect application-specific mappings in the taglib argument of the cfimport tag (don't believe the status=resolved, it's not)...

Ok, fine, we got lucky and were only using this in a few places. I fixed those, on to the next speed bump.

The Last Straw

We use a neat, custom, home-made solution to do role-based security throughout our application applying restrictions in the global FW/1 before() event shared by all of our controllers. Essentially, each FW/1 controller action method can optionally have a bit of metadata like iq:role="users:delete" and if the logged in user doesn't have this role, then they are redirected away from the action.

Unfortunately Railo handles metadata differently, and rewriting this portion of our code would be pretty drastic and take time that we don't have to invest right now.

That was the straw that broke the camel's back. We're going back to Adobe ColdFusion... for now.

I'm really disappointed. Certain people have been espousing Railo as their go-to CFML server for so long that I figured it would be pretty smooth sailing. (No ill will directed at you chaps... You're not to blame!) While Railo certainly does have a track record of responding to (some?) bugs much more rapidly than Adobe, their track record for feature parity is apparently pretty low.

In fairness to Railo, if we had spent equal time developing against Railo as our base and then tried our code on Adobe ColdFusion, it's equally likely we would be having similar problems. I just wish there was better parity and that old bugs weren't simply written off and ignored the way they sometimes seem to be.

Guess we'll keep using ACF until we're ready to switch to Node. ;)

Published 2015-01-06 @ 08:00 in ColdFusion Railo

2014 In Review

Partially inspired by Ray Camden's similar post (a fun read, by the way), I thought I would take a look at how 2014 shaped up for me.

Blog Stats

This year I continued my trend of writing at least one blog post every month, so my streak since starting this blog in April 2007 remains unbroken. The most popular post that I wrote this year (as determined by total hits) was Creating Cordova (PhoneGap) Android Prod/Dist Builds on the CLI... However Heavily Customizing a Bootstrap Typeahead remains my most popular post of all time... despite that version of Bootstrap being outdated and the new version not including a Typeahead control. I'm pretty consistently getting between 300 and 500 hits a day between all of my various content, and a surprising amount of comments on older posts. The long tail on these things is crazy.

Reading Stats

While not a prolific reader, I do like to read and can occasionally get sucked into a book and do little else for a day or two. I set a goal of 12 books for the year, and it looks like I'm going to finish the year out having read 15. My favorite book of the year has got to go to Sugar Alpha: The Life and Times of Señor Huevos Grandes, which is sort of a cross between some early-to-mid skydiving history (the fun parts), and an action/thriller movie about drug smuggling. In fact, I hear that it may become a movie in the next few years. If you hear anything about it, definitely go see it!

A close second place would be The Martian, a ridiculously good hard science fiction story about an astronaut that gets stranded on Mars and has to get very resourceful to save his own life.

I Wrote a Book!

Sort of on a whim, in December I wrote a book: REST Web APIs: The Book. Inspired in part by Amy Hoy's book Just F#*!ing Ship, which she wrote and shipped in 24 hours, I decided to try my hand at shipping a book in just over two weeks time. I knew I didn't have 24 consecutive hours to work on it, so I broke it up over two weeks, working about 3-4 hours per night, most nights. That works out to about 42 hours instead of 24, but still quite the accomplishment, I think.

The point was this: You really don't need to get hung up on all of the little details. You can self-publish and skip anything that doesn't absolutely need to be there. My book still doesn't have a table of contents or an index. It's just a rudimentary cover image and a collection of chapters, available as PDF, ePub, and Mobi (kindle). It was proofread by volunteers and does still have a few errors that slipped through (to buyers: expect an update soon!). I shipped a true Minimum Viable Product, and I can improve on it over time as needed.

Anyway, I wrote a book. I'm (self-) published now! That's cool. If you're interested, I released a sample chapter, too.

Conferences/Presentations and Career

The only conference I went to this year for self-betterment was cf.Objective() — now dev.Objective(). 2014 was also my first year on the Content Advisory Board for the same conference. I'm finding myself more and more frustrated with Adobe's handling of ColdFusion by the day at times, so it's no surprise that I chose not to attend CFSummit and find myself more interested in Node/JavaScript focused events.

Oh, I forgot about the Node Philly event at Philly Tech Week, where I gave a brief presentation on integrating things in your life by building custom IFTTT-like implementations with Node.js and hosting them for free on Heroku.

I also participated as a vendor at the CASE District II conference in February, where Steve and I started selling our warez in earnest. I hinted at this at the beginning of the year, and we are well on our way. We have our first paying client for our product suite; and development is going really, really well. We're on the path to a product based business, and I couldn't be happier.

I was invited to apply for a job at Netflix by someone that I really respect; and while I ultimately passed on it (for now!) I was honored to have been thought of and humbled by the thought. I'm fully invested in making AlumnIQ into a successful business, and even if I was looking for a new job (and I'm not!), convincing my wife to move across the country would be about as difficult as convincing fish to come live on land.

Favorite Movies and Music

I've been using last.fm to track my listening habits since I first heard of it in December of 2007. I haven't always listened via methods that could be tracked (e.g. early iPods, etc), but when tracking was possible, I was doing it. My most played artist/song for the year came as just a little bit of a surprise to me, because I feel like I haven't listened to them in a while thanks to the holiday music season and associated family gatherings.

Indeed, because during family gatherings or just a day at home with my family we tend to shuffle-play a huge Spotify Christmas-themed playlist I created, that stuff dominated my stats for the year. But because it's not "active" listening I'm not going to include it in my "favorites" determination. So with that in mind, my most-played artist/song of the year:

More stats on my listening habits are available here.

I don't put much effort into rating movies on IMDB, but I do tend to rate what I watch on Netflix in hopes that it will generate good suggestions. Unfortunately Netflix doesn't seem to show my ratings (there appears to be a page for it but it's empty, so I'm guessing it's buggy at the moment)... So I'll just wing it. Memorable movies from this year include Guardians of the Galaxy, How to Train Your Dragon 2 (I have kids, after all), Edge of Tomorrow, and Gone Girl. I suspect Interstellar would easily top this list if I had been able to see it yet, but I haven't. So I think my favorite movie of the year has to go to Gone Girl just for the shear mind-f§¢k of it. I didn't even want to sleep next to my own wife after watching that movie.

Other Memorable Stuff

If you've been reading this blog, you probably know that I've switched to a standing desk in my home office. It's been great at doing exactly what I hoped it would: Working out muscles that were otherwise going mostly unused. My lower back, calves, and thighs are all getting much more work during a workday. I haven't been terrific at sticking with the standing desk —there were many days toward the end of the year that I sat on the couch instead— but I really do like it and I will be back at it again in the new year.

Also this year I decided to become a licensed skydiver and take it up as a regular hobby. That has been a great success! I went out for my first day of training in late April, and in early August I received my A license, which allows me to jump at any USPA-member dropzone in the country. I've since bought my own set of equipment so I don't need to rent it for every jump, which is very nice. As I write this on the afternoon of the 30th I have 41 jumps to my name, but when I post it on the morning of the 31st I'll be on my way to the dropzone, hopefully to add a few more to that total, if the wind cooperates! (Hooray for holiday vacation!)

Here's some video from my most recent jump. I'm in the blue helmet and black sunglasses, facing the camera as the video starts:

This year I participated in my first color run, which was pretty cool. Running with a 5 year old, though, is predictably fairly slow going, especially when that 5 year old falls down within the first kilometer and scrapes his hands and knees pretty badly. He did finish, but he was kind of shaken up by it! Here I am with my mom and my oldest, whose school was holding the color run as a fundraiser:

Color Run!

I was terrible at keeping in shape this year. I'm up more than 13 pounds from the beginning of the year, and I haven't even weighed myself since the traditional holiday gorging began. I didn't run anywhere near as much as I wanted this year, nor did I do as much yoga as I should have. Definitely need to work harder on that in 2015.

What it looks like when you let yourself go...

And finally, one of my brothers got married this year, in Niagara Falls. I had never been to the falls before, so it was double cool to get to go there and to see my brother get married. Triple cool: First time my kids were out of the country! (First time they stayed in a hotel, too...)

Brother's Wedding

It's funny how the days can seem to drag on, but the weeks fly by, and the months more so.

Published 2014-12-31 @ 10:00 in Meta

15 Days Ago I Was Not Writing a Book; Today You Can Buy My Book

So early on the morning of December the 4th that it was still the 3rd in my mind, I decided to write a book. Within a few hours I had drawn up a basic outline and an aggressive but accomplishable timeline for getting it written and shipped without wasting time trying to get every little detail perfect (a.k.a. not shipping)... That was almost two weeks ago to the hour, as I write this, and that deadline was noon today.

I've just delivered the final files —that is, the PDF, ePub, and Mobi (for Kindle) files— to be shipped today at Noon US-Eastern. It feels good to ship on schedule!

With literally hours left before the files are delivered to everyone that pre-ordered, this is your last chance to save 36%. At noon the price goes up to $19. I'm not complaining if you're holding off to pay full price (Thanks for that, Andy!), but as a fellow deal-lover, I can't help but want you to get a bargain too. Get a deal... Buy Now!

From the bottom of my heart, Thank You to everyone that believed in me to get it done on schedule, and to deliver something worth some of your hard-earned money. Your support has been a huge motivating factor, so in a way you helped me finish on schedule too!

Published 2014-12-19 @ 09:00 in REST Taffy