So I implemented a callout to an external service where first we should validate that our token wasn't expired. So I implemented a refresh token method, that updated our custom setting where we stored that info.
So here's the thing, since I wanted to avoid the uncommited work pending I moved the dml to update the Custom setting to the end of my code.
Months after setting this up and explaining to another peer how I did it I realized my code shouldn't be working at all. The modification of my custom setting wasn't being neither passed as a parameter or stored in database, but when I accessed it, it was updated.
Here's the code :
Class 1:
@future(callout=true)
public static void integrate(Id accId){
Account a = [IRRELEVANT QUERY];
Quiter_Token__c tokenT = QuiterTokenBearer.getToken();
if(String.isBlank(a.OM_VCAR_ExternalId__c)){
QuiterWS.sendAccountsCreate(a,tokenT.token__c);
}
else{
QuiterWS.sendAccountsUpdate(a,tokenT.token__c);
}
update tokenT;
}
Class QuiterTokenBearer:
public static Quiter_Token__c getToken(){
/*
* Returns the token for authentification to Quiter, either using the last token retrieved if it's still valid or requesting a new one
*/
if(isTokenExpired()){
refreshToken();
}
Quiter_Token__c token = Quiter_Token__c.getValues('Quiter Token');
return token;
}
public static void refreshToken() {
/*
* Retrieves a refreshed token and store it, alongside its expiration window and datime of reception on Quiter_Token__c
*/
Quiter_Token__c token = Quiter_Token__c.getValues('Quiter Token');
System.debug('Token : ' + token);
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(token.Url__c);
request.setHeader('Accept','application/json');
String payload = 'client_id='+EncodingUtil.urlEncode(token.Client_ID__c,'UTF-8')+'&client_secret='+EncodingUtil.urlEncode(token.Secret__c,'UTF-8')+'&code='+EncodingUtil.urlEncode(token.Code__c,'UTF-8')+'&grant_type=authorization_code';
request.setBody(payload);
request.setMethod('POST');
HttpResponse response = new HttpResponse();
if(!Test.isRunningTest()){
response = http.send(request);
}else{
response = QuiterWSMock.respond(request);
}
if (response.getStatusCode() == 200) {
System.debug(response.getBody());
Map<String, Object> json = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
token.Token__c = (String)json.get('access_token');
token.Expires_In__c = (Decimal) json.get('expires_in');
token.Last_Token_Time__c = system.now();
}
}
public static Boolean isTokenExpired(){
/*
* This function returns false if System.Now - Last time token was retrieved is greater than the token expiration window and true otherwise
*/
Quiter_Token__c token = Quiter_Token__c.getValues('Quiter Token');
if(token.Token__c!=null && token.Last_Token_Time__c != null && token.Expires_in__c !=null){
System.debug((System.now().getTime()-token.Last_Token_Time__c.getTime())/1000 > token.Expires_in__c);
return (System.now().getTime()-token.Last_Token_Time__c.getTime())/1000 > token.Expires_in__c;
}
return true;
}
If you check the function refreshToken
you see the custom setting gets modified with the received values on the response of the callout, but never gets passed to getToken
, but still when we retrieve the values again from the custom setting the values ARE THERE.
I also tried this setting a system.debug just after
Quiter_Token__c tokenT = QuiterTokenBearer.getToken();
on integrate
function, and , yes, the value is stored across the whole Transaction, but only stored in memory if the dml is performed.
I searched information about this but couldn't find anything that adresses custom settings behaving different than Standard/Custom Objects in this matter. If more information needs to be clarified please, ask on the comments and I'll try to provide more context as required.
Why is this code working?
Quiter_Token__c
settings returned fromQuiter_Token__c.getValues('Quiter Token');
inrefreshToken
. I'm guessing that the in-memory cache of this is directly updated, so all later calls get this same cached in-memory object, that includes your changes.update
on CS as an SObject is not documented at all. Updating through UI and its effect on in-memory cache would be an interesting experiment. Bug or not, if you can switch to Custom Metadata, you'd be better off.