Friday, October 21, 2011

CDI Events solve fancy problems elegantly (Part 2)

Problem: Does your application have a heartbeat?

I used to spread the phrase "every decent application needs a timer bean". Since JEE6, I have rephrased it to "every decent application needs a heart beat".

Whaddaya?

The @Schedule annotation improves upon the old timer service because it has zero admin effort. However, it has issues:

The configuration of @Schedule is in the static code, you cannot modify it at runtime, for example with values from a configuration file outside the deployment package.

Further, I think most timed routine jobs in an application are of the simple sort every minute, every quarter hour, every hour, at time x every day. I rarely find a reason to be more granular than this.

@Scheduled has been inspired by cron, which is flexibly configured with a text file. This is way different to compiling the static config into a class file in an EAR and not being able to modify it in the deployed application. That's very "un-cronish".

How about this idea: a heartbeat event is fired every minute and subscribers can observe it. The event has inspection methods to find out about the event time properties. The observer can be configured at runtime using data from an external config file.

The HeartbeatEvent has a bunch of inquiry methods. isTime(), isMinuteInterval, isHourInterval() take parameters, which can be fetched from a configuration, for example. Having the Calendar at hand, it is possible to invent all kinds of inquiries. Those are the ones that I find most useful.
public class HeartbeatEvent {
    private Calendar calendar;
    public HeartbeatEvent() {
        this.calendar = new GregorianCalendar();
    }
    /**
     * "at x hour and y minutes"
     * Use minute == 0 to test against a specific full hour.
     * @param hour 0-23
     * @param minute 0-59
     * @return true if match
     */
    public boolean isTime(int hour, int minute) {
        return calendar.get(Calendar.HOUR_OF_DAY) == hour && calendar.get(Calendar.MINUTE) == minute;
    }
    /**
     * "every n minutes"
     * @param minutes minute interval 0-59
     * @return true if match
     */
    public boolean isMinuteInterval(int minutes) {
        return calendar.get(Calendar.MINUTE) % minutes == 0;
    }
    /**
     * "every n hours"
     * @param hour 0-23
     * @return true if match
     */
    public boolean isHourInterval(int hour) {
        return calendar.get(Calendar.HOUR_OF_DAY) % hour == 0 && isFullHour();
    }
    public boolean isFullHour() {
        return calendar.get(Calendar.MINUTE) == 0;
    }
    public boolean isHalfHour() {
        return isMinuteInterval(30);
    }
    public boolean isQuarterHour() {
        return isMinuteInterval(15);
    }
}

The HeartbeatEmitter:
@Singleton
public class HeartbeatEmitter {
    @Inject
    private Event<HeartbeatEvent> heartbeat;

    @Schedule(hour="*", minute="*", persistent = false)
    public void emit() {
        heartbeat.fire(new HeartbeatEvent());
    }
}
Example heartbeat observer:
public void fileCleanup(@Observes HeartbeatEvent heartbeat) {
  if (heartbeat.isFullHour()) {
      // do file cleanup every full hour
  }
}

3 comments:

  1. David, thanks for your comment. The @Singleton @Lock twist is interesting.
    You are right, in the synchronous event scenario I have proposed, a job running longer than a minute is a problem, provided that another job is waiting right behind. I guess it would also be possible to declare @Asynchronous observers to get around this limitation (thread pool exhaustion not covered). Haven't tried that.

    For the purpose I have originally designed it, that is regular short running maintenance jobs in long time intervals, this wasn't a problem. The minute precision is probably over the top for this.

    ReplyDelete
  2. Hi herman. I'm Brazilian, i was reading your article http://alpengeist-de.blogspot.com.br/2011/10/cdi-events-solve-fancy-problems_21.html... and I need help! I have an application all developed in cdi. Without ejb session beans! I like your code! The heartbeat is a great idea and i'm trying to implement it! But, my observer method in a cdi namedbean don't get the fired heartbeat. Reading about it i see that it's because ejb resources are processed in another thread, but i'm not right about it. Can you help me with this? How I do to my method in a @Named @ViewScoped bean get the fired event in a @Singleton bean?
    thanks for the attention. Sorry my poor english...
    have a good day!

    ReplyDelete
  3. I'll right away snatch your rss feed as I can't to find your email subscription hyperlink or newsletter service. Do you've any? Please permit me recognise in order that I may subscribe. Thanks.

    ReplyDelete