Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotation-Based API for Consuming Messages #243

Open
dblevins opened this issue Sep 29, 2019 · 4 comments
Open

Annotation-Based API for Consuming Messages #243

dblevins opened this issue Sep 29, 2019 · 4 comments
Labels
4.0 CDI integration Improvements related to fit better with the CDI programming model New Feature use case

Comments

@dblevins
Copy link
Contributor

dblevins commented Sep 29, 2019

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: #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:

  • User manual required to know what names can be used in @ActivationConfigProperty.
  • Loosely typed. All values are string, but have an implied type which is not compile-time checked.
  • Poor targeting. The metadata for the onMessage method is on the class, not the method, making additional methods impossible.
  • Too Course-grained. The MessageListener.onMessage(Message msg) method is similar to the HttpServlet.service(ServletRequest req, ServletResponse res) in being too course-grained and requires boilerplate; casting, message property checking, string parsing.
  • EJB-specific. The above API is only available to EJB Message-Driven beans.

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:

  • Multiple user-defined message consuming methods allowed.
  • Method signature implies message type, avoiding casting.
  • Message Properties can be passed as annotated method arguments. (not shown above)
  • Strongly-typed annotations replace string properties so names and values are compile-time checked.
  • Opens the door for use outside EJB

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
Copy link

hantsy commented Apr 28, 2023

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
Copy link
Contributor

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
Copy link

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
Copy link

hantsy commented May 29, 2023

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.0 CDI integration Improvements related to fit better with the CDI programming model New Feature use case
Projects
None yet
Development

No branches or pull requests

4 participants