Description
Landscape
It is possible to subscribe to a rejection which is caused by some command:
public class CancellationWatch extends AbstractEventSubscriber {
@Subscribe
void on(Rejections.OrderAlreadyCompleted r, CancelOrder c) {
...
}
}
In this example, a subscriber wants to consume only those OrderAlreadyCompleted
rejections which are thrown while handling order cancellations (hence, CancelOrder
command is the second argument).
At the same time, it is also possible to declare several command handlers which would throw a rejection of the same type:
public class OrderAggregate extends Aggregate<OrderId, Order, Order.Builder> {
@Assign
OrderCompleted handle(CompleteOrder c) throws OrderAlreadyCompleted {
...
}
@Assign
OrderCancelled handle(CancelOrder c) throws OrderAlreadyCompleted {
...
}
}
In the example above, the OrderAggregate
rejects all commands with OrderAlreadyCompleted
if the order is already completed.
Action
Now, if some completed order handles CompleteOrder
command, the aggregate throws a OrderAlreadyCompleted
rejection. In theory, if anyone has subscribed to this rejection, they would receive it.
But, one thing for sure, CancellationWatch
is not interested in this rejection—because the origin command is not what it expects.
So if no subscribers to OrderAlreadyCompleted
rejection is found, it should just pass through the event bus, just as any other rejection or event.
Issue
In reality, the framework attempts to select the handler for this rejection anyway, and fails to do so:
java.lang.IllegalStateException: Unable to find handler with the key: DispatchKey{messageClass=`com.acme.order.Rejections$OrderAlreadyCompleted}.
at com.google.common.base.Preconditions.checkState(Preconditions.java:589)
at io.spine.server.model.HandlerMap.handlersOf(HandlerMap.java:145)
at io.spine.server.model.HandlerMap.handlersOf(HandlerMap.java:174)
at io.spine.server.event.model.EventReceivingClassDelegate.handlersOf(EventReceivingClassDelegate.java:127)
at io.spine.server.event.model.EventSubscriberClass.subscribersOf(EventSubscriberClass.java:79)
at io.spine.server.event.model.SubscribingClass.subscriberOf(SubscribingClass.java:47)
at io.spine.server.event.AbstractEventSubscriber.canDispatch(AbstractEventSubscriber.java:140)
at io.spine.server.event.AbstractEventSubscriber.canDispatch(AbstractEventSubscriber.java:60)
at io.spine.server.bus.DispatcherRegistry.lambda$dispatchersOf$0(DispatcherRegistry.java:105)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
at java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1556)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
at io.spine.server.bus.DispatcherRegistry.dispatchersOf(DispatcherRegistry.java:107)
at io.spine.server.bus.DeadMessageFilter.filter(DeadMessageFilter.java:56)
at io.spine.server.bus.FilterChain.filter(FilterChain.java:57)
at io.spine.server.bus.Bus.filter(Bus.java:296)
at io.spine.server.bus.Bus.filter(Bus.java:272)
at io.spine.server.bus.Bus.filterAndPost(Bus.java:150)
at io.spine.server.bus.Bus.post(Bus.java:146)
The reason is as follows. From the dispatcher registry, the framework knows that someone does handle the rejections of this type. And from this point, it is going to get that someone. There is no option to discover that in fact that "someone" does not match the "origin message" criterion.