Home » Eclipse Projects » Eclipse Scout » Backend client bean and dependency injection in services
Backend client bean and dependency injection in services [message #1856823] |
Wed, 04 January 2023 14:04 |
Nils Israel Messages: 73 Registered: May 2010 |
Member |
|
|
Hi,
we are using a generated OpenAPI client (ApiClient) as a backend for our Scout 22 application. Since we want to transmit the
scout user id and the scout correlation id to the backend as additional http headers, we created a factory for our ApiClient class.
The idea is to let scout inject the ApiClient bean into the scout server services.
Since the ApiClient class is generated we can't annotate it with scout annotations. According to the documentation it is also possible to
define the bean metadata programmatically. So that is, what we've tried:
public class RegisterBeansListener implements IPlatformListener {
@Override
public void stateChanged(PlatformEvent event) {
if (event.getState() == IPlatform.State.BeanManagerPrepared) {
register(ApiClient.class, ApiClientProducer.class, false);
}
}
private void register(Class<?> clazz, Class<?> producerClazz, boolean isSingleton) {
IBeanInstanceProducer<?> producer = (IBeanInstanceProducer<?>) BEANS.get(producerClazz);
BeanMetaData beanData = new BeanMetaData(clazz)
.withProducer(producer)
.withApplicationScoped(isSingleton);
BEANS.getBeanManager().registerBean(beanData);
}
}
@ApplicationScoped
public class ApiClientProducer extends NonSingeltonBeanInstanceProducer<ApiClient> {
private static final Logger log = LoggerFactory.getLogger(ApiClientProducer.class);
private final ApiClientConfig apiClientConfig;
@InjectBean
public ApiClientProducer(ApiClientConfig apiClientConfig) {
this.apiClientConfig = apiClientConfig;
}
@Override
public ApiClient produce(IBean<ApiClient> bean) {
ApiClient apiClient = super.produce(bean);
apiClient.setBasePath(apiClientConfig.getBasePath());
apiClient.setUsername(apiClientConfig.getUsername());
apiClient.setPassword(apiClientConfig.getPassword());
var currentUserId = Sessions.getCurrentUserId();
apiClient.addDefaultHeader("x-frontend-username", currentUserId);
var cid = CorrelationId.CURRENT.get();
apiClient.addDefaultHeader("X-Request-ID", cid);
log.info("created API client for user: {} -> {}", currentUserId, apiClient);
return apiClient;
}
}
@ApplicationScoped
public class PriceGroupRestControllerApiAdapter implements RestControllerApiAdapter<PriceGroup> {
private final PriceGroupRestControllerApi api;
@InjectBean
public PriceGroupRestControllerApiAdapter(ApiClient apiClient) {
this.api = new PriceGroupRestControllerApi(apiClient);
}
@Override
public PriceGroup create(PriceGroup entity) throws ApiException {
return api.createPriceGroup(entity);
}
}
Now, we should get a new ApiClient for every "injection". Is this the recommended way to handle such a bean?
We had some trouble with incorrect user ids transmitted to the backend and couldn't figure out the cause of it.
I would be glad if someone can give me a hint or a best practice for this scenario.
Thanks and best regards
Nils
|
|
| | |
Re: Backend client bean and dependency injection in services [message #1856854 is a reply to message #1856851] |
Thu, 05 January 2023 16:05 |
Nils Israel Messages: 73 Registered: May 2010 |
Member |
|
|
Hi Mat,
we discussed this internally and now came up with another solution. Maybe it can help someone with a similiar problem:
We liked the idea of using the EntityRestControllerApiAdapters as Singleton. The PriceGroupRestControllerApiAdapter is an example of such a EntityRestControllerApiAdapter, which implement
the usual Scout CRUD service operations, like prepareCreate, create, update, delete by adapting it to the generated OpenAPI client.
So, instead of changing all the adapters to "NotSingleton", we made the ApiClient a Singleton as well and added the possibility to add headers via callback functions, in this case method references of type Supplier<String> in the context of the request.
Here is the result:
public class RegisterBeansListener implements IPlatformListener {
@Override
public void stateChanged(PlatformEvent event) {
if (event.getState() == IPlatform.State.BeanManagerPrepared) {
// changed isSingleton => true
register(ApiClient.class, ApiClientProducer.class, true);
}
}
private void register(Class<?> clazz, Class<?> producerClazz, boolean isSingleton) {
IBeanInstanceProducer<?> producer = (IBeanInstanceProducer<?>) BEANS.get(producerClazz);
BeanMetaData beanData = new BeanMetaData(clazz)
.withProducer(producer)
.withApplicationScoped(isSingleton);
BEANS.getBeanManager().registerBean(beanData);
}
}
@ApplicationScoped
// implement necessary Interface instead of extending the provided ProducerClasses
public class ApiClientProducer implements IBeanInstanceProducer<ApiClient> {
private static final Logger log = LoggerFactory.getLogger(ApiClientProducer.class);
private final ApiClientConfig apiClientConfig;
private final String userAgent;
@InjectBean
public ApiClientProducer(
ApiClientConfig apiClientConfig,
UserAgentFactory userAgentFactory
) {
this.apiClientConfig = apiClientConfig;
this.userAgent = userAgentFactory.create();
}
@Override
public ApiClient produce(IBean<ApiClient> bean) {
// created a new anonymous subclass of ApiClient with the additional methods
var apiClient = new ApiClient() {
// HashMap of the functions, which create the header values
private final Map<String, Supplier<String>> lazyHeaderSuppliers = new HashMap<>();
public void addLazyDefaultHeader(String header, Supplier<String> lazyHeaderValueSupplier) {
lazyHeaderSuppliers.put(header, lazyHeaderValueSupplier);
}
@Override
public <T> ApiResponse<T> invokeAPI(String operation,
String path,
String method,
List<Pair> queryParams,
Object body,
Map<String, String> headerParams,
Map<String, String> cookieParams,
Map<String, Object> formParams,
String accept,
String contentType,
String[] authNames,
GenericType<T> returnType,
boolean isBodyNullable) throws ApiException {
// call the functions to create the header values and put it into the provided hashmap
lazyHeaderSuppliers.keySet().forEach(header -> {
var headerValueSupplier = lazyHeaderSuppliers.get(header);
headerParams.put(header, headerValueSupplier.get());
});
// call super with the enriched HashMap for the header information
return super.invokeAPI(operation, path, method, queryParams, body, headerParams, cookieParams,
formParams, accept, contentType, authNames, returnType, isBodyNullable);
}
};
apiClient.setBasePath(apiClientConfig.getBasePath());
apiClient.setUsername(apiClientConfig.getUsername());
apiClient.setPassword(apiClientConfig.getPassword());
// use the method reference Sessions::getCurrentUserId as a Supplier<String> for the header
// note that the method is executed in the context of the call to ApiClient.invokeApi and therefore returns the correct userId
apiClient.addLazyDefaultHeader("x-frontend-username", Sessions::getCurrentUserId);
apiClient.addLazyDefaultHeader("X-Request-ID", CorrelationId.CURRENT::get);
apiClient.setUserAgent(userAgent);
return apiClient;
}
}
// unchanged
@ApplicationScoped
public class PriceGroupRestControllerApiAdapter implements RestControllerApiAdapter<PriceGroup> {
private final PriceGroupRestControllerApi api;
@InjectBean
public PriceGroupRestControllerApiAdapter(ApiClient apiClient) {
this.api = new PriceGroupRestControllerApi(apiClient);
}
@Override
public PriceGroup create(PriceGroup entity) throws ApiException {
return api.createPriceGroup(entity);
}
}
Best regards
Nils
|
|
| |
Goto Forum:
Current Time: Tue Nov 05 23:39:53 GMT 2024
Powered by FUDForum. Page generated in 0.07775 seconds
|