We are migrating our API Gateway application from Netflix Zuul library to Spring Cloud Gateway framework. We are migrating one of the custom global pre-filter which deals with retrieving auth token from another microservice/API. This auth token has expiry time associated with it. The token in refreshed once it gets expired or idle beyond its limited duration. The existing code is written in imperative style using synchronized block as follows so that the retrieved auth token is cached locally and third party is invoked only one time for a valid auth token:

class AuthTokenManager {
    private volatile String authToken;

    private final Connector connector;
   
    public AuthTokenManager(final Connector connector) {
        this.connector = connector;
    }

    public String getAuthToken() {
       if (StringUtils.isBlank(authToken) || isAuthTokenExpired()) {
           fetchAuthTokenFromApi();
       }
       return this.authToken;
    }

    public void fetchAuthTokenFromApi() {
        final String oldAuthToken = this.authToken;
        if(oldAuthToken.equals(this.authToken)) {
           synchronized (this) {
               if(oldAuthToken.equals(this.authToken)) {
                   this.authToken = this.connector.generateAuthToken(userName, password);
               }        
           }
        }
    }

    private boolean isAuthTokenExpired() {
        return isExpired() || isIdle();
    }

    private boolean isExpired() {
       // token age exceeded expiry timeout
    }

    private boolean isIdle() {
       // token access time from now has exceeded idle timeout
    }
}

We tried to migrate above code in reactive paradigm using synchronized block as follows:

    public Mono<String> fetchAuthTokenFromApi() {
         final String oldAuthToken = this.authToken;
         if(oldAuthToken.equals(this.authToken)) {
           synchronized (this) {
               if(oldAuthToken.equals(this.authToken)) {
                   this.connector.generateAuthToken(username, password)
                     .map(token ->  {
                         this.authToken = token;
                         return this.authToken;
                   }).cache(Duration.ofSeconds(expiryTimeout));
               }
         }
         return Mono.create(consumer -> consumer.success(this.authToken));
    }

Above code works fine in case of single request/thread, however in case of concurrent threads, each request is calling third party API to retrieve authToken and does not uses cached authToken.

I have read multiple articles on this issue and found that most of the suggestion is around using Mono.cache() to cache the token. I tried with that also however still the issue occurs in case of concurrent requests.

How can we write synchronized critical section mutuable code in reactive paradigm so that third party API is invoked only once and result is cached and other requests uses cached token? Any lead will be of great help.

0

There are 0 best solutions below