Expand my Community achievements bar.

SOLVED

Static services, OSGi, and reducing unnecessary API calls- what's the Java best practice here?

Avatar

Level 5

I have a component which is fetching data from a rest API.  This data changes infrequently (at most once per week, as little as once per year), but it's required to be present on every single page within my website.

 

I have created it like so:

 

 

 

/* Simplified for illustration purposes */

@Model(adaptables = SlingHttpServletRequest.class,
       adapters = MyComponentInterface.class,
       resourceType = "...",
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class MyComponent implements MyComponentInterface {
    private ExternalData externalData;

    @OSGiService
    ExternalDataService externalDataService;

    @PostConstruct
    protected void init() {
        this.externalData = externalDataService.getExternalData();
    }

    // other properties and functions as necessary
}

 

 

 

And ExternalDataService looks like this:

 

 

 

@component(service = ExternalDataService.class, immediate = true)
public class ExternalDataServiceImpl implements ExternalDataService {
    
    public ExternalData getExternalData() {
        ExternalData retVal = FetchDataFromApi();
        return retVal;
    }

    private ExternalData FetchDataFromApi() {
        // makes API calls, unmarshall's response, etc
    }

    // other functions, properties, etc
}

 

 

 

At this point, everything is wired up and working correctly.  However, as my content authors are editing pages, and as visitors are visiting the website, I'm afraid that my API is getting hammered with too many requests.  I want to instead only fetch data from the API periodically, and cache the rest so that I'm not re-making the same call over and over again.

 

To do this, I brought Caffeine into my project and wired it into the ExternalDataServiceImpl.  However, I need the cache to be static (e.g., utilize the same cache for all instances of the component across pages).

 

 

 

@component(service = ExternalDataService.class, immediate = true)
public class ExternalDataServiceImpl implements ExternalDataService {
    private static Cache<String, ExternalData> cache;

    public ExternalDataServiceImpl() {
        if (cache == null) {
            cache = Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .maximumSize(100)
                .build();
        }
    }

    
    public ExternalData getExternalData() {
        String key = "external-data";

        ExternalData retVal = cache.getIfPresent(key);

        if (retVal == null) { // cache miss, fetch from API
            retVal = FetchDataFromApi();

            cache.put(key, retVal);
        }

        return retVal;
    }

    private ExternalData FetchDataFromApi() {
        // makes API calls, unmarshall's response, etc
    }

    // other functions, properties, etc
}

 

 

 

In local testing, this works.  The cache is persisted across page loads and my API is only hit occasionally as the cached value expires.

 

My question is: Is there a better way to do this, will a static property in OSGi become an issue, and is there a way I can register Caffeine as a static service in OSGi?

 

1 Accepted Solution

Avatar

Correct answer by
Employee Advisor

 

OSGI services are singletons (unless you are using Service Factories, which you don't do here),so you don't have to make the cache a static property.

I don't know Caffeine, but I would wonder if it registers a cache as service out-of-the-box. Your approach feels sound to me.

View solution in original post

5 Replies

Avatar

Community Advisor

Instead of this approch why can't you use the scheduler to run the API call periodically and store that response in any custom node.

Create one more component which reads the data from the the custom node.

This will make sure you use the data from custom node and scheduler plays role to call the API every week/or anytime you wanted.

https://aem.redquark.org/2018/10/day-13-schedulers-in-aem.html

Refer this for writing the custom scheduler.

Avatar

Community Advisor

As you mentioned data frequency is very less (less changes in year) then you can pull data weekly(as you mentioned atmost once per week) from api and push to AEM crx via scheduler. Fix your cron job for weekly. Then you can use these data.

There is no need to call api everytime.

https://medium.com/@toimrank/aem-scheduler-258eef7a7b5

OR

You can cache api and data for a week. After  a week it will call external api and cache it again.

Avatar

Community Advisor

You can write a servlet which makes the API call and return the response. You can cache the servlet response and periodically clear by using acs-commons Dispatcher flush rule. You can make XHR call to servlet from the component and use the API response.

Avatar

Correct answer by
Employee Advisor

 

OSGI services are singletons (unless you are using Service Factories, which you don't do here),so you don't have to make the cache a static property.

I don't know Caffeine, but I would wonder if it registers a cache as service out-of-the-box. Your approach feels sound to me.

Avatar

Level 5

Thanks for confirming. This is also the conclusion I reached after reading this thread: Solved: Re: how to make a singleton and how to store state... - Adobe Experience League Community - ...

Basically- I have removed the static modifier on the cache property. In both debugging and checking the logs, I can confirm that I'm hitting the cache after the first request and my service is indeed a singleton in memory being re-used as needed.