Session based transactions with external services :
And how Singleton Design Pattern solves that efficiently
How would you typically structure your code when you have to do multiple session id/token based operations with an external service from your Apex code?
Would you establish a session with the external service just once which is sufficient enough to carry out all the rest of the transactions within the user session?
Would you create a session every time you need to transact with the external service?
It is obviously wise to create the session once and use it until expires. Okay, so do I mean that we just have to use the same session id within a single user transaction? That is easy. Isn’t it? We can keep the session id in a global variable and use it throughout the user session.WIn that case, the code would something similar to below:
In the above code sell() and buy() are actual operations which are dependent on sessionId and we are getting that via authenticate() method. Observe that the code checks whether sessionId is null or not before doing authenticate() call because each of these methods are unaware whether the authentication() was called earlier to retrieve sessionId or not. So, tomorrow if I have to add one more method to do exchange transaction, I will write exchange() method but I will still check if the sessionId is null or not before calling authenticate(). Now, It started sounding like an inefficient way to handle the stuff to you. Isn’t it? Yes, it is an inefficient way and with a little more reasoning we will confirm it.
To do sell() or buy() or any other operations which I want to add in future, it is required to have session Id acquired prior. So, this is simply a default operation which should be performed just when the container class i.e. TradingService was first loaded/instantiated and get the required context setup before doing the actual operation are performed. As in this case, the required context is to have session Id ready. The above approach clearly fails there. In other words, I would want to just instantiate TradingService and forget about user’s session when I am writing code for actual operations i..e sell() or buy(). Now, isn’t that a leap towards efficient code?
Even forget about setting up context etc., what if a new developer comes in after I coded sell() and buy() and adds exchange() without the if-check on session Id? That will result fetching session Id twice in case the user performs exchange() after buy() which is not required because the session Id has already been fetched and could be reused. You might catch it in code review but that still ain’t the code that protects itself. Worse, if it is missed in code review and the code becomes not just inefficient but results poorly performance too. It can breach governor limits too.
Forget even the above scenario, someone just made the access modifier of authenticate() as public. Now, you lost all the control of managing a user session. Any novice developer might just leverage that loophole and mess up with code.
What’s the solution for this?
Let me introduce you to simple yet mechanism to deal with this mess called ‘Singleton Design Pattern’.
Code in singleton design pattern as below:
Let’s find out how the above is different and more efficient. Start with the constructor of TradingService class. It is a private constructor which means any external consumer entity can not instantiate this class at all. The total control of creating and giving the instance of TradingService class is with the TradingService class itself. The private constructor also handles the code to fetch and keep the sessionId value from external service. Clean encapsulation. Isn’t it?
And then, we coded a public static method getInstance() that will give you the reference to instance of TradingService which is coded within the class. The if-check in getInstance() ensures that session Id is fetched just once per user session. This is also called lazy-initialization.
So, with the Singleton design pattern we solved the problem of repeated reference to an object instance in an optimal manner.
Reference(s): Salesforce : Apex Design Patterns