Better Living Through Transfer and ColdSpring
We have a live system with customer data and a new requirement comes along that a particular piece of customer data must be encrypted in the database from now on. We already encrypt some columns (using Triple DES - which you might have guessed given the recent posts on my blog and here about mimicking ColdFusion's encryption in Java/Groovy). What is the smallest possible code change to ensure that as any user updates their data in future, this item will automatically be encrypted - whilst still handling the case of legacy data being unencrypted?
We use Transfer for all our persistent business objects and almost all of our business objects have a decorator defined (for validation or some additional business logic). We also use Brian's TDOBeanInjectorObserver to automatically inject services into our business objects - just add a setter for a service and the bean injector takes care of the rest.
Here's the bean injector definition in our ColdSpring file:
<bean id="transferObjectInjector" class="coldspring.transfer.TDOBeanInjectorObserver">
<constructor-arg name="transfer"><ref bean="transfer" /></constructor-arg>
<constructor-arg name="suffixList"><value>service,datasource</value></constructor-arg>
<constructor-arg name="debugMode"><value>true</value></constructor-arg>
</bean>
<cfset bf.getBean("transferObjectInjector") />
So how do we add the on-demand encryption to our business object's data?
First, we add a setter to the Transfer object decorator (our business object) to match our encryption service:
function setEncryptionService(encryptionService) {
variables.encryptionService = encryptionService;
}
Then we override the setter for the data we need to encrypt:
function setSFPassword(password) {
getTransferObject().setSFPassword( variables.encryptionService.encryptStr(password) );
}
Here's the matching setter:
function getSFPassword() {
var password = getTransferObject().getSFPassword();
var decrypted = "";
try {
decrypted = variables.encryptionService.decryptStr(password);
if (decrypted == "") decrypted = password;
return decrypted;
} catch (any ex) {
// must be unencrypted already
return password;
}
}
This allows us to encrypt data upon any user updates while still handled unencrypted legacy data during the migration period.


>We also use Brian's TDOBeanInjectorObserver to automatically inject services into our business objects - just
Could you explain this a bit further please? I would have thought your application controller would make a call to the relevant service layer passing in any business object it needed. Either that or the service layer will get the business object from a factory (or simply instantiate it itself etc).
From there the Service can perform any application/business logic and possibly call a method on the business object which may or may not have some sort of DAO/Gateway composed with in to handle database operations. The other common approach being to pass the business object (from the Service layer) into a DAO/Gateway etc
I've never considered composing a Service layer into and actual business object. So any further info would be much appreciated :)
Wouldn't it be easier to just try/catch in the EncryptionService and return "", or check for "" in there and throw an exception so you didn't need the double check everywhere?
@Michael
That's actually a fairly common technique for factoring out behavior for business objects (at least in the OO CF world, and certainly in Transfer users, probably in Java).
The goal being to attach as rich as possible an API onto the BOs, but still break the logic up into services that can be reused/remoted/accessed/etc. directly if needed.
It honestly feels more natural to me too, since we want to ask objects to do stuff for us in the application and make that the entry point; thus we should talk to the objects, not to some services that hide them. If all we do is talk to the services and not the objects, then why not just use structs for the BOs?
From the super BO/ORM purist perspective putting the DAO or the Gateway into the object is a serious error too. Even having getTransfer() inside the object is considered a mistake. From this perspective objects are merely representations of some kind of data that talk to other objects (in classical sense, like an ArrayList, not like a Model) and Services and have no knowledge of the fact that they're special (ie in a Database and would need a Gateway, or would even require special treatment for persistence). This approach is highly unpractical (in CF and others) for a lot of tasks and reasons, so we cheat now and again, like with getTransfer().
Of course there's a number of other approaches like ActiveRecord that take a different look at how this should all work. :)
@Michael, Elliott's answer covers much of what I would have said and I've asked Brian to comment as well since he wrote the bean injector.
My stance is to inject services that a business object needs in order for it to transparently "do its job" and be as rich a business object as possible. My services generally serve only two purposes: to provide broad (system-wide) operations (like encryption) that can be used by many business objects; or to provide an API onto the business model in general that can be exposed to clients (i.e., a service-as-remote-facade or a service exposed to a framework-specific controller, such as in Model-Glue).
Without this kind of rich business object, you quickly get into the "Anemic Domain Model" antipattern, where your BOs are nothing but dumb data containers and all of the business logic resides in the service layer. Since many CF'ers tend to come from procedural coding backgrounds, breaking out of this approach can be a challenge. But I've found that at the end of the day, making the BOs the central player in your model results in a much more flexible system that is much easier to change over time.
At login, we encrypt the username / password submitted and use readByPropertyMap() to retrieve the single matching author record. In our admin console, we can search by email address but use a full exact match (so again, we can use the encrypted value as a key).