jingkecn/blog

How To: Implement Event-driven Architecture in Java

jingkecn opened this issue · 0 comments

Introduction

In computing systems, Event-driven Architecture (abbr. EDA,same in the following context) is
a pattern to implement a loosely coupled system.

Inspired by .Net

In .Net, Delegation is the basic pattern to implement EDA, and Event is declared and defined by delegate keyword and event keyword:

// A delegate is a type that represents references to methods 
// with a particular parameter list and return type.
delegate void SampleEventHandler(/* params */);

// The event keyword is used to declare an event in a publisher class.
event SampleEventHandler SampleEvent;

However, an event is supposed to be dispatched by an event target and listened by an event listener.

Event Target

An event target is in charge of:

  • Definition of an event
  • Dispatching an event
/**
 * A sample of event target in C#.
 **/
class SampleEventTarget {
    // Event definition.
    delegate void SampleEventHandler(/* params */);
    event SampleEventHandler SampleEvent;

    // Event dispatching.
    // Dispatch SampleEvent somewhere in member methods.
    //     SampleEvent?.invoke(/* params */);
}

Event Listener

An event listener is in charge of handling event if any invoked.

/**
 * A sample of event listener in C#.
 **/
class SampleEventListener {
    void OnSampleEventInvoked(/* params */) {
        // Handle event here.
    }
}

Registration and Cancellation

In general, a central controller in global scope is supposed to be in charge of:

  • Register an event listener to an event target.
  • Cancel an event listener from an event target.
var sampleEventTarget = new SampleEventTarget();
var sampleEventListener = new SampleEventListener();

del lambdaEventHandler = (/* params */) => {
    sampleEventListener.OnSampleEventInvoked(/* params */);
};
// Register lambda event handler to event target.
sampleEventTarget.SampleEvent += lambdaEventHandler;
// Cancel lambda event handler from event target.
sampleEventTarget.SampleEvent -= lambdaEventHandler;

// Register event handler reference to event target.
sampleEventTarget.SampleEvent += sampleEventListener.OnSampleEventInvoked;
// Cancel event handler reference from event target.
sampleEventTarget.SampleEvent -= sampleEventListener.OnSampleEventInvoked;

Implementation in Java

Then here comes the question: what can be things corresponding to delegate and event of .Net in Java?

From the observation on previous .Net samples, we can conclude that its delegate declares and defines a signature of the method handling event. Thus functional interface in Java is quite close to it.

// Defines an event handler.
@FunctionalInterface
interface ISampleEventHandler {
    void invoke(/* params */);
}

Then what can be corresponding to event?
Still from the observation on previous .Net samples, an object declared by keyword event has two operators: += and -=, it means that an event should be a collection. Then the elements of this collection should be defined by the delegate: SampleEventHandler

// An event in Java can be a collection of event handlers.
HashSet<ISampleEventHandler> sampleEvent = new HashSet();

Then all we have to do is to copy mechanically:

Event Target

/**
 * A sample of event target in Java.
 **/
class SampleEventTarget {
    // Defines an event handler.
    @FunctionalInterface
    interface ISampleEventHandler {
        void invoke(/* params */);
    }

    // An event in Java can be a collection of event handlers.
    HashSet<ISampleEventHandler> sampleEvent = new HashSet();

    // Event dispatching.
    // Dispatch sampleEvent somewhere in member methods.
    //     sampleEvent.forEach { handler -> handler.invoke(/* params */); };
}

Event Listener

/**
 * A sample of event listener in Java.
 **/
class SampleEventListener {
    void onSampleEventInvoked(/* params */) {
        // Handle event here.
    }
}

Registration and Cancellation

SampleEventTarget sampleEventTarget = new SampleEventTarget();
SampleEventListener sampleEventListener = new SampleEventListener();

ISampleEventHandler sampleEventHandler = new ISampleEventHandler() {
    @Override
    void invoke(/* params */) {
        sampleEventListener.onSampleEventInvoked(/* params */)
    }
};
// Register event handler to event target.
sampleEventTarget.sampleEvent.add(sampleEventHandler);
// Cancel event handler from event target.
sampleEventTarget.sampleEvent.remove(sampleEventHandler);

Function</* param types */, Void> lambdaEventHandler = (/* params */) => {
    sampleEventListener.onSampleEventInvoked(/* params */);
    return null;
};
// Register lambda event handler to event target (Java 8+ required).
sampleEventTarget.sampleEvent.add(lambdaEventHandler);
// Cancel lambda event handler from event target.
sampleEventTarget.sampleEvent.remove(lambdaEventHandler);

// Register event handler reference to event target (Java 8+ required).
sampleEventTarget.SampleEvent.add(sampleEventListener::OnSampleEventInvoked);
// Cancel event handler reference from event target.
sampleEventTarget.SampleEvent.remove(sampleEventListener::OnSampleEventInvoked);

Conclusion

The most counterintuitive part of implementations suggested by this post lies in:

Lambda Expressions

Lambda expressions were introduced as a new feature in Java 8 to invoke existing methods, so

sampleEventTarget.sampleEvent.add((/* params */) -> { /* ... */ });

is actually a syntax sugar of

sampleEventTarget.sampleEvent.add(new ISampleEventHandler() { /* ... */ });

Method References

Java 8 has also introduced Method References, so that we can get rid of the coerciveness of implementing the functional interface ISampleEventHandler, instead, we can register directly a method as an event handler as long as it has the same signature as defined in ISampleEventHandler.

[!TODO]
Android has recently supported Kotlin, EDA implementation in Kotlin should be much closer to .Net pattern and should also be much more comprehensive.

See Also

No. Link
[1] Wikipedia - Event-driven Architecture
[2] How to: Publish Events that Conform to .NET Framework Guidelines (C# Programming Guide).
[3] The Java™ Tutorials > Method References.