Problem: Arbitrary-time-delayed execution of operations
Imagine an application where user A triggers an operation, which needs the confirmation of another user B before it can be executed. The point of time when B decides to confirm is not known in advance and arbitrary.
Possible scenario: the request for confirmation is sent from A to B via the internal notification system of the application. B opens the notification, clicks on the "Confirm" button, and executes the operation on behalf of A.
"I need a workflow system!" some enterprise architects blurb immediately.
"I need Lotus Notes!" ...no comment :-)
No you don't. With CDI it is surprisingly little code.
With CDI you can go about like this:
- (user A) Create an event (more on that later) and serialize it to a format, which can be stored easily. I've used JSON for that purpose.
- (user A) Transport the serialized event to user B in the application. Store it in a DB for example, where B can pick it up later.
- (user B much later) Deserialize the event and fire it.
- (system) Observe the event and carry out the operation associated with it. It behaves as if user A had started the operation directly.
From an architectural view, there are some nice aspects to mention:
- The inversion of dependency with events decouples the creator of the event from the implementation of the operation. This is analogous to putting an interface between caller and implementation
- The event observer has no knowledge about the mechanism how the event is transported from A to B, nor how the event has been created. All it knows is what event to observe and what method to call with the data from the event.
- In a publish-subscribe fashion, more observers can be added non-invasively.
- The same observer can be used without time delay when the event creator fires the event immediately, without prior serialization.
To define an Event in CDI you need two things:
- A class, which represents the event (actually the message payload) an Observer listens to.
- An Event instance, which is used to fire (publish) the event object.
public class HelloEvent {
public String greetings;
public HelloEvent(String greetings) //... as usual
// to hide the preferred (de)serialization format
// the event has two methods to take care of that.
// XML or JSON are possible choices.
public String toString() // ...
public static HelloEvent fromString(String s) //...
}
Designing the Hello event service
The event producer must be injected into a class. An event service is a good place. It's job is to encapsulate the event mechanism.
@Inject private Event<HelloEvent> helloEventProducer;
To allow clients to fire a HelloEvent without knowing the plumbing, the event service exposes a method like this:
public void sendHello(HelloEvent event) {
helloEventProducer.fire(event);
}
The observer listens to HelloEvents. It also lives in the event service. This keeps all the event business in one place.
public void receiveHello(@Observes HelloEvent event) {
// How the hello greetings are actually processed
// is not important here. Some imaginary HelloProcessor
// is responsible for that. It does not know about the
// Event business and only cares for the greeting text.
helloProcessor(event.greetings);
}
I needed a transactional event service, so I chose a stateless EJB:
@Stateless
public class HelloEventService {
@Inject private Event<HelloEvent> helloEventProducer;
public void sendHello(HelloEvent event) //...
public void receiveHello(@Observes HelloEvent event) //...
}
We are done plumbing the CDI events.
Designing the Hello confirmation service
Next is a rough sketch of a HelloService, which encapsulates the time-delayed confirmation process.
@Stateless
public class HelloService {
@EJB
private HelloEventService eventSvc;
public void sendHelloConfirmed(String greeting) {
// Extremely simplified: store the event
// serialized for later retrieval.
storeSomewhere(new HelloEvent(greeting).toString());
}
public void executeHello(String serializedEvent) {
eventSvc.sendHello(HelloEvent.fromString(serializedEvent));
}
}
The HelloService has no dependency to CDI event handling, nor has it any clue about the event (de)serialization format. It only knows the HelloEvent and the HelloEventService.
How the serialized event string is passed around is not relevant for the concept. I think you get the idea. To wrap up, let's look at the client code, which is executed by user A (the sender) and user B (the guy who confirms A's intended operation).
The client code
Code executed by user A:
@EJB HelloService hello;
...
hello.sendHelloConfirmed("My goodness, you became fat!");
User B code executed at some later time:
@EJB HelloService hello;
...
String serializedEvent = ... // comes from some storage
hello.executeHello(serializedEvent);
Neither EventService, HelloEvent, nor any CDI Event artifacts appear in the client code. The mechanism of the confirmation process is encapsulated.
The concept as such is not exclusive to CDI events, of course, and could have been realized with JMS and message-driven beans as well. However, the simple and elegant integration with CDI makes the message passing (aka event) architectural pattern easier accessible than before.
Sidenote: JSON Serialization
I like JSON for its natural mapping of composed objects, lists, arrays, and maps. As with XML, with JSON mapping libraries there is a choice between comfort and flexibility.
XStream for example can (de)serialize instances, but is sensitive to versioning issues. It dies when it hits content during deserialization, which it cannot bind. This is typical for tight language bindings and I've seen that with XML already.
json-simple is totally generic, handles any valid JSON, but you need to know how to find things in the nested Maps and Arrays, and you need to map values yourself.
I think JXPath + json-simple is a very flexible and powerful combination for nested structures.
No comments:
Post a Comment