Skip to content
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

Allow optional command generation on event #1114

Merged
merged 9 commits into from
Jul 15, 2019
2 changes: 1 addition & 1 deletion config
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package io.spine.server.aggregate;

import com.google.protobuf.Message;
import io.spine.annotation.Experimental;
import io.spine.annotation.Internal;
import io.spine.protobuf.ValidatingBuilder;
import io.spine.reflect.GenericTypeIndex;
Expand Down Expand Up @@ -58,6 +59,7 @@
* the type of the aggregate root
* @see Aggregate
*/
@Experimental
public abstract class AggregatePart<I,
S extends Message,
B extends ValidatingBuilder<S>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package io.spine.server.aggregate;

import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper;
import io.spine.annotation.Experimental;
import io.spine.annotation.Internal;
import io.spine.server.BoundedContext;
import io.spine.server.aggregate.model.AggregatePartClass;
Expand All @@ -37,6 +38,7 @@
* @param <R>
* the type of the aggregate root associated with the type of parts
*/
@Experimental
public abstract class AggregatePartRepository<I,
A extends AggregatePart<I, ?, ?, R>,
R extends AggregateRoot<I>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package io.spine.server.aggregate;

import com.google.protobuf.Message;
import io.spine.annotation.Experimental;
import io.spine.server.BoundedContext;

import java.util.Optional;
Expand All @@ -33,6 +34,7 @@
*
* @param <I> the type for IDs of this class of aggregates
*/
@Experimental
public class AggregateRoot<I> {

/** The {@code BoundedContext} to which the aggregate belongs. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package io.spine.server.aggregate;

import com.google.protobuf.Message;
import io.spine.annotation.Experimental;
import io.spine.annotation.SPI;

import java.util.Optional;
Expand All @@ -31,6 +32,7 @@
* <p>In the directory, the aggregate root is represented by its type and the parts - by their
* repositories.
*/
@Experimental
@SPI
public interface AggregateRootDirectory {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected Set<Function<Method, MethodAttribute<?>>> attributeSuppliers() {
}

@Override
public EventClass getMessageClass() {
public EventClass messageClass() {
return EventClass.from(rawMessageClass());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public abstract class CommandAcceptingMethod<T extends EventProducer,
}

@Override
public CommandClass getMessageClass() {
public CommandClass messageClass() {
return CommandClass.from(rawMessageClass());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import io.spine.server.type.CommandClass;
import io.spine.server.type.CommandEnvelope;
import io.spine.server.type.EventClass;
import io.spine.server.type.MessageEnvelope;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.lang.reflect.Method;
Expand All @@ -55,7 +54,7 @@ public final class CommandHandlerMethod
@Override
public Success toSuccessfulOutcome(@Nullable Object rawResult,
CommandHandler target,
MessageEnvelope<?, ?, ?> handledSignal) {
CommandEnvelope handledSignal) {
Success outcome = EventProducingMethod.super.toSuccessfulOutcome(rawResult, target,
handledSignal);
if (outcome.getProducedEvents().getEventCount() == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package io.spine.server.command.model;

import io.spine.base.EventMessage;
import io.spine.server.dispatch.Success;
import io.spine.server.event.EventReceiver;
import io.spine.server.model.AbstractHandlerMethod;
import io.spine.server.model.declare.ParameterSpec;
Expand All @@ -46,7 +47,17 @@ public final class CommandReactionMethod
}

@Override
public EventClass getMessageClass() {
public EventClass messageClass() {
return EventClass.from(rawMessageClass());
}

/**
* Returns an empty {@code Success} instance which means that the method ignored
* the incoming event.
*/
@Override
public Success fromEmpty(EventEnvelope handledSignal) {
return Success.newBuilder()
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@
package io.spine.server.command.model;

import io.spine.server.command.CommandReceiver;
import io.spine.server.dispatch.Success;
import io.spine.server.model.IllegalOutcomeException;
import io.spine.server.model.declare.ParameterSpec;
import io.spine.server.type.CommandClass;
import io.spine.server.type.CommandEnvelope;

import java.lang.reflect.Method;

import static java.lang.String.format;

/**
* A method that produces one or more command messages in response to an incoming command.
*/
Expand All @@ -37,4 +41,21 @@ public final class CommandSubstituteMethod
CommandSubstituteMethod(Method method, ParameterSpec<CommandEnvelope> paramSpec) {
super(method, paramSpec);
}

/**
* Always throws {@code IllegalOutcomeException} because command substitution method must
* produce a new command in response to incoming.
*
* @return nothing ever
* @throws IllegalOutcomeException always
*/
@Override
public Success fromEmpty(CommandEnvelope handledSignal) {
String errorMessage = format(
"Commander method `%s` did not produce any result for command with ID `%s`.",
this,
handledSignal.id()
);
throw new IllegalOutcomeException(errorMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ protected void handle(EventEnvelope event) {
private DispatchOutcome notSupported(EventEnvelope event) {
Ignore ignore = Ignore
.newBuilder()
.setReason(format("Event %s[%s] does not match subscriber filters in %s.",
.setReason(format("Event `%s[%s]` does not match subscriber filters in `%s`.",
event.messageClass(),
event.id().value(),
this.getClass().getCanonicalName()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ protected EventAcceptingMethodParams parameterSpec() {

@Override
public HandlerId id() {
EventClass eventClass = getMessageClass();
EventClass eventClass = messageClass();
if (!parameterSpec().isAwareOfCommandType()) {
return createId(eventClass);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public final class EventReactorMethod
}

@Override
public EventClass getMessageClass() {
public EventClass messageClass() {
return EventClass.from(rawMessageClass());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public HandlerId id() {
}

@Override
public EventClass getMessageClass() {
public EventClass messageClass() {
return EventClass.from(rawMessageClass());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ public String getFullName() {

@Override
public HandlerId id() {
return Handlers.createId(getMessageClass());
return Handlers.createId(messageClass());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private void checkNotRemembered(H handler) {
}

private void checkNotClashes(H handler) {
MessageClass handledClass = handler.getMessageClass();
MessageClass handledClass = handler.messageClass();
FieldPath field = handler.filter().getField();
if (isNotDefault(field)) {
FilteredHandler<H> previousValue = fieldFilters.put(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@

import java.util.List;

import static java.lang.String.format;
import static java.util.stream.Collectors.toList;

/**
Expand All @@ -54,29 +53,32 @@ public interface CommandProducingMethod<T,
E extends MessageEnvelope<?, ?, ?>>
extends HandlerMethod<T, C, E, CommandClass> {

/**
* Creates success result from the empty result of the method execution.
*
* @param handledSignal
* the signal passed to the method
* @throws IllegalOutcomeException
* if the method is not allowed to return empty result
*/
Success fromEmpty(E handledSignal);

@Override
default Success toSuccessfulOutcome(@Nullable Object rawResult,
T target,
MessageEnvelope<?, ?, ?> handledSignal) {
default Success toSuccessfulOutcome(@Nullable Object rawResult, T target, E handledSignal) {
MethodResult result = MethodResult.from(rawResult);
ActorContext actorContext = handledSignal.asMessageOrigin()
.getActorContext();
if (result.isEmpty()) {
return fromEmpty(handledSignal);
}
ActorContext actorContext =
handledSignal.asMessageOrigin()
.getActorContext();
CommandFactory commandFactory = ActorRequestFactory
.fromContext(actorContext)
.command();
List<Command> commands = result
.messages(CommandMessage.class)
List<Command> commands = result.messages(CommandMessage.class)
.stream()
.map(commandFactory::create)
.collect(toList());
if (commands.isEmpty()) {
String errorMessage = format(
"Commander method `%s` did not produce any result for command with ID %s.",
this,
handledSignal.id()
);
throw new IllegalOutcomeException(errorMessage);
}
ProducedCommands signals = ProducedCommands
.newBuilder()
.addAllCommand(commands)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ public interface EventProducingMethod<T extends EventProducer,
extends HandlerMethod<T, C, E, EventClass> {

@Override
default Success toSuccessfulOutcome(@Nullable Object rawResult,
T target,
MessageEnvelope<?, ?, ?> handledSignal) {
default Success toSuccessfulOutcome(@Nullable Object rawResult, T target, E handledSignal) {
MethodResult result = MethodResult.from(rawResult);
EventFactory eventFactory = EventFactory.on(handledSignal, target.producerId());
Version version = target.version();
Expand Down
6 changes: 2 additions & 4 deletions server/src/main/java/io/spine/server/model/HandlerMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public interface HandlerMethod<T,
/**
* Obtains the type of the incoming message class.
*/
C getMessageClass();
C messageClass();

@PostConstruct
void discoverAttributes();
Expand Down Expand Up @@ -95,9 +95,7 @@ public interface HandlerMethod<T,
* @throws IllegalOutcomeException
* if the method produced result of an unexpected format
*/
Success toSuccessfulOutcome(@Nullable Object rawResult,
T target,
MessageEnvelope<?, ?, ?> handledSignal)
Success toSuccessfulOutcome(@Nullable Object rawResult, T target, E handledSignal)
throws IllegalOutcomeException;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ private H checkSingle(Collection<H> handlers, M targetType) {
private static <M extends MessageClass, H extends HandlerMethod<?, M, ?, ?>>
ImmutableSet<M> messageClasses(Iterable<H> handlerMethods) {
ImmutableSet<M> result = Streams.stream(handlerMethods)
.map(HandlerMethod::getMessageClass)
.map(HandlerMethod::messageClass)
.collect(toImmutableSet());
return result;
}
Expand Down
7 changes: 7 additions & 0 deletions server/src/main/java/io/spine/server/model/MethodResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ private MethodResult(ImmutableList<Message> messages) {
this.messages = messages;
}

/**
* Tells if this result is empty.
*/
boolean isEmpty() {
return messages.isEmpty();
}

static MethodResult from(@Nullable Object rawMethodOutput) {
List<Message> messages = toMessages(rawMethodOutput);
ImmutableList<Message> filtered = filterIgnored(messages);
Expand Down
4 changes: 1 addition & 3 deletions server/src/main/java/io/spine/server/model/VoidMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ public interface VoidMethod<T,
extends HandlerMethod<T, C, E, EmptyClass> {

@Override
default Success toSuccessfulOutcome(@Nullable Object result,
T target,
MessageEnvelope<?, ?, ?> handledSignal) {
default Success toSuccessfulOutcome(@Nullable Object result, T target, E handledSignal) {
if (result != null) {
String errorMessage = format(
"Method `%s` should NOT produce any result. Produced: %s",
Expand Down
7 changes: 7 additions & 0 deletions server/src/main/proto/spine/server/dispatch/dispatching.proto
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ message DispatchOutcome {
}

// A successful signal handling result.
//
// This message may be empty if:
// 1. The handling method does not return a result by its nature (e.g. a subscribing method), or
// 2. The method returns `Nothing` (wrapped inside a tuple) because the entity ignores the signal.
// For example, a reacting method or a commanding method may chose not to product outgoing
// signal in response to incoming event.
//
message Success {

oneof exhaust {
Expand Down
Loading