Consume RabbitMQ queue as Observable in Grails 3 with RxJava - grails

Is there a way to consume a RabbitMq in a Grails 3 controller or service as if the Consumer was an Observable?
My actual code is something like that
def consumer
rx.stream { Subscriber subscriber ->
consumer = new DefaultConsumer(channel) {
#Override
void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8")
subscriber.onNext(rx.render(message))
}
}
channel.basicConsume(queueName, true, consumer)
}
but I don't really like this kind of implementation and I think adhering to Rx paradigm would be better.

Can you change your code to use a pull-based model on the RabbitMQ side instead of a callback-based model? If yes, then you can use SyncOnSubscribe to easily fetch messages.

Related

Microsoft Orleans - grains calling grains

I am looking into Microsoft Orleans. I have setup a cluster in docker, tt is working perfectly. I am trying to read the documentation on message delivery, but I can't seem to find anything on retry. If I have a grain calling a grain, like this:
public class HelloGrain : Orleans.Grain, IHello
{
private readonly ILogger logger;
private IOtherGrain otherGrain;
public HelloGrain(ILogger<HelloGrain> logger)
{
this.logger = logger;
}
public override async Task OnActivateAsync()
{
otherGrain = GrainFactory.GetGrain(this.GetPrimaryKeyString());
await base.OnActivateAsync();
}
Task<string> IHello.SayHello(string greeting)
{
string otherGrainReturn = await this.otherGrain.MethodAsync();
return Task.FromResult($"\n Client said: '{greeting}', so HelloGrain says: Hello!");
}
}
Is the string otherGrainReturn = await this.otherGrain.MethodAsync(); getting retried, on failure, somehow? Is it only a timeout that decide when the call fails? Is this to be handled as a basic HTTP call, and should i therefore be retrying myself?
It could be awesome with som link to documentation that say something more on the subject (at-least-once retry etc).
You can see here that by design, Orleans doesn't have any kind of retry policy.
But you can add it by yourself with a simple try catch block (Or with a specific library like Polly).

SignalR with orleans how to pass SignalR from startup to grain

I am very new with orleans and trying to grasp everything with grains and so forth.
What i got is that in my startup.cs file i add the SignalR like this
public IServiceProvider ConfigureServices(IServiceCollection services)
{
Program.WriteConsole("Adding singletons");
services
.AddSingleton(achievementManager)
.AddMvc();
services.AddSingleton(SignalRClient);
return services.BuildServiceProvider();
}
So far everything is fine i can start my host/application and it connects to SignalR as it should. But what i cant wrap my head around is how do i get this down to my grain? if i had a controller i would simply send it down in the constructor on startup but how do i do this with a grain? Or can i even do it like this. Any guidance is appreciated.
In the grain then i want to do something like this
[StatelessWorker]
[Reentrant]
public class NotifierGrain : Grain, INotifierGrain
{
private HubConnection SignalRClient { get; }
public NotifierGrain(HubConnection signalRClient)
{
SignalRClient = signalRClient;
SignalRClient.SendAsync(Methods.RegisterService, Constants.ServiceName);
}
public Task NotifyClients(object message, MessageType type)
{
var registerUserNotification = (RegisterUserNotificationModel)message;
SignalRClient.SendAsync(Methods.RegisterUserToMultipleGroups, registerUserNotification.UserId, registerUserNotification.InfoIds);
}
return Task.CompletedTask;
}
Then i try to call the Notify method from another grain like this
var notifier = GrainFactory.GetGrain<INotifierGrain>(Constants.NotifierGrain);
await notifier.NotifyClients(notification, MessageType.RegisterUser);
But trying to do this ends up with an error like this
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.SignalR.Client.HubConnection' while attempting to activate 'User.Implementation.Grains.NotifierGrain'.
Orleans supports constructor injection, so you can inject the SignalRClient into your grain constructor. In your code you are already correctly registering the client using services.AddSingleton(SignalRClient), so I will focus on how to inject the type into your grain.
I do not know what the type the SignalR client object is, but in this example I assume that the type is "SignalRClient":
[StatelessWorker]
[Reentrant]
public class NotifierGrain : Grain, INotifierGrain
{
private readonly SignalRClient signalRClient;
public NotifierGrain(SignalRClient signalRClient)
{
this.signalRClient = signalRClient;
}
public async Task NotifyClients(object message, MessageType type)
{
var registerUserNotification = (RegisterUserNotificationModel)message;
await this.signalRClient.SendAsync(
MessageMethods.RegisterUserToMultipleGroups,
registerUserNotification.UserId,
registerUserNotification.infoIds);
}
}
Depends how you're thinking to use SignalR Server, if you're going to host your SignalR server with Microsoft Orleans for sure you need to have backplane to handle the Orleans cluster communications.
You can use SignalR Orleans which has everything done out of the box for you :)
Also if you need a reactive SignalR library for the frontend, you can use Sketch7 SignalR Client
PS I m one of the authors of both libraries.

spring-amqp unable to use custom MessagingMessageListenerAdapter in SimpleMessageListenerContainerFactory

In spring-amqp 2.0.3.RELEASE module it's no possible to use custom MessagingMessageListenerAdapter in SimpleMessageListenerContainerFactory.
Even thought registration own bean we stuck at highest one, where last object instance just hard created thought "new MethodRabbitListenerEndpoint" at org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor#processAmqpListener.
Maybe spring developers could add some producer registration to SimpleMessageListenerContainerFactory like "smlcf.setMessageListenerAdapterCreator"
I think what you are asking can be done via a RabbitListenerAnnotationBeanPostProcessor extension:
#Bean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static RabbitListenerAnnotationBeanPostProcessor myRabbitListenerAnnotationBeanPostProcessor() {
return new RabbitListenerAnnotationBeanPostProcessor() {
#Override
protected void processListener(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object bean,
Object adminTarget, String beanName) {
super.processListener(new MyMethodRabbitListenerEndpoint(), rabbitListener, proxy, adminTarget, beanName);
}
};
}
But what you are saying about retry for the reply really makes sense and we definitely should consider to let to inject a RabbitTemplate into the RabbitListenerContainerFactory.
Feel free to raise a JIRA on the matter.
I have created a story for adding retry functionality for ReplyTo https://jira.spring.io/browse/AMQP-825

Fetch message details in Spring RecoveryCallback

I'm publishing messages into RabbitMQ and I would like to track the errors when RabbitMQ is down, for this I added one RetryTemplate with the recovery callback, but the recovery callback only provides this method getLastThrowable() and I'm not sure how to provide the details of the messages that failed when RabbitMQ is down. (as per documentation "The RecoveryCallback is somewhat limited in that the retry context only contains the
lastThrowable field. For more sophisticated use cases, you should use an external
RetryTemplate so that you can convey additional information to the RecoveryCallback via
the context’s attributes") but I don't know how to do that, if anyone could help me with one example that will be awesome.
Rabbit Template
public RabbitTemplate rabbitMqTemplate(RecoveryCallback publisherRecoveryCallback) {
RabbitTemplate r = new RabbitTemplate(rabbitConnectionFactory);
r.setExchange(exchangeName);
r.setRoutingKey(routingKey);
r.setConnectionFactory(rabbitConnectionFactory);
r.setMessageConverter(jsonMessageConverter());
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(500);
backOffPolicy.setMultiplier(10.0);
backOffPolicy.setMaxInterval(10000);
retryTemplate.setBackOffPolicy(backOffPolicy);
r.setRetryTemplate(retryTemplate);
r.setRecoveryCallback(publisherRecoveryCallback);
return r;
}
Recovery Callback
#Component
public class PublisherRecoveryCallback implements RecoveryCallback<AssortmentEvent> {
#Override
public AssortmentEvent recover(RetryContext context) throws Exception {
log.error("Error publising event",context.getLastThrowable());
//how to get message details here??
return null;
}
}
AMQP Outbound Adapter
return IntegrationFlows.from("eventsChannel")
.split()
.handle(Amqp.outboundAdapter(rabbitMqTemplate)
.exchangeName(exchangeName)
.confirmCorrelationExpression("payload")
.confirmAckChannel(ackChannel)
.confirmNackChannel(nackChannel)
)
.get();
The isn't possible because the function RabbitTemplate.execute() is already not aware about message you send, because it may be performed from any other method, where we might not have messages to deal:
return this.retryTemplate.execute(
(RetryCallback<T, Exception>) context -> RabbitTemplate.this.doExecute(action, connectionFactory),
(RecoveryCallback<T>) this.recoveryCallback);
What I suggest you to do is like storing message to the ThreadLocal before send and get it from there from your custom RecoveryCallback.

Messages not reaching destination queue when using ServerInitializerFactory Netty 4

I am using apache camel netty4 in grails and I have declared mycustom ServerInitializerFactory as follows
public class MyServerInitializerFactory extends ServerInitializerFactory {
private int maxLineSize = 1048576;
NettyConsumer nettyConsumer
public MimacsServerInitializerFactory() {}
#Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline()
pipeline.addLast("logger", new LoggingHandler(LogLevel.INFO))
pipeline.addLast("framer", new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, maxLineSize, 2, 2, 6, 0, false))
pipeline.addLast("decoder", new MfuDecoder())
pipeline.addLast("encoder", new MfuEncoder())
pipeline.addLast("handler", new MyServerHandler())
}
}
I have a route which I setup as follows in my routebuilder.
from('netty4:tcp://192.168.254.3:553?serverInitializerFactory=#sif&keepAlive=true&sync=true&allowDefaultCodec=false').to('activemq:queue:Tracking.Queue')
My Camel Context is setup in the BootStrap.groovy as follows
def serverInitializerFactory = new MyServerInitializerFactory()
SimpleRegistry registry = new SimpleRegistry()
registry.put("sif", serverInitializerFactory)
CamelContext camelContext = new DefaultCamelContext(registry)
camelContext.addComponent("activemq", activeMQComponent.activeMQComponent("failover:tcp://localhost:61616"))
camelContext.addRoutes new TrackingMessageRoute()
camelContext.start()
When I run my app, my route is started and my framer, decoder, handler and encoders are all invoked but messages do not reach the Tracking. Queue and responses do not get back to the client.
If I do not use serverInitializerFactory in the netty url and user encoders and decoders instead, My messages are hitting the queue but I lose control of the acknowledgement that I want to sent for each type of message that I receive. It seems activemq tries to sent its own response which is rejected by my encoder.
Am I supposed to then write code to send again or is there something I am missing?
You need to add a handler with the consumer so it can be routed, see the unit test how its done:
https://github.com/apache/camel/blob/master/components/camel-netty4/src/test/java/org/apache/camel/component/netty4/NettyCustomPipelineFactoryAsynchTest.java#L112
I managed to get around that problem. In my channelRead0 method. I added the following lines
Exchange exchange = this.consumer.getEndpoint().createExchange(ctx, msg);
where ctx is the ChannelContextHandler and msg is the Message Object, the two are both parameters of the channelRead0 method.
I also added the following lines
this.consumer.createUoW(exchange);
and after my handling code I inserted the following line
this.consumer.doneUoW(exchange);
and everything works like a charm.

Resources