Problem publishing spring application events in CUBA app

Hi,

i’ve a question with a sample project i created here: https://github.com/mariodavid/cuba-example-application-events

What i want to achieve is to get a more decoupled business logic architecture through an event system. Therefore i used Spring’s application event system to publish events in the system and catch it in other parts of the system to perform any kind of actions.

Generally speaking, it works very well, but i have some gotchas, where i’m not sure how to resolve them. First of all here’s a brief description of the classes responsible for doing the different parts:
Entities: Order, Customer
Entity Listener: CustomerCreatedEntityListener, OrderCreatedEventListener (turn DB events into application events)
Application Events: CustomerCreatedEvent, OrderCreatedEvent
Application Event Handler: CustomerUserCreator, OrderCreatedLogger
Application Event Producer: ApplicationEventProducerService

Basically i created an event when a customer is created. In the entityListener for Customer i publish a CustomerCreatedEvent that will be catched from the CustomerUserCreator, that will use the customer information to create a corresponding user for this customer in the system (asynchronously). Everything works well here in this scenario, except that in the logs of the application occur the following errors / warnings:

2016-10-02 13:27:41.203 ERROR [http-nio-8080-exec-29/app-core/admin] com.haulmont.cuba.core.sys.ServiceInterceptor - Invoking void com.company.ceae.service.ApplicationEventProducerService.produceApplicationEvent(Object) from another service
2016-10-02 13:27:41.265 WARN  [http-nio-8080-exec-29/app-core/admin] com.company.ceae.listener.application.CustomerUserCreator - The customer was created...
2016-10-02 13:27:41.572 WARN  [http-nio-8080-exec-29/app-core/admin] com.company.ceae.listener.application.CustomerUserCreator - The user Nay was created...
2016-10-02 13:27:41.572 WARN  [http-nio-8080-exec-29/app-core/admin] com.haulmont.cuba.core.sys.ServiceInterceptor - Open transaction left in ApplicationEventProducerService.produceApplicationEvent(..)

Nevertheless, everthing worked out fine -but i’m not really sure what this message (esp. error) is going to tell me.

Next thing i tried is to do the same for Order creation. In this case OrderCreatedEventListener catches the DB event and publishes a OrderCreatedEvent that will be catched from OrderCreatedLogger which will just log the information with the source of event creation. This works as well.

What doesn’t seem to work is, that if i want to publish events from the frontend. In this case i added a postCommit method to the OrderEdit that should additionally publish the OrderCreatedEvent. Unfortunately it will not get picked up from the OrderCreatedLogger. I think the reason is that the other stuff is happening all on the middleware layer, but this one not. But, actually the OrderEdit just calls ApplicationEventProducerService, so the execution of the event creation through Spring is done on the middleware (since ApplicationEventProducerServiceBean is in the core module).

I think i misunderstand something here regarding what can be executed when etc. Actually i don’t think it’s a problem directly related to CUBA, but i thought you might have an opinion on that anyway.

I think using application events are a really cool way of decomposing the application components dependencies structure, so i’d really like to see this working properly.

I would appreciate your help.

Bye,
Mario

Hi Mario,
The error message in the log states that you invoke a service from another service. This is really the case, the execution chain is something like this: client > DataService > DataManager > entity listener > ApplicationEventProducerService. And services are designed to handle client requests only, this is explained here: https://doc.cuba-platform.com/manual-6.2/service_creation.html. So to avoid this error message and possible problems just decompose your service to a managed bean for calling from entity listener and a service (wrapper) for calling from the client.

As for publishing events from the client, the only difference that I see is that the event object from the client is deserialized and actually passed by value, while the event from the entity listener is passed by reference. I would recommend debugging the code step-by-step to find out the reason.

Hi Konstantin,

thanks for the link. I once read about the 2nd level invocation of a service, but could not remember properly.
Besides the statement in the docs:

Services are only intended for calling from outside of Middleware. Do not call service methods from other components of the middle tier. An error message is logged upon detection of a service call from another service.

and the corresponding check in the ServiceInterceptor class: https://github.com/cuba-platform/cuba/blob/518b0a83cc535eabf59dd3f62cc303ff934dc3ca/modules/core/src/com/haulmont/cuba/core/sys/ServiceInterceptor.java#L64 i couldn’t really figure out why it is not allowed… Can you elaborate a little bit on the reason for it?

I’ll give another try why it’s not publishing the event properly. I mean, i could probably make a service call like this in the OrderEdit:

        applicationEventProducerService.productOrderCreatedEvent(order: item, source: OrderEdit.getSimpleName())

instead of:

        applicationEventProducerService.produceApplicationEvent(new OrderCreatedEvent(order: item, source: OrderEdit.getSimpleName()))

which will eliminate the difference with “call by reference / value”.

Generally, i’d be interested if you have used an internal event system internally and if so, what’s are your thoughts on that.

Hi Mario,
Your question about why we restrict invocation of services from inside middleware prompted me to remove this restriction. It was introduced because of our view that services form an “application boundary” and it is not good to mix them with other beans from the architectural point of view. Besides, there was a technical aspect with transforming and logging exceptions in ServiceInterceptor.
Time has shown that such non-obvious restriction constantly creates questions (you are not the first) and now I think that simpler is better and let developers decide how to call their business methods. So there is no more restriction in 6.3, ServiceInterceptor understands how it was called and behaves appropriately.
Thank you for the hint!

As for an event system, we extensively use Guava EventBus in Studio. For the platform, I don’t think we should propose something out-of-the-box, developers can choose any third-party mechanism if needed.

Hi Konstantin,

i took a little closer look at this topic and found out that my original implementation using @Asnyc was just not executed asynchronously.

So i looked a little bit around this topic and found the general CUBA docs on this topic: https://doc.cuba-platform.com/manual-6.4/scheduled_tasks_spring.html

Especially to make this thing async i found an example in the spring docs. I did this but it turned out, that it is in fact asyncronous, but the security context is missing. It is the exact same problem as stated here:


2017-03-13 19:53:05.687 ERROR [scheduler-7] org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler - Unexpected error occurred invoking async method 'public void com.company.ceae.listener.application.CustomerCreatedUserNotificator.handleCustomerCreated(com.company.ceae.event.CustomerCreatedEvent)'.
java.lang.SecurityException: No security context bound to the current thread
 at com.haulmont.cuba.core.sys.AppContext.getSecurityContextNN(AppContext.java:148) ~[cuba-global-6.4.2.jar:6.4.2]

I also tried to use the CUBA bean for scheduling (i hope this was at all appropriate) but the error remains the same.
Here’s the complete spring.xml.

When trying to apply the idea from the other question:


// your web service method
Configuration configuration = AppBeans.get(Configuration.class);
WebAuthConfig webAuthConfig = configuration.getConfig(WebAuthConfig.class);
LoginService loginService = AppBeans.get(LoginService.class);
UserSession userSession = loginService.getSystemSession(webAuthConfig.getTrustedClientPassword());
try {
    AppContext.setSecurityContext(new SecurityContext(userSession));
    // call middleware services here
} finally {
    AppContext.setSecurityContext(null);
}

I could not create a reference to WebAuthConfig because there is a missing dependency to cuba-web-auth (my application event listener is in core).

Can you give me a hint on how to resolve the issue?

Bye
Mario

Sorry,

just resolved it myself with:



    @Inject
    Authentication authentication

    //...

    authentication.begin()
        try {
            createNotificationsForCustomerCreatedEvent(event.customer)
        }
        finally {
            authentication.end()
        }

@see https://github.com/mariodavid/cuba-example-application-events/blob/master/modules/core/src/com/company/ceae/listener/application/CustomerCreatedUserNotificator.groovy#L51

Bye
Mario

Hi Mario,

Thank you for posting, I’ve just added it to the docs.

I am doing exactly this and getting a null pointer exception on line authentication.begin(). I referred your example as well for this. Putting a debug point there hangs the application forever.

Can you please provide a hint what I am missing