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?
Solved! Go to Solution.
Views
Replies
Total Likes
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.
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.
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.
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.
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.
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.
Views
Likes
Replies
Views
Likes
Replies