jakartaee / messaging

Jakarta Messaging
https://eclipse.org/ee4j/messaging
Other
39 stars 32 forks source link

Annotation-Based API for Consuming Messages #243

Open dblevins opened 4 years ago

dblevins commented 4 years ago

The only means to declare message consumers via configuration or annotation is currently via JMS Message-Driven Beans. A new expressive annotation-based approach modeled after JAX-RS is desired to bring JMS into the future.

A follow-up to an older issue: https://github.com/jakartaee/messaging/issues/134.

Current MDB API Example:

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;

@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "maxSessions", propertyValue = "3"),
        @ActivationConfigProperty(propertyName = "maxMessagesPerSessions", propertyValue = "1"),
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "TASK.QUEUE")
})
public class BuildTasksMessageListener implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {

            if (!(message instanceof ObjectMessage)) {
                throw new JMSException("Expected ObjectMessage, received " + message.getJMSType());
            }

            final ObjectMessage objectMessage = (ObjectMessage) message;

            final BuildTask buildTask = (BuildTask) objectMessage.getObject();

            doSomethingUseful(buildTask);

        } catch (JMSException e) {
            // Why can't I throw a JMS Exception
            throw new RuntimeException(e);
        }
    }

    // This is the only "useful" code in the class
    private void doSomethingUseful(BuildTask buildTask) {
        System.out.println(buildTask);
    }
}

Issues with this API involve:

Some form of annotation-based approach styled after JAX-RS could solve all of the above issues. For example:

import io.breezmq.MaxMessagesPerSession;
import io.breezmq.MaxSessions;

import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.JMSMessageDrivenBean;
import javax.jms.ObjectMessage;
import javax.jms.QueueListener;
import javax.jms.TopicListener;

@MessageDriven
@MaxSessions(3)
@MaxMessagesPerSession(1)
public class BuildTasksMessageListener implements JMSMessageDrivenBean {

    @QueueListener("TASK.QUEUE")
    public void processBuildTask(final ObjectMessage objectMessage) throws JMSException {

        final BuildTask buildTask = (BuildTask) objectMessage.getObject();

        doSomethingUseful(buildTask);

    }

    @TopicListener("BUILD.TOPIC")
    public void processBuildNotification(final ObjectMessage objectMessage) throws JMSException {

        final BuildNotification notification = (BuildNotification) objectMessage.getObject();

        System.out.println("Something happened " + notification);
    }

    // This is the only "useful" code in the class
    private void doSomethingUseful(BuildTask buildTask) {
        System.out.println(buildTask);
    }
}

Benefits:

The above API should be considered a straw-man just to get conversations started.

Proposals

Actual proposals from the community are welcome, but should achieve or not-conflict with the same 5 benefits. Partial proposals are welcome, such as #241 which focuses on one aspect required to make an annotation-based API work.

File your proposals and mention in the comments below and we'll add it to the list, regardless of state.

TODO: find JMS 2.1 proposal and link it

Related

hantsy commented 1 year ago

Spring can process jms via simple @JmsListener, how hard we still need the interface?


@ApplicationScoped 
@Slf4j
public class Receiver {

    @JmsListener(destination = "hello")
    public void onMessage(String message) {
        log.debug("receving message: {}", message);
    }

}

Provides programmatic APIs to set the JSM global properties, and also allow to set properties attribute(a Map or a string of properties joint by ",") on the JmsListener annotation.

OndroMih commented 1 year ago

I agree with @hantsy, no interface is needed. And I also like that any CDI bean can be turned into a JMS observer. However, I'm not sure whether a single @JmsListener is enough to distinguish between a queue and a topic. An implementation might need to know whether the listener wants to connect to a queue or a topic.

Another idea is to turn this into a native CDI event observer, with a @JmsListener qualifier. Then all the CDI event API could be used, including asynchronous API. Using @JmsListener on the method could be also allowed but would work as a shorthand, e.g.:

@JmsListener(destination = "hello")
    public void onMessage(String message) {
        log.debug("receving message: {}", message);
    }

would be equivalent to:

    public void onMessage(@Observes @JmsListener(destination = "hello") String message) {
        log.debug("receving message: {}", message);
    }

The latter syntax also allows injection per method call, e.g. the following would inject a myProcessor CDI bean:

    public void onMessage(@Observes @JmsListener(destination = "hello") String message, MyProcessor myProcessor) {
        myProcessor.process(message);
    }

We could allow injecting some contextual information, if it's technically possible. E.g. inject a CDI bean of type of Topic/Queue for the current queue/topic. But we would need to explore that separately, I'm not sure if CDI allows this dynamically for each message.

m-reza-rahman commented 1 year ago

This is a very deep topic with a number of discussions that took place over a long time but never implemented. If the idea is to finally do this work, I suggest starting with a very simple straw man and detailed follow up discussions on the mailing list. I would be delighted to participate and share incrementally all the things from the past. It’s too much for this one issue I think. The interim outcome may be creating several smaller issues based on some initial progress that hopefully can go into Jakarta EE 11.

hantsy commented 1 year ago

@m-reza-rahman I remember JBoss Seam2/3(Seam 3 is based CDI) has very simple approach to bridge the JMS to CDI event observer, this JMS handling should be simple as possible.