We'll take back some ideas from A notification strategy for business errors and related comments by Johannes Brodwall.
We'll use the same concept of Notification object, but intended as a carrier of generic messages (not only errors).
So, let's start with some code.
First, we need a simple Message class.
public interface Message {
/**
* Set the message code.
* @param code The code to set.
*/
public void setCode(String code);
/**
* Get the message code.
* @return The message code.
*/
public String getCode();
/**
* Set the default message string of this message object.
* @param message The default message string.
*/
public void setDefaultMessage(String message);
/**
* Get the default message string of this message object.
* @return The default message string.
*/
public String getDefaultMessage();
}
It is very simple and contains just the message code (intended to be resolved using i18n), and a default message string.
We need also the Notification class, for carrying messages all around.
public interface Notification {
public enum Type { ERROR, WARNING, INFO };
/**
* Add a message.
* @param message The message to add.
* @param type The notification type of the message to add.
*/
public void addMessage(Message message, Notification.Type type);
/**
* Remove a message.
* @param message The message to remove.
* @param type The notification type of the message to remove.
* @return True if removed.
*/
public boolean removeMessage(Message message, Notification.Type type);
/**
* Get messages of the given type.
* @param type The type of messages to retrieve.
* @return An array of messages.
*/
public Message[] getMessages(Notification.Type type);
/**
* Check if this notification has messages of the given type.
* @param type The type of messages to look for.
* @return True if this notification has messages of the given type,
* false otherwise.
*/
public boolean hasMessages(Notification.Type type);
/**
* Get all messages contained in this notification.
* @return An array of messages.
*/
public Message[] getAllMessages();
/**
* Check if this notification has messages of whatever type.
* @return True if this notification has messages,
* false otherwise.
*/
public boolean hasMessages();
/**
* Add to this notification all the messages contained in the notification in argument.
* @param notification The notification whose messages must be added.
*/
public void addAllMessages(Notification notification);
}
The most important thing to note is that you can add generic messages and assign them a notification type: ERROR, WARNING or INFO.
This actually classify your messages and let clients do different actions based on the notification type.
How to integrate this with our CompositeSpecification?
First, let us modify the base Specification interface:
public interface Specification<O> {
/**
* Specification evaluation method.
*
* @param object The object to evaluate.
* @return True if satisfied, false otherwise.
*/
public boolean evaluate(O object);
/**
* Specification evaluation method, with a Notification object for collecting error messages.
*
* @param object The object to evaluate.
* @param notification A notification object where errors regarding specification evaluation will be put.<br>
* It can be left <code>null</code> if not used.
* @return True if satisfied, false otherwise.
*/
public boolean evaluate(O object, Notification notification);
/**
* Add a notification message to be notified by this specification.
*
* @param message The message to add.
* @param type The notification type of the message.
* @param whenSatisfied True if the message must be notified when the specification gets satisfied,
* false if notified when unsatisfied.
*/
public void addNotificationMessage(Message message, Notification.Type type, boolean whenSatisfied);
/**
* Remove a notification message.
*
* @param message The message to remove.
* @param type The notification type of the message.
* @param whenSatisfied True if the message was to be notified once satisfied,
* false otherwise.
* @return True if removed.
*/
public boolean removeNotificationMessage(Message message, Notification.Type type, boolean whenSatisfied);
}
Now you have:
- The old evaluate(Object o) method, to be used when you're not interested in notifications.
- A new evaluate(Object o, Notification n) method, where the Notification object in argument will collect message notifications fired by the specification.
- Te addNotificationMessage(Message message, Notification.Type type, boolean whenSatisfied); method: it is used not only to add the message notifications the specification can fire, but also to define their notification type and when to fire them.
Doing so, you can specify what (and what type of) message notifications to fire when the specification gets satisfied, and what to fire when not satisfied.
We've finally arrived to the new CompositeSpecification.
Here I'll show only the CompositeSpecification interface: I'm not going to show the complete code for managing Notifications because it would clutter all the blog entry.
All this stuff will be released soon as part of an Open Source project, so those interested will be able soon to take a look at the whole implementation.
Here is the refined CompositeSpecification interface:
public interface CompositeSpecification<S, O> extends Specification<O> {
/**
* Apply the logical <code>and</code> operator to this composite specification and another, supplied, composite specificaton.
* @param specification The other composite specification.
*/
public CompositeSpecification and(CompositeSpecification<S, O> specification);
/**
* Apply the logical <code>and</code> operator to this composite specification and the supplied one.
* @param specification The supplied specification to compose.
*/
public CompositeSpecification and(S specification);
/**
* Apply the logical, negated, <code>and</code> operator to this composite specification and another, supplied, composite specificaton.
* @param specification The other composite specification.
*/
public CompositeSpecification andNot(CompositeSpecification<S, O> specification);
/**
* Apply the logical, negated, <code>and</code> operator to this composite specification and the supplied one.
* @param specification The supplied specification to compose.
*/
public CompositeSpecification andNot(S specification);
/**
* Start composing the specification.<br>
* This is the first method to be called for composition.
*
* @param specification The actual specification to compose.
*/
public CompositeSpecification compose(S specification);
/**
* Add a notification message to the last composed specification.
* @param message The message to add.
* @param type The notification type of the message.
* @param whenSatisfied True if the message must be notified when the specification gets satisfied,
* false if notified when unsatisfied.
*/
public CompositeSpecification withMessage(Message message, Notification.Type type, boolean whenSatisfied);
/**
* Composite specification evaluation.
*
* @param object The object to evaluate.
*/
public boolean evaluate(O object);
/**
* Apply the logical <code>or</code> operator to this composite specification and another, supplied, composite specificaton.
* @param specification The other composite specification.
*/
public CompositeSpecification or(CompositeSpecification<S, O> specification);
/**
* Apply the logical <code>or</code> operator to this composite specification and the supplied one.
* @param specification The supplied specification to compose.
*/
public CompositeSpecification or(S specification);
/**
* Apply the logical, negated, <code>or</code> operator to this composite specification and another, supplied, composite specificaton.
* @param specification The other composite specification.
*/
public CompositeSpecification orNot(CompositeSpecification<S, O> specification);
/**
* Apply the logical, negated, <code>or</code> operator to this composite specification and the supplied one.
* @param specification The actual specification to compose.
*/
public CompositeSpecification orNot(S specification);
}
The most important things are:
- The evaluate(Object o, Notification n) method, inherited by the previously described Specification interface, to be used for collecting message notifications
from all the composed specifications. - The fluent-style CompositeSpecification withMessage(Message message, Notification.Type type, boolean whenSatisfied) method: it has to be used for defining a message notification fired by a composed specification (specifically, the last one added), its type and when it has to be fired.
So, considering our example classes (defined here), you can now write something like this:
CompositeSpecification<Example Specification> compositeSpecification = new CompositeSpecification<Specification>(ExampleSpecification.class, "isSatisfiedBy", IOffice.class);
OfficeIdSpecification spec1 = new OfficeIdSpecification();
FullOfficeSpecification spec2 = new FullOfficeSpecification();
IOffice office1 = new Office();
IEmployee emp1 = new Employee();
office1.setOfficeId("o1");
emp1.setMatriculationCode("1");
office1.addEmployee(emp1);
Notification notification = ...;
compositeSpecification.compose(spec1).withMessage(new Message(...), Notification.Type.ERROR, false)
.andNot(spec2).withMessage(new Message(...), Notification.Type.WARNING, true)
.evaluate(office1, notification);
Here is how you can read the composite specification declaration: compose the specification defining the office id structure, with an error message notification to be fired when not satisfied, and the specification defining when the office is full, with a warning message notification to be fired when satisfied.
After evaluation, you'll have your notification object filled with all message notifications fired by the composite specification (obviously, if any).
Suggestions and every type of feedback are welcome!
8 comments:
Hi!
I looked over the Message and Notification code. In my opinion, the Message should be a class and not an interface. And it should be immutable, passing the parameters to the constructor. Immutability is important here, because it helps preserving the original message. If one wants to modify it, it can do so by explicitly creating a new object, based on the first one, and possibly use a different message string.
I also think that the message type should be included in the message object. The source of the message knows if at that level the message is a simple warning, or an error. It is also simpler to carry the meaning (type) of a message that way, because it stays with the Message. In the end, one may investigate the message more thoroughly by looking at its code and make a decision. The message can be simply disregarded, or its type can be changed from ERROR to INFO, or whatever is appropriate. This also makes the method signatures simpler (not a great achievement, but can be counted). Not to mention that message type is more of an attribute than a behavior.
Abel A.
Hi!
I looked over the Message and Notification code. In my opinion, the Message should be a class and not an interface. And it should be immutable, passing the parameters to the constructor. Immutability is important here, because it helps preserving the original message. If one wants to modify it, it can do so by explicitly creating a new object, based on the first one, and possibly use a different message string.
I also think that the message type should be included in the message object. The source of the message knows if at that level the message is a simple warning, or an error. It is also simpler to carry the meaning (type) of a message that way, because it stays with the Message. In the end, one may investigate the message more thoroughly by looking at its code and make a decision. The message can be simply disregarded, or its type can be changed from ERROR to INFO, or whatever is appropriate. This also makes the method signatures simpler (not a great achievement, but can be counted). Not to mention that message type is more of an attribute than a behavior.
Abel A.
Hi Abel,
thanks for your reply.
> I looked over the Message and Notification
> code. In my opinion, the Message should
> be
> a class and not an interface. And it
> should be immutable
I tend to agree with you: maybe the Message should be immutable.
However, I think this doesn't prevent us to use an interface.
> I also think that the message type
> should be included in the message
> object. The source of the message knows
> if at that level the message is a simple
> warning, or an error.
I see your point and it is good.
However, I left the type out of the message because it is actually not a "message type": it is rather the type of the notification which the message is associated to.
So, I see it as a concept belonging to the association between Notifications and Messages (hence taking place into a Notification method). Moreover, doing so you can share the same message between different Notifications, assigning it different types.
What do you think?
Moreover, I'd like to know your opinion about the fluent interface, if you care.
Thanks much,
Regards,
Sergio B.
Here is how I see it. There are 2 main participants: the notifier and the observer. The central interface is Observer (or a similar name like Listener), and should contain methods to post messages. A postMessage(Message m) might be enough. This interface is the binding contract. The notifiers will have to implement a different interface with 2 main methods: addObserver(Observer o), removeObserver(Observer o).
The EventAggregator will implement the Observer interface, and will have to be registered as listener to those interested in posting events. It could be a singleton.
I do not see a reason for EventAggregator to have methods like getMessage(), because that means the EventAggregator becomes a message repository, and I don't think it is its purpose to keep messages.
I don't see a reason to introduce another interface for Message. This is a simple Value Object passed between the notifier and the observer. An interface for it won't do any help.
And I still believe that the Message type is part of the Message. Considering the addMessage(Message message, Notification.Type type) in your code, it is still the resonsibility of the notifier to provide a type, but instead of incapsulating the type in the Message, it passes it as an additional parameter. Your statement about message type makes sense if the notifier does not pass any type, and the Observer makes a decision on its type, but it is not so. The decision is still made by the notifier, so it makes no sense to separate the message type from Message.
What is "fluent interface"?
Best Regards,
Abel A.
Abel said ...
> Here is how I see it. There are 2 main
> participants: the notifier and the
> observer.
> [CUT]
Abel, your thoughts on designing an EventAggregator are interesting, but I think you are confusing my Notification class with your EventAggregator/Observer.
My Notification is a container/carrier of messages (of different types) generated by the same object.
It is the object that should be get notified TO the EventAggregator ... and it is NOT the EventAggregator itself.
I hope to explore the Event Aggregator pattern in depth in my next technical post.
> And I still believe that the Message
> type is part of the Message. Considering
> the addMessage(Message message,
> Notification.Type type) in your code, it
> is still the resonsibility of the
> notifier to provide a type, but instead
> of incapsulating the type in the
> Message, it passes it as an additional
> parameter. Your statement about message
> type makes sense if the notifier does
> not pass any type, and the Observer
> makes a decision on its type, but it is
> not so. The decision is still made by
> the notifier, so it makes no sense to
> separate the message type from Message.
Now I better see your point.
If so, the Notification.Type should be renamed and moved to Message.Type.
> What is "fluent interface"?
It is a more declarative and readable way of designing interfaces, which when applied to particular domains creates a sort of Domain Specific Language; take a look at this article by Martin Fowler.
Regards,
Sergio B.
> My Notification is a container/carrier of messages (of different types) generated by the same object.
> It is the object that should be get notified TO the EventAggregator ... and it is NOT the EventAggregator itself.
I see now what you mean. Thinking about notifications, I can't notice their short transitory lives. Their purpose is to inform someone about an event that took place. They can be logged, so one can investigate later what happened, but usually there is no need to keep them around. I don't see a reason to save them. Also, consider if you can't just skip the Notification, and send the Message directly to the Aggregator. Is it really necessary to have an intermediary? If there is a need to have some domain logic to process messages before they are sent to other subsystems, it can be done in the Aggregator based on their code and type.
I read about fluent interface and I remembered the old days when I learned Prolog in university. It's a nice way to accomplish complex things. Not many developers have what it takes to create such powerful abstractions. Fluent interface = Think more, type less.
Abel
> I see now what you mean.
> Thinking about notifications
> [CUT]
> I don't see a reason to save them. Also,
> consider if you can't just skip the
> Notification, and send the Message
> directly to the Aggregator. Is it really
> necessary to have an intermediary?
The Notification object is not here for simply storing Messages.
It is here for collecting various messages of various types fired by the same object, and carrying them wherever the client wants.
Notifications are not coupled to the EventAggregator: clients could use them without sending to the Aggregator.
So, again, Notification objects are IMO necessary for collecting messages and carrying them as a whole, because collecting and carrying messages without a specialized container object would be pain.
> I read about fluent interface
> [CUT]
> Not many developers have what it takes
> to create such powerful abstractions.
Yes, designing fluent interfaces may be difficult, but for client developers, once they get confident with the style, I think fluent APIs are a lot easier to use.
Regards,
Sergio B.
Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!
Post a Comment