Identifying and eliminating double instantiation of objects and single-synchronous-request 'caching'
'Logic Busters' is a continuation of the 'Schema Busters' series with an emphasis on business-logic practices that you can implement when engineering your web-applications. The concepts discussed in this series will differ from the previous in that they are geared towards programming practices and tips as opposed to database schema and query design. In this first section we will discuss the concept of singletons, how to implement singletons, and efficiency gains from the utilization of singletons. Since singletons deal heavily with object management and objects have a tendency to be reflective of database entities I hope this subject will serve as a proper transition from our database studies into the realm of application logic.
What is a Singleton?
The singleton is a mathematical concept that simply states any instance of an object must be equivalent to itself and is universally unique from all other objects. If there were another object with the exact same properties it would be one and the same as the original. In programming it is quite possible and easy to violate the Singleton concept. Imagine instantiating two copies of an object and performing separate and distinct operations on them effectively make them unequal. This in turn destroys any possibility of maintaining object singularity.
Why use the Singleton pattern?
Before continuing let's analyze the benefits and costs of such behaviors. Obviously being able to manipulate two copies of the same object independently provides great flexibility, but at what cost? An immediate concern rises in the situation of multithreaded or asynchronous environments (multitasking in laymen's terms), where data integrity and symmetry can prove vital. This issue is best illustrated by looking at a concept called conflicting transactions which is more commonly associated with database transactions. Imagine that we have the following database table:
tblUseruserId login password email active
...and the following two transactions where the value of '@parameter-name-here' is passed at transaction call:
Name:Trans_UpdateUserIdDescription:Updates the user idQuery:UPDATE tblUser SET userId = @userId WHERE userLogin = @userLogin
Name:Trans_UpdateUserEmailDescription:Updates the user emailQuery:UPDATE tblUser SET email = @email WHERE userId = @userId
If we are to run these two transactions independent of each other on a user with the below described data, we now have three scenarios:
User:1. userId = 12. login = 'JGFree'3. password = 'secret'4. email = 'firstname.lastname@example.org'
Transaction Scenario 1:1. Trans_UpdateUserId is run with @login = 'JGFree' and @userId = 22. Trans_UpdateUserId finishes3. Trans_UpdateUserEmail is run with @userId = 1 and @email = 'email@example.com'4. Trans_UpdateUserEmail finishes5. Final state of user:userId = 2, login = 'JGFree', password = 'secret', email = 'firstname.lastname@example.org'
Transaction Scenario 2:1. Trans_UpdateUserEmail is run with @userId = 1 and @email = 'email@example.com'2. Trans_UpdateUserEmail finishes3. Trans_UpdateUserId is run with @login = 'JGFree' and @userId = 24. Trans_UpdateUserId finishes5. Final state of user:userId = 2, login = 'JGFree', password = 'secret', email = 'firstname.lastname@example.org'
Transaction Scenario 3:1. Trans_UpdateUserId is run with @login = 'JGFree' and @userId = 22. Trans_UpdateUserEmail is run with @userId = 1 and @email = 'email@example.com'3. Inconsistent behavior from here on out since both transactions will try to access same record at the same time.4. Note that this inconsistency can also be caused by switching 1 and 2 of this scenario.
For the sake of our discussion we will disregard Transaction Scenario 3 since this kind of conflict is more appropriately addressed by Database Management Systems and Memory Access Systems and how they handle this kind of simultaneous access conflict, a concept that is not pertinent for us at this moment. The reason I use these transactions is to show that two copies of the same object, like the above user with the above described properties, subjected to two different transaction scenarios can result in different object states.Now we should stop and consider that this behavior is either desirable or undesirable depending on your project's technical specifications. If it happens to be desirable then we can disregard this entire study and get back to work (you time-thieving, blog-trolling slacker). Otherwise we can begin the implementation of Singletons to make sure that object state is maintained across multiple transactions.
Implementing Singleton objects and how they work
Implementing a Singleton requires little additional coding, but a strong consideration for consistency. The basic concept is to statically store an instance of whatever object we are creating a singleton out of, and upon redundant load calls we simply reference the static singleton object. Most object-oriented classes have a constructor or instantiate method that loads all of the properties of an object based on its identifier. When initially called, we should check if the identifier matches that of our singleton object. If so we return the singleton object. Otherwise we continue loading the new instance and replace our singleton object with the resultant object. In a similar manner, every time we make a change to our object we must also update the singleton.
Immediately we can see how this kind of structure can reduce our database calls significantly. Imagine that we load the same instance of an object three times throughout the span of a request. Utilizing the singleton concept we now reduce our database use by 66%. We can expand upon this idea by allowing for Singleton Dictionaries which is simply a static collection of objects mapped by their identifier. If we are loading all users three times throughout the span of a request, we are now producing significant traffic (dependent on your user pool obviously). Since it is semi-difficult to include code in this blog and the code I would provide is available elsewhere, I will simply refer those interested to sites already containing examples of singleton use:
Now hopefully your program does not require you to load every single user three distinct times in a single request. Singletons are extremely situational and are not your primary means of reducing resource use. An obvious place to implement a singleton would be for managing the object currently logged in since it often gets referenced multiple times in a page load. Look for these kinds of situations to implement this data structure.
Looking back at our example where we performed asynchronous transactions on a non-singleton object yielding different possible results, we can now see how a singleton would enforce the legitimacy of both transactions. Each transaction would update the singleton which would then be the result of both transactions as opposed to any kind of deviation produced by the order of transaction operation. Transaction 1 will now produce the same results as Transaction 2 since the userId is consistent for both transactions, Trans_UpdateUserEmail and Trans_UpdateUserId.
Singletons are a great data structure to consider when tweaking your application to maximize performance. They are not the answer to all of your resource management needs, but they serve their purpose beautifully. If you enjoyed learning about this data structure and how it can be applied, I encourage you to seek out other data structures as they have the potential to improve your site/application performance and reduce the complexity of your code. I recommend beginning with the 'Current User' example I provided above, and then reevaluate your program to see where this kind of technology makes sense.