My team has a Lagom microservice which exposes a resource modeled using HAL+JSON, i.e. we expose links to related resources from our resource. We let our clients set some of these links. We would like to crawl some of these links in our service to get related information, mostly in read side processors, so our service is not synchronously coupled to the external service.
Because the link is dynamically set per-resource, we don’t think we can use an injected ServiceClient. I think we possibly could use the LagomClientFactory, but we’re hesitant to do so because it seems like it would create an entire client instance per invocation, which seems heavy. We think we can use the underlying WSClient. Would the WSClient be a suggested approach here for making dynamic calls to services if their url is not know prior to the invocation? Are we overthinking avoiding the LagomClientFactory for this?
We actually have built this using the underlying WSClient, and it worked well for some time. However, we ran into an issue recently when we introduced a separate injected ServiceClient which happened to use the same url as some of the links in our resources. We started (intermittently, sometimes hours, sometimes days apart) to see some exceptions being thrown by the WSClient specifically when we call the url that the injected service client also uses. Somehow, whenever the exception is thrown, a Read Side processor silently stops executing, like it is deadlocked. So we are curious if it is intended to be safe to directly use both of these interfaces in tandem.
We are on Lagom 1.4.8. Exception trace (some links to source code to help me better understand what requirement has failed):
Caused by: java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:212)
at scala.concurrent.BatchingExecutor$Batch.run(BatchingExecutor.scala:51)
at scala.concurrent.Future$InternalCallbackExecutor$.unbatchedExecute(Future.scala:601)
at scala.concurrent.BatchingExecutor$Batch.blockOn(BatchingExecutor.scala:92)
at scala.concurrent.Await$.result(package.scala:190)
at play.api.libs.ws.ahc.cache.CachingAsyncHttpClient.execute(CachingAsyncHttpClient.scala:74)
at play.api.libs.ws.ahc.cache.CachingAsyncHttpClient.executeRequest(CachingAsyncHttpClient.scala:54)
at play.libs.ws.ahc.StandaloneAhcWSClient.execute(StandaloneAhcWSClient.java:86)
at play.libs.ws.ahc.StandaloneAhcWSRequest.lambda$execute$1(StandaloneAhcWSRequest.java:373)
at play.libs.ws.ahc.StandaloneAhcWSRequest.execute(StandaloneAhcWSRequest.java:375)
at play.libs.ws.ahc.StandaloneAhcWSRequest.execute(StandaloneAhcWSRequest.java:365)
at play.libs.ws.ahc.StandaloneAhcWSRequest.get(StandaloneAhcWSRequest.java:330)
at play.libs.ws.ahc.AhcWSRequest.get(AhcWSRequest.java:56)
Looking at this exception, it seems like we are sharing an instance of something that isn’t intended to be shared, because we broke the BatchingExecutor’s execution invariant. But I’m not the most proficient in Scala, so I could be far off base.
We are going to try removing this injected ServiceClient and just use the WSClient for these calls to see if using the two in tandem is causing some sort of locked resource, because we know in the past we had multiple threads utilizing the WSClient concurrently without problems.