-
Notifications
You must be signed in to change notification settings - Fork 32
Refactor notification center to use generics. #288
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
Conversation
Pull Request Test Coverage Report for Build 997
💛 - Coveralls |
|
This is failing compatibility-suite changes since I removed the explicit DecisionNotificationListener class and methods for a more extensible pattern. This should be resolved with updates to the test-app. |
aliabbasrizvi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
We will have to update documentation here: https://docs.developers.optimizely.com/full-stack/docs/register-notification-listeners
Work is tracked in OASIS-4583
|
|
||
| public int addHandler(NotificationHandler<T> newHandler) { | ||
|
|
||
| // Prevent registering a duplication listener. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit. duplicate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to flip the map to be Map<NotificationHandler, Int>?
| } | ||
|
|
||
| /** | ||
| * Convenience method for adding TrackNotification Handlers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding DecisionNotification handlers
| import com.optimizely.ab.event.LogEvent; | ||
|
|
||
| import javax.annotation.Nonnull; | ||
| import java.rmi.activation.ActivateFailedException; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed? I am not seeing it used below
| package com.optimizely.ab.notification; | ||
|
|
||
| /** | ||
| * Interface used to identify supported annotations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Supported notifications?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are going to identify the type of notification by its Class object, we should could do a little more to restrict the type hierarchy. Can we make the Notification implementations declared here final? it wouldn't make sense AFAICT to want to/be able to extend those three classes.
Also, since there are a closed set of notification types that the system supports, the Notification is ideally not public.
I think the subtleties of restricted type hierarchy is more clearly conveyed if the type family is isolated to a single file or package.
loganlinn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should try to add comments everywhere there’s a @Deprecated annotation to describe the canonical/preferred alternative
|
|
||
| public int addHandler(NotificationHandler<T> newHandler) { | ||
|
|
||
| // Prevent registering a duplication listener. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to flip the map to be Map<NotificationHandler, Int>?
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(NotificationManager.class); | ||
|
|
||
| private final Map<Integer, NotificationHandler<T>> handlers = new HashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use LinkedHashMap so there’s consistent order in handler invocation?
| } | ||
|
|
||
| @Override | ||
| public final void notify(ActivateNotification message) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it doesn’t break public API compatibility, it would nice to not be overloading Object.notify anymore...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point I can change that. Suggestion? handle? since the new interface is NotificationHandler<T>
| private final AtomicInteger counter; | ||
|
|
||
| public NotificationManager(AtomicInteger counter) { | ||
| this.counter = counter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems odd for this constructor to be public; could use a null check if it remains public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made package private
| @SuppressWarnings("unchecked") | ||
| public <T> NotificationManager<T> getNotificationManager(Class<? extends Notification> clazz) { | ||
| NotificationManager<T> newManager = new NotificationManager<>(counter); | ||
| NotificationManager<T> manager = (NotificationManager<T>) notifierMap.putIfAbsent(clazz, newManager); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use computeIfAbsent() to avoid creating throw-away instance when manager exists? Alternatively, the set of notification types is (all likely will remain) trivially small, so could just pre-populate the map.
| decisionListenerHolder.clear(); | ||
| for (NotificationType type : NotificationType.values()) { | ||
| clearNotificationListeners(type); | ||
| for (NotificationManager<?> manager : notifierMap.values()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: just clearing the entire notifierMap would be more consistent with the current scheme of lazy population used with putIfAbsent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: We're now creating a unmodifiable map containing all possible NotificationManager types, so clearing the individual managers is required.
| public void send(Notification notification) { | ||
| NotificationManager handler = notifierMap.get(notification.getClass()); | ||
| if (handler == null) { | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems worth logging a warn or error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced with an OptimizelyRuntimeException since "we" control the notifications and should be catching this in our CI
| package com.optimizely.ab.notification; | ||
|
|
||
| public interface NotificationHandler<T> { | ||
| void notify(T message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a comment elsewhere but I’ll leave one here too: not a great name for any Java method as every Object has notify() for very different purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still need to do this.... sticking with "handle" since no other option has been proposed :)
|
Passing custom build: https://travis-ci.com/optimizely/fullstack-sdk-compatibility-suite/jobs/197018131 |
|
@loganlinn as always, thanks for the feedback:) I ended up defining an unmodifiable Map of allowable notification types to control access in lieu of the |
|
Passing build after new changes here: https://travis-ci.com/optimizely/fullstack-sdk-compatibility-suite/builds/110421443 |
Summary
Notificationinterface to mark supported notification models.NotificationHandler<T>interfaceNotifcationManager<T>class to own specific class of notifications.NotificationManagerinstances.ActivateNotificationclassActivateNotificationListenerto implementNotificationHandler<ActivateNotification>TrackNotificationclassTrackNotificationListenerto implementNotificationHandler<TrackNotification>The current patterns surrounding the
NotificationCenter did not extend well and relied onObject...array arguments into theNotificationListenerinterface. This resulted in the need for dedicated abstract classes, enums and helper methods to be implemented for each notification. Moving to a genericNotificationHandler<T>interface andNotifcationManager<T>means that new notifications can be defined simply by implementing a newNotificationmodel which is type safe, discoverable and self describing.Test plan
Unit and integration testing along with the compatibility suite.