We have a ruby rails front end and distributed back end services running batch jobs via Redis.
The front end will acquire the initial Xero oauth2 token.
The token will expire in 30 minutes, the refresh token lasts 60 days (great), but can only be used once.
When multiple back end processes try to access Xero and the token has expired, only one can succeed all others will fail because the refresh token is single use.
Database transaction boundaries make sharing the token via the Database impractical.
I’ve considered the following solutions (I don’t like any of them):
- A singleton to handle all xero interactions.
- Wrap xero in a retry process and check Redis for tokens that may have
been updated by another process. - Making the user go through oauth2 login repeatedly to obtain a token
for each back end process.
Unfortunately I’m new to both Ruby and Redis so specifics on implementation in this environment would be very useful.
2
Answers
So this is the approach I took.
When fetching the token from the database or refreshing the token: Do it on a new thread which ensures active record uses a separate database connection. This ensures that database transactions don't interfere with seeing the latest version of the token.
I would highly recommend that you don’t enqueue the jobs with the actual token credentials as metadata passed to Redis.
Your access_token and refresh_token should be stored in just a single place. When you enqueue a job, you should be passing it only a user_id, in which case when it was processed it would look up the user.token_set and then pass in that user.token_set[‘access_token’] to the xero_client (or however you have it setup) with the xero-ruby SDK.
When you re-fresh the access_token, a new token_set is returned, including a new refresh_token. Rinse and repeat.
This is a bit confusing to me. What processes are the background jobs doing? Are they not processing data and reading/writing it to the same database you would be storing that token_set in? Are they strictly calling to non app level db/apis?