I’m new to Spring and am trying to write a wrapper for Rabbitmq with Spring. I intend to use everything provided by Spring AMQP, the purpose of this is to only configure common defaults, define tracing, error handling etc so I can write applications using this library and not have to worry about getting everything ‘right’.
I expect this to be very lightweight and would like to do it the Spring way. I’m focused on a consumer right now.
Intention
my wrapper
I would like to provide only an annotation ( I’ve looked into it then being a meta-annotation for @RabbitListener.
I would like to do something like this : ( I’ve added what I’ve done so far below )
public @interface MyAwesomeListener {
String consumer();
}
application
An app using my wrapper would then simply provide the configuration as :
configuration
Provided by the application in it’s properties file ( please ignore the syntax )
I want to make the consumers
field to be mandatory, within each consumer, certain fields will also be mandatory (for eg queue
)
consumers:
myConsumer:
queue: foo
exchange: fooEx
binding: fooExRK
prefetch: 2
minThreads: 5
anotherConsumer:
queue: bar
exchange: barEx
binding: barExRK
using my wrapper
@MyLib.MyAwesomeListener(consumer='myConsumer')
public void consumeMessages1_2_3(Object object) {
//application's own way to consumer a message
}
The idea is all someone writing an application needs to do is:
- provide the right config under their own ‘consumer’ under the
consumers
property in their application - Decorate their own method with the common annotation
MyAwesomeListener
what I expect
When an application decorates their method with MyAwesomeListener
, I would like to read the provided value from the annotation ‘s property (consumer
) then find it’s configuration ( well defined valid properties within each ‘consumer’ ) in the application’s `application.properties. Then pass on those values to override any configured defaults I have ( for example connectionFactory/other defaults etc. ) and ultimately ‘call’ it’s meta-annotation – the @RabbitListener. So it’s like a bring your own consumer.
I can devise a default connection factory, prefetch counts, always override the errorHandler etc. so I don’t have to do these in every application.
If the provided consumer or ‘consumers’ field does not exist, I’d like to raise a compilation/runtime error.
what I’ve tried
In my library
@RabbitListener(containerFactory = "rabbitListenerContainerFactoryConsumer") // my predefined factory
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAwesomeListener {
@AliasFor(annotation = RabbitListener.class, attribute = "bindings")
QueueBinding[] queueBinding();
@AliasFor(annotation = RabbitListener.class, attribute = "id")
String name();
}
In my application
@MyLib.MyAwesomeListener(
queueBinding =
{@QueueBinding
(value = @Queue(value = "${consumer.myConsumer.queue}",
durable = "true"),
exchange = @Exchange(value = "${consumer.myConsumer.exchange}",
type = "direct", durable = "true"),
key = "${consumer.myConsumer.routingKey}")}
, name = "myConsumer")
)
public Boolean consumeMessages1_2_3(MyProperties myProperties) {
//MyProperties is a class extending Serializable
//print / consumer ... do something ...
return true;
}
What does not work?
- Users have to specify so many things when decorating with
MyAwesomeListener
– I would like to control these via a configuration - Cannot default – to say min threads are 6 unless overidden
- Cannot use variables provided to the annotation – there’s a name property
name
, I’d like to use it to provide the connection name as well as theid
property of RabbitListener ( spring amqp providesconnectionFactory.setConnectionNameStrategy(connectionFactory -> "MY_CONNECTION")
) – these are static and cannot be loaded into the method(MyLib.class.getAnnotation(MyAwesomeListener.class).name()
- I have to repeat this if I need more consumers
- Defeats the purpose of common defaults altogether
The question?
I don’t believe this should be as hard as I’m making it out to be in my mind, what’s the obvious,simplest solution that I’m missing?