Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

CDI events

When a post appears in the blog, several things need doing: notify subscribers, update the search index, send emails. If the publishing service calls all of them, it has to know about every one of them and keeps growing. CDI events solve this differently: the service only announces "a post was created", and everyone interested subscribes on their own.

The event

An event is an ordinary object with data. A record describes it nicely:

// src/main/java/com/example/blog/PostCreated.java
package com.example.blog;
 
public record PostCreated(long id, String title) {
}

Firing

The service gets an Event<PostCreated> object and calls fire. Update src/main/java/com/example/blog/PostService.java:

// src/main/java/com/example/blog/PostService.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;
 
@ApplicationScoped
public class PostService {
 
    @Inject
    PostStore store;
 
    @Inject
    Event<PostCreated> postCreated;
 
    public Post publish(NewPost data) {
        Post post = store.add(data);
        postCreated.fire(new PostCreated(post.id(), post.title()));
        return post;
    }
}

Here the dependencies are injected into fields — it is shorter, and no no-argument constructor is needed.

Observers

An observer is a bean method whose parameter is marked @Observes:

// src/main/java/com/example/blog/NotificationService.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import java.util.logging.Logger;
 
@ApplicationScoped
public class NotificationService {
 
    private static final Logger LOG = Logger.getLogger(NotificationService.class.getName());
 
    void onPostCreated(@Observes PostCreated event) {
        LOG.info("Notifying subscribers about the post: " + event.title());
    }
}

A second observer of the same event knows nothing about the first:

// src/main/java/com/example/blog/SearchIndex.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import java.util.logging.Logger;
 
@ApplicationScoped
public class SearchIndex {
 
    private static final Logger LOG = Logger.getLogger(SearchIndex.class.getName());
 
    void onPostCreated(@Observes PostCreated event) {
        LOG.info("Adding post " + event.id() + " to the search index");
    }
}

Let's create a post:

curl -H "Content-Type: application/json" -d '{"title":"CDI events","body":"Fire and observe."}' http://localhost:8080/blog/api/posts

target/liberty/wlp/usr/servers/defaultServer/logs/messages.log gets these lines (date shortened):

com.example.blog.NotificationService  I Notifying subscribers about the post: CDI events
com.example.blog.SearchIndex          I Adding post 3 to the search index

fire calls the observers right away, in the same thread: the response goes to the client only after all of them finish.

Asynchronous events

Long work, such as sending emails, is better kept out of the request. The fireAsync method sends the event to another thread, and methods with @ObservesAsync receive it:

// src/main/java/com/example/blog/EmailSender.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.ObservesAsync;
import java.util.logging.Logger;
 
@ApplicationScoped
public class EmailSender {
 
    private static final Logger LOG = Logger.getLogger(EmailSender.class.getName());
 
    void onPostCreated(@ObservesAsync PostCreated event) {
        LOG.info("Sending emails about post " + event.id() + " in thread "
                + Thread.currentThread().getName());
    }
}

In PostService, after fire add postCreated.fireAsync(event), keeping the event in an event variable. The log gets a line from another thread:

com.example.blog.EmailSender  I Sending emails about post 3 in thread Default Executor-thread-101
FiringObserverThread
fire(event)@ObservesThe same, the response waits
fireAsync(event)@ObservesAsyncAnother, the response does not wait

Common mistake

Firing an event with fireAsync but marking the observer @Observes, or the other way round. There is no error — the method is simply never called. Synchronous and asynchronous observers do not mix: if an event must reach both, call both fire and fireAsync, as in the example above.

Thanks for reading!

Donate to the site — we work hard for you.