I am working on a Spring-MVC application and thanks to users on SO, we already have a working Cometd chat functionality. Another functionality we have in the application is notifications, but we would like to integrate Real-time notifications as soon as they happen, kinda like what Facebook has.
Basically the idea is, whenever a new notification is created, it will be saved in the database, and its information from the backend has to be passed to the notifications for logged in users on unique channel for each user.
I would like to know if this approach will work, as it will take me some doing to route notifications to the chat class. Please note, I don't have an interface for the ChatServiceImpl class too. Is that okay? Enough talking, here's code :
ChatServiceImpl :
#Named
#Singleton
#Service
public class ChatServiceImpl {
#Inject
private BayeuxServer bayeux;
#Session
private ServerSession serverSession;
public void sendNotification(Notification notification,int id
// And then I send notification here like below, by extracting information from the notification object.
ServerChannel serverChannel = bayeux.createChannelIfAbsent("/person/notification/" + id).getReference();
serverChannel.setPersistent(true);
serverChannel.publish(serverSession, output);
}
}
The above class has no interface, so I was planning to use the method as follows :
#Service
#Transactional
public class GroupCanvasServiceImpl implements GroupCanvasService{
private ChatServiceImpl chatService;
public void someMethod(){
chatService.sendNotification(notification, id);
}
}
BayeuxInitializer :
#Component
public class BayeuxInitializer implements DestructionAwareBeanPostProcessor, ServletContextAware
{
private BayeuxServer bayeuxServer;
private ServerAnnotationProcessor processor;
#Inject
private void setBayeuxServer(BayeuxServer bayeuxServer)
{
this.bayeuxServer = bayeuxServer;
}
#PostConstruct
private void init()
{
this.processor = new ServerAnnotationProcessor(bayeuxServer);
}
#PreDestroy
private void destroy()
{
System.out.println("Bayeux in PreDestroy");
}
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException
{
processor.processDependencies(bean);
processor.processConfigurations(bean);
processor.processCallbacks(bean);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException
{
return bean;
}
public void postProcessBeforeDestruction(Object bean, String name) throws BeansException
{
processor.deprocessCallbacks(bean);
}
#Bean(initMethod = "start", destroyMethod = "stop")
public BayeuxServer bayeuxServer()
{
return new BayeuxServerImpl();
}
public void setServletContext(ServletContext servletContext)
{
servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bayeuxServer);
}
}
Kindly let me know if this approach is okay. Thanks a lot.
The #Listener annotation is meant for methods that handle messages received from remote clients.
If you only need to send server-to-client messages, you don't strictly need to annotate any method with #Listener: it is enough that you retrieve the ServerChannel you want to publish to, and use it to publish the message.
In your particular case, it seems that you don't really need to broadcast a message on a channel for multiple subscribers, but you only need to send a message to a particular client, identified by the id parameter.
If that's the case, then it's probably better to just use peer-to-peer messaging in this way:
public void sendNotification(Notification notification, int id)
{
ServerSession remoteClient = retrieveSessionFromId(id);
remoteClient.deliver(serverSession, "/person/notification", notification);
}
This solution has the advantage to create a lot less channels (you don't need a channel per id).
Even better, you can replace the /person/notification channel (which is a broadcast channel) with a service channel such as /service/notification.
In this way, it is clear that the channel used to convey notifications is for peer-to-peer communication (because service channels cannot be used to broadcast messages).
The retrieveSessionFromId() method is something that you have to map upon user login, see for example the documentation about CometD authentication.
Related
I'm trying to implement an application using Spring Boot/Spring Data, following DDD architecture guidelines. I have an Aggregate Root which publish domain events using the method AbstractAggregateRoot::registerEvent() . Furthermore, I need to intercept those events for Logging/Tracing purposes so I decided to make an experiment:
First, implement a custom ApplicationEvent Publisher
public class CustomEventPublisher implements ApplicationEventPublisher {
private final ApplicationEventPublisher publisher;
private final Logger logger = getLogger(CustomEventPublisher.class);
public CustomEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
#Override
public void publishEvent(ApplicationEvent event) {
logger.info("sending an event...");
publisher.publishEvent(event);
}
//.....
}
And then registering as bean
#Configuration
public class CustomEventPublisherConfig {
#Bean
#Primary
public ApplicationEventPublisher getCustomEventPublisher(ApplicationEventPublisher publisher , RabbitTemplate rabbitTemplate) {
return new CustomEventPublisher(publisher, rabbitTemplate);
}
}
this works fine once I explicitly publish events from some sample object with an injected ApplicationEventPublisher
public void pub() {
publisher.publishEvent(new Event(this , 1));
}
#EventListener
public void sub(Event e) {
this.value = e.getValue();
}
and I got the "sending an event..." log entry
then I've tried to define the aggregate root
#Entity
public class AggregateRoot extends AbstractAggregateRoot {
#Id
#GeneratedValue
private Long id;
private int value = 0;
public AggregateRoot setValue(int value) {
registerEvent(new Event(this , value));
return this;
}
}
and
public void pub() {
repository.save(new AggregateRoot().setValue(1));
}
Test pass again but I can clearly see that Spring Data is not using the CustomEventPublisher. I've tried to understand if there is some way to intercept repository.save() call and override the default behaviour, this approach could work even if needs to reinvent the wheel (I don't think that the domain event publishing code is so complicated though) but the only thing I've found is about Spring Data REST that is out of my scope
Any suggestion to overcome this problem?
Thanks in advance
As far as I know, Spring does not provide a way to replace the publisher used by EventPublishingRepositoryProxyPostProcessor. And it seems to me that you have chosen not quite the right path to get what you want to achieve, so I am not answering your direct question, but your requirements described in the beginning.
I would advise you to register a listener using #EventListener and handle your event there:
#EventListener
public void handleEvent(Event event) {
System.out.println(event);
}
Or you can use #TransactionalEventListener to bind the listener to a transaction phase:
#TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleEvent(Event event) {
System.out.println(event);
}
Before Akka 2.6, or with classic Actors, one can write an Akka Extension to have access to Spring's #Inject annotation in Akka untyped actors.
One example of this is: https://github.com/typesafehub/activator-akka-java-spring/blob/master/src/main/java/sample/SpringExtension.java
However, this does not work for the new Akka Typed actors.
Akka's documentation does not show how to make such an extension (but it does show how to make simple extensions: https://doc.akka.io/docs/akka/current/typed/extending.html#building-an-extension).
So far, I wrote this beginning of extension, but I don't know how to link Spring's ApplicationContext with the actor system:
import org.springframework.context.ApplicationContext;
import akka.actor.typed.ActorSystem;
import akka.actor.typed.Extension;
import akka.actor.typed.ExtensionId;
public class SpringExtension implements Extension {
private volatile ApplicationContext applicationContext;
private SpringExtension(final ActorSystem<?> system) {
// TODO: What do you put here?
}
void initialize(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public static class Id extends ExtensionId<SpringExtension> {
private static final Id instance = new Id();
private Id() {}
// called once per ActorSystem
#Override
public SpringExtension createExtension(final ActorSystem<?> system) {
return new SpringExtension(system);
}
public static SpringExtension get(final ActorSystem<?> system) {
return instance.apply(system);
}
}
}
How do you write an Akka Extension for Typed Actors allowing to use Spring DI in typed actors?
May be this is not exactly what you need, but I think found a way how to inject typed actors without using an extension.
We can create Behavior as a bean, inject all needed dependencies and pass it to another actor, where spawn the actor based on defined Behavior.
Let's assume we have PrintActor that can print messages using PrintService, and GreetActor that uses GreetService and spawns PrintActor. We can define beans like this:
#Bean
public Behavior<String> printActorBehavior(PrintService printService) {
return Behaviors.setup(ctx -> new PrintActor(ctx, printService));
}
#Bean
public Behavior<GreetActor.Greet> greetActorBehavior(GreetService greetService,
Behavior<String> printerActorBehavior) {
return Behaviors.setup(ctx -> new GreetActor(ctx, greetService, printerActorBehavior));
}
And then, in GreetActor we just create actor from injected Behavior by calling getContext().spawn(printerActorBehavior, "printer");
public class GreetActor extends AbstractBehavior<GreetActor.Greet> {
private GreetService greetService;
private Behavior<String> printerActorBehavior;
public GreetActor(ActorContext<Greet> context,
GreetService greetService,
Behavior<String> printerActorBehavior) {
super(context);
this.greetService = greetService;
this.printerActorBehavior = printerActorBehavior;
}
#Override
public Receive<Greet> createReceive() {
return newReceiveBuilder()
.onMessage(Greet.class, this::onGreet)
.build();
}
private Behavior<Greet> onGreet(Greet msg) {
ActorRef<String> printer = getContext().spawn(printerActorBehavior, "printer");
printer.tell(greetService.greet(msg.name));
return this;
}
#Value
static class Greet {
String name;
}
}
Since we can not create actors from outside of actor system in Akka Typed, I think there is no proper way to inject ActorRef with Spring.
We can try to inject Spring context into actor, spawn some actors and put them into Spring context, but I think this is not a good way to work with both frameworks.
I've got a fairly simple CQRS setup here using Axon & Spring.
This is the configuration class.
#AnnotationDriven
#Configuration
public class AxonConfig {
#Bean
public EventStore eventStore() {
...
}
#Bean
public CommandBus commandBus() {
return new SimpleCommandBus();
}
#Bean
public EventBus eventBus() {
return new SimpleEventBus();
}
}
This is my Aggregate...
#Aggregate
public class ThingAggregate {
#AggregateIdentifier
private String id;
public ThingAggregate() {
}
public ThingAggregate(String id) {
this.id = id;
}
#CommandHandler
public handle(CreateThingCommand cmd) {
apply(new ThingCreatedEvent('1234', cmd.getThing()));
}
#EventSourcingHandler
public void on(ThingCreatedEvent event) {
// this is called!
}
}
This is my EventHandler in a separate .java file...
#Component
public class ThingEventHandler {
private ThingRepository repository;
#Autowired
public ThingEventHandler(ThingRepository thingRepository) {
this.repository = conditionRepository;
}
#EventHandler
public void handleThingCreatedEvent(ThingCreatedEvent event) {
// this is only called if I publish directly to the EventBus
// apply within the Aggregate does not call it!
repository.save(event.getThing());
}
}
I'm using the CommandGateway to send the original creation command. My CommandHandler in the Aggregate receives the command fine, but when I call apply within my Aggregate, passing a new Event, my EventHandler in the external class, does not get called. Only EventHandlers directly inside the Aggregate class are called.
If I try and publish an Event directly to the EventBus, my external EventHandler is called.
Any idea why my EventHandler in an external java class is not being called when I call apply within the Aggregate?
In Axon 3, the Event Store is a replacement for the Event Bus. It is basically a specialized implementation that doesn't only forward events to subscribed, but also stores them.
In your configuration, you have both an Event Bus and an Event Store. The Aggregate's events are probably published to the Event Store. Since you receive events in your handler when publishing directly to the Event Bus, your handlers are subscribed there.
The solution: remove the Event Bus from your configuration and use the Event Store exclusively.
i am trying to implement server side events.
I have very simple resource exposed by a RESTful web service with Jersey/Grizzly. I try to broadcast the events with the SseBroadcaster. An event is created, whenever a data item comes in and is added to an internal list. A client should open a connection to the URL /events to receive the events.
#Path("sensordataelements")
public class SensorDataResource {
private SseBroadcaster broadcaster = new SseBroadcaster();
#GET
#Path("events")
#Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents() {
final EventOutput eventOutput = new EventOutput();
broadcaster.add(eventOutput);
return eventOutput;
}
#POST
#Path("/addraw")
#Produces(MediaType.APPLICATION_JSON)
public Response addRawSensorData(String elementBody) {
... data processing stuff ...
cList.add(
new SensorDataElement.SensorDataElementBuilder().id()
.sensorReading(tmpValue)
.build()
);
OutboundEvent evt = new OutboundEvent.Builder()
.data(Float.class, Float.valueOf(tmpValue))
.build();
broadcaster.broadcast(evt);
return Response.status(201).build();
}
...
I tried to connect with
curl -v http://localhost:8080/sensordataapp/sensordataelements/events
The connection is fine, but i do not get any events. I looked at some examples, but got the impression that this should work. What did i miss?
Thanks!
By default, a new instance of the resource class is created for each request. This means that a new broadcaster is created for each request, which isn't what you want. If you want to make the resource class a Singleton, you can simply annotate the class with #Singleton
#Singleton
#Path("sensordataelements")
public class SensorDataResource {
...
}
Now, only one instance of the resource class will be created for the entire application, and it will be shared for all requests.
The other option, is if you inject the broadcaster, instead of instantiating it yourself, you can inject it as a Singleton. Whether or not the resource class is a singleton or not, it will still get injected the same broadcaster instance. To do that, you can do something like the following in your ResourceConfig subclass
public class AppConfig extends ResourceConfig {
public AppConfig() {
register(new AbstractBinder() {
#Override
public void configure() {
bind(new SseBroadcaster()).to(SseBroadcaster.class);
}
});
}
}
Then in your resource class, just inject it
#Path("sensordataelements")
public class SensorDataResource {
#Inject
private SseBroadcaster broadcaster;
See also:
Dependency injection with Jersey 2.0
I'm working with AngularJS, Ionic and Java as backend. I'm using websocket to create a realtime chat.
I want to set up a listener to receive all the messages anywhere in my app to handler it and keep it without nodejs.
Is there a way to do it that?
I don't think there is a well prepared way to meet such requirements in Java WebSocket API. (Although, it would be possible with JMX without a little bit of effort.) However you can simply do that by writing a parent endpoint class, having every endpoint class extend it like the following:
// Assumes you use some dependency injection framework
#Component
// Your own POJO to be notified of every socket events
public class WebSocketListener {
public void onOpen(String id) {
// Your logic here
}
public void onMessage(String id, Object message) {
// Your logic here
}
public void onClose(String id) {
// Your logic here
}
}
// A parent class to notify your own global listener
// Extend it if you want to listen socket's event through some specific path
public abstract class NotifiableEndpoint {
#Inject
private WebSocketListener listener;
#OnOpen
public void onOpen(Session session) {
listener.onOpen(session.getId());
}
#OnMessage
public void onMessage(Session session, StringOrByteBuffer data) {
listener.onOpen(session.getId(), data);
}
#OnClose
public void OnClose(Session session) {
listener.onOpen(session.getId());
}
}
// A concrete class to write your business logic
#ServerEndpoint("/hello")
public class YourEndpoint extends NotifiableEndpoint {
// Your logic
}
Not to use inheritance, reflection through ServerEndpointConfig.Configurator.getEndpointInstance might work.