• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Object Oriented Programming (OOP) Question Regarding Data Access Objects (DAO)

Contributor ,
Jun 15, 2017 Jun 15, 2017

Copy link to clipboard

Copied

I just finished reading a book on introducing Object Oriented Programming (OOP) in ColdFusion.  I found the book to be very enlightening on many levels however I was left with some questions and there didn't seem to be anywhere to go after the book to learn more advanced topics or to seek clarification or more information.

I may be creating a few questions on this forum over the next few days/weeks with OOP related subjects and ColdFusion that I would love your feedback and opinions on - especially from people that have experience working with OOP.

This question pertains to Data Access Objects (DAO) and best practices or recommendations for using them in a ColdFusion application. I'll be using a generic "User DAO" as an example.

Historically whenever I've created a DAO method that updates a database table within a CFC I've always included a <cfargument> for every single column that can be updated in the database.  Then within my method I check for the existence of an argument and if passed, I update that column.  The end result is an update method which only updates database columns that were passed as arguments.  This has always made sense to me for example if the user wants to update their password you can simply update that one field.

Here's an example method to show what I mean:

<cffunction name="updateUser">

  <cfargument name="usersID" type="numeric" required="yes" />

  <cfargument name="firstName" type="string" required="no" />

  <cfargument name="lastName" type="string" required="no" />

  <cfargument name="emailAddress" type="string" required="no" />

  <cfargument name="passwordHash" type="string" required="no" />

  <!--- update the database --->

  <cfquery datasource="#application.dsn#">

  UPDATE Users

  SET DateModified = #createODBCDateTime(now())#

  <!--- go through each column, check to see if an argument was passed and if so, update the column --->

  <cfif structKeyExists(arguments, "firstName")>

  , firstName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.firstName#" />

  </cfif>

  <cfif structKeyExists(arguments, "lastName")>

  , lastName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.lastName#" />

  </cfif>

  <cfif structKeyExists(arguments, "emailAddress")>

  , emailAddress= <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.emailAddress#" />

  </cfif>

  <cfif structKeyExists(arguments, "passwordHash")>

  , passwordHash = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.passwordHash#" />

  </cfif>

  WHERE UsersID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usersID#" />

  </cfquery>

  <cfreturn true />

</cffunction>

In the book, the author shows a similar method however instead of passing individual arguments, he suggests passing a single bean object to the method and then updating the data.  Here's an example:

<!--- UPDATE --->

<cffunction name="updateUser">

  <cfargument name="userBean" type="any" required="yes" />

  <!--- update the database --->

  <cfquery datasource="#application.dsn#">

  UPDATE Users

  SET DateModified = #createODBCDateTime(now())#

  , firstName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.userBean.getFirstName()#" />

  , lastName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.userBean.getLastName()#" />

  , emailAddress= <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.userBean.getemailAddress()#" />

  , passwordHash = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.userBean.getPasswordHash()#" />

  WHERE UsersID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.userBean.getUsersID()#" />

  </cfquery>

  <cfreturn true />

</cffunction>

While this makes sense because you can pass a single object as an argument instead of a bunch of arguments it doesn't make sense to me to update database columns for data that hasn't necessarily changed or that the calling service object wasn't designed to update (again like updating a password).

I've also seen discussion online for other languages where people seem to discourage writing methods with a lot of arguments but I haven't been able to figure out why.  My only guess is because many other languages don't allow for passing named argument collections (like CFML does) so you'd have to be very careful about the order you submit your arguments.

I'd love your opinion on whether creating a DAO object method that accepts a single bean object is better than a method requiring only the arguments that actually need updating.

Views

720

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jun 15, 2017 Jun 15, 2017

Copy link to clipboard

Copied

I've done it both ways and my recommendation on the "best" way: it depends. You have another thread going with a bean question and my "dirty" example would work here by only performing the update if dirty. You could go further in that hole by adding a dirty flag per field and building the update query based on only the fields that have change. Another option that simplifies things might be specific update function for single task like updateLastLogin which would only update a dateTime field. I'm not sure there is a one size fits all answer here.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jun 18, 2017 Jun 18, 2017

Copy link to clipboard

Copied

It's a trade-off. It depends on what you have at hand. If you have lemons, make lemonade, they say.

If you're working just with beans, then it may make sense to pass the beans. The method of passing the bean has the advantage of simplicity and versatility. There are fewer lines of code and the caller need know nothing about the arguments of the function. But this method has the disadvantage of redundancy, because it may update fields that require no updates.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Jun 24, 2017 Jun 24, 2017

Copy link to clipboard

Copied

Thanks @Steve and @BKBK, you both have great points.

After reading your responses I decided to scan StackOverflow to get opinions on how people tackle this in other languages (Java, C++, etc...). I discovered an interesting discussion where a developer was having a hard time with his/her DAO - specifically when updating data.

Basically the issue boiled down to a "race condition" where multiple users could be editing a single record at a time but editing different fields.  Passing the entire bean to the DAO could cause your web application to overwrite another user's changes to the bean object!  Yikes!

Timeline Example:

User 1 updates field A in the bean

User 2 updates field B in the bean

User 2 passes bean to DAO, DAO updates all fields.

User 1 passes bean to DAO, DAO updates all fields.

In the above example, User 2's updates to field B would be overwritten by user 1!

I couldn't find any concrete way to avoid this situation with standard CRUD operations in a DAO. Some users suggested locking or using some type of transnational control in the service layer but that was a little over my head.  However it got me wondering  if the bean object itself could be smart enough to return an object containing only the changed fields?  This could easily be accomplished with a small utility class that every bean could contain.

So for example, lets say you have a User bean, and you just want to update FirstName:

1. The bean object when instantiated would have a sub-class of utilities inside it I'm calling "BeanUtilities".

2. During the constructor the bean would run the backup() method from the utilities class to make a copy of all of the bean data and stores it in the instance.

3. The user runs the setFirstName("Steve") on the bean which changes the FirstName of the object from "Dave" To "Steve".

3. The user wants to then save the data via the service class which passes the data to the DAO.  The DAO object would know that all beans have a method called getChanges() which would spit out an object containing only the key/value pairs of fields that were changed in the bean.  The DAO could run this method and then update only the relevant fields.

ivT6sVj.png

EDIT: Note you could also have an argument somewhere which would "force" a full save overwriting all fields just in case the business logic demands it.

Perhaps this violates some concrete rule of OOP but at first glance this type of setup appears to let us have our cake and eat it too. I'd love to hear your thoughts on this approach.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jun 26, 2017 Jun 26, 2017

Copy link to clipboard

Copied

LATEST

I don't believe there is a concrete rule for this as it's really application or even instance specific of what the best choice is. Sometimes allowing one user to change the first name while another is updating the address is perfectly fine so you want the application to accommodate this. Other times the full name and address are treated as a single unit and the application may intentionally prevent isolated edits and return an error to one or both users. A better example of a single unit would be one person changing the street address while another is changing the city. The full address would/should be a single unit. So the the best answer may be: It depends.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Resources
Documentation