diff --git a/baas-node-client/src/main/scala/com/ing/baker/baas/javadsl/BakerClient.scala b/baas-node-client/src/main/scala/com/ing/baker/baas/javadsl/BakerClient.scala new file mode 100644 index 000000000..eab67fe02 --- /dev/null +++ b/baas-node-client/src/main/scala/com/ing/baker/baas/javadsl/BakerClient.scala @@ -0,0 +1,13 @@ +package com.ing.baker.baas.javadsl + +import akka.actor.ActorSystem +import akka.stream.Materializer +import com.ing.baker.baas.scaladsl.{BakerClient => ScalaRemoteBaker} +import com.ing.baker.runtime.javadsl.{Baker => JavaBaker} +import com.ing.baker.runtime.serialization.Encryption + +object BakerClient { + + def build(hostname: String, actorSystem: ActorSystem, mat: Materializer, encryption: Encryption = Encryption.NoEncryption): JavaBaker = + new JavaBaker(ScalaRemoteBaker.build(hostname, encryption)(actorSystem, mat)) +} diff --git a/baas-node-client/src/main/scala/com/ing/baker/baas/scaladsl/BakerClient.scala b/baas-node-client/src/main/scala/com/ing/baker/baas/scaladsl/BakerClient.scala new file mode 100644 index 000000000..be2194fb0 --- /dev/null +++ b/baas-node-client/src/main/scala/com/ing/baker/baas/scaladsl/BakerClient.scala @@ -0,0 +1,337 @@ +package com.ing.baker.baas.scaladsl + +import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.http.scaladsl.marshalling.Marshal +import akka.http.scaladsl.model.Uri.Path +import akka.http.scaladsl.model.{HttpMethods, HttpRequest, MessageEntity, Uri} +import akka.stream.Materializer +import com.ing.baker.baas.protocol.BaaSProtocol +import com.ing.baker.baas.protocol.BaaSProto._ +import com.ing.baker.baas.protocol.MarshallingUtils._ +import com.ing.baker.il.{CompiledRecipe, RecipeVisualStyle} +import com.ing.baker.runtime.common.SensoryEventStatus +import com.ing.baker.runtime.scaladsl.{BakerEvent, EventInstance, EventMoment, EventResolutions, InteractionInstance, RecipeEventMetadata, RecipeInformation, RecipeInstanceMetadata, RecipeInstanceState, SensoryEventResult, Baker => ScalaBaker} +import com.ing.baker.runtime.serialization.{Encryption, SerializersProvider} +import com.ing.baker.types.Value + +import scala.concurrent.Future + +object BakerClient { + + def build(hostname: String, encryption: Encryption = Encryption.NoEncryption)(implicit system: ActorSystem, mat: Materializer) = + BakerClient(Uri(hostname), encryption) +} + +case class BakerClient(hostname: Uri, encryption: Encryption = Encryption.NoEncryption)(implicit system: ActorSystem, mat: Materializer) extends ScalaBaker { + + import system.dispatcher + + implicit val serializersProvider: SerializersProvider = + SerializersProvider(system, encryption) + + val root: Path = Path./("api")./("v3") + + def withPath(path: Path): Uri = hostname.withPath(path) + + /** + * Adds a recipe to baker and returns a recipeId for the recipe. + * + * This function is idempotent, if the same (equal) recipe was added earlier this will return the same recipeId + * + * @param compiledRecipe The compiled recipe. + * @return A recipeId + */ + override def addRecipe(compiledRecipe: CompiledRecipe): Future[String] = + for { + encoded <- Marshal(BaaSProtocol.AddRecipeRequest(compiledRecipe)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("addRecipe")), entity = encoded) + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.AddRecipeResponse](response).withBakerExceptions + } yield decoded.recipeId + + /** + * Returns the recipe information for the given RecipeId + * + * @param recipeId + * @return + */ + override def getRecipe(recipeId: String): Future[RecipeInformation] = + for { + encoded <- Marshal(BaaSProtocol.GetRecipeRequest(recipeId)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("getRecipe")), entity = encoded) + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.GetRecipeResponse](response).withBakerExceptions + } yield decoded.recipeInformation + + /** + * Returns all recipes added to this baker instance. + * + * @return All recipes in the form of map of recipeId -> CompiledRecipe + */ + override def getAllRecipes: Future[Map[String, RecipeInformation]] = { + val request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("getAllRecipes"))) + for { + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.GetAllRecipesResponse](response).withBakerExceptions + } yield decoded.map + } + + /** + * Creates a process instance for the given recipeId with the given RecipeInstanceId as identifier + * + * @param recipeId The recipeId for the recipe to bake + * @param recipeInstanceId The identifier for the newly baked process + * @return + */ + override def bake(recipeId: String, recipeInstanceId: String): Future[Unit] = + for { + encoded <- Marshal(BaaSProtocol.BakeRequest(recipeId, recipeInstanceId)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("bake")), entity = encoded) + response <- Http().singleRequest(request) + _ <- unmarshalBakerExceptions(response) + } yield () + + /** + * Notifies Baker that an event has happened and waits until the event was accepted but not executed by the process. + * + * Possible failures: + * `NoSuchProcessException` -> When no process exists for the given id + * `ProcessDeletedException` -> If the process is already deleted + * + * @param recipeInstanceId The process identifier + * @param event The event object + * @param correlationId Id used to ensure the process instance handles unique events + */ + override def fireEventAndResolveWhenReceived(recipeInstanceId: String, event: EventInstance, correlationId: Option[String]): Future[SensoryEventStatus] = + for { + encoded <- Marshal(BaaSProtocol.FireEventAndResolveWhenReceivedRequest(recipeInstanceId, event, correlationId)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("fireEventAndResolveWhenReceived")), entity = encoded) + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.FireEventAndResolveWhenReceivedResponse](response).withBakerExceptions + } yield decoded.sensoryEventStatus + + /** + * Notifies Baker that an event has happened and waits until all the actions which depend on this event are executed. + * + * Possible failures: + * `NoSuchProcessException` -> When no process exists for the given id + * `ProcessDeletedException` -> If the process is already deleted + * + * @param recipeInstanceId The process identifier + * @param event The event object + * @param correlationId Id used to ensure the process instance handles unique events + */ + override def fireEventAndResolveWhenCompleted(recipeInstanceId: String, event: EventInstance, correlationId: Option[String]): Future[SensoryEventResult] = + for { + encoded <- Marshal(BaaSProtocol.FireEventAndResolveWhenCompletedRequest(recipeInstanceId, event, correlationId)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("fireEventAndResolveWhenCompleted")), entity = encoded) + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.FireEventAndResolveWhenCompletedResponse](response).withBakerExceptions + } yield decoded.sensoryEventResult + + /** + * Notifies Baker that an event has happened and waits until an specific event has executed. + * + * Possible failures: + * `NoSuchProcessException` -> When no process exists for the given id + * `ProcessDeletedException` -> If the process is already deleted + * + * @param recipeInstanceId The process identifier + * @param event The event object + * @param onEvent The name of the event to wait for + * @param correlationId Id used to ensure the process instance handles unique events + */ + override def fireEventAndResolveOnEvent(recipeInstanceId: String, event: EventInstance, onEvent: String, correlationId: Option[String]): Future[SensoryEventResult] = + for { + encoded <- Marshal(BaaSProtocol.FireEventAndResolveOnEventRequest(recipeInstanceId, event, onEvent, correlationId)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("fireEventAndResolveOnEvent")), entity = encoded) + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.FireEventAndResolveOnEventResponse](response).withBakerExceptions + } yield decoded.sensoryEventResult + + /** + * Notifies Baker that an event has happened and provides 2 async handlers, one for when the event was accepted by + * the process, and another for when the event was fully executed by the process. + * + * Possible failures: + * `NoSuchProcessException` -> When no process exists for the given id + * `ProcessDeletedException` -> If the process is already deleted + * + * @param recipeInstanceId The process identifier + * @param event The event object + * @param correlationId Id used to ensure the process instance handles unique events + */ + override def fireEvent(recipeInstanceId: String, event: EventInstance, correlationId: Option[String]): EventResolutions = { + for { + encoded <- Marshal(BaaSProtocol.FireEventRequest(recipeInstanceId, event, correlationId)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("fireEvent")), entity = encoded) + response <- Http().singleRequest(request) + //decoded <- unmarshal(response)[BaaSProtocol.???] TODO f.withBakerExceptionsigure out what to do on this situation with the two futures + } yield () //decoded.recipeInformation + ??? + } + + /** + * Returns an index of all running processes. + * + * Can potentially return a partial index when baker runs in cluster mode + * and not all shards can be reached within the given timeout. + * + * Does not include deleted processes. + * + * @return An index of all processes + */ + override def getAllRecipeInstancesMetadata: Future[Set[RecipeInstanceMetadata]] = { + val request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("getAllRecipeInstancesMetadata"))) + for { + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.GetAllRecipeInstancesMetadataResponse](response).withBakerExceptions + } yield decoded.set + } + + /** + * Returns the process state. + * + * @param recipeInstanceId The process identifier + * @return The process state. + */ + override def getRecipeInstanceState(recipeInstanceId: String): Future[RecipeInstanceState] = + for { + encoded <- Marshal(BaaSProtocol.GetRecipeInstanceStateRequest(recipeInstanceId)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("getRecipeInstanceState")), entity = encoded) + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.GetRecipeInstanceStateResponse](response).withBakerExceptions + } yield decoded.recipeInstanceState + + /** + * Returns all provided ingredients for a given RecipeInstance id. + * + * @param recipeInstanceId The process id. + * @return The provided ingredients. + */ + override def getIngredients(recipeInstanceId: String): Future[Map[String, Value]] = + getRecipeInstanceState(recipeInstanceId).map(_.ingredients) + + /** + * Returns all fired events for a given RecipeInstance id. + * + * @param recipeInstanceId The process id. + * @return The events + */ + override def getEvents(recipeInstanceId: String): Future[Seq[EventMoment]] = + getRecipeInstanceState(recipeInstanceId).map(_.events) + + /** + * Returns all names of fired events for a given RecipeInstance id. + * + * @param recipeInstanceId The process id. + * @return The event names + */ + override def getEventNames(recipeInstanceId: String): Future[Seq[String]] = + getRecipeInstanceState(recipeInstanceId).map(_.events.map(_.name)) + + /** + * Returns the visual state (.dot) for a given process. + * + * @param recipeInstanceId The process identifier. + * @return A visual (.dot) representation of the process state. + */ + override def getVisualState(recipeInstanceId: String, style: RecipeVisualStyle): Future[String] = + for { + encoded <- Marshal(BaaSProtocol.GetVisualStateRequest(recipeInstanceId)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("getVisualState")), entity = encoded) + response <- Http().singleRequest(request) + decoded <- unmarshal[BaaSProtocol.GetVisualStateResponse](response).withBakerExceptions + } yield decoded.state + + /** + * Registers a listener to all runtime events for recipes with the given name run in this baker instance. + * + * Note that the delivery guarantee is *AT MOST ONCE*. Do not use it for critical functionality + */ + override def registerEventListener(recipeName: String, listenerFunction: (RecipeEventMetadata, EventInstance) => Unit): Future[Unit] = + throw new NotImplementedError("registerEventListener is not implemented for client bakers") + + /** + * Registers a listener to all runtime events for all recipes that run in this Baker instance. + * + * Note that the delivery guarantee is *AT MOST ONCE*. Do not use it for critical functionality + */ + override def registerEventListener(listenerFunction: (RecipeEventMetadata, EventInstance) => Unit): Future[Unit] = + throw new NotImplementedError("registerEventListener is not implemented for client bakers") + + /** + * Registers a listener function that listens to all BakerEvents + * + * Note that the delivery guarantee is *AT MOST ONCE*. Do not use it for critical functionality + * + * @param listenerFunction + * @return + */ + override def registerBakerEventListener(listenerFunction: BakerEvent => Unit): Future[Unit] = + throw new NotImplementedError("registerBakerEventListener is not implemented for client bakers") + + /** + * Adds an interaction implementation to baker. + * + * @param implementation The implementation object + */ + override def addInteractionInstance(implementation: InteractionInstance): Future[Unit] = + throw new NotImplementedError("addInteractionInstance is not implemented for client bakers, instances are deployed independently, please view the documentation") + + /** + * Adds a sequence of interaction implementation to baker. + * + * @param implementations The implementation object + */ + override def addInteractionInstances(implementations: Seq[InteractionInstance]): Future[Unit] = + throw new NotImplementedError("addInteractionInstances is not implemented for client bakers, instances are deployed independently, please view the documentation") + + /** + * Attempts to gracefully shutdown the baker system. + */ + override def gracefulShutdown(): Future[Unit] = + system.terminate().map(_ => ()) + + /** + * Retries a blocked interaction. + * + * @return + */ + override def retryInteraction(recipeInstanceId: String, interactionName: String): Future[Unit] = + for { + encoded <- Marshal(BaaSProtocol.RetryInteractionRequest(recipeInstanceId, interactionName)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("retryInteraction")), entity = encoded) + response <- Http().singleRequest(request) + _ <- unmarshalBakerExceptions(response) + } yield () + + /** + * Resolves a blocked interaction by specifying it's output. + * + * !!! You should provide an event of the original interaction. Event / ingredient renames are done by Baker. + * + * @return + */ + override def resolveInteraction(recipeInstanceId: String, interactionName: String, event: EventInstance): Future[Unit] = + for { + encoded <- Marshal(BaaSProtocol.ResolveInteractionRequest(recipeInstanceId, interactionName, event)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("resolveInteraction")), entity = encoded) + response <- Http().singleRequest(request) + _ <- unmarshalBakerExceptions(response) + } yield () + + /** + * Stops the retrying of an interaction. + * + * @return + */ + override def stopRetryingInteraction(recipeInstanceId: String, interactionName: String): Future[Unit] = + for { + encoded <- Marshal(BaaSProtocol.StopRetryingInteractionRequest(recipeInstanceId, interactionName)).to[MessageEntity] + request = HttpRequest(method = HttpMethods.POST, uri = withPath(root./("stopRetryingInteraction")), entity = encoded) + response <- Http().singleRequest(request) + _ <- unmarshalBakerExceptions(response) + } yield () +} diff --git a/baas-node-event-listener/src/main/scala/com/ing/baker/baas/akka/EventListenerAgent.scala b/baas-node-event-listener/src/main/scala/com/ing/baker/baas/akka/EventListenerAgent.scala new file mode 100644 index 000000000..6a4080ee2 --- /dev/null +++ b/baas-node-event-listener/src/main/scala/com/ing/baker/baas/akka/EventListenerAgent.scala @@ -0,0 +1,36 @@ +package com.ing.baker.baas.akka + +import akka.actor.{Actor, ActorRef, Props} +import akka.cluster.pubsub.{DistributedPubSub, DistributedPubSubMediator} +import com.ing.baker.baas.protocol.ProtocolDistributedEventPublishing +import com.ing.baker.runtime.scaladsl.{EventInstance, RecipeEventMetadata} + +object EventListenerAgent { + + case object CommitTimeout + + def apply(recipeName: String, listenerFunction: (RecipeEventMetadata, EventInstance) => Unit): Props = + Props(new EventListenerAgent(recipeName, listenerFunction)) +} + +class EventListenerAgent(recipeName: String, listenerFunction: (RecipeEventMetadata, EventInstance) => Unit) extends Actor { + + val mediator: ActorRef = + DistributedPubSub(context.system).mediator + + val eventsTopic: String = + ProtocolDistributedEventPublishing.eventsTopic(recipeName) + + def subscribeToEvents(): Unit = + mediator ! DistributedPubSubMediator.Subscribe(eventsTopic, self) + + def unsubscribeToEvents(): Unit = + mediator ! DistributedPubSubMediator.Unsubscribe(eventsTopic, self) + + subscribeToEvents() + + def receive: Receive = { + case ProtocolDistributedEventPublishing.Event(recipeEventMetadata, event) => + listenerFunction(recipeEventMetadata, event) + } +} diff --git a/baas-node-event-listener/src/main/scala/com/ing/baker/baas/common/BaaSEventListener.scala b/baas-node-event-listener/src/main/scala/com/ing/baker/baas/common/BaaSEventListener.scala new file mode 100644 index 000000000..a3c5beec6 --- /dev/null +++ b/baas-node-event-listener/src/main/scala/com/ing/baker/baas/common/BaaSEventListener.scala @@ -0,0 +1,14 @@ +package com.ing.baker.baas.common + +import com.ing.baker.runtime.common.{EventInstance, InteractionInstance, RecipeEventMetadata} +import com.ing.baker.runtime.common.LanguageDataStructures.LanguageApi + +trait BaaSEventListener[F[_]] extends LanguageApi { self => + + type EventInstanceType <: EventInstance { type Language <: self.Language } + + type RecipeEventMetadataType <: RecipeEventMetadata { type Language <: self.Language } + + def registerEventListener(recipeName: String, listenerFunction: language.BiConsumerFunction[RecipeEventMetadataType, EventInstanceType]): F[Unit] + +} diff --git a/baas-node-event-listener/src/main/scala/com/ing/baker/baas/javadsl/BaaSEventListener.scala b/baas-node-event-listener/src/main/scala/com/ing/baker/baas/javadsl/BaaSEventListener.scala new file mode 100644 index 000000000..698914388 --- /dev/null +++ b/baas-node-event-listener/src/main/scala/com/ing/baker/baas/javadsl/BaaSEventListener.scala @@ -0,0 +1,22 @@ +package com.ing.baker.baas.javadsl + +import java.util.concurrent.CompletableFuture +import java.util.function.BiConsumer + +import akka.actor.ActorSystem +import com.ing.baker.baas.common +import com.ing.baker.baas.scaladsl +import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi +import com.ing.baker.runtime.javadsl.{EventInstance, RecipeEventMetadata} + +import scala.compat.java8.FutureConverters + +class BaaSEventListener(actorSystem: ActorSystem) extends common.BaaSEventListener[CompletableFuture] with JavaApi { + + override type EventInstanceType = EventInstance + + override type RecipeEventMetadataType = RecipeEventMetadata + + override def registerEventListener(recipeName: String, listenerFunction: BiConsumer[RecipeEventMetadata, EventInstance]): CompletableFuture[Unit] = + FutureConverters.toJava(scaladsl.BaaSEventListener(actorSystem).registerEventListener(recipeName, (metadata, event) => listenerFunction.accept(metadata.asJava, event.asJava))).toCompletableFuture +} diff --git a/baas-node-event-listener/src/main/scala/com/ing/baker/baas/scaladsl/BaaSEventListener.scala b/baas-node-event-listener/src/main/scala/com/ing/baker/baas/scaladsl/BaaSEventListener.scala new file mode 100644 index 000000000..8fdcf65e3 --- /dev/null +++ b/baas-node-event-listener/src/main/scala/com/ing/baker/baas/scaladsl/BaaSEventListener.scala @@ -0,0 +1,20 @@ +package com.ing.baker.baas.scaladsl + +import akka.actor.ActorSystem +import com.ing.baker.baas.akka.EventListenerAgent +import com.ing.baker.baas.common +import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi +import com.ing.baker.runtime.scaladsl.{EventInstance, RecipeEventMetadata} + +import scala.concurrent.Future + +case class BaaSEventListener(actorSystem: ActorSystem) extends common.BaaSEventListener[Future] with ScalaApi { + + override type EventInstanceType = EventInstance + + override type RecipeEventMetadataType = RecipeEventMetadata + + override def registerEventListener(recipeName: String, listenerFunction: (RecipeEventMetadata, EventInstance) => Unit): Future[Unit] = + Future.successful { actorSystem.actorOf(EventListenerAgent(recipeName, listenerFunction)) } +} + diff --git a/baas-node-interaction/src/main/scala/com/ing/baker/baas/akka/InteractionAgent.scala b/baas-node-interaction/src/main/scala/com/ing/baker/baas/akka/InteractionAgent.scala new file mode 100644 index 000000000..41bc1cb05 --- /dev/null +++ b/baas-node-interaction/src/main/scala/com/ing/baker/baas/akka/InteractionAgent.scala @@ -0,0 +1,112 @@ +package com.ing.baker.baas.akka + +import java.util.UUID + +import akka.actor.{Actor, ActorRef, Props} +import akka.cluster.pubsub.{DistributedPubSub, DistributedPubSubMediator} +import com.ing.baker.baas.akka.InteractionAgent.{CommitTimeout, log} +import com.ing.baker.baas.protocol.{ProtocolInteractionExecution, ProtocolPushPullMatching, ProtocolQuestCommit} +import com.ing.baker.runtime.scaladsl.{EventInstance, InteractionInstance} +import org.slf4j.LoggerFactory + +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} + +object InteractionAgent { + + case object CommitTimeout + + def apply(instance: InteractionInstance): Props = + Props(new InteractionAgent(instance)) + + /** + * Closes over the agent actor references, just like the pipe pattern does, except it sends a more expressive + * message in the case of failure. + * + * TODO: Handle invalid ingredients scenario + * + * @param agent actor reference + * @param result outcome of invoking the interaction instance + * @param ec execution context to use + */ + def pipeBackExecutionResponse(agent: ActorRef, mandated: ActorRef)(result: Future[Option[EventInstance]])(implicit ec: ExecutionContext): Unit = { + result.onComplete { + case Success(value) => + mandated.tell(ProtocolInteractionExecution.InstanceExecutedSuccessfully(value), agent) + case Failure(exception) => + mandated.tell(ProtocolInteractionExecution.InstanceExecutionFailed(), agent) + } + } + + private val log = LoggerFactory.getLogger(classOf[InteractionAgent]) +} + +class InteractionAgent(interaction: InteractionInstance) extends Actor { + + import context.dispatcher + + val mediator: ActorRef = DistributedPubSub(context.system).mediator + + val pullTopic: String = + ProtocolPushPullMatching.pullTopic(interaction.name) + + val pushTopic: String = + ProtocolPushPullMatching.pushTopic(interaction.name) + + def pull(): Unit = + mediator ! DistributedPubSubMediator.Publish(pullTopic, ProtocolPushPullMatching.Pull(self)) + + def subscribePush(): Unit = + mediator ! DistributedPubSubMediator.Subscribe(pushTopic, self) + + def unsubscribePush(): Unit = + mediator ! DistributedPubSubMediator.Unsubscribe(pushTopic, self) + + subscribePush() + pull() + + private val timeout: FiniteDuration = 10.seconds + + def receive: Receive = { + case ProtocolPushPullMatching.Push(mandated, uuid) => + // start Quest commit protocol + log.info(s"${interaction.name}:$uuid: Considering quest from $mandated") + mandated ! ProtocolQuestCommit.Considering(self) + unsubscribePush() + context.system.scheduler.scheduleOnce(timeout, self, CommitTimeout)(context.dispatcher, self) + context.become(considering(uuid)) + + case ProtocolPushPullMatching.AvailableQuest(mandated, uuid) => + // start Quest commit protocol + log.info(s"${interaction.name}:$uuid: Considering quest from $mandated") + mandated ! ProtocolQuestCommit.Considering(self) + unsubscribePush() + context.system.scheduler.scheduleOnce(timeout, self, CommitTimeout)(context.dispatcher, self) + context.become(considering(uuid)) + } + + def considering(uuid: UUID): Receive = { + case ProtocolQuestCommit.Commit(mandated, executeMessage) => + log.info(s"${interaction.name}:$uuid: Commited to quest from $mandated") + // start the execution protocol by already starting the computation and become committed + InteractionAgent.pipeBackExecutionResponse(self, mandated)(interaction.run(executeMessage.input)) + subscribePush() + pull() + context.become(receive) + + case ProtocolQuestCommit.QuestTaken => + log.info(s"${interaction.name}:$uuid: Quest taken, starting the protocol again") + // quest taIken, start all over again + subscribePush() + pull() + context.become(receive) + + case CommitTimeout => + log.info(s"${interaction.name}:$uuid: not received a response after commit timeout") + subscribePush() + pull() + context.become(receive) + + } +} diff --git a/baas-node-interaction/src/main/scala/com/ing/baker/baas/common/BaaSInteractionInstance.scala b/baas-node-interaction/src/main/scala/com/ing/baker/baas/common/BaaSInteractionInstance.scala new file mode 100644 index 000000000..766dc0686 --- /dev/null +++ b/baas-node-interaction/src/main/scala/com/ing/baker/baas/common/BaaSInteractionInstance.scala @@ -0,0 +1,12 @@ +package com.ing.baker.baas.common + +import com.ing.baker.runtime.common.InteractionInstance +import com.ing.baker.runtime.common.LanguageDataStructures.LanguageApi + +trait BaaSInteractionInstance[F[_]] extends LanguageApi { self => + + type InteractionInstanceType <: InteractionInstance[F] { type Language <: self.Language } + + def load(implementation: InteractionInstanceType*): Unit + +} diff --git a/baas-node-interaction/src/main/scala/com/ing/baker/baas/javadsl/BaaSInteractionInstance.scala b/baas-node-interaction/src/main/scala/com/ing/baker/baas/javadsl/BaaSInteractionInstance.scala new file mode 100644 index 000000000..7d865b7cb --- /dev/null +++ b/baas-node-interaction/src/main/scala/com/ing/baker/baas/javadsl/BaaSInteractionInstance.scala @@ -0,0 +1,17 @@ +package com.ing.baker.baas.javadsl + +import java.util.concurrent.CompletableFuture + +import akka.actor.ActorSystem +import com.ing.baker.baas.common +import com.ing.baker.baas.scaladsl +import com.ing.baker.runtime.javadsl.InteractionInstance +import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi + +class BaaSInteractionInstance(actorSystem: ActorSystem) extends common.BaaSInteractionInstance[CompletableFuture] with JavaApi { + + override type InteractionInstanceType = InteractionInstance + + override def load(implementation: InteractionInstance*): Unit = + scaladsl.BaaSInteractionInstance(actorSystem).load(implementation.map(_.asScala): _*) +} diff --git a/baas-node-interaction/src/main/scala/com/ing/baker/baas/scaladsl/BaaSInteractionInstance.scala b/baas-node-interaction/src/main/scala/com/ing/baker/baas/scaladsl/BaaSInteractionInstance.scala new file mode 100644 index 000000000..fa079e407 --- /dev/null +++ b/baas-node-interaction/src/main/scala/com/ing/baker/baas/scaladsl/BaaSInteractionInstance.scala @@ -0,0 +1,20 @@ +package com.ing.baker.baas.scaladsl + +import akka.actor.ActorSystem +import com.ing.baker.baas.akka.InteractionAgent +import com.ing.baker.baas.common +import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi +import com.ing.baker.runtime.scaladsl.InteractionInstance + +import scala.concurrent.Future + +case class BaaSInteractionInstance(actorSystem: ActorSystem) extends common.BaaSInteractionInstance[Future] with ScalaApi { + + override type InteractionInstanceType = InteractionInstance + + override def load(implementation: InteractionInstance*): Unit = + implementation.foreach { implementation => + actorSystem.actorOf(InteractionAgent(implementation)) + } +} + diff --git a/baas-node-state/src/main/resources/application.conf b/baas-node-state/src/main/resources/application.conf new file mode 100644 index 000000000..ecd52f396 --- /dev/null +++ b/baas-node-state/src/main/resources/application.conf @@ -0,0 +1,98 @@ +include "baker.conf" + +service { + + actorSystemName = "BaaS" + actorSystemName = ${?ACTOR_SYSTEM_NAME} + + clusterHost = "127.0.0.1" + clusterHost = ${?CLUSTER_HOST} + + clusterPort = 2551 + clusterPort = ${?CLUSTER_PORT} + + seedHost = "127.0.0.1" + seedHost = ${?CLUSTER_SEED_HOST} + + seedPort = 2551 + seedPort = ${?CLUSTER_SEED_PORT} + + httpServerPort = 8080 + httpServerPort = ${?HTTP_SERVER_PORT} + + memory-dump-path = "/home/demiourgos728/memdump" + memory-dump-path = ${?APP_MEMORY_DUMP_PATH} +} + +baker { + + interaction-manager = "remote" + + actor { + provider = "cluster-sharded" + idle-timeout = 1 minute + } +} + +cassandra-journal.contact-points.0 = "127.0.0.1" +cassandra-journal.contact-points.0 = ${?CASSANDRA_CONTACT_POINTS_0} + +cassandra-snapshot-store.contact-points.0 = "127.0.0.1" +cassandra-snapshot-store.contact-points.0 = ${?CASSANDRA_CONTACT_POINTS_0} + +akka.actor.allow-java-serialization = on + +akka { + + actor { + provider = "cluster" + } + + persistence { + # See https://doc.akka.io/docs/akka-persistence-cassandra/current/journal.html#configuration + journal.plugin = "cassandra-journal" + # See https://doc.akka.io/docs/akka-persistence-cassandra/current/snapshots.html#configuration + snapshot-store.plugin = "cassandra-snapshot-store" + } + + remote { + log-remote-lifecycle-events = off + netty.tcp { + hostname = ${service.clusterHost} + port = ${service.clusterPort} + } + } + + cluster { + + seed-nodes = [ + "akka.tcp://"${service.actorSystemName}"@"${service.seedHost}":"${service.seedPort}] + + # auto downing is NOT safe for production deployments. + # you may want to use it during development, read more about it in the docs. + # + # auto-down-unreachable-after = 10s + } + + management.http.routes { + cluster-management = "" + } +} + +kamon.instrumentation.akka.filters { + + actors.track { + includes = [ ${service.actorSystemName}"/user/*" ] + excludes = [] + # ${service.actorSystemName}"/system/**", ${service.actorSystemName}"/user/worker-helper" + #] + } + + dispatchers { + includes = [ ${service.actorSystemName}"/akka.actor.default-dispatcher" ] + } + + routers { + includes = [ ${service.actorSystemName}"/user/*" ] + } +} diff --git a/baas-node-state/src/main/resources/logback.xml b/baas-node-state/src/main/resources/logback.xml new file mode 100644 index 000000000..417dc94e9 --- /dev/null +++ b/baas-node-state/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + \ No newline at end of file diff --git a/baas-node-state/src/main/scala/com/ing/baker/baas/state/BaaSServer.scala b/baas-node-state/src/main/scala/com/ing/baker/baas/state/BaaSServer.scala new file mode 100644 index 000000000..b7bc99868 --- /dev/null +++ b/baas-node-state/src/main/scala/com/ing/baker/baas/state/BaaSServer.scala @@ -0,0 +1,152 @@ +package com.ing.baker.baas.state + +import akka.actor.ActorSystem +import akka.cluster.pubsub.{DistributedPubSub, DistributedPubSubMediator} +import akka.http.scaladsl.Http +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server.Route +import akka.stream.Materializer +import com.ing.baker.baas.protocol.{BaaSProtocol, ProtocolDistributedEventPublishing} +import com.ing.baker.baas.protocol.BaaSProto._ +import com.ing.baker.baas.protocol.MarshallingUtils._ +import com.ing.baker.runtime.scaladsl.Baker +import com.ing.baker.runtime.serialization.{Encryption, SerializersProvider} + +import scala.concurrent.{ExecutionContext, Future} + +object BaaSServer { + + def run(baker: Baker, host: String, port: Int)(implicit system: ActorSystem, mat: Materializer): Future[Http.ServerBinding] = { + import system.dispatcher + val encryption = Encryption.NoEncryption + val server = new BaaSServer()(system, mat, baker, encryption) + for { + _ <- initializeEventListeners(baker, system) + binding <- Http().bindAndHandle(server.route, host, port) + } yield binding + } + + private[state] def registerEventListenerForRemote(recipeName: String, baker: Baker, system: ActorSystem): Future[Unit] = { + println(Console.YELLOW + s"Event listener for: $recipeName" + Console.RESET) + baker.registerEventListener(recipeName, (metadata, event) => { + val eventsTopic: String = + ProtocolDistributedEventPublishing.eventsTopic(recipeName) + DistributedPubSub(system).mediator ! DistributedPubSubMediator.Publish(eventsTopic, ProtocolDistributedEventPublishing.Event(metadata, event)) + }) + } + + private[state] def initializeEventListeners(baker: Baker, system: ActorSystem)(implicit ec: ExecutionContext): Future[Unit] = + for { + recipes <- baker.getAllRecipes + _ <- Future.traverse(recipes.toList) { case (_, recipe) => registerEventListenerForRemote(recipe.compiledRecipe.name, baker, system) } + } yield () +} + +class BaaSServer(implicit system: ActorSystem, mat: Materializer, baker: Baker, encryption: Encryption) { + + import system.dispatcher + + implicit private val serializersProvider: SerializersProvider = + SerializersProvider(system, encryption) + + def route: Route = concat(pathPrefix("api" / "v3")(concat(health, addRecipe, getRecipe, getAllRecipes, bake, + fireEventAndResolveWhenReceived, fireEventAndResolveWhenCompleted, fireEventAndResolveOnEvent, fireEvent, + getAllRecipeInstancesMetadata, getRecipeInstanceState, getVisualState, retryInteraction, resolveInteraction, + stopRetryingInteraction + ))) + + private def health: Route = pathPrefix("health")(get(complete(StatusCodes.OK))) + + private def addRecipe: Route = post(path("addRecipe") { + entity(as[BaaSProtocol.AddRecipeRequest]) { request => + val result = for { + recipeId <- baker.addRecipe(request.compiledRecipe) + _ <- BaaSServer.registerEventListenerForRemote(request.compiledRecipe.name, baker, system) + } yield BaaSProtocol.AddRecipeResponse(recipeId) + completeWithBakerFailures(result) + } + }) + + private def getRecipe: Route = post(path("getRecipe") { + entity(as[BaaSProtocol.GetRecipeRequest]) { request => + completeWithBakerFailures(baker.getRecipe(request.recipeId).map(BaaSProtocol.GetRecipeResponse)) + } + }) + + private def getAllRecipes: Route = post(path("getAllRecipes") { + completeWithBakerFailures(baker.getAllRecipes.map(BaaSProtocol.GetAllRecipesResponse)) + }) + + private def bake: Route = post(path("bake") { + entity(as[BaaSProtocol.BakeRequest]) { request => + completeWithBakerFailures(baker.bake(request.recipeId, request.recipeInstanceId)) + } + }) + + private def fireEventAndResolveWhenReceived: Route = post(path("fireEventAndResolveWhenReceived") { + entity(as[BaaSProtocol.FireEventAndResolveWhenReceivedRequest]) { request => + completeWithBakerFailures(baker.fireEventAndResolveWhenReceived(request.recipeInstanceId, request.event, request.correlationId) + .map(BaaSProtocol.FireEventAndResolveWhenReceivedResponse)) + } + }) + + private def fireEventAndResolveWhenCompleted: Route = post(path("fireEventAndResolveWhenCompleted") { + entity(as[BaaSProtocol.FireEventAndResolveWhenCompletedRequest]) { request => + completeWithBakerFailures(baker.fireEventAndResolveWhenCompleted(request.recipeInstanceId, request.event, request.correlationId) + .map(BaaSProtocol.FireEventAndResolveWhenCompletedResponse)) + } + }) + + private def fireEventAndResolveOnEvent: Route = post(path("fireEventAndResolveOnEvent") { + entity(as[BaaSProtocol.FireEventAndResolveOnEventRequest]) { request => + completeWithBakerFailures(baker.fireEventAndResolveOnEvent(request.recipeInstanceId, request.event, request.onEvent, request.correlationId) + .map(BaaSProtocol.FireEventAndResolveOnEventResponse)) + } + }) + + private def fireEvent: Route = post(path("fireEvent") { + entity(as[BaaSProtocol.FireEventRequest]) { request => + complete(baker.fireEvent(request.recipeInstanceId, request.event, request.correlationId).resolveWhenReceived + .map(_ => "TODO")) // TODO figure out what to do here with the 2 different futures + } + }) + + private def getAllRecipeInstancesMetadata: Route = post(path("getAllRecipeInstancesMetadata") { + completeWithBakerFailures(baker.getAllRecipeInstancesMetadata + .map(BaaSProtocol.GetAllRecipeInstancesMetadataResponse)) + }) + + private def getRecipeInstanceState: Route = post(path("getRecipeInstanceState") { + entity(as[BaaSProtocol.GetRecipeInstanceStateRequest]) { request => + completeWithBakerFailures(baker.getRecipeInstanceState(request.recipeInstanceId) + .map(BaaSProtocol.GetRecipeInstanceStateResponse)) + } + }) + + private def getVisualState: Route = post(path("getVisualState") { + entity(as[BaaSProtocol.GetVisualStateRequest]) { request => + completeWithBakerFailures(baker.getVisualState(request.recipeInstanceId) + .map(BaaSProtocol.GetVisualStateResponse)) + } + }) + + private def retryInteraction: Route = post(path("retryInteraction") { + entity(as[BaaSProtocol.RetryInteractionRequest]) { request => + completeWithBakerFailures(baker.retryInteraction(request.recipeInstanceId, request.interactionName)) + } + }) + + private def resolveInteraction: Route = post(path("resolveInteraction") { + entity(as[BaaSProtocol.ResolveInteractionRequest]) { request => + completeWithBakerFailures(baker.resolveInteraction(request.recipeInstanceId, request.interactionName, request.event)) + } + }) + + private def stopRetryingInteraction: Route = post(path("stopRetryingInteraction") { + entity(as[BaaSProtocol.StopRetryingInteractionRequest]) { request => + completeWithBakerFailures(baker.stopRetryingInteraction(request.recipeInstanceId, request.interactionName)) + } + }) + +} diff --git a/baas-node-state/src/main/scala/com/ing/baker/baas/state/Main.scala b/baas-node-state/src/main/scala/com/ing/baker/baas/state/Main.scala new file mode 100644 index 000000000..7a85b7aca --- /dev/null +++ b/baas-node-state/src/main/scala/com/ing/baker/baas/state/Main.scala @@ -0,0 +1,30 @@ +package com.ing.baker.baas.state + +import akka.actor.ActorSystem +import akka.stream.ActorMaterializer +import com.ing.baker.runtime.akka.AkkaBaker +import com.typesafe.config.ConfigFactory + +import scala.concurrent.Await +import scala.concurrent.duration._ + +object Main extends App { + private val timeout: FiniteDuration = 20.seconds + + println(Console.YELLOW + "Starting State Node..." + Console.RESET) + + val config = ConfigFactory.load() + val systemName = config.getString("service.actorSystemName") + val httpServerPort = config.getInt("service.httpServerPort") + val stateNodeSystem = ActorSystem(systemName) + val stateNodeBaker = AkkaBaker(config, stateNodeSystem) + val materializer = ActorMaterializer()(stateNodeSystem) + + import stateNodeSystem.dispatcher + + Await.result(BaaSServer.run(stateNodeBaker, "0.0.0.0", httpServerPort)(stateNodeSystem, materializer).map { hook => + println(Console.GREEN + "State Node started..." + Console.RESET) + println(hook.localAddress) + sys.addShutdownHook(Await.result(hook.unbind(), timeout)) + }, timeout) +} diff --git a/baas-protocol-baker/src/main/protobuf/baas.proto b/baas-protocol-baker/src/main/protobuf/baas.proto new file mode 100644 index 000000000..e7c263194 --- /dev/null +++ b/baas-protocol-baker/src/main/protobuf/baas.proto @@ -0,0 +1,112 @@ +syntax = "proto2"; + +import "scalapb/scalapb.proto"; +import "common.proto"; + +option java_package = "com.ing.baker.baas.protocol.protobuf"; +option (scalapb.options) = { + flat_package: true +}; + +message BaaSRemoteFailure { + optional BakerException failure = 1; +} + +message AddRecipeRequest { + optional CompiledRecipe compiledRecipe = 1; +} + +message AddRecipeResponse { + optional string recipeId = 1; +} + +message GetRecipeRequest { + optional string recipeId = 1; +} + +message GetRecipeResponse { + optional RecipeInformation recipeInformation = 1; +} + +message GetAllRecipesResponse { + map mapping = 1; +} + +message BakeRequest { + optional string recipeId = 1; + optional string recipeInstanceId = 2; +} + +message FireEventAndResolveWhenReceivedRequest { + optional string recipeInstanceId = 1; + optional RuntimeEvent event = 2; + optional string correlationId = 3; +} + +message FireEventAndResolveWhenReceivedResponse { + optional SensoryEventStatus sensoryEventStatus = 1; +} + +message FireEventAndResolveWhenCompletedRequest { + optional string recipeInstanceId = 1; + optional RuntimeEvent event = 2; + optional string correlationId = 3; +} + +message FireEventAndResolveWhenCompletedResponse { + optional SensoryEventResult sensoryEventResult = 1; +} + +message FireEventAndResolveOnEventRequest { + optional string recipeInstanceId = 1; + optional RuntimeEvent event = 2; + optional string onEvent = 3; + optional string correlationId = 4; +} + +message FireEventAndResolveOnEventResponse { + optional SensoryEventResult sensoryEventResult = 1; +} + +message FireEventRequest { + optional string recipeInstanceId = 1; + optional RuntimeEvent event = 2; + optional string correlationId = 3; +} + +message GetAllRecipeInstancesMetadataResponse { + repeated RecipeInstanceMetadata set = 1; +} + +message GetRecipeInstanceStateRequest { + optional string recipeInstanceId = 1; +} + +message GetRecipeInstanceStateResponse { + optional ProcessState recipeInstanceState = 1; +} + +message GetVisualStateRequest { + optional string recipeInstanceId = 1; +} + +message GetVisualStateResponse { + optional string state = 1; +} + +message RetryInteractionRequest { + optional string recipeInstanceId = 1; + optional string interactionName = 2; +} + +message ResolveInteractionRequest { + optional string recipeInstanceId = 1; + optional string interactionName = 2; + optional RuntimeEvent event = 3; +} + +message StopRetryingInteractionRequest { + optional string recipeInstanceId = 1; + optional string interactionName = 2; +} + diff --git a/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/BaaSProto.scala b/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/BaaSProto.scala new file mode 100644 index 000000000..ceabee45b --- /dev/null +++ b/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/BaaSProto.scala @@ -0,0 +1,363 @@ +package com.ing.baker.baas.protocol + +import cats.implicits._ +import com.ing.baker.baas.protocol.BaaSProtocol._ +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider} +import scalapb.GeneratedMessageCompanion +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.protomappings.SensoryEventStatusMappingHelper + +import scala.util.Try + +object BaaSProto { + + implicit val baaSRemoteFailureProto: ProtoMap[BaaSRemoteFailure, protobuf.BaaSRemoteFailure] = + new ProtoMap[BaaSRemoteFailure, protobuf.BaaSRemoteFailure] { + + override def companion: GeneratedMessageCompanion[protobuf.BaaSRemoteFailure] = + protobuf.BaaSRemoteFailure + + override def toProto(a: BaaSRemoteFailure): protobuf.BaaSRemoteFailure = + protobuf.BaaSRemoteFailure(Some(ctxToProto(a.error))) + + override def fromProto(message: protobuf.BaaSRemoteFailure): Try[BaaSRemoteFailure] = + versioned(message.failure, "failure") + .flatMap(ctxFromProto(_)) + .map(BaaSRemoteFailure) + } + + implicit def addRecipeRequestProto(implicit ev0: SerializersProvider): ProtoMap[AddRecipeRequest, protobuf.AddRecipeRequest] = + new ProtoMap[AddRecipeRequest, protobuf.AddRecipeRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.AddRecipeRequest] = + protobuf.AddRecipeRequest + + override def toProto(a: AddRecipeRequest): protobuf.AddRecipeRequest = + protobuf.AddRecipeRequest(Some(ctxToProto(a.compiledRecipe))) + + override def fromProto(message: protobuf.AddRecipeRequest): Try[AddRecipeRequest] = + versioned(message.compiledRecipe, "compiledRecipe") + .flatMap(ctxFromProto(_)) + .map(AddRecipeRequest) + } + + implicit def addRecipeResponseProto: ProtoMap[AddRecipeResponse, protobuf.AddRecipeResponse] = + new ProtoMap[AddRecipeResponse, protobuf.AddRecipeResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.AddRecipeResponse] = + protobuf.AddRecipeResponse + + override def toProto(a: AddRecipeResponse): protobuf.AddRecipeResponse = + protobuf.AddRecipeResponse(Some(a.recipeId)) + + override def fromProto(message: protobuf.AddRecipeResponse): Try[AddRecipeResponse] = + versioned(message.recipeId, "recipeId") + .map(AddRecipeResponse) + } + + implicit def getRecipeRequestProto: ProtoMap[GetRecipeRequest, protobuf.GetRecipeRequest] = + new ProtoMap[GetRecipeRequest, protobuf.GetRecipeRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.GetRecipeRequest] = + protobuf.GetRecipeRequest + + override def toProto(a: GetRecipeRequest): protobuf.GetRecipeRequest = + protobuf.GetRecipeRequest(Some(a.recipeId)) + + override def fromProto(message: protobuf.GetRecipeRequest): Try[GetRecipeRequest] = + versioned(message.recipeId, "recipeId") + .map(GetRecipeRequest) + } + + implicit def getRecipeResponseProto(implicit ev0: SerializersProvider): ProtoMap[GetRecipeResponse, protobuf.GetRecipeResponse] = + new ProtoMap[GetRecipeResponse, protobuf.GetRecipeResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.GetRecipeResponse] = + protobuf.GetRecipeResponse + + override def toProto(a: GetRecipeResponse): protobuf.GetRecipeResponse = + protobuf.GetRecipeResponse(Some(ctxToProto(a.recipeInformation))) + + override def fromProto(message: protobuf.GetRecipeResponse): Try[GetRecipeResponse] = + for { + recipeInformationProto <- versioned(message.recipeInformation, "recipeInformation") + recipeInformation <- ctxFromProto(recipeInformationProto) + } yield GetRecipeResponse(recipeInformation) + } + + implicit def getAllRecipesResponseProto(implicit ev0: SerializersProvider): ProtoMap[GetAllRecipesResponse, protobuf.GetAllRecipesResponse] = + new ProtoMap[GetAllRecipesResponse, protobuf.GetAllRecipesResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.GetAllRecipesResponse] = + protobuf.GetAllRecipesResponse + + override def toProto(a: GetAllRecipesResponse): protobuf.GetAllRecipesResponse = + protobuf.GetAllRecipesResponse(a.map.mapValues(ctxToProto(_))) + + override def fromProto(message: protobuf.GetAllRecipesResponse): Try[GetAllRecipesResponse] = + for { + allRecipes <- message.mapping.toList.traverse { case (id, recipeProto) => + ctxFromProto(recipeProto).map(x => (id, x)) + } + } yield GetAllRecipesResponse(allRecipes.toMap) + } + + implicit def bakeRequestProto: ProtoMap[BakeRequest, protobuf.BakeRequest] = + new ProtoMap[BakeRequest, protobuf.BakeRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.BakeRequest] = + protobuf.BakeRequest + + override def toProto(a: BakeRequest): protobuf.BakeRequest = + protobuf.BakeRequest(Some(a.recipeId), Some(a.recipeInstanceId)) + + override def fromProto(message: protobuf.BakeRequest): Try[BakeRequest] = + for { + recipeId <- versioned(message.recipeId, "recipeId") + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + } yield BakeRequest(recipeId, recipeInstanceId) + } + + implicit def fireEventAndResolveWhenReceivedRequestProto: ProtoMap[FireEventAndResolveWhenReceivedRequest, protobuf.FireEventAndResolveWhenReceivedRequest] = + new ProtoMap[FireEventAndResolveWhenReceivedRequest, protobuf.FireEventAndResolveWhenReceivedRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.FireEventAndResolveWhenReceivedRequest] = + protobuf.FireEventAndResolveWhenReceivedRequest + + override def toProto(a: FireEventAndResolveWhenReceivedRequest): protobuf.FireEventAndResolveWhenReceivedRequest = + protobuf.FireEventAndResolveWhenReceivedRequest(Some(a.recipeInstanceId), Some(ctxToProto(a.event)), a.correlationId) + + override def fromProto(message: protobuf.FireEventAndResolveWhenReceivedRequest): Try[FireEventAndResolveWhenReceivedRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + event <- versioned(message.event, "event") + decodedEvent <- ctxFromProto(event) + } yield FireEventAndResolveWhenReceivedRequest(recipeInstanceId, decodedEvent, message.correlationId) + } + + implicit def fireEventAndResolveWhenReceivedResponseProto: ProtoMap[FireEventAndResolveWhenReceivedResponse, protobuf.FireEventAndResolveWhenReceivedResponse] = + new ProtoMap[FireEventAndResolveWhenReceivedResponse, protobuf.FireEventAndResolveWhenReceivedResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.FireEventAndResolveWhenReceivedResponse] = + protobuf.FireEventAndResolveWhenReceivedResponse + + override def toProto(a: FireEventAndResolveWhenReceivedResponse): protobuf.FireEventAndResolveWhenReceivedResponse = + protobuf.FireEventAndResolveWhenReceivedResponse(Some(SensoryEventStatusMappingHelper.toProto(a.sensoryEventStatus))) + + override def fromProto(message: protobuf.FireEventAndResolveWhenReceivedResponse): Try[FireEventAndResolveWhenReceivedResponse] = + for { + sensoryEventStatus <- versioned(message.sensoryEventStatus, "sensoryEventStatus") + decodedSensoryEventStatus <- SensoryEventStatusMappingHelper.fromProto(sensoryEventStatus) + } yield FireEventAndResolveWhenReceivedResponse(decodedSensoryEventStatus) + } + + implicit def fireEventAndResolveWhenCompletedRequestProto: ProtoMap[FireEventAndResolveWhenCompletedRequest, protobuf.FireEventAndResolveWhenCompletedRequest] = + new ProtoMap[FireEventAndResolveWhenCompletedRequest, protobuf.FireEventAndResolveWhenCompletedRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.FireEventAndResolveWhenCompletedRequest] = + protobuf.FireEventAndResolveWhenCompletedRequest + + override def toProto(a: FireEventAndResolveWhenCompletedRequest): protobuf.FireEventAndResolveWhenCompletedRequest = + protobuf.FireEventAndResolveWhenCompletedRequest(Some(a.recipeInstanceId), Some(ctxToProto(a.event)), a.correlationId) + + override def fromProto(message: protobuf.FireEventAndResolveWhenCompletedRequest): Try[FireEventAndResolveWhenCompletedRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + event <- versioned(message.event, "event") + decodedEvent <- ctxFromProto(event) + } yield FireEventAndResolveWhenCompletedRequest(recipeInstanceId, decodedEvent, message.correlationId) + } + + implicit def fireEventAndResolveWhenCompletedResponseProto: ProtoMap[FireEventAndResolveWhenCompletedResponse, protobuf.FireEventAndResolveWhenCompletedResponse] = + new ProtoMap[FireEventAndResolveWhenCompletedResponse, protobuf.FireEventAndResolveWhenCompletedResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.FireEventAndResolveWhenCompletedResponse] = + protobuf.FireEventAndResolveWhenCompletedResponse + + override def toProto(a: FireEventAndResolveWhenCompletedResponse): protobuf.FireEventAndResolveWhenCompletedResponse = + protobuf.FireEventAndResolveWhenCompletedResponse(Some(ctxToProto(a.sensoryEventResult))) + + override def fromProto(message: protobuf.FireEventAndResolveWhenCompletedResponse): Try[FireEventAndResolveWhenCompletedResponse] = + for { + sensoryEventResult <- versioned(message.sensoryEventResult, "sensoryEventResult") + decodedSensoryEventResult <- ctxFromProto(sensoryEventResult) + } yield FireEventAndResolveWhenCompletedResponse(decodedSensoryEventResult) + } + + implicit def fireEventAndResolveOnEventRequestProto: ProtoMap[FireEventAndResolveOnEventRequest, protobuf.FireEventAndResolveOnEventRequest] = + new ProtoMap[FireEventAndResolveOnEventRequest, protobuf.FireEventAndResolveOnEventRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.FireEventAndResolveOnEventRequest] = + protobuf.FireEventAndResolveOnEventRequest + + override def toProto(a: FireEventAndResolveOnEventRequest): protobuf.FireEventAndResolveOnEventRequest = + protobuf.FireEventAndResolveOnEventRequest(Some(a.recipeInstanceId), Some(ctxToProto(a.event)), Some(a.onEvent), a.correlationId) + + override def fromProto(message: protobuf.FireEventAndResolveOnEventRequest): Try[FireEventAndResolveOnEventRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + onEvent <- versioned(message.onEvent, "onEvent") + event <- versioned(message.event, "event") + decodedEvent <- ctxFromProto(event) + } yield FireEventAndResolveOnEventRequest(recipeInstanceId, decodedEvent, onEvent, message.correlationId) + } + + implicit def fireEventAndResolveOnEventResponseProto: ProtoMap[FireEventAndResolveOnEventResponse, protobuf.FireEventAndResolveOnEventResponse] = + new ProtoMap[FireEventAndResolveOnEventResponse, protobuf.FireEventAndResolveOnEventResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.FireEventAndResolveOnEventResponse] = + protobuf.FireEventAndResolveOnEventResponse + + override def toProto(a: FireEventAndResolveOnEventResponse): protobuf.FireEventAndResolveOnEventResponse = + protobuf.FireEventAndResolveOnEventResponse(Some(ctxToProto(a.sensoryEventResult))) + + override def fromProto(message: protobuf.FireEventAndResolveOnEventResponse): Try[FireEventAndResolveOnEventResponse] = + for { + sensoryEventResult <- versioned(message.sensoryEventResult, "sensoryEventResult") + decodedSensoryEventResult <- ctxFromProto(sensoryEventResult) + } yield FireEventAndResolveOnEventResponse(decodedSensoryEventResult) + } + + implicit def fireEventRequestProto: ProtoMap[FireEventRequest, protobuf.FireEventRequest] = + new ProtoMap[FireEventRequest, protobuf.FireEventRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.FireEventRequest] = + protobuf.FireEventRequest + + override def toProto(a: FireEventRequest): protobuf.FireEventRequest = + protobuf.FireEventRequest(Some(a.recipeInstanceId), Some(ctxToProto(a.event)), a.correlationId) + + override def fromProto(message: protobuf.FireEventRequest): Try[FireEventRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + event <- versioned(message.event, "event") + decodedEvent <- ctxFromProto(event) + } yield FireEventRequest(recipeInstanceId, decodedEvent, message.correlationId) + } + + implicit def getAllRecipeInstancesMetadataResponseProto: ProtoMap[GetAllRecipeInstancesMetadataResponse, protobuf.GetAllRecipeInstancesMetadataResponse] = + new ProtoMap[GetAllRecipeInstancesMetadataResponse, protobuf.GetAllRecipeInstancesMetadataResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.GetAllRecipeInstancesMetadataResponse] = + protobuf.GetAllRecipeInstancesMetadataResponse + + override def toProto(a: GetAllRecipeInstancesMetadataResponse): protobuf.GetAllRecipeInstancesMetadataResponse = + protobuf.GetAllRecipeInstancesMetadataResponse(a.set.toSeq.map(ctxToProto(_))) + + override def fromProto(message: protobuf.GetAllRecipeInstancesMetadataResponse): Try[GetAllRecipeInstancesMetadataResponse] = + for { + set <- message.set.toList.traverse(ctxFromProto(_)) + } yield GetAllRecipeInstancesMetadataResponse(set.toSet) + } + + implicit def getRecipeInstanceStateRequestProto: ProtoMap[GetRecipeInstanceStateRequest, protobuf.GetRecipeInstanceStateRequest] = + new ProtoMap[GetRecipeInstanceStateRequest, protobuf.GetRecipeInstanceStateRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.GetRecipeInstanceStateRequest] = + protobuf.GetRecipeInstanceStateRequest + + override def toProto(a: GetRecipeInstanceStateRequest): protobuf.GetRecipeInstanceStateRequest = + protobuf.GetRecipeInstanceStateRequest(Some(a.recipeInstanceId)) + + override def fromProto(message: protobuf.GetRecipeInstanceStateRequest): Try[GetRecipeInstanceStateRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + } yield GetRecipeInstanceStateRequest(recipeInstanceId) + } + + implicit def getRecipeInstanceStateResponseProto: ProtoMap[GetRecipeInstanceStateResponse, protobuf.GetRecipeInstanceStateResponse] = + new ProtoMap[GetRecipeInstanceStateResponse, protobuf.GetRecipeInstanceStateResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.GetRecipeInstanceStateResponse] = + protobuf.GetRecipeInstanceStateResponse + + override def toProto(a: GetRecipeInstanceStateResponse): protobuf.GetRecipeInstanceStateResponse = + protobuf.GetRecipeInstanceStateResponse(Some(ctxToProto(a.recipeInstanceState))) + + override def fromProto(message: protobuf.GetRecipeInstanceStateResponse): Try[GetRecipeInstanceStateResponse] = + for { + recipeInstanceState <- versioned(message.recipeInstanceState, "recipeInstanceState") + decodedSensoryEventResult <- ctxFromProto(recipeInstanceState) + } yield GetRecipeInstanceStateResponse(decodedSensoryEventResult) + } + + implicit def getVisualStateRequestProto: ProtoMap[GetVisualStateRequest, protobuf.GetVisualStateRequest] = + new ProtoMap[GetVisualStateRequest, protobuf.GetVisualStateRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.GetVisualStateRequest] = + protobuf.GetVisualStateRequest + + override def toProto(a: GetVisualStateRequest): protobuf.GetVisualStateRequest = + protobuf.GetVisualStateRequest(Some(a.recipeInstanceId)) + + override def fromProto(message: protobuf.GetVisualStateRequest): Try[GetVisualStateRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + } yield GetVisualStateRequest(recipeInstanceId) + } + + implicit def getVisualStateResponseProto: ProtoMap[GetVisualStateResponse, protobuf.GetVisualStateResponse] = + new ProtoMap[GetVisualStateResponse, protobuf.GetVisualStateResponse] { + + override def companion: GeneratedMessageCompanion[protobuf.GetVisualStateResponse] = + protobuf.GetVisualStateResponse + + override def toProto(a: GetVisualStateResponse): protobuf.GetVisualStateResponse = + protobuf.GetVisualStateResponse(Some(a.state)) + + override def fromProto(message: protobuf.GetVisualStateResponse): Try[GetVisualStateResponse] = + for { + state <- versioned(message.state, "state") + } yield GetVisualStateResponse(state) + } + + implicit def retryInteractionRequestProto: ProtoMap[RetryInteractionRequest, protobuf.RetryInteractionRequest] = + new ProtoMap[RetryInteractionRequest, protobuf.RetryInteractionRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.RetryInteractionRequest] = + protobuf.RetryInteractionRequest + + override def toProto(a: RetryInteractionRequest): protobuf.RetryInteractionRequest = + protobuf.RetryInteractionRequest(Some(a.recipeInstanceId), Some(a.interactionName)) + + override def fromProto(message: protobuf.RetryInteractionRequest): Try[RetryInteractionRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + interactionName <- versioned(message.interactionName, "interactionName") + } yield RetryInteractionRequest(recipeInstanceId, interactionName) + } + + implicit def resolveInteractionRequestProto: ProtoMap[ResolveInteractionRequest, protobuf.ResolveInteractionRequest] = + new ProtoMap[ResolveInteractionRequest, protobuf.ResolveInteractionRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.ResolveInteractionRequest] = + protobuf.ResolveInteractionRequest + + override def toProto(a: ResolveInteractionRequest): protobuf.ResolveInteractionRequest = + protobuf.ResolveInteractionRequest(Some(a.recipeInstanceId), Some(a.interactionName), Some(ctxToProto(a.event))) + + override def fromProto(message: protobuf.ResolveInteractionRequest): Try[ResolveInteractionRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + interactionName <- versioned(message.interactionName, "interactionName") + event <- versioned(message.event, "event") + decodedEvent <- ctxFromProto(event) + } yield ResolveInteractionRequest(recipeInstanceId, interactionName, decodedEvent) + } + + implicit def stopRetryingInteractionRequestProto: ProtoMap[StopRetryingInteractionRequest, protobuf.StopRetryingInteractionRequest] = + new ProtoMap[StopRetryingInteractionRequest, protobuf.StopRetryingInteractionRequest] { + + override def companion: GeneratedMessageCompanion[protobuf.StopRetryingInteractionRequest] = + protobuf.StopRetryingInteractionRequest + + override def toProto(a: StopRetryingInteractionRequest): protobuf.StopRetryingInteractionRequest = + protobuf.StopRetryingInteractionRequest(Some(a.recipeInstanceId), Some(a.interactionName)) + + override def fromProto(message: protobuf.StopRetryingInteractionRequest): Try[StopRetryingInteractionRequest] = + for { + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + interactionName <- versioned(message.interactionName, "interactionName") + } yield StopRetryingInteractionRequest(recipeInstanceId, interactionName) + } +} diff --git a/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/BaaSProtocol.scala b/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/BaaSProtocol.scala new file mode 100644 index 000000000..296913f59 --- /dev/null +++ b/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/BaaSProtocol.scala @@ -0,0 +1,54 @@ +package com.ing.baker.baas.protocol + +import com.ing.baker.il.CompiledRecipe +import com.ing.baker.runtime.common.{BakerException, SensoryEventStatus} +import com.ing.baker.runtime.scaladsl._ + +object BaaSProtocol { + + case class BaaSRemoteFailure(error: BakerException) + + case class AddRecipeRequest(compiledRecipe: CompiledRecipe) + + case class AddRecipeResponse(recipeId: String) + + case class GetRecipeRequest(recipeId: String) + + case class GetRecipeResponse(recipeInformation: RecipeInformation) + + case class GetAllRecipesResponse(map: Map[String, RecipeInformation]) + + case class BakeRequest(recipeId: String, recipeInstanceId: String) + + case class FireEventAndResolveWhenReceivedRequest(recipeInstanceId: String, event: EventInstance, correlationId: Option[String]) + + case class FireEventAndResolveWhenReceivedResponse(sensoryEventStatus: SensoryEventStatus) + + case class FireEventAndResolveWhenCompletedRequest(recipeInstanceId: String, event: EventInstance, correlationId: Option[String]) + + case class FireEventAndResolveWhenCompletedResponse(sensoryEventResult: SensoryEventResult) + + case class FireEventAndResolveOnEventRequest(recipeInstanceId: String, event: EventInstance, onEvent: String, correlationId: Option[String]) + + case class FireEventAndResolveOnEventResponse(sensoryEventResult: SensoryEventResult) + + case class FireEventRequest(recipeInstanceId: String, event: EventInstance, correlationId: Option[String]) + + // case class FireEventResponse() TODO figure out how to deal with this one + + case class GetAllRecipeInstancesMetadataResponse(set: Set[RecipeInstanceMetadata]) + + case class GetRecipeInstanceStateRequest(recipeInstanceId: String) + + case class GetRecipeInstanceStateResponse(recipeInstanceState: RecipeInstanceState) + + case class GetVisualStateRequest(recipeInstanceId: String) + + case class GetVisualStateResponse(state: String) + + case class RetryInteractionRequest(recipeInstanceId: String, interactionName: String) + + case class ResolveInteractionRequest(recipeInstanceId: String, interactionName: String, event: EventInstance) + + case class StopRetryingInteractionRequest(recipeInstanceId: String, interactionName: String) +} diff --git a/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/MarshallingUtils.scala b/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/MarshallingUtils.scala new file mode 100644 index 000000000..79dd21603 --- /dev/null +++ b/baas-protocol-baker/src/main/scala/com/ing/baker/baas/protocol/MarshallingUtils.scala @@ -0,0 +1,70 @@ +package com.ing.baker.baas.protocol + +import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller} +import akka.http.scaladsl.model.{ContentTypes, HttpResponse, MediaTypes, StatusCodes} +import akka.http.scaladsl.server.Directives.{complete, onSuccess} +import akka.http.scaladsl.server.Route +import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshal, Unmarshaller} +import akka.stream.Materializer +import com.ing.baker.runtime.common.BakerException +import com.ing.baker.runtime.serialization.ProtoMap +import BaaSProto._ + +import scala.concurrent.{ExecutionContext, Future} + +object MarshallingUtils { + + type ProtoMessage[A] = scalapb.GeneratedMessage with scalapb.Message[A] + + def completeWithBakerFailures[A, P <: ProtoMessage[P]](f: Future[A])(implicit ec: ExecutionContext, m1: ProtoMap[A, P]): Route = + complete(f.map(Right(_)).recover { case e: BakerException => Left(BaaSProtocol.BaaSRemoteFailure(e)) }) + + def completeWithBakerFailures(f: Future[Unit])(implicit ec: ExecutionContext): Route = + onSuccess(f.map(_ => None).recover { case e: BakerException => Some(e) }) { + case Some(e) => complete(BaaSProtocol.BaaSRemoteFailure(e)) + case None => complete(StatusCodes.OK) + } + + case class UnmarshalWithBakerExceptions[A](response: HttpResponse) { + + def withBakerExceptions[P <: ProtoMessage[P]](implicit ec: ExecutionContext, mat: Materializer, m1: ProtoMap[A, P]): Future[A] = { + for { + decoded <- Unmarshal(response).to[Either[BaaSProtocol.BaaSRemoteFailure, A]] + response <- decoded match { + case Left(e) => Future.failed(e.error) + case Right(a) => Future.successful(a) + } + } yield response + } + } + + def unmarshal[A](response: HttpResponse): UnmarshalWithBakerExceptions[A] = + UnmarshalWithBakerExceptions[A](response) + + def unmarshalBakerExceptions(response: HttpResponse)(implicit ec: ExecutionContext, mat: Materializer): Future[Unit] = + response.entity.httpEntity.contentType match { + case ContentTypes.`application/octet-stream` => + Unmarshal(response) + .to[BaaSProtocol.BaaSRemoteFailure] + .flatMap(e => Future.failed(e.error)) + case _ => + Future.successful(()) + } + + implicit def protoMarshaller[A, P <: ProtoMessage[P]](implicit mapping: ProtoMap[A, P]): ToEntityMarshaller[A] = + Marshaller.ByteArrayMarshaller.wrap(MediaTypes.`application/octet-stream`)(mapping.toByteArray) + + implicit def protoUnmarshaller[A, P <: ProtoMessage[P]](implicit mapping: ProtoMap[A, P]): FromEntityUnmarshaller[A] = + Unmarshaller.byteArrayUnmarshaller.map(mapping.fromByteArray(_).get) + + implicit def protoEitherMarshaller[A, P0 <: ProtoMessage[P0], B, P1 <: ProtoMessage[P1]](implicit m1: ProtoMap[A, P0], m2: ProtoMap[B, P1]): ToEntityMarshaller[Either[A, B]] = + Marshaller.ByteArrayMarshaller.wrap(MediaTypes.`application/octet-stream`) { + case Left(a) => m1.toByteArray(a) + case Right(b) => m2.toByteArray(b) + } + + implicit def protoEitherUnmarshaller[A, P0 <: ProtoMessage[P0], B, P1 <: ProtoMessage[P1]](implicit m1: ProtoMap[A, P0], m2: ProtoMap[B, P1]): FromEntityUnmarshaller[Either[A, B]] = + Unmarshaller.byteArrayUnmarshaller.map { byteArray => + m1.fromByteArray(byteArray).map(Left(_)).orElse(m2.fromByteArray(byteArray).map(Right(_))).get + } +} diff --git a/baas-protocol-interaction-scheduling/src/main/protobuf/interaction_schedulling.proto b/baas-protocol-interaction-scheduling/src/main/protobuf/interaction_schedulling.proto new file mode 100644 index 000000000..fe7a0497f --- /dev/null +++ b/baas-protocol-interaction-scheduling/src/main/protobuf/interaction_schedulling.proto @@ -0,0 +1,61 @@ +syntax = "proto2"; + +import "scalapb/scalapb.proto"; +import "common.proto"; + +option java_package = "com.ing.baker.baas.protocol.protobuf"; +option (scalapb.options) = { + flat_package: true +}; + +message ExecuteInstance { + repeated Ingredient input = 1; +} + +message InstanceExecutedSuccessfully { + optional RuntimeEvent result = 1; +} + +message InstanceExecutionFailed { + +} + +message InstanceExecutionTimedOut { + +} + +message NoInstanceFound { + +} + +message InvalidExecution { + +} + +message Push { + optional ActorRefId mandated = 1; + optional string uuid = 2; +} + +message Pull { + optional ActorRefId agent = 1; +} + +message AvailableQuest { + optional ActorRefId mandated = 1; + optional string uuid = 2; +} + +message Considering { + optional ActorRefId agent = 1; +} + +message Commit { + optional ActorRefId mandated = 1; + optional ExecuteInstance execute = 2; +} + +message QuestTaken { + +} + diff --git a/baas-protocol-interaction-scheduling/src/main/resources/reference.conf b/baas-protocol-interaction-scheduling/src/main/resources/reference.conf new file mode 100644 index 000000000..9c3844d9f --- /dev/null +++ b/baas-protocol-interaction-scheduling/src/main/resources/reference.conf @@ -0,0 +1,15 @@ + +akka { + actor { + + serializers { + interaction-scheduling-protobuf = "com.ing.baker.baas.akka.InteractionSchedulingProtocolsSerializer" + } + + serialization-bindings { + "com.ing.baker.baas.protocol.ProtocolInteractionExecution" = interaction-scheduling-protobuf + "com.ing.baker.baas.protocol.ProtocolPushPullMatching" = interaction-scheduling-protobuf + "com.ing.baker.baas.protocol.ProtocolQuestCommit" = interaction-scheduling-protobuf + } + } +} diff --git a/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/akka/InteractionSchedulingProtocolsSerializer.scala b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/akka/InteractionSchedulingProtocolsSerializer.scala new file mode 100644 index 000000000..43aeb9375 --- /dev/null +++ b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/akka/InteractionSchedulingProtocolsSerializer.scala @@ -0,0 +1,43 @@ +package com.ing.baker.baas.akka + +import akka.actor.ExtendedActorSystem +import com.ing.baker.runtime.serialization.{SerializersProvider, TypedProtobufSerializer} +import com.ing.baker.runtime.serialization.TypedProtobufSerializer.{BinarySerializable, forType} +import com.ing.baker.baas.protocol.InteractionSchedulingProto._ + +object InteractionSchedulingProtocolsSerializer { + + val identifier: Int = 102 + + def entries(ev0: SerializersProvider): List[BinarySerializable] = { + implicit val ev = ev0 + List( + forType[com.ing.baker.baas.protocol.ProtocolInteractionExecution.ExecuteInstance] + .register("com.ing.baker.baas.protocol.ProtocolInteractionExecution.ExecuteInstance"), + forType[com.ing.baker.baas.protocol.ProtocolInteractionExecution.InstanceExecutedSuccessfully] + .register("com.ing.baker.baas.protocol.ProtocolInteractionExecution.InstanceExecutedSuccessfully"), + forType[com.ing.baker.baas.protocol.ProtocolInteractionExecution.InstanceExecutionFailed] + .register("com.ing.baker.baas.protocol.ProtocolInteractionExecution.InstanceExecutionFailed"), + forType[com.ing.baker.baas.protocol.ProtocolInteractionExecution.InstanceExecutionTimedOut] + .register("com.ing.baker.baas.protocol.ProtocolInteractionExecution.InstanceExecutionTimedOut"), + forType[com.ing.baker.baas.protocol.ProtocolInteractionExecution.NoInstanceFound.type] + .register("com.ing.baker.baas.protocol.ProtocolInteractionExecution.NoInstanceFound"), + forType[com.ing.baker.baas.protocol.ProtocolInteractionExecution.InvalidExecution] + .register("com.ing.baker.baas.protocol.ProtocolInteractionExecution.InvalidExecution"), + forType[com.ing.baker.baas.protocol.ProtocolPushPullMatching.Push] + .register("com.ing.baker.baas.protocol.ProtocolPushPullMatching.Push"), + forType[com.ing.baker.baas.protocol.ProtocolPushPullMatching.Pull] + .register("com.ing.baker.baas.protocol.ProtocolPushPullMatching.Pull"), + forType[com.ing.baker.baas.protocol.ProtocolPushPullMatching.AvailableQuest] + .register("com.ing.baker.baas.protocol.ProtocolPushPullMatching.AvailableQuest"), + forType[com.ing.baker.baas.protocol.ProtocolQuestCommit.Considering] + .register("com.ing.baker.baas.protocol.ProtocolQuestCommit.Considering"), + forType[com.ing.baker.baas.protocol.ProtocolQuestCommit.Commit] + .register("com.ing.baker.baas.protocol.ProtocolQuestCommit.Commit"), + forType[com.ing.baker.baas.protocol.ProtocolQuestCommit.QuestTaken.type] + .register("com.ing.baker.baas.protocol.ProtocolQuestCommit.QuestTaken") + ) + } +} + +class InteractionSchedulingProtocolsSerializer(system: ExtendedActorSystem) extends TypedProtobufSerializer(system, InteractionSchedulingProtocolsSerializer.identifier ,InteractionSchedulingProtocolsSerializer.entries) diff --git a/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/InteractionSchedulingProto.scala b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/InteractionSchedulingProto.scala new file mode 100644 index 000000000..cf3dd67ca --- /dev/null +++ b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/InteractionSchedulingProto.scala @@ -0,0 +1,184 @@ +package com.ing.baker.baas.protocol + +import java.util.UUID + +import cats.implicits._ +import com.ing.baker.baas.protocol.ProtocolInteractionExecution._ +import com.ing.baker.baas.protocol.ProtocolPushPullMatching._ +import com.ing.baker.baas.protocol.ProtocolQuestCommit._ +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider} +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} + +import scala.util.{Success, Try} + +object InteractionSchedulingProto { + + implicit def executeInstanceProto: ProtoMap[ExecuteInstance, protobuf.ExecuteInstance] = + new ProtoMap[ExecuteInstance, protobuf.ExecuteInstance] { + + val companion = protobuf.ExecuteInstance + + def toProto(a: ExecuteInstance): protobuf.ExecuteInstance = + protobuf.ExecuteInstance(a.input.map(ctxToProto(_))) + + def fromProto(message: protobuf.ExecuteInstance): Try[ExecuteInstance] = + for { + result <- message.input.toList.traverse(ctxFromProto(_)) + } yield ExecuteInstance(result) + } + + implicit def instanceExcutedSuccessfullyProto: ProtoMap[InstanceExecutedSuccessfully, protobuf.InstanceExecutedSuccessfully] = + new ProtoMap[InstanceExecutedSuccessfully, protobuf.InstanceExecutedSuccessfully] { + + val companion = protobuf.InstanceExecutedSuccessfully + + def toProto(a: InstanceExecutedSuccessfully): protobuf.InstanceExecutedSuccessfully = + protobuf.InstanceExecutedSuccessfully(a.result.map(ctxToProto(_))) + + def fromProto(message: protobuf.InstanceExecutedSuccessfully): Try[InstanceExecutedSuccessfully] = + for { + result <- message.result.traverse(ctxFromProto(_)) + } yield InstanceExecutedSuccessfully(result) + } + + implicit def instanceExecutionFailedProto: ProtoMap[InstanceExecutionFailed, protobuf.InstanceExecutionFailed] = + new ProtoMap[InstanceExecutionFailed, protobuf.InstanceExecutionFailed] { + + val companion = protobuf.InstanceExecutionFailed + + def toProto(a: InstanceExecutionFailed): protobuf.InstanceExecutionFailed = + protobuf.InstanceExecutionFailed() + + def fromProto(message: protobuf.InstanceExecutionFailed): Try[InstanceExecutionFailed] = + Success(InstanceExecutionFailed()) + } + + implicit def instanceExecutionTimedOutProto: ProtoMap[InstanceExecutionTimedOut, protobuf.InstanceExecutionTimedOut] = + new ProtoMap[InstanceExecutionTimedOut, protobuf.InstanceExecutionTimedOut] { + + val companion = protobuf.InstanceExecutionTimedOut + + def toProto(a: InstanceExecutionTimedOut): protobuf.InstanceExecutionTimedOut = + protobuf.InstanceExecutionTimedOut() + + def fromProto(message: protobuf.InstanceExecutionTimedOut): Try[InstanceExecutionTimedOut] = + Success(InstanceExecutionTimedOut()) + } + + implicit def noInstanceFoundProto: ProtoMap[NoInstanceFound.type, protobuf.NoInstanceFound] = + new ProtoMap[NoInstanceFound.type, protobuf.NoInstanceFound] { + + val companion = protobuf.NoInstanceFound + + def toProto(a: NoInstanceFound.type): protobuf.NoInstanceFound = + protobuf.NoInstanceFound() + + def fromProto(message: protobuf.NoInstanceFound): Try[NoInstanceFound.type] = + Success(NoInstanceFound) + } + + implicit def invalidExecutionProto: ProtoMap[InvalidExecution, protobuf.InvalidExecution] = + new ProtoMap[InvalidExecution, protobuf.InvalidExecution] { + + val companion = protobuf.InvalidExecution + + def toProto(a: InvalidExecution): protobuf.InvalidExecution = + protobuf.InvalidExecution() + + def fromProto(message: protobuf.InvalidExecution): Try[InvalidExecution] = + Success(InvalidExecution()) + } + + implicit def pushProto(implicit ev0: SerializersProvider): ProtoMap[Push, protobuf.Push] = + new ProtoMap[Push, protobuf.Push] { + + val companion = protobuf.Push + + def toProto(a: Push): protobuf.Push = + protobuf.Push(Some(ctxToProto(a.mandated)), Some(a.uuid.toString)) + + def fromProto(message: protobuf.Push): Try[Push] = + for { + mandatedId <- versioned(message.mandated, "mandated") + mandated <- ctxFromProto(mandatedId) + uuidRaw <- versioned(message.uuid, "uuid") + uuid <- Try(UUID.fromString(uuidRaw)) + } yield Push(mandated, uuid) + } + + implicit def pullProto(implicit ev0: SerializersProvider): ProtoMap[Pull, protobuf.Pull] = + new ProtoMap[Pull, protobuf.Pull] { + + val companion = protobuf.Pull + + def toProto(a: Pull): protobuf.Pull = + protobuf.Pull(Some(ctxToProto(a.agent))) + + def fromProto(message: protobuf.Pull): Try[Pull] = + for { + agentId <- versioned(message.agent, "agent") + agent <- ctxFromProto(agentId) + } yield Pull(agent) + } + + implicit def availableQuestProto(implicit ev0: SerializersProvider): ProtoMap[AvailableQuest, protobuf.AvailableQuest] = + new ProtoMap[AvailableQuest, protobuf.AvailableQuest] { + + val companion = protobuf.AvailableQuest + + def toProto(a: AvailableQuest): protobuf.AvailableQuest = + protobuf.AvailableQuest(Some(ctxToProto(a.mandated)), Some(a.uuid.toString)) + + def fromProto(message: protobuf.AvailableQuest): Try[AvailableQuest] = + for { + mandatedId <- versioned(message.mandated, "mandated") + mandated <- ctxFromProto(mandatedId) + uuidRaw <- versioned(message.uuid, "uuid") + uuid <- Try(UUID.fromString(uuidRaw)) + } yield AvailableQuest(mandated, uuid) + } + + implicit def consideringProto(implicit ev0: SerializersProvider): ProtoMap[Considering, protobuf.Considering] = + new ProtoMap[Considering, protobuf.Considering] { + + val companion = protobuf.Considering + + def toProto(a: Considering): protobuf.Considering = + protobuf.Considering(Some(ctxToProto(a.agent))) + + def fromProto(message: protobuf.Considering): Try[Considering] = + for { + agentId <- versioned(message.agent, "agent") + agent <- ctxFromProto(agentId) + } yield Considering(agent) + } + + implicit def commitProto(implicit ev0: SerializersProvider): ProtoMap[Commit, protobuf.Commit] = + new ProtoMap[Commit, protobuf.Commit] { + + val companion = protobuf.Commit + + def toProto(a: Commit): protobuf.Commit = + protobuf.Commit(Some(ctxToProto(a.mandated)), Some(ctxToProto(a.execute))) + + def fromProto(message: protobuf.Commit): Try[Commit] = + for { + mandatedId <- versioned(message.mandated, "mandated") + mandated <- ctxFromProto(mandatedId) + executeProto <- versioned(message.execute, "execute") + execute <- ctxFromProto(executeProto) + } yield Commit(mandated, execute) + } + + implicit def questTakenProto: ProtoMap[QuestTaken.type, protobuf.QuestTaken] = + new ProtoMap[QuestTaken.type, protobuf.QuestTaken] { + + val companion = protobuf.QuestTaken + + def toProto(a: QuestTaken.type): protobuf.QuestTaken = + protobuf.QuestTaken() + + def fromProto(message: protobuf.QuestTaken): Try[QuestTaken.type] = + Success(QuestTaken) + } +} diff --git a/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolInteractionExecution.scala b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolInteractionExecution.scala new file mode 100644 index 000000000..e6e7d0968 --- /dev/null +++ b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolInteractionExecution.scala @@ -0,0 +1,45 @@ +package com.ing.baker.baas.protocol + +import com.ing.baker.runtime.scaladsl.{EventInstance, IngredientInstance} + +/** + * Protocol executed after a match between a QuestMandate and InteractionAgent has been made and after both + * have committed. + * + * A simple request from the manager to the agent for execution with specific ingredients is done using the + * ExecuteInstance message, the outcome comes in the form of either the response messages InstanceExecutedSuccessfully, + * InstanceExecutionFailed or InvalidExecution + * + */ +sealed trait ProtocolInteractionExecution + +object ProtocolInteractionExecution { + + case class ExecuteInstance(input: Seq[IngredientInstance]) extends ProtocolInteractionExecution + + /** + * Instance executed successfully + * @param result the EventInstance that is created, empty if interaction does not return an Event + */ + case class InstanceExecutedSuccessfully(result: Option[EventInstance]) extends ProtocolInteractionExecution + + /** + * Technical failure of the interaction + */ + case class InstanceExecutionFailed() extends ProtocolInteractionExecution + + /** + * Technical failure of the interaction + */ + case class InstanceExecutionTimedOut() extends ProtocolInteractionExecution + + /** + * Technical failure of the interaction + */ + case object NoInstanceFound extends ProtocolInteractionExecution + + /** + * Invalid request, bad ingredients given + */ + case class InvalidExecution() extends ProtocolInteractionExecution +} diff --git a/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolPushPullMatching.scala b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolPushPullMatching.scala new file mode 100644 index 000000000..dbf8c2e74 --- /dev/null +++ b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolPushPullMatching.scala @@ -0,0 +1,26 @@ +package com.ing.baker.baas.protocol + +import java.util.UUID + +import akka.actor.ActorRef + +/** + * Protocol done to find a possible matching between a QuestMandated and an available InteractionAgent + */ +sealed trait ProtocolPushPullMatching + +object ProtocolPushPullMatching { + + def pushTopic(interactionName: String): String = + s"Push|:||:|$interactionName|:|" + + def pullTopic(interactionName: String): String = + s"Pull|:||:|$interactionName|:|" + + case class Push(mandated: ActorRef, uuid: UUID) extends ProtocolPushPullMatching + + case class Pull(agent: ActorRef) extends ProtocolPushPullMatching + + case class AvailableQuest(mandated: ActorRef, uuid: UUID) extends ProtocolPushPullMatching + +} diff --git a/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolQuestCommit.scala b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolQuestCommit.scala new file mode 100644 index 000000000..05e621f7a --- /dev/null +++ b/baas-protocol-interaction-scheduling/src/main/scala/com/ing/baker/baas/protocol/ProtocolQuestCommit.scala @@ -0,0 +1,18 @@ +package com.ing.baker.baas.protocol + +import akka.actor.ActorRef + +/** + * A Protocol executed after finding a candidate match between a QuestMandated and an InteractionAgent, it makes sure + * that 1 QuestMandated commits with 1 InteractionAgent only and vice versa, without leaving orphan agents. + */ +sealed trait ProtocolQuestCommit + +object ProtocolQuestCommit { + + case class Considering(agent: ActorRef) extends ProtocolQuestCommit + + case class Commit(mandated: ActorRef, execute: ProtocolInteractionExecution.ExecuteInstance) extends ProtocolQuestCommit + + case object QuestTaken extends ProtocolQuestCommit +} diff --git a/baas-protocol-recipe-event-publishing/src/main/protobuf/recipe_event_publishing.proto b/baas-protocol-recipe-event-publishing/src/main/protobuf/recipe_event_publishing.proto new file mode 100644 index 000000000..2119eab3a --- /dev/null +++ b/baas-protocol-recipe-event-publishing/src/main/protobuf/recipe_event_publishing.proto @@ -0,0 +1,14 @@ +syntax = "proto2"; + +import "scalapb/scalapb.proto"; +import "common.proto"; + +option java_package = "com.ing.baker.baas.protocol.protobuf"; +option (scalapb.options) = { + flat_package: true +}; + +message Event { + optional RecipeEventMetadata recipeEventMetadata = 1; + optional RuntimeEvent event = 2; +} diff --git a/baas-protocol-recipe-event-publishing/src/main/resources/reference.conf b/baas-protocol-recipe-event-publishing/src/main/resources/reference.conf new file mode 100644 index 000000000..b82403e45 --- /dev/null +++ b/baas-protocol-recipe-event-publishing/src/main/resources/reference.conf @@ -0,0 +1,13 @@ + +akka { + actor { + + serializers { + event-publishing-protobuf = "com.ing.baker.baas.akka.DistributedEventPublishingProtocolSerializer" + } + + serialization-bindings { + "com.ing.baker.baas.protocol.ProtocolDistributedEventPublishing" = event-publishing-protobuf + } + } +} diff --git a/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/akka/DistributedEventPublishingProtocolSerializer.scala b/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/akka/DistributedEventPublishingProtocolSerializer.scala new file mode 100644 index 000000000..a8b87d4ac --- /dev/null +++ b/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/akka/DistributedEventPublishingProtocolSerializer.scala @@ -0,0 +1,26 @@ +package com.ing.baker.baas.akka + +import akka.actor.ExtendedActorSystem +import com.ing.baker.baas.protocol.DistributedEventPublishingProto._ +import com.ing.baker.baas.protocol.ProtocolDistributedEventPublishing +import com.ing.baker.runtime.serialization.TypedProtobufSerializer.{BinarySerializable, forType} +import com.ing.baker.runtime.serialization.{SerializersProvider, TypedProtobufSerializer} + +object DistributedEventPublishingProtocolSerializer { + + val identifier: Int = 103 + + def entries(ev0: SerializersProvider): List[BinarySerializable] = { + implicit val ev = ev0 + List( + forType[ProtocolDistributedEventPublishing.Event] + .register("ProtocolDistributedEventPublishing.Event") + ) + } +} + +class DistributedEventPublishingProtocolSerializer(system: ExtendedActorSystem) extends TypedProtobufSerializer( + system, + DistributedEventPublishingProtocolSerializer.identifier, + DistributedEventPublishingProtocolSerializer.entries +) diff --git a/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/protocol/DistributedEventPublishingProto.scala b/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/protocol/DistributedEventPublishingProto.scala new file mode 100644 index 000000000..d0fe1af9b --- /dev/null +++ b/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/protocol/DistributedEventPublishingProto.scala @@ -0,0 +1,28 @@ +package com.ing.baker.baas.protocol + +import com.ing.baker.baas.protocol.ProtocolDistributedEventPublishing.Event +import com.ing.baker.runtime.serialization.ProtoMap +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} + +import scala.util.Try + +object DistributedEventPublishingProto { + + implicit def eventProto: ProtoMap[Event, protobuf.Event] = + new ProtoMap[Event, protobuf.Event] { + + val companion = protobuf.Event + + def toProto(a: Event): protobuf.Event = + protobuf.Event(Some(ctxToProto(a.recipeEventMetadata)), Some(ctxToProto(a.event))) + + def fromProto(message: protobuf.Event): Try[Event] = + for { + recipeEventMetadataProto <- versioned(message.recipeEventMetadata, "recipeEventMetadata") + recipeEventMetadata <- ctxFromProto(recipeEventMetadataProto) + eventProto <- versioned(message.event, "event") + event <- ctxFromProto(eventProto) + } yield Event(recipeEventMetadata, event) + } + +} diff --git a/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/protocol/ProtocolDistributedEventPublishing.scala b/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/protocol/ProtocolDistributedEventPublishing.scala new file mode 100644 index 000000000..29d19b2ba --- /dev/null +++ b/baas-protocol-recipe-event-publishing/src/main/scala/com/ing/baker/baas/protocol/ProtocolDistributedEventPublishing.scala @@ -0,0 +1,13 @@ +package com.ing.baker.baas.protocol + +import com.ing.baker.runtime.scaladsl.{EventInstance, RecipeEventMetadata} + +sealed trait ProtocolDistributedEventPublishing + +object ProtocolDistributedEventPublishing { + + def eventsTopic(recipeName: String): String = + s"recipe-event-publishing:$recipeName:event" + + case class Event(recipeEventMetadata: RecipeEventMetadata, event: EventInstance) extends ProtocolDistributedEventPublishing +} diff --git a/baas-tests/src/test/resources/application.conf b/baas-tests/src/test/resources/application.conf new file mode 100644 index 000000000..14b0925f8 --- /dev/null +++ b/baas-tests/src/test/resources/application.conf @@ -0,0 +1,93 @@ +baker { + + config-file-included = true + + actor { + # the id of the journal to read events from + read-journal-plugin = "inmemory-read-journal" + + # either "local" or "cluster-sharded" + provider = "cluster-sharded" + + # the recommended nr is number-of-cluster-nodes * 10 + cluster.nr-of-shards = 10 + + # the time that inactive actors (processes) stay in memory + idle-timeout = 5 minutes + + # The interval that a check is done of processes should be deleted + retention-check-interval = 1 minutes + } + + # the default timeout for Baker.bake(..) process creation calls + bake-timeout = 10 seconds + + # the timeout for refreshing the local recipe cache + process-index-update-cache-timeout = 5 seconds + + # the default timeout for Baker.processEvent(..) + process-event-timeout = 10 seconds + + # the default timeout for inquires on Baker, this means getIngredients(..) & getEvents(..) + process-inquire-timeout = 10 seconds + + # when baker starts up, it attempts to 'initialize' the journal connection, this may take some time + journal-initialize-timeout = 30 seconds + + # the default timeout for adding a recipe to Baker + add-recipe-timeout = 10 seconds + + # the time to wait for a gracefull shutdown + shutdown-timeout = 30 seconds + + # The ingredients that are filtered out when getting the process instance. + # This should be used if there are big ingredients to improve performance and memory usage. + # The ingredients will be in the ingredients map but there value will be an empty String. + filtered-ingredient-values = [] + + # encryption settings + encryption { + + # whether to encrypt data stored in the journal, off or on + enabled = off + + # if enabled = on, a secret should be set + # secret = ??? + } + + # use "local" unless you are configuring a BaaS environment, then you will need "remote" + interaction-manager = "remote" +} + +akka { + + cluster { + jmx.multi-mbeans-in-same-jvm = on + sharding.state-store-mode = persistence + } + + persistence { + journal.plugin = "inmemory-journal" + snapshot-store.plugin = "inmemory-snapshot-store" + } + + actor { + provider = "cluster" + idle-timeout = 1 minute + allow-java-serialization = off + + serializers { + baker-typed-protobuf = "com.ing.baker.runtime.akka.actor.serialization.BakerTypedProtobufSerializer" + } + + serialization-bindings { + "com.ing.baker.runtime.serialization.BakerSerializable" = baker-typed-protobuf + "com.ing.baker.types.Value" = baker-typed-protobuf + "com.ing.baker.types.Type" = baker-typed-protobuf + "com.ing.baker.il.CompiledRecipe" = baker-typed-protobuf + "com.ing.baker.runtime.scaladsl.EventInstance" = baker-typed-protobuf + "com.ing.baker.runtime.scaladsl.RecipeInstanceState" = baker-typed-protobuf + "com.ing.baker.runtime.scaladsl.RecipeEventMetadata" = baker-typed-protobuf + } + } +} diff --git a/baas-tests/src/test/scala/com/ing/baker/baas/recipe/CheckoutFlowRecipe.scala b/baas-tests/src/test/scala/com/ing/baker/baas/recipe/CheckoutFlowRecipe.scala new file mode 100644 index 000000000..bbc41cc27 --- /dev/null +++ b/baas-tests/src/test/scala/com/ing/baker/baas/recipe/CheckoutFlowRecipe.scala @@ -0,0 +1,130 @@ +package com.ing.baker.baas.recipe + +import com.ing.baker.baas.recipe.CheckoutFlowEvents._ +import com.ing.baker.baas.recipe.CheckoutFlowIngredients._ +import com.ing.baker.baas.recipe.CheckoutFlowInteractions._ +import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff.UntilDeadline +import com.ing.baker.recipe.common.InteractionFailureStrategy.{BlockInteraction, RetryWithIncrementalBackoff} +import com.ing.baker.recipe.scaladsl.{Event, Ingredient, Interaction, Recipe} + +import scala.concurrent.Future +import scala.concurrent.duration._ + +object CheckoutFlowIngredients { + + case class OrderId(orderId: String) + + case class Item(itemId: String) + + case class ReservedItems(items: List[Item], data: Array[Byte]) + + case class ShippingAddress(address: String) + + case class PaymentInformation(info: String) + + case class ShippingOrder(items: List[Item], data: Array[Byte], address: ShippingAddress) + +} + +object CheckoutFlowEvents { + + case class OrderPlaced(orderId: OrderId, items: List[Item]) + + case class PaymentInformationReceived(paymentInformation: PaymentInformation) + + case class ShippingAddressReceived(shippingAddress: ShippingAddress) + + sealed trait ReserveItemsOutput + + case class OrderHadUnavailableItems(unavailableItems: List[Item]) extends ReserveItemsOutput + + case class ItemsReserved(reservedItems: ReservedItems) extends ReserveItemsOutput + + sealed trait MakePaymentOutput + + case class PaymentSuccessful(shippingOrder: ShippingOrder) extends MakePaymentOutput + + case class PaymentFailed() extends MakePaymentOutput + + case class ShippingConfirmed() + +} + +object CheckoutFlowInteractions { + + trait ReserveItems { + + def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] + } + + def ReserveItemsInteraction: Interaction = Interaction( + name = "ReserveItems", + inputIngredients = Seq( + Ingredient[OrderId]("orderId"), + Ingredient[List[Item]]("items") + ), + output = Seq( + Event[OrderHadUnavailableItems], + Event[ItemsReserved] + ) + ) + + trait MakePayment { + + def apply(items: ReservedItems, address: ShippingAddress, payment: PaymentInformation): Future[MakePaymentOutput] + } + + def MakePaymentInteraction: Interaction = Interaction( + name = "MakePayment", + inputIngredients = Seq( + Ingredient[ReservedItems]("reservedItems"), + Ingredient[ShippingAddress]("shippingAddress"), + Ingredient[PaymentInformation]("paymentInformation") + ), + output = Seq( + Event[PaymentSuccessful], + Event[PaymentFailed] + ) + ) + + trait ShipItems { + + def apply(order: ShippingOrder): Future[ShippingConfirmed] + } + + def ShipItemsInteraction: Interaction = Interaction( + name = "ShipItems", + inputIngredients = Seq( + Ingredient[ShippingOrder]("shippingOrder") + ), + output = Seq( + Event[ShippingConfirmed] + ) + ) +} + +object CheckoutFlowRecipe { + + private def recipeBase = Recipe("Webshop") + .withSensoryEvents( + Event[OrderPlaced], + Event[PaymentInformationReceived], + Event[ShippingAddressReceived]) + .withInteractions( + ReserveItemsInteraction, + MakePaymentInteraction, + ShipItemsInteraction) + + def recipe: Recipe = + recipeBase.withDefaultFailureStrategy( + RetryWithIncrementalBackoff + .builder() + .withInitialDelay(100.milliseconds) + .withUntil(Some(UntilDeadline(24.hours))) + .withMaxTimeBetweenRetries(Some(10.minutes)) + .build()) + + def recipeWithBlockingStrategy: Recipe = + recipeBase.withDefaultFailureStrategy( + BlockInteraction()) +} diff --git a/baas-tests/src/test/scala/com/ing/baker/baas/recipe/MakePaymentInstance.scala b/baas-tests/src/test/scala/com/ing/baker/baas/recipe/MakePaymentInstance.scala new file mode 100644 index 000000000..470b1fa8b --- /dev/null +++ b/baas-tests/src/test/scala/com/ing/baker/baas/recipe/MakePaymentInstance.scala @@ -0,0 +1,18 @@ +package com.ing.baker.baas.recipe + +import cats.effect.{IO, Timer} +import com.ing.baker.baas.recipe.CheckoutFlowEvents.MakePaymentOutput +import com.ing.baker.baas.recipe.CheckoutFlowIngredients.{PaymentInformation, ReservedItems, ShippingAddress, ShippingOrder} +import com.ing.baker.baas.recipe.CheckoutFlowInteractions.MakePayment + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class MakePaymentInstance(implicit timer: Timer[IO]) extends MakePayment { + + override def apply(items: ReservedItems, address: ShippingAddress, payment: PaymentInformation): Future[MakePaymentOutput] = { + IO.sleep(5.second) + .map(_ => CheckoutFlowEvents.PaymentSuccessful(ShippingOrder(items.items, items.data, address))) + .unsafeToFuture() + } +} diff --git a/baas-tests/src/test/scala/com/ing/baker/baas/recipe/ReserveItemsInstance.scala b/baas-tests/src/test/scala/com/ing/baker/baas/recipe/ReserveItemsInstance.scala new file mode 100644 index 000000000..7d1b0f715 --- /dev/null +++ b/baas-tests/src/test/scala/com/ing/baker/baas/recipe/ReserveItemsInstance.scala @@ -0,0 +1,34 @@ +package com.ing.baker.baas.recipe + +import cats.effect.{IO, Timer} +import cats.implicits._ +import com.ing.baker.baas.recipe.CheckoutFlowEvents.ReserveItemsOutput +import com.ing.baker.baas.recipe.CheckoutFlowIngredients.{Item, OrderId, ReservedItems} +import com.ing.baker.baas.recipe.CheckoutFlowInteractions.ReserveItems + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class ReserveItemsInstance(implicit timer: Timer[IO]) extends ReserveItems { + + override def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] = { + IO.sleep(1.second) + .as(CheckoutFlowEvents.ItemsReserved(ReservedItems(items, Array.fill(1000)(Byte.MaxValue)))) + .unsafeToFuture() + } +} + +class FailingOnceReserveItemsInstance extends ReserveItems { + + var times = 1; + + override def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] = + if (times == 1) { times = times + 1; Future.failed(new RuntimeException("oups")) } + else Future.successful(CheckoutFlowEvents.ItemsReserved(ReservedItems(items, Array.fill(1000)(Byte.MaxValue)))) +} + +class FailingReserveItemsInstance extends ReserveItems { + + override def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] = + Future.failed(new RuntimeException("oups")) +} diff --git a/baas-tests/src/test/scala/com/ing/baker/baas/recipe/ShipItemsInstance.scala b/baas-tests/src/test/scala/com/ing/baker/baas/recipe/ShipItemsInstance.scala new file mode 100644 index 000000000..723eeaaf9 --- /dev/null +++ b/baas-tests/src/test/scala/com/ing/baker/baas/recipe/ShipItemsInstance.scala @@ -0,0 +1,20 @@ +package com.ing.baker.baas.recipe + +import cats.effect.{IO, Timer} +import cats.implicits._ +import com.ing.baker.baas.recipe.CheckoutFlowEvents.ShippingConfirmed +import com.ing.baker.baas.recipe.CheckoutFlowIngredients.ShippingOrder +import com.ing.baker.baas.recipe.CheckoutFlowInteractions.ShipItems + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class ShipItemsInstance(implicit timer: Timer[IO]) extends ShipItems { + + override def apply(order: ShippingOrder): Future[ShippingConfirmed] = { + IO.sleep(500.millis) + .as(ShippingConfirmed()) + .unsafeToFuture() + } +} + diff --git a/baas-tests/src/test/scala/com/ing/baker/baas/spec/BaaSIntegrationSpec.scala b/baas-tests/src/test/scala/com/ing/baker/baas/spec/BaaSIntegrationSpec.scala new file mode 100644 index 000000000..77600547b --- /dev/null +++ b/baas-tests/src/test/scala/com/ing/baker/baas/spec/BaaSIntegrationSpec.scala @@ -0,0 +1,448 @@ +package com.ing.baker.baas.spec + +import java.util.UUID + +import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.stream.{ActorMaterializer, Materializer} +import cats.data.StateT +import cats.effect.{IO, Timer} +import cats.implicits._ +import com.ing.baker.baas.recipe.CheckoutFlowEvents.ItemsReserved +import com.ing.baker.baas.recipe.CheckoutFlowIngredients.{Item, OrderId, ReservedItems, ShippingAddress} +import com.ing.baker.baas.recipe._ +import com.ing.baker.baas.scaladsl.{BaaSEventListener, BaaSInteractionInstance, BakerClient} +import com.ing.baker.baas.spec.BaaSIntegrationSpec._ +import com.ing.baker.baas.state.BaaSServer +import com.ing.baker.compiler.RecipeCompiler +import com.ing.baker.il.CompiledRecipe +import com.ing.baker.runtime.akka.AkkaBaker +import com.ing.baker.runtime.common.LanguageDataStructures.LanguageApi +import com.ing.baker.runtime.common.{BakerException, SensoryEventStatus} +import com.ing.baker.runtime.scaladsl.{EventInstance, InteractionInstance, Baker => ScalaBaker} +import com.ing.baker.runtime.serialization.Encryption +import com.typesafe.config.{Config, ConfigFactory} +import org.jboss.netty.channel.ChannelException +import org.scalatest._ + +import scala.collection.mutable +import scala.concurrent.{ExecutionContext, Future} + +class BaaSIntegrationSpec + extends AsyncFunSpec + with Matchers + with BeforeAndAfterAll + with BeforeAndAfterEach { + + val test: IntegrationTest = BaaSIntegrationSpec.testWith + + implicit val timer: Timer[IO] = IO.timer(executionContext) + + describe("Baker Client-Server") { + it("Baker.addRecipe") { + test { (client, stateNode, interactionNode, _) => + for { + compiledRecipe <- setupHappyPath(interactionNode) + recipeId <- client.addRecipe(compiledRecipe) + recipeInformation <- stateNode.getRecipe(recipeId) + } yield recipeInformation.compiledRecipe shouldBe compiledRecipe + } + } + + it("Baker.getRecipe") { + test { (client, _, interactionNode, _) => + for { + compiledRecipe <- setupHappyPath(interactionNode) + recipeId <- client.addRecipe(compiledRecipe) + recipeInformation <- client.getRecipe(recipeId) + } yield recipeInformation.compiledRecipe shouldBe compiledRecipe + } + } + + it("Baker.getRecipe (fail with NoSuchRecipeException)") { + test { (client, _, _, _) => + for { + e <- client + .getRecipe("non-existent") + .map(_ => None) + .recover { case e: BakerException => Some(e) } + } yield e shouldBe Some(BakerException.NoSuchRecipeException("non-existent")) + } + } + + it("Baker.getAllRecipes") { + test { (client, stateNode, interactionNode, _) => + for { + compiledRecipe <- setupHappyPath(interactionNode) + recipeId <- client.addRecipe(compiledRecipe) + recipes <- client.getAllRecipes + } yield recipes.get(recipeId).map(_.compiledRecipe) shouldBe Some(compiledRecipe) + } + } + + it("Baker.bake") { + test { (client, stateNode, interactionNode, eventListenerNode) => + val recipeInstanceId: String = UUID.randomUUID().toString + for { + compiledRecipe <- setupHappyPath(interactionNode) + events <- setupEventListener(compiledRecipe, eventListenerNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + state <- stateNode.getRecipeInstanceState(recipeInstanceId) + } yield { + events.toList shouldBe List.empty + state.recipeInstanceId shouldBe recipeInstanceId + } + } + } + + it("Baker.bake (fail with ProcessAlreadyExistsException)") { + test { (client, stateNode, interactionNode, _) => + val recipeInstanceId: String = UUID.randomUUID().toString + for { + compiledRecipe <- setupHappyPath(interactionNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + e <- client + .bake(recipeId, recipeInstanceId) + .map(_ => None) + .recover { case e: BakerException => Some(e) } + state <- stateNode.getRecipeInstanceState(recipeInstanceId) + } yield { + e shouldBe Some(BakerException.ProcessAlreadyExistsException(recipeInstanceId)) + state.recipeInstanceId shouldBe recipeInstanceId + } + } + } + + it("Baker.bake (fail with NoSuchRecipeException)") { + test { (client, _, _, _) => + val recipeInstanceId: String = UUID.randomUUID().toString + for { + e <- client + .bake("non-existent", recipeInstanceId) + .map(_ => None) + .recover { case e: BakerException => Some(e) } + } yield e shouldBe Some(BakerException.NoSuchRecipeException("non-existent")) + } + } + + it("Baker.getRecipeInstanceState (fails with NoSuchProcessException)") { + test { (_, stateNode, _, _) => + for { + e <- stateNode + .getRecipeInstanceState("non-existent") + .map(_ => None) + .recover { case e: BakerException => Some(e) } + } yield e shouldBe Some(BakerException.NoSuchProcessException("non-existent")) + } + } + + it("Baker.fireEventAndResolveWhenReceived") { + test { (client, _, interactionNode, _) => + val recipeInstanceId: String = UUID.randomUUID().toString + val event = EventInstance.unsafeFrom( + CheckoutFlowEvents.ShippingAddressReceived(ShippingAddress("address"))) + for { + compiledRecipe <- setupHappyPath(interactionNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + status <- client.fireEventAndResolveWhenReceived(recipeInstanceId, event) + } yield status shouldBe SensoryEventStatus.Received + } + } + + it("Baker.fireEventAndResolveWhenCompleted") { + test { (client, stateNode, interactionNode, eventListenerNode) => + val recipeInstanceId: String = UUID.randomUUID().toString + val event = EventInstance.unsafeFrom( + CheckoutFlowEvents.ShippingAddressReceived(ShippingAddress("address"))) + for { + compiledRecipe <- setupHappyPath(interactionNode) + events <- setupEventListener(compiledRecipe, eventListenerNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + result <- client.fireEventAndResolveWhenCompleted(recipeInstanceId, event) + serverState <- stateNode.getRecipeInstanceState(recipeInstanceId) + } yield { + result.eventNames should contain("ShippingAddressReceived") + serverState.events.map(_.name) should contain("ShippingAddressReceived") + events.toList.map(_.name) should contain("ShippingAddressReceived") + } + } + } + + it("Baker.fireEventAndResolveWhenCompleted (fails with IllegalEventException)") { + test { (client, stateNode, interactionNode, _) => + val recipeInstanceId: String = UUID.randomUUID().toString + val event = EventInstance("non-existent", Map.empty) + for { + compiledRecipe <- setupHappyPath(interactionNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + result <- client + .fireEventAndResolveWhenCompleted(recipeInstanceId, event) + .map(_ => None) + .recover { case e: BakerException => Some(e) } + serverState <- stateNode.getRecipeInstanceState(recipeInstanceId) + } yield { + result shouldBe Some(BakerException.IllegalEventException("No event with name 'non-existent' found in recipe 'Webshop'")) + serverState.events.map(_.name) should not contain("ShippingAddressReceived") + } + } + } + + it("Baker.fireEventAndResolveOnEvent") { + test { (client, stateNode, interactionNode, eventListenerNode) => + val recipeInstanceId: String = UUID.randomUUID().toString + val event = EventInstance.unsafeFrom( + CheckoutFlowEvents.ShippingAddressReceived(ShippingAddress("address"))) + for { + compiledRecipe <- setupHappyPath(interactionNode) + events <- setupEventListener(compiledRecipe, eventListenerNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + result <- client.fireEventAndResolveOnEvent(recipeInstanceId, event, "ShippingAddressReceived") + serverState <- stateNode.getRecipeInstanceState(recipeInstanceId) + } yield { + result.eventNames should contain("ShippingAddressReceived") + serverState.events.map(_.name) should contain("ShippingAddressReceived") + events.toList.map(_.name) should contain("ShippingAddressReceived") + } + } + } + + it("Baker.getAllRecipeInstancesMetadata") { + test { (client, stateNode, interactionNode, _) => + val recipeInstanceId: String = UUID.randomUUID().toString + for { + compiledRecipe <- setupHappyPath(interactionNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + clientMetadata <- client.getAllRecipeInstancesMetadata + serverMetadata <- stateNode.getAllRecipeInstancesMetadata + } yield clientMetadata shouldBe serverMetadata + } + } + + it("Baker.getVisualState") { + test { (client, stateNode, interactionNode, _) => + val recipeInstanceId: String = UUID.randomUUID().toString + for { + compiledRecipe <- setupHappyPath(interactionNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + clientState <- client.getVisualState(recipeInstanceId) + serverState <- stateNode.getVisualState(recipeInstanceId) + } yield clientState shouldBe serverState + } + } + + it("Baker.retryInteraction") { + test { (client, _, interactionNode, eventListenerNode) => + val recipeInstanceId: String = UUID.randomUUID().toString + val event = EventInstance.unsafeFrom( + CheckoutFlowEvents.OrderPlaced(orderId = OrderId("order1"), List.empty)) + for { + compiledRecipe <- setupFailingOnceReserveItems(interactionNode) + events <- setupEventListener(compiledRecipe, eventListenerNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + _ <- client.fireEventAndResolveWhenCompleted(recipeInstanceId, event) + state1 <- client.getRecipeInstanceState(recipeInstanceId).map(_.events.map(_.name)) + _ <- client.retryInteraction(recipeInstanceId, "ReserveItems") + state2 <- client.getRecipeInstanceState(recipeInstanceId).map(_.events.map(_.name)) + } yield { + state1 should contain("OrderPlaced") + state1 should not contain("ItemsReserved") + state2 should contain("OrderPlaced") + state2 should contain("ItemsReserved") + events.toList.map(_.name) should contain("OrderPlaced") + events.toList.map(_.name) should contain("ItemsReserved") + } + } + } + + it("Baker.resolveInteraction") { + test { (client, _, interactionNode, eventListenerNode) => + val recipeInstanceId: String = UUID.randomUUID().toString + val event = EventInstance.unsafeFrom( + CheckoutFlowEvents.OrderPlaced(orderId = OrderId("order1"), List.empty)) + val resolutionEvent = EventInstance.unsafeFrom( + ItemsReserved(reservedItems = ReservedItems(items = List(Item("item1")), data = Array.empty)) + ) + for { + compiledRecipe <- setupFailingOnceReserveItems(interactionNode) + events <- setupEventListener(compiledRecipe, eventListenerNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + _ <- client.fireEventAndResolveWhenCompleted(recipeInstanceId, event) + state1 <- client.getRecipeInstanceState(recipeInstanceId).map(_.events.map(_.name)) + _ <- client.resolveInteraction(recipeInstanceId, "ReserveItems", resolutionEvent) + state2data <- client.getRecipeInstanceState(recipeInstanceId) + state2 = state2data.events.map(_.name) + eventState = state2data.ingredients.get("reservedItems").map(_.as[ReservedItems].items.head.itemId) + } yield { + state1 should contain("OrderPlaced") + state1 should not contain("ItemsReserved") + state2 should contain("OrderPlaced") + state2 should contain("ItemsReserved") + eventState shouldBe Some("item1") + events.toList.map(_.name) should contain("OrderPlaced") + events.toList.map(_.name) should not contain("ItemsReserved") // Manually resolving an interaction does not fire the event to the listeners? + } + } + } + + it("Baker.stopRetryingInteraction") { + test { (client, _, interactionNode, eventListenerNode) => + val recipeInstanceId: String = UUID.randomUUID().toString + val event = EventInstance.unsafeFrom( + CheckoutFlowEvents.OrderPlaced(orderId = OrderId("order1"), List.empty)) + for { + compiledRecipe <- setupFailingWithRetryReserveItems(interactionNode) + events <- setupEventListener(compiledRecipe, eventListenerNode) + recipeId <- client.addRecipe(compiledRecipe) + _ <- client.bake(recipeId, recipeInstanceId) + _ <- client.fireEventAndResolveWhenReceived(recipeInstanceId, event) + state1 <- client.getRecipeInstanceState(recipeInstanceId).map(_.events.map(_.name)) + _ <- client.stopRetryingInteraction(recipeInstanceId, "ReserveItems") + state2data <- client.getRecipeInstanceState(recipeInstanceId) + state2 = state2data.events.map(_.name) + } yield { + state1 should contain("OrderPlaced") + state1 should not contain("ItemsReserved") + state2 should contain("OrderPlaced") + state2 should not contain("ItemsReserved") + events.toList.map(_.name) should contain("OrderPlaced") + events.toList.map(_.name) should not contain("ItemsReserved") + } + } + } + } +} + +object BaaSIntegrationSpec { + + def setupHappyPath(interactionNode: BaaSInteractionInstance)(implicit ec: ExecutionContext, timer: Timer[IO]): Future[CompiledRecipe] = { + val makePaymentInstance = InteractionInstance.unsafeFrom(new MakePaymentInstance()) + val reserveItemsInstance = InteractionInstance.unsafeFrom(new ReserveItemsInstance()) + val shipItemsInstance = InteractionInstance.unsafeFrom(new ShipItemsInstance()) + val compiledRecipe = RecipeCompiler.compileRecipe(CheckoutFlowRecipe.recipe) + for { + _ <- Future { interactionNode.load(makePaymentInstance, reserveItemsInstance, shipItemsInstance) } + } yield compiledRecipe + } + + def setupFailingOnceReserveItems(interactionNode: BaaSInteractionInstance)(implicit ec: ExecutionContext, timer: Timer[IO]): Future[CompiledRecipe] = { + val makePaymentInstance = InteractionInstance.unsafeFrom(new MakePaymentInstance()) + val reserveItemsInstance = InteractionInstance.unsafeFrom(new FailingOnceReserveItemsInstance()) + val shipItemsInstance = InteractionInstance.unsafeFrom(new ShipItemsInstance()) + val compiledRecipe = RecipeCompiler.compileRecipe(CheckoutFlowRecipe.recipeWithBlockingStrategy) + for { + _ <- Future { interactionNode.load(makePaymentInstance, reserveItemsInstance, shipItemsInstance) } + } yield compiledRecipe + } + + def setupFailingWithRetryReserveItems(interactionNode: BaaSInteractionInstance)(implicit ec: ExecutionContext, timer: Timer[IO]): Future[CompiledRecipe] = { + val makePaymentInstance = InteractionInstance.unsafeFrom(new MakePaymentInstance()) + val reserveItemsInstance = InteractionInstance.unsafeFrom(new FailingReserveItemsInstance()) + val shipItemsInstance = InteractionInstance.unsafeFrom(new ShipItemsInstance()) + val compiledRecipe = RecipeCompiler.compileRecipe(CheckoutFlowRecipe.recipe) + for { + _ <- Future { interactionNode.load(makePaymentInstance, reserveItemsInstance, shipItemsInstance) } + } yield compiledRecipe + } + + def setupEventListener(recipe: CompiledRecipe, eventListenerNode: BaaSEventListener)(implicit ec: ExecutionContext): Future[mutable.MutableList[EventInstance]] = { + val buffer = mutable.MutableList.empty[EventInstance] + eventListenerNode.registerEventListener(recipe.name, (_, event) => { + buffer.+=(event) + }).map(_ => buffer) + } + + type IntegrationTest = ((ScalaBaker, ScalaBaker, BaaSInteractionInstance, BaaSEventListener) => Future[Assertion]) => Future[Assertion] + + type WithOpenPort[A] = StateT[Future, Stream[Int], A] + + def testWith[F[_], Lang <: LanguageApi] + (test: (ScalaBaker, ScalaBaker, BaaSInteractionInstance, BaaSEventListener) => Future[Assertion]) + (implicit ec: ExecutionContext): Future[Assertion] = { + val testId: UUID = UUID.randomUUID() + val systemName: String = "BaaSIntegrationSpec-" + testId + val allPorts: Stream[Int] = Stream.from(50000, 1) + val program = for { + clusterTuple <- buildClusterActorSystem(systemName, seedPortCandidate = 0) + (stateNodeSystem, serverConfig) = clusterTuple + materializer = ActorMaterializer()(stateNodeSystem) + stateNodeBaker = AkkaBaker(serverConfig, stateNodeSystem) + httpPortAndBinding <- runStateNodeHttpServer(stateNodeBaker, stateNodeSystem, materializer) + (httpPort, httpServerBinding) = httpPortAndBinding + clientBaker = BakerClient.build(s"http://localhost:$httpPort/", Encryption.NoEncryption)(stateNodeSystem, materializer) + interactionNode = BaaSInteractionInstance(stateNodeSystem) + eventListenerNode = BaaSEventListener(stateNodeSystem) + a <- liftF(test(clientBaker, stateNodeBaker, interactionNode, eventListenerNode)) + _ <- liftF(httpServerBinding.unbind()) + _ <- liftF(stateNodeBaker.gracefulShutdown()) + } yield a + program.run(allPorts).map(_._2) + } + + private def buildClusterActorSystem(systemName: String, seedPortCandidate: Int)(implicit ec: ExecutionContext): WithOpenPort[(ActorSystem, Config)] = + withOpenPort { port => + val config = genClusterConfig(systemName, port, seedPortCandidate) + Future { (ActorSystem(systemName, config), config) } + } + + private def runStateNodeHttpServer(stateNodeBaker: ScalaBaker, stateNodeSystem: ActorSystem, materializer: Materializer)(implicit ec: ExecutionContext): WithOpenPort[(Int, Http.ServerBinding)] = + withOpenPort(port => BaaSServer.run(stateNodeBaker, "localhost", port)(stateNodeSystem, materializer).map(port -> _)) + + private def withOpenPort[T](f: Int => Future[T])(implicit ec: ExecutionContext): WithOpenPort[T] = { + def search(ports: Stream[Int]): Future[(Stream[Int], T)] = + ports match { + case #::(port, tail) => f(port).map(tail -> _).recoverWith { + case _: java.net.BindException => search(tail) + case _: ChannelException => search(tail) + case other => println("REVIEW withOpenPort function implementation, uncaught exception: " + Console.RED + other + Console.RESET); Future.failed(other) + } + } + StateT(search) + } + + private def liftF[A](fa: Future[A])(implicit ec: ExecutionContext): WithOpenPort[A] = + StateT.liftF[Future, Stream[Int], A](fa) + + private def genClusterConfig(systemName: String, port: Int, seedPortCandidate: Int): Config = { + val seedPort: Int = if(seedPortCandidate == 0) port else seedPortCandidate + ConfigFactory.parseString( + s""" + |baker { + | cluster { + | seed-nodes = [ + | "akka.tcp://"$systemName"@localhost:"$seedPort] + | } + |} + | + |akka { + | + | remote { + | log-remote-lifecycle-events = off + | netty.tcp { + | hostname = "localhost" + | port = $port + | } + | } + | + | cluster { + | + | seed-nodes = [ + | "akka.tcp://"$systemName"@localhost:"$seedPort] + | + | # auto downing is NOT safe for production deployments. + | # you may want to use it during development, read more about it in the akka docs. + | auto-down-unreachable-after = 10s + | } + |} + |""".stripMargin).withFallback(ConfigFactory.load()) + } +} \ No newline at end of file diff --git a/baas/src/main/protobuf/baas.proto b/baas/src/main/protobuf/baas.proto deleted file mode 100644 index 97400fc0a..000000000 --- a/baas/src/main/protobuf/baas.proto +++ /dev/null @@ -1,67 +0,0 @@ -syntax = "proto2"; - -import "scalapb/scalapb.proto"; -import "common.proto"; - -option java_package = "com.ing.baker.runtime.baas.protobuf"; -option (scalapb.options) = { - flat_package: true -}; - -message AddInteractionHTTPRequest { - optional string name = 1; - optional string uri = 2; - repeated Type input = 3; -} - -message AddInteractionHTTPResponse { - optional string message = 1; -} - -message AddRecipeRequest { - optional CompiledRecipe compiledRecipe = 1; -} - -message AddRecipeResponse { - optional string recipeId = 1; -} - -message BakeRequest { - optional string recipeId= 1; -} - -message BakeResponse { - optional ProcessState processState= 1; -} - -message EventsResponse { - repeated RuntimeEvent runtimeEvent = 1; -} - -message IngredientsResponse { - map ingredients = 1; -} - -message ProcessEventRequest { - optional RuntimeEvent runtimeEvent = 1; -} - -message ProcessEventResponse { - optional SensoryEventStatus sensoryEventStatus = 1; -} - -message StateResponse { - optional ProcessState processState= 1; -} - -message VisualStateResponse { - optional string VisualStateResponse = 1; -} - -message ExecuteInteractionHTTPRequest { - repeated Ingredient ingredient = 1; -} - -message ExecuteInteractionHTTPResponse { - optional RuntimeEvent runtimeEvent = 1; -} \ No newline at end of file diff --git a/baas/src/main/resources/reference.conf b/baas/src/main/resources/reference.conf deleted file mode 100644 index b4faba05f..000000000 --- a/baas/src/main/resources/reference.conf +++ /dev/null @@ -1,20 +0,0 @@ -baker.engine.baas { - client-host = "localhost" - client-port = 8081 - baas-host = "localhost" - baas-port = 8091 -} - -akka { - actor { - serializers { - baas-protobuf = "com.ing.baker.baas.serialization.BaasProtobufSerializer" - } - - serialization-bindings { - // map baker classes to use protobuf serialization - "com.ing.baker.baas.server.protocol.BaasRequest" = baas-protobuf - "com.ing.baker.baas.server.protocol.BaasResponse" = baas-protobuf - } - } -} \ No newline at end of file diff --git a/baas/src/test/resources/application.conf b/baas/src/test/resources/application.conf deleted file mode 100644 index 08bf154e7..000000000 --- a/baas/src/test/resources/application.conf +++ /dev/null @@ -1,42 +0,0 @@ -include "baker.conf" -//include "cluster.conf" -// include "cassandra.conf" - -baker { - engine-provider = "com.ing.baker.baas.client.BaasBakerProvider" - engine.baas { - actor-system-name = "ActorSystemName" - client-host = "localhost" - client-port = 8091 - baas-host = "localhost" - baas-port = 8081 - } -} - -akka { - log-config-on-start = off - jvm-exit-on-fatal-error = false - loglevel = "DEBUG" - coordinated-shutdown.run-by-jvm-shutdown-hook = off - - // No need to see the java serialization warnings for the tests in this module - actor.allow-java-serialization = on - actor.warn-about-java-serializer-usage = off - -// actor.serialize-messages = on - actor.serialize-creators = off - - test.timefactor = 4 - - loggers = ["akka.event.slf4j.Slf4jLogger"] - loglevel = "DEBUG" - logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" -} - -inmemory-read-journal { - write-plugin = "inmemory-journal" - offset-mode = "sequence" - ask-timeout = "10s" - refresh-interval = "50ms" - max-buffer-size = "100" -} diff --git a/baas/src/test/resources/cassandra.conf b/baas/src/test/resources/cassandra.conf deleted file mode 100644 index ae18df370..000000000 --- a/baas/src/test/resources/cassandra.conf +++ /dev/null @@ -1,15 +0,0 @@ -akka.persistence.journal.plugin = "cassandra-journal" -akka.persistence.snapshot-store.plugin = "cassandra-snapshot-store" - -baker.actor.read-journal-plugin = "cassandra-query-journal" -baker.journal-initialize-timeout = 30 seconds - -cassandra-config = { - keyspace-autocreate = true - tables-autocreate = true - keyspace = baas - cassandra-2x-compat = on -} - -cassandra-journal = ${cassandra-config} -cassandra-snapshot-store = ${cassandra-config} \ No newline at end of file diff --git a/baas/src/test/resources/cluster.conf b/baas/src/test/resources/cluster.conf deleted file mode 100644 index c69da64b4..000000000 --- a/baas/src/test/resources/cluster.conf +++ /dev/null @@ -1,34 +0,0 @@ -akka { - actor.provider = "akka.cluster.ClusterActorRefProvider" - - remote { - maximum-payload-bytes = 10000000 bytes - netty.tcp { - hostname = "127.0.0.1" - port = 2552 - message-frame-size = 10000000b - send-buffer-size = 10000000b - receive-buffer-size = 10000000b - maximum-frame-size = 10000000b - } - } - - cluster { - - seed-nodes = [ - "akka.tcp://BAASAPIActorSystem@127.0.0.1:2552" - ] - - sharding { - least-shard-allocation-strategy.rebalance-threshold = 5 - remember-entities = on - } - -// http.management { -// hostname = "127.0.0.1" -// port = 19999 -// } - } -} - -baker.actor.provider = "cluster-sharded" diff --git a/baas/src/test/resources/logback.xml b/baas/src/test/resources/logback.xml deleted file mode 100644 index 03c45b50f..000000000 --- a/baas/src/test/resources/logback.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - %d{ISO8601} [%thread] [%X{recipeInstanceId}] %-5level %logger{36} - %msg%n - - - - - - - - - - - - - \ No newline at end of file diff --git a/runtime/src/main/protobuf/common.proto b/baker-interface/src/main/protobuf/common.proto similarity index 92% rename from runtime/src/main/protobuf/common.proto rename to baker-interface/src/main/protobuf/common.proto index 6b86bd0b8..04fccf804 100644 --- a/runtime/src/main/protobuf/common.proto +++ b/baker-interface/src/main/protobuf/common.proto @@ -7,6 +7,14 @@ option (scalapb.options) = { flat_package: true }; +// other + +message RecipeInformation { + optional CompiledRecipe compiledRecipe = 1; + optional int64 recipeCreatedTime = 2; + repeated string errors = 3; +} + // wrapper object for 'any' data message SerializedData { optional int32 serializer_id = 1; @@ -144,6 +152,11 @@ message EventMoment { optional int64 occurredOn = 2; } +message RecipeInstanceMetadata { + optional string recipeId = 1; + optional string recipeInstanceId = 2; + optional int64 createdTime = 3; +} message ProcessState { optional string recipeInstanceId = 1; @@ -157,6 +170,12 @@ message SensoryEventResult { map ingredients = 3; } +message RecipeEventMetadata { + optional string recipeId = 1; + optional string recipeName = 2; + optional string recipeInstanceId = 3; +} + enum SensoryEventStatus { RECEIVED = 1; COMPLETED = 2; @@ -165,6 +184,11 @@ enum SensoryEventStatus { ALREADY_RECEIVED = 5; PROCESS_DELETED = 6; } + +message BakerException { + optional string message = 1; + optional int32 enum = 2; +} // END DATA // DESCRIPTORS (Intermediate Language messages) diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/Baker.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/Baker.scala similarity index 98% rename from runtime/src/main/scala/com/ing/baker/runtime/common/Baker.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/Baker.scala index db80f412a..98fa9dd5c 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/common/Baker.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/common/Baker.scala @@ -30,6 +30,8 @@ trait Baker[F[_]] extends LanguageApi { type EventMomentType <: EventMoment { type Language <: self.Language} + type RecipeMetadataType <: RecipeEventMetadata { type Language <: self.Language } + /** * Adds a recipe to baker and returns a recipeId for the recipe. * @@ -279,14 +281,14 @@ trait Baker[F[_]] extends LanguageApi { * * Note that the delivery guarantee is *AT MOST ONCE*. Do not use it for critical functionality */ - def registerEventListener(recipeName: String, listenerFunction: language.BiConsumerFunction[String, EventInstanceType]): F[Unit] + def registerEventListener(recipeName: String, listenerFunction: language.BiConsumerFunction[RecipeMetadataType, EventInstanceType]): F[Unit] /** * Registers a listener to all runtime events for all recipes that run in this Baker instance. * * Note that the delivery guarantee is *AT MOST ONCE*. Do not use it for critical functionality */ - def registerEventListener(listenerFunction: language.BiConsumerFunction[String, EventInstanceType]): F[Unit] + def registerEventListener(listenerFunction: language.BiConsumerFunction[RecipeMetadataType, EventInstanceType]): F[Unit] /** * Registers a listener function that listens to all BakerEvents diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/BakerEvent.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/BakerEvent.scala similarity index 94% rename from runtime/src/main/scala/com/ing/baker/runtime/common/BakerEvent.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/BakerEvent.scala index 81433b2e0..f98132191 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/common/BakerEvent.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/common/BakerEvent.scala @@ -1,12 +1,11 @@ package com.ing.baker.runtime.common -import akka.actor.NoSerializationVerificationNeeded import com.ing.baker.il.CompiledRecipe import com.ing.baker.il.failurestrategy.ExceptionStrategyOutcome import com.ing.baker.runtime.common.LanguageDataStructures.LanguageApi // TODO: rename subtypes of BakerEvent to resamble the new names -trait BakerEvent extends LanguageApi with NoSerializationVerificationNeeded { +trait BakerEvent extends LanguageApi { self => type Event <: EventInstance {type Language <: self.Language} } diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/common/BakerException.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/BakerException.scala new file mode 100644 index 000000000..957bff926 --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/common/BakerException.scala @@ -0,0 +1,56 @@ +package com.ing.baker.runtime.common + +import scala.util.{Failure, Success, Try} + +sealed abstract class BakerException(val message: String = "An exception occurred at Baker", val enum: Int, val cause: Throwable = null) + extends RuntimeException(message, cause) + +object BakerException { + + // TODO this has to be renamed to NoSuchRecipeInstanceException + case class NoSuchProcessException(recipeInstanceId: String) + extends BakerException(s"Recipe instance $recipeInstanceId does not exist in the index", 1) + + // TODO this has to be renamed to RecipeInstanceDeletedException + case class ProcessDeletedException(recipeInstanceId: String) + extends BakerException(s"Recipe instance $recipeInstanceId is deleted", 2) + + case class RecipeValidationException(validationErrors: String) + extends BakerException(validationErrors, 3) + + case class ImplementationsException(implementationErrors: String) + extends BakerException(implementationErrors, 4) + + case class NoSuchRecipeException(recipeId: String) + extends BakerException(s"No recipe found for recipe with id: $recipeId", 5) + + // TODO this has to be renamed to RecipeInstanceAlreadyExistsException + case class ProcessAlreadyExistsException(recipeInstanceId: String) + extends BakerException(s"Process '$recipeInstanceId' already exists.", 6) + + case class IllegalEventException(reason: String) + extends BakerException(reason, 7) + + def encode(bakerException: BakerException): (String, Int) = + bakerException match { + case e @ NoSuchProcessException(recipeInstanceId) => (recipeInstanceId, e.enum) + case e @ ProcessDeletedException(recipeInstanceId) => (recipeInstanceId, e.enum) + case e @ RecipeValidationException(validationErrors) => (validationErrors, e.enum) + case e @ ImplementationsException(implementationErrors) => (implementationErrors, e.enum) + case e @ NoSuchRecipeException(recipeId) => (recipeId, e.enum) + case e @ ProcessAlreadyExistsException(recipeInstanceId) => (recipeInstanceId, e.enum) + case e @ IllegalEventException(reason) => (reason, e.enum) + } + + def decode(message: String, enum: Int): Try[BakerException] = + enum match { + case 1 => Success(NoSuchProcessException(message)) + case 2 => Success(ProcessDeletedException(message)) + case 3 => Success(RecipeValidationException(message)) + case 4 => Success(ImplementationsException(message)) + case 5 => Success(NoSuchRecipeException(message)) + case 6 => Success(ProcessAlreadyExistsException(message)) + case 7 => Success(IllegalEventException(message)) + case _ => Failure(new IllegalArgumentException(s"No BakerException with enum flag $enum")) + } +} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/EventInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/EventInstance.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/EventInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/EventInstance.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/EventMoment.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/EventMoment.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/EventMoment.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/EventMoment.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/EventResolutions.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/EventResolutions.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/EventResolutions.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/EventResolutions.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/IngredientInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/IngredientInstance.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/IngredientInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/IngredientInstance.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/InteractionInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/InteractionInstance.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/InteractionInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/InteractionInstance.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/LanguageDataStructures.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/LanguageDataStructures.scala similarity index 97% rename from runtime/src/main/scala/com/ing/baker/runtime/common/LanguageDataStructures.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/LanguageDataStructures.scala index 355fb766d..03fe3eb53 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/common/LanguageDataStructures.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/common/LanguageDataStructures.scala @@ -1,7 +1,5 @@ package com.ing.baker.runtime.common -import java.util.Optional - sealed trait LanguageDataStructures { type Map[A, B] diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeEventMetadata.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeEventMetadata.scala new file mode 100644 index 000000000..34f5b1f4c --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeEventMetadata.scala @@ -0,0 +1,13 @@ +package com.ing.baker.runtime.common + +import com.ing.baker.runtime.common.LanguageDataStructures.LanguageApi + +trait RecipeEventMetadata extends LanguageApi { + + def recipeId: String + + def recipeName: String + + def recipeInstanceId: String + +} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/RecipeInformation.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeInformation.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/RecipeInformation.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeInformation.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/RecipeInstanceMetadata.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeInstanceMetadata.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/RecipeInstanceMetadata.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeInstanceMetadata.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/RecipeInstanceState.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeInstanceState.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/RecipeInstanceState.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/RecipeInstanceState.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/RejectReason.java b/baker-interface/src/main/scala/com/ing/baker/runtime/common/RejectReason.java similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/RejectReason.java rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/RejectReason.java diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/SensoryEventResult.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/SensoryEventResult.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/SensoryEventResult.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/SensoryEventResult.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/SensoryEventStatus.java b/baker-interface/src/main/scala/com/ing/baker/runtime/common/SensoryEventStatus.java similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/common/SensoryEventStatus.java rename to baker-interface/src/main/scala/com/ing/baker/runtime/common/SensoryEventStatus.java diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/common/package.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/common/package.scala new file mode 100644 index 000000000..2532bca51 --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/common/package.scala @@ -0,0 +1,28 @@ +package com.ing.baker.runtime + +package object common { + + /** + * Mockito breaks reflection when mocking classes, for example: + * + * interface A { } + * class B extends A + * val b = mock[B] + * + * When inspecting b, the fact that it extends from A can no longer be reflected. + * + * Here we obtain the original class that was mocked. + * + * @param clazz The (potentially mocked) class + * @return The original class + */ + private[runtime] def unmock(clazz: Class[_]) = { + + if (clazz.getName.contains("$$EnhancerByMockitoWithCGLIB$$")) { + val originalName: String = clazz.getName.split("\\$\\$EnhancerByMockitoWithCGLIB\\$\\$")(0) + clazz.getClassLoader.loadClass(originalName) + } else + clazz + } + +} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala similarity index 73% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala index e31589c5d..58df4170d 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/Baker.scala @@ -1,46 +1,22 @@ package com.ing.baker.runtime.javadsl -import akka.actor.{ ActorSystem, Address } -import cats.data.NonEmptyList -import com.ing.baker.il.{ CompiledRecipe, RecipeVisualStyle } -import com.ing.baker.runtime.akka.{ AkkaBaker, _ } -import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi -import com.ing.baker.runtime.common.SensoryEventStatus -import com.ing.baker.runtime.{ common, scaladsl } -import com.ing.baker.types.Value -import com.typesafe.config.Config import java.util import java.util.Optional import java.util.concurrent.CompletableFuture -import java.util.function.{ BiConsumer, Consumer } +import java.util.function.{BiConsumer, Consumer} + +import com.ing.baker.il.{CompiledRecipe, RecipeVisualStyle} +import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi +import com.ing.baker.runtime.common.SensoryEventStatus +import com.ing.baker.runtime.{common, scaladsl} +import com.ing.baker.types.Value import javax.annotation.Nonnull + import scala.collection.JavaConverters._ import scala.compat.java8.FutureConverters import scala.concurrent.Future -object Baker { - - def akkaLocalDefault(actorSystem: ActorSystem): Baker = - new Baker(new AkkaBaker(AkkaBakerConfig.localDefault(actorSystem))) - - def akkaClusterDefault(seedNodes: java.util.List[Address], actorSystem: ActorSystem): Baker = { - val nodes = - if (seedNodes.isEmpty) throw new IllegalStateException("Baker cluster configuration without baker.cluster.seed-nodes") - else NonEmptyList.fromListUnsafe(seedNodes.asScala.toList) - new Baker(new AkkaBaker(AkkaBakerConfig.clusterDefault(nodes, actorSystem))) - } - - def akka(config: AkkaBakerConfig): Baker = - new Baker(scaladsl.Baker.akka(config)) - - def akka(config: Config, actorSystem: ActorSystem): Baker = - new Baker(scaladsl.Baker.akka(config, actorSystem)) - - def other(baker: scaladsl.Baker) = - new Baker(baker) -} - -class Baker private(private val baker: scaladsl.Baker) extends common.Baker[CompletableFuture] with JavaApi { +class Baker private[baker](private val baker: scaladsl.Baker) extends common.Baker[CompletableFuture] with JavaApi { override type SensoryEventResultType = SensoryEventResult @@ -60,6 +36,8 @@ class Baker private(private val baker: scaladsl.Baker) extends common.Baker[Comp override type EventMomentType = EventMoment + override type RecipeMetadataType = RecipeEventMetadata + /** * Adds a recipe to baker and returns a recipeId for the recipe. * @@ -102,57 +80,57 @@ class Baker private(private val baker: scaladsl.Baker) extends common.Baker[Comp def bake(@Nonnull recipeId: String, @Nonnull recipeInstanceId: String): CompletableFuture[Unit] = toCompletableFuture(baker.bake(recipeId, recipeInstanceId)) - def fireEventAndResolveWhenReceived(recipeInstanceId: String, event: EventInstance, correlationId: String): CompletableFuture[SensoryEventStatus] = + def fireEventAndResolveWhenReceived(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull correlationId: String): CompletableFuture[SensoryEventStatus] = fireEventAndResolveWhenReceived(recipeInstanceId, event, Optional.of(correlationId)) - def fireEventAndResolveWhenCompleted(recipeInstanceId: String, event: EventInstance, correlationId: String): CompletableFuture[SensoryEventResult] = + def fireEventAndResolveWhenCompleted(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull correlationId: String): CompletableFuture[SensoryEventResult] = fireEventAndResolveWhenCompleted(recipeInstanceId, event, Optional.of(correlationId)) - def fireEventAndResolveOnEvent(recipeInstanceId: String, event: EventInstance, onEvent: String, correlationId: String): CompletableFuture[SensoryEventResult] = + def fireEventAndResolveOnEvent(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull onEvent: String, @Nonnull correlationId: String): CompletableFuture[SensoryEventResult] = fireEventAndResolveOnEvent(recipeInstanceId, event, onEvent, Optional.of(correlationId)) - def fireEvent(recipeInstanceId: String, event: EventInstance, correlationId: String): EventResolutions = + def fireEvent(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull correlationId: String): EventResolutions = fireEvent(recipeInstanceId, event, Optional.of(correlationId)) - def fireEventAndResolveWhenReceived(recipeInstanceId: String, event: EventInstance): CompletableFuture[SensoryEventStatus] = + def fireEventAndResolveWhenReceived(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance): CompletableFuture[SensoryEventStatus] = fireEventAndResolveWhenReceived(recipeInstanceId, event, Optional.empty[String]()) - def fireEventAndResolveWhenCompleted(recipeInstanceId: String, event: EventInstance): CompletableFuture[SensoryEventResult] = + def fireEventAndResolveWhenCompleted(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance): CompletableFuture[SensoryEventResult] = fireEventAndResolveWhenCompleted(recipeInstanceId, event, Optional.empty[String]()) - def fireEventAndResolveOnEvent(recipeInstanceId: String, event: EventInstance, onEvent: String): CompletableFuture[SensoryEventResult] = + def fireEventAndResolveOnEvent(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull onEvent: String): CompletableFuture[SensoryEventResult] = fireEventAndResolveOnEvent(recipeInstanceId, event, onEvent, Optional.empty[String]()) - def fireEvent(recipeInstanceId: String, event: EventInstance): EventResolutions = + def fireEvent(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance): EventResolutions = fireEvent(recipeInstanceId, event, Optional.empty[String]()) - def fireEventAndResolveWhenReceived(recipeInstanceId: String, event: EventInstance, correlationId: Optional[String]): CompletableFuture[SensoryEventStatus] = + def fireEventAndResolveWhenReceived(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull correlationId: Optional[String]): CompletableFuture[SensoryEventStatus] = toCompletableFuture(baker.fireEventAndResolveWhenReceived(recipeInstanceId, event.asScala, Option.apply(correlationId.orElse(null)))) - def fireEventAndResolveWhenCompleted(recipeInstanceId: String, event: EventInstance, correlationId: Optional[String]): CompletableFuture[SensoryEventResult] = + def fireEventAndResolveWhenCompleted(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull correlationId: Optional[String]): CompletableFuture[SensoryEventResult] = toCompletableFuture(baker.fireEventAndResolveWhenCompleted(recipeInstanceId, event.asScala, Option.apply(correlationId.orElse(null)))).thenApply { result => - new SensoryEventResult( + SensoryEventResult( sensoryEventStatus = result.sensoryEventStatus, eventNames = result.eventNames.asJava, ingredients = result.ingredients.asJava ) } - def fireEventAndResolveOnEvent(recipeInstanceId: String, event: EventInstance, onEvent: String, correlationId: Optional[String]): CompletableFuture[SensoryEventResult] = + def fireEventAndResolveOnEvent(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull onEvent: String, @Nonnull correlationId: Optional[String]): CompletableFuture[SensoryEventResult] = toCompletableFuture(baker.fireEventAndResolveOnEvent(recipeInstanceId, event.asScala, onEvent, Option.apply(correlationId.orElse(null)))).thenApply { result => - new SensoryEventResult( + SensoryEventResult( sensoryEventStatus = result.sensoryEventStatus, eventNames = result.eventNames.asJava, ingredients = result.ingredients.asJava ) } - def fireEvent(recipeInstanceId: String, event: EventInstance, correlationId: Optional[String]): EventResolutions = { + def fireEvent(@Nonnull recipeInstanceId: String, @Nonnull event: EventInstance, @Nonnull correlationId: Optional[String]): EventResolutions = { val scalaResult = baker.fireEvent(recipeInstanceId, event.asScala) - new EventResolutions( + EventResolutions( resolveWhenReceived = toCompletableFuture(scalaResult.resolveWhenReceived), resolveWhenCompleted = toCompletableFuture(scalaResult.resolveWhenCompleted).thenApply { result => - new SensoryEventResult( + SensoryEventResult( sensoryEventStatus = result.sensoryEventStatus, eventNames = result.eventNames.asJava, ingredients = result.ingredients.asJava @@ -278,10 +256,9 @@ class Baker private(private val baker: scaladsl.Baker) extends common.Baker[Comp * @param recipeName the name of all recipes this event listener should be triggered for * @param listenerFunction The listener to subscribe to events. */ - override def registerEventListener(@Nonnull recipeName: String, @Nonnull listenerFunction: BiConsumer[String, EventInstance]): CompletableFuture[Unit] = + override def registerEventListener(@Nonnull recipeName: String, @Nonnull listenerFunction: BiConsumer[RecipeEventMetadata, EventInstance]): CompletableFuture[Unit] = toCompletableFuture(baker.registerEventListener(recipeName, - (recipeInstanceId: String, event: scaladsl.EventInstance) => listenerFunction.accept(recipeInstanceId, event.asJava))) - + (recipeEventMetadata: scaladsl.RecipeEventMetadata, event: scaladsl.EventInstance) => listenerFunction.accept(recipeEventMetadata.asJava, event.asJava))) /** * Registers a listener function to all runtime events for this baker instance. @@ -300,10 +277,9 @@ class Baker private(private val baker: scaladsl.Baker) extends common.Baker[Comp * * @param listenerFunction The listener function that is called once these events occur */ - override def registerEventListener(listenerFunction: BiConsumer[String, EventInstance]): CompletableFuture[Unit] = + override def registerEventListener(@Nonnull listenerFunction: BiConsumer[RecipeEventMetadata, EventInstance]): CompletableFuture[Unit] = toCompletableFuture(baker.registerEventListener( - (recipeInstanceId: String, event: scaladsl.EventInstance) => listenerFunction.accept(recipeInstanceId, event.asJava))) - + (recipeEventMetadata: scaladsl.RecipeEventMetadata, event: scaladsl.EventInstance) => listenerFunction.accept(recipeEventMetadata.asJava, event.asJava))) /** * Registers a listener function to all runtime events for this baker instance. @@ -323,9 +299,8 @@ class Baker private(private val baker: scaladsl.Baker) extends common.Baker[Comp * @param eventListener The EventListener class the processEvent will be called once these events occur */ @deprecated(message = "Replaced with the consumer function variant", since = "3.0.0") - def registerEventListener(eventListener: EventListener): Future[Unit] = - baker.registerEventListener((recipeInstanceId: String, runtimeEvent: scaladsl.EventInstance) => eventListener.processEvent(recipeInstanceId, runtimeEvent.asJava)) - + def registerEventListener(@Nonnull eventListener: EventListener): Future[Unit] = + baker.registerEventListener((recipeEventMetadata: scaladsl.RecipeEventMetadata, runtimeEvent: scaladsl.EventInstance) => eventListener.processEvent(recipeEventMetadata.recipeInstanceId, runtimeEvent.asJava)) /** * Registers a listener that listens to all Baker events @@ -333,7 +308,7 @@ class Baker private(private val baker: scaladsl.Baker) extends common.Baker[Comp * @param listenerFunction * @return */ - override def registerBakerEventListener(listenerFunction: Consumer[BakerEvent]): CompletableFuture[Unit] = + override def registerBakerEventListener(@Nonnull listenerFunction: Consumer[BakerEvent]): CompletableFuture[Unit] = toCompletableFuture(baker.registerBakerEventListener((event: scaladsl.BakerEvent) => listenerFunction.accept(event.asJava()))) @@ -352,19 +327,13 @@ class Baker private(private val baker: scaladsl.Baker) extends common.Baker[Comp * @param recipeInstanceId The process identifier * @return */ - def getVisualState(@Nonnull recipeInstanceId: String, style: RecipeVisualStyle): CompletableFuture[String] = + def getVisualState(@Nonnull recipeInstanceId: String, @Nonnull style: RecipeVisualStyle): CompletableFuture[String] = toCompletableFuture(baker.getVisualState(recipeInstanceId, style)) - private def toCompletableFuture[T](scalaFuture: Future[T]): CompletableFuture[T] = + private def toCompletableFuture[T](@Nonnull scalaFuture: Future[T]): CompletableFuture[T] = FutureConverters.toJava(scalaFuture).toCompletableFuture - private def toCompletableFutureSet[T](scalaFuture: Future[Set[T]]): CompletableFuture[java.util.Set[T]] = - FutureConverters.toJava( - scalaFuture) - .toCompletableFuture - .thenApply(_.asJava) - - private def toCompletableFutureMap[K, V](scalaFuture: Future[Map[K, V]]): CompletableFuture[java.util.Map[K, V]] = + private def toCompletableFutureMap[K, V](@Nonnull scalaFuture: Future[Map[K, V]]): CompletableFuture[java.util.Map[K, V]] = FutureConverters.toJava( scalaFuture) .toCompletableFuture diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/BakerEvent.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/BakerEvent.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/BakerEvent.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/BakerEvent.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventInstance.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventInstance.scala index 850a5e1cc..d6f4f3840 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventInstance.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventInstance.scala @@ -2,9 +2,9 @@ package com.ing.baker.runtime.javadsl import java.util +import com.ing.baker.runtime.{common, scaladsl} import com.ing.baker.il.EventDescriptor import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi -import com.ing.baker.runtime.{common, scaladsl} import com.ing.baker.types.Value import scala.collection.JavaConverters._ diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventListener.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventListener.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventListener.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventListener.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventMoment.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventMoment.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventMoment.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventMoment.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventResolutions.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventResolutions.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventResolutions.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventResolutions.scala index 9275cce24..456e8ab8b 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/EventResolutions.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/EventResolutions.scala @@ -2,9 +2,9 @@ package com.ing.baker.runtime.javadsl import java.util.concurrent.CompletableFuture -import com.ing.baker.runtime.common.SensoryEventStatus import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi +import com.ing.baker.runtime.common.SensoryEventStatus case class EventResolutions(resolveWhenReceived: CompletableFuture[SensoryEventStatus], resolveWhenCompleted: CompletableFuture[SensoryEventResult] diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/IngredientInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/IngredientInstance.scala similarity index 84% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/IngredientInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/IngredientInstance.scala index 675694c73..b94fee297 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/IngredientInstance.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/IngredientInstance.scala @@ -1,8 +1,7 @@ package com.ing.baker.runtime.javadsl -import com.ing.baker.runtime.scaladsl -import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi +import com.ing.baker.runtime.{common, scaladsl} import com.ing.baker.types.Value case class IngredientInstance(name: String, value: Value) extends common.IngredientInstance with JavaApi { diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/InteractionInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/InteractionInstance.scala similarity index 95% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/InteractionInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/InteractionInstance.scala index 7a76866e5..3add46f5e 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/InteractionInstance.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/InteractionInstance.scala @@ -5,11 +5,9 @@ import java.util import java.util.Optional import java.util.concurrent.CompletableFuture -import com.ing.baker.runtime.akka -import com.ing.baker.runtime.scaladsl -import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi -import com.ing.baker.types.{Converters, Type, Value} +import com.ing.baker.runtime.{common, scaladsl} +import com.ing.baker.types.{Converters, Type} import scala.collection.JavaConverters._ import scala.compat.java8.FutureConverters @@ -54,7 +52,7 @@ object InteractionInstance { new InteractionInstance { val method: Method = { - val unmockedClass = akka.unmock(implementation.getClass) + val unmockedClass = common.unmock(implementation.getClass) unmockedClass.getMethods.count(_.getName == "apply") match { case 0 => throw new IllegalArgumentException("Implementation does not have a apply function") case n if n > 1 => throw new IllegalArgumentException("Implementation has multiple apply functions") diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeEventMetadata.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeEventMetadata.scala new file mode 100644 index 000000000..8dd1b5c55 --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeEventMetadata.scala @@ -0,0 +1,21 @@ +package com.ing.baker.runtime.javadsl + +import com.ing.baker.runtime.common +import com.ing.baker.runtime.scaladsl +import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi + +case class RecipeEventMetadata(recipeId: String, recipeName: String, recipeInstanceId: String) extends common.RecipeEventMetadata with JavaApi { + + def getRecipeId: String = recipeId + + def getRecipeName: String = recipeName + + def getRecipeInstanceId: String = recipeInstanceId + + def asScala: scaladsl.RecipeEventMetadata = + scaladsl.RecipeEventMetadata( + recipeId = recipeId, + recipeName = recipeName, + recipeInstanceId = recipeInstanceId + ) +} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInformation.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInformation.scala similarity index 91% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInformation.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInformation.scala index 3116a13a7..3e11bf57a 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInformation.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInformation.scala @@ -2,8 +2,7 @@ package com.ing.baker.runtime.javadsl import com.ing.baker.il.CompiledRecipe import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi -import com.ing.baker.runtime.common -import com.ing.baker.runtime.scaladsl +import com.ing.baker.runtime.{common, scaladsl} import scala.collection.JavaConverters._ diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceMetadata.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceMetadata.scala similarity index 93% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceMetadata.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceMetadata.scala index 626168bbf..fbf28db72 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceMetadata.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceMetadata.scala @@ -1,8 +1,7 @@ package com.ing.baker.runtime.javadsl -import com.ing.baker.runtime.scaladsl -import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi +import com.ing.baker.runtime.{common, scaladsl} case class RecipeInstanceMetadata(recipeId: String, recipeInstanceId: String, createdTime: Long) extends common.RecipeInstanceMetadata with JavaApi { diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceState.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceState.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceState.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/RecipeInstanceState.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/SensoryEventResult.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/SensoryEventResult.scala similarity index 74% rename from runtime/src/main/scala/com/ing/baker/runtime/javadsl/SensoryEventResult.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/SensoryEventResult.scala index b9ea4b617..b761c0f20 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/javadsl/SensoryEventResult.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/javadsl/SensoryEventResult.scala @@ -1,10 +1,12 @@ package com.ing.baker.runtime.javadsl -import com.ing.baker.runtime.common.SensoryEventStatus -import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.JavaApi +import com.ing.baker.runtime.common.SensoryEventStatus +import com.ing.baker.runtime.{common, scaladsl} import com.ing.baker.types.Value +import scala.collection.JavaConverters._ + case class SensoryEventResult( sensoryEventStatus: SensoryEventStatus, eventNames: java.util.List[String], @@ -16,4 +18,7 @@ case class SensoryEventResult( def getEventNames: java.util.List[String] = eventNames def getIngredients: java.util.Map[String, Value] = ingredients + + def asScala: scaladsl.SensoryEventResult = + scaladsl.SensoryEventResult(sensoryEventStatus, eventNames.asScala, ingredients.asScala.toMap) } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/Baker.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/Baker.scala similarity index 79% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/Baker.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/Baker.scala index 7216e89f2..da0d0d715 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/Baker.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/Baker.scala @@ -1,29 +1,10 @@ package com.ing.baker.runtime.scaladsl -import akka.actor.{ ActorSystem, Address } -import cats.data.NonEmptyList -import com.ing.baker.runtime.akka.{ AkkaBaker, AkkaBakerConfig } import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi import com.ing.baker.runtime.common.SensoryEventStatus -import com.typesafe.config.Config -import scala.concurrent.Future - -object Baker { - - def akkaLocalDefault(actorSystem: ActorSystem): AkkaBaker = - new AkkaBaker(AkkaBakerConfig.localDefault(actorSystem)) - - def akkaClusterDefault(seedNodes: NonEmptyList[Address], actorSystem: ActorSystem): AkkaBaker = - new AkkaBaker(AkkaBakerConfig.clusterDefault(seedNodes, actorSystem)) - - def akka(config: AkkaBakerConfig): AkkaBaker = - new AkkaBaker(config) - def akka(config: Config, actorSystem: ActorSystem): AkkaBaker = - new AkkaBaker(AkkaBakerConfig.from(config, actorSystem)) - -} +import scala.concurrent.Future /** * The Baker is the component of the Baker library that runs one or multiples recipes. @@ -49,6 +30,8 @@ trait Baker extends common.Baker[Future] with ScalaApi { override type EventMomentType = EventMoment + override type RecipeMetadataType = RecipeEventMetadata + override def fireEventAndResolveWhenReceived(recipeInstanceId: String, event: EventInstance): Future[SensoryEventStatus] = fireEventAndResolveWhenReceived(recipeInstanceId, event, None) diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/BakerEvent.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/BakerEvent.scala similarity index 98% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/BakerEvent.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/BakerEvent.scala index 07b00ac5e..f488c212a 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/BakerEvent.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/BakerEvent.scala @@ -5,10 +5,9 @@ import java.util.Optional import com.ing.baker.il.CompiledRecipe import com.ing.baker.il.failurestrategy.ExceptionStrategyOutcome -import com.ing.baker.runtime.common -import com.ing.baker.runtime.javadsl import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi import com.ing.baker.runtime.common.RejectReason +import com.ing.baker.runtime.{common, javadsl} sealed trait BakerEvent extends common.BakerEvent with ScalaApi { type Event = EventInstance diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/EventInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/EventInstance.scala similarity index 96% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/EventInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/EventInstance.scala index 213a57941..d1365be2c 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/EventInstance.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/EventInstance.scala @@ -1,9 +1,8 @@ package com.ing.baker.runtime.scaladsl import com.ing.baker.il.EventDescriptor -import com.ing.baker.runtime.javadsl -import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi +import com.ing.baker.runtime.{common, javadsl} import com.ing.baker.types.{Converters, NullValue, RecordValue, Value} import scala.collection.JavaConverters._ diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/EventMoment.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/EventMoment.scala similarity index 80% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/EventMoment.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/EventMoment.scala index fb90dfe88..3319ab5a8 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/EventMoment.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/EventMoment.scala @@ -1,8 +1,7 @@ package com.ing.baker.runtime.scaladsl -import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi -import com.ing.baker.runtime.javadsl +import com.ing.baker.runtime.{common, javadsl} case class EventMoment(name: String, occurredOn: Long) diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/EventResolutions.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/EventResolutions.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/EventResolutions.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/EventResolutions.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/IngredientInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/IngredientInstance.scala similarity index 61% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/IngredientInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/IngredientInstance.scala index 49e3506bc..ffbfa778b 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/IngredientInstance.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/IngredientInstance.scala @@ -1,12 +1,11 @@ package com.ing.baker.runtime.scaladsl -import com.ing.baker.runtime.javadsl -import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi +import com.ing.baker.runtime.{common, javadsl} import com.ing.baker.types.Value case class IngredientInstance(name: String, value: Value) extends common.IngredientInstance with ScalaApi { - def asJava: javadsl.IngredientInstance = new javadsl.IngredientInstance(name, value) + def asJava: javadsl.IngredientInstance = javadsl.IngredientInstance(name, value) } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/InteractionInstance.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/InteractionInstance.scala similarity index 96% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/InteractionInstance.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/InteractionInstance.scala index 622ea9c3e..c0bacb34f 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/InteractionInstance.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/InteractionInstance.scala @@ -5,15 +5,13 @@ import java.util import java.util.Optional import java.util.concurrent.CompletableFuture -import com.ing.baker.runtime.akka -import com.ing.baker.runtime.javadsl -import com.ing.baker.runtime.common import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi +import com.ing.baker.runtime.{common, javadsl} import com.ing.baker.types.{Converters, Type} -import scala.concurrent.{ExecutionContext, Future} import scala.collection.JavaConverters._ import scala.compat.java8.FutureConverters +import scala.concurrent.{ExecutionContext, Future} import scala.reflect.ClassTag import scala.util.Try @@ -58,7 +56,7 @@ object InteractionInstance { def unsafeFrom(implementation: AnyRef)(implicit ec: ExecutionContext): InteractionInstance = { val method: Method = { - val unmockedClass = akka.unmock(implementation.getClass) + val unmockedClass = common.unmock(implementation.getClass) unmockedClass.getMethods.count(_.getName == "apply") match { case 0 => throw new IllegalArgumentException("Implementation does not have a apply function") case n if n > 1 => throw new IllegalArgumentException("Implementation has multiple apply functions") diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeEventMetadata.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeEventMetadata.scala new file mode 100644 index 000000000..fb37f161c --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeEventMetadata.scala @@ -0,0 +1,15 @@ +package com.ing.baker.runtime.scaladsl + +import com.ing.baker.runtime.common +import com.ing.baker.runtime.javadsl +import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi + +case class RecipeEventMetadata(recipeId: String, recipeName: String, recipeInstanceId: String) extends common.RecipeEventMetadata with ScalaApi { + + def asJava: javadsl.RecipeEventMetadata = + javadsl.RecipeEventMetadata( + recipeId = recipeId, + recipeName = recipeName, + recipeInstanceId = recipeInstanceId + ) +} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInformation.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInformation.scala similarity index 75% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInformation.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInformation.scala index 6e3306aac..d6e8853be 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInformation.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInformation.scala @@ -1,10 +1,9 @@ package com.ing.baker.runtime.scaladsl import com.ing.baker.il.CompiledRecipe - import com.ing.baker.runtime.common.LanguageDataStructures.ScalaApi -import com.ing.baker.runtime.common -import com.ing.baker.runtime.javadsl +import com.ing.baker.runtime.{common, javadsl} + import scala.collection.JavaConverters._ case class RecipeInformation( @@ -13,5 +12,5 @@ case class RecipeInformation( errors: Set[String]) extends common.RecipeInformation with ScalaApi { def asJava: javadsl.RecipeInformation = - new javadsl.RecipeInformation(compiledRecipe, recipeCreatedTime, errors.asJava) + javadsl.RecipeInformation(compiledRecipe, recipeCreatedTime, errors.asJava) } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInstanceMetadata.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInstanceMetadata.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInstanceMetadata.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInstanceMetadata.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInstanceState.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInstanceState.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInstanceState.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/RecipeInstanceState.scala diff --git a/runtime/src/main/scala/com/ing/baker/runtime/scaladsl/SensoryEventResult.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/SensoryEventResult.scala similarity index 100% rename from runtime/src/main/scala/com/ing/baker/runtime/scaladsl/SensoryEventResult.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/scaladsl/SensoryEventResult.scala diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/BakerSerializable.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/BakerSerializable.scala new file mode 100644 index 000000000..8eddd451e --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/BakerSerializable.scala @@ -0,0 +1,3 @@ +package com.ing.baker.runtime.serialization + +trait BakerSerializable diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/Encryption.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/Encryption.scala similarity index 95% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/Encryption.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/Encryption.scala index 585bb48df..295d6d359 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/Encryption.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/Encryption.scala @@ -1,4 +1,4 @@ -package com.ing.baker.runtime.akka.actor.serialization +package com.ing.baker.runtime.serialization import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/ProtoMap.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/ProtoMap.scala similarity index 76% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/ProtoMap.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/ProtoMap.scala index 3fed2ef5d..96a4b2bd4 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/ProtoMap.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/ProtoMap.scala @@ -1,12 +1,12 @@ -package com.ing.baker.runtime.akka.actor.serialization +package com.ing.baker.runtime.serialization import akka.actor.ActorRef -import com.ing.baker.il -import com.ing.baker.types -import com.ing.baker.runtime.akka.actor.protobuf -import com.ing.baker.runtime.akka.actor.serialization.protomappings._ -import com.ing.baker.runtime.scaladsl.{EventMoment, RecipeInstanceState, EventInstance, SensoryEventResult} +import com.ing.baker.runtime.common.BakerException +import com.ing.baker.runtime.scaladsl._ +import com.ing.baker.runtime.serialization.protomappings._ +import com.ing.baker.{il, types} import scalapb.GeneratedMessageCompanion +import com.ing.baker.runtime.akka.actor.protobuf import scala.util.{Success, Try} @@ -44,12 +44,21 @@ object ProtoMap { implicit def akkaActorRefMapping(implicit ev0: SerializersProvider): ProtoMap[ActorRef, protobuf.ActorRefId] = new ActorRefMapping(ev0) + implicit def recipeInformationMapping(implicit ev0: SerializersProvider): ProtoMap[RecipeInformation, protobuf.RecipeInformation] = + new RecipeInformationMapping() + + implicit val bakerExceptionMapping: ProtoMap[BakerException, protobuf.BakerException] = + new BakerExceptionMapping + implicit val eventDescriptorMapping: ProtoMap[il.EventDescriptor, protobuf.EventDescriptor] = new EventDescriptorMapping implicit val ingredientDescriptorMapping: ProtoMap[il.IngredientDescriptor, protobuf.IngredientDescriptor] = new IngredientDescriptorMapping + implicit val ingredientInstanceMapping: ProtoMap[IngredientInstance, protobuf.Ingredient] = + new IngredientInstanceMapping + implicit val bakerTypeMapping: ProtoMap[types.Type, protobuf.Type] = new BakerTypesMapping @@ -65,9 +74,15 @@ object ProtoMap { implicit val runtimeEventMapping: ProtoMap[EventInstance, protobuf.RuntimeEvent] = new RuntimeEventMapping + implicit val recipeInstanceMetadataMapping: ProtoMap[RecipeInstanceMetadata, protobuf.RecipeInstanceMetadata] = + new RecipeInstanceMetadataMapping + implicit val processStateMapping: ProtoMap[RecipeInstanceState, protobuf.ProcessState] = new ProcessStateMapping + implicit val recipeEventMetadataMapping: ProtoMap[RecipeEventMetadata, protobuf.RecipeEventMetadata] = + new RecipeEventMetadataMapping + implicit val eventMomentMapping: ProtoMap[EventMoment, protobuf.EventMoment] = new EventMomentMapping diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/SerializersProvider.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/SerializersProvider.scala similarity index 56% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/SerializersProvider.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/SerializersProvider.scala index 766d28c05..7efdfe6ad 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/SerializersProvider.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/SerializersProvider.scala @@ -1,4 +1,4 @@ -package com.ing.baker.runtime.akka.actor.serialization +package com.ing.baker.runtime.serialization import akka.actor.{ActorRefProvider, ActorSystem} import akka.serialization.{Serialization, SerializationExtension, Serializer} @@ -7,7 +7,7 @@ case class SerializersProvider(getSerializerFor: AnyRef => Serializer, serialize object SerializersProvider { - def apply(system: ActorSystem, actorRefProvider: ActorRefProvider, encryption: Encryption = Encryption.NoEncryption): SerializersProvider = { + def apply(system: ActorSystem, actorRefProvider: ActorRefProvider, encryption: Encryption): SerializersProvider = { val serialization: Serialization = SerializationExtension.get(system) SerializersProvider( serialization.findSerializerFor, @@ -16,4 +16,16 @@ object SerializersProvider { actorRefProvider ) } + + def apply(system: ActorSystem, actorRefProvider: ActorRefProvider): SerializersProvider = { + apply(system, actorRefProvider, Encryption.NoEncryption) + } + + def apply(system: ActorSystem, encryption: Encryption): SerializersProvider = { + apply(system, null, encryption) + } + + def apply(system: ActorSystem): SerializersProvider = { + apply(system, null, Encryption.NoEncryption) + } } diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/TokenIdentifier.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/TokenIdentifier.scala new file mode 100644 index 000000000..f8e4c9674 --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/TokenIdentifier.scala @@ -0,0 +1,23 @@ +package com.ing.baker.runtime.serialization + +import java.security.MessageDigest + +object TokenIdentifier { + + /** + * TODO: + * + * This approach is fragile, the identifier function cannot change ever or recovery breaks + * a more robust alternative is to generate the ids and persist them + */ + def apply(tokenValue: Any): Long = tokenValue match { + case null => -1 + case str: String => sha256(str) + case obj => obj.hashCode() + } + + def sha256(str: String) = { + val sha256Digest: MessageDigest = MessageDigest.getInstance("SHA-256") + BigInt(1, sha256Digest.digest(str.getBytes("UTF-8"))).toLong + } +} diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/TypedProtobufSerializer.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/TypedProtobufSerializer.scala new file mode 100644 index 000000000..de3041130 --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/TypedProtobufSerializer.scala @@ -0,0 +1,101 @@ +package com.ing.baker.runtime.serialization + +import akka.actor.ExtendedActorSystem +import akka.serialization.SerializerWithStringManifest +import com.ing.baker.runtime.serialization.TypedProtobufSerializer.BinarySerializable +import org.slf4j.LoggerFactory + +import scala.reflect.ClassTag +import scala.util.Try + +object TypedProtobufSerializer { + + private val log = LoggerFactory.getLogger(classOf[TypedProtobufSerializer]) + + def forType[A <: AnyRef](implicit tag: ClassTag[A]): RegisterFor[A] = new RegisterFor[A](tag) + + class RegisterFor[A <: AnyRef](classTag: ClassTag[A]) { + + def register[P <: scalapb.GeneratedMessage with scalapb.Message[P]](implicit protoMap: ProtoMap[A, P]): BinarySerializable = + register[P](None) + + def register[P <: scalapb.GeneratedMessage with scalapb.Message[P]](overrideName: String)(implicit protoMap: ProtoMap[A, P]): BinarySerializable = + register[P](Some(overrideName)) + + def register[P <: scalapb.GeneratedMessage with scalapb.Message[P]](overrideName: Option[String])(implicit protoMap: ProtoMap[A, P]): BinarySerializable = { + new BinarySerializable { + + override type Type = A + + override val tag: Class[_] = classTag.runtimeClass + + override val manifest: String = overrideName.getOrElse(classTag.runtimeClass.getName) + + override def toBinary(a: Type): Array[Byte] = protoMap.toByteArray(a) + + override def fromBinary(binary: Array[Byte]): Try[Type] = protoMap.fromByteArray(binary) + } + } + } + + trait BinarySerializable { + + type Type <: AnyRef + + val tag: Class[_] + + def manifest: String + + def toBinary(a: Type): Array[Byte] + + // The actor resolver is commented for future Akka Typed implementation + def fromBinary(binary: Array[Byte]/*, resolver: ActorRefResolver*/): Try[Type] + + def isInstance(o: AnyRef): Boolean = + tag.isInstance(o) + + def unsafeToBinary(a: AnyRef): Array[Byte] = + toBinary(a.asInstanceOf[Type]) + + // The actor resolver is commented for future Akka Typed implementation + def fromBinaryAnyRef(binary: Array[Byte]/*, resolver: ActorRefResolver*/): Try[AnyRef] = + fromBinary(binary) + + } +} + +abstract class TypedProtobufSerializer(system: ExtendedActorSystem, _indentifier: Int, entries: SerializersProvider => List[BinarySerializable]) extends SerializerWithStringManifest { + + implicit def serializersProvider: SerializersProvider = + SerializersProvider(system, system.provider) + + lazy val entriesMem: List[BinarySerializable] = + entries(serializersProvider) + + override def identifier: Int = + _indentifier + + override def manifest(o: AnyRef): String = { + entriesMem + .find(_.isInstance(o)) + .map(_.manifest) + .getOrElse(throw new IllegalStateException(s"Unsupported object of type: ${o.getClass}")) + } + + override def toBinary(o: AnyRef): Array[Byte] = + entriesMem + .find(_.isInstance(o)) + .map(_.unsafeToBinary(o)) + .getOrElse(throw new IllegalStateException(s"Unsupported object of type: ${o.getClass}")) + + override def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = + entriesMem + .find(_.manifest == manifest) + .map(_.fromBinaryAnyRef(bytes)) + .getOrElse(throw new IllegalStateException(s"Unsupported object with manifest $manifest")) + .fold( + { e => TypedProtobufSerializer.log.error(s"Failed to deserialize bytes with manifest $manifest", e); throw e }, + identity + ) +} + diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/ActorRefMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/ActorRefMapping.scala similarity index 69% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/ActorRefMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/ActorRefMapping.scala index 1b647a0f9..361d8c4ef 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/ActorRefMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/ActorRefMapping.scala @@ -1,10 +1,10 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import akka.actor.ActorRef -import com.ing.baker.runtime.akka.actor.serialization.{ProtoMap, SerializersProvider} -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.versioned +import com.ing.baker.runtime.serialization.ProtoMap.versioned import com.ing.baker.runtime.akka.actor.protobuf import com.ing.baker.runtime.akka.actor.protobuf.ActorRefId +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider} import scala.util.Try @@ -12,7 +12,7 @@ class ActorRefMapping(provider: SerializersProvider) extends ProtoMap[ActorRef, val companion = protobuf.ActorRefId - override def toProto(a: ActorRef): ActorRefId = + override def toProto(a: ActorRef): protobuf.ActorRefId = protobuf.ActorRefId(Some(akka.serialization.Serialization.serializedActorPath(a))) override def fromProto(message: ActorRefId): Try[ActorRef] = diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/AnyRefMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/AnyRefMapping.scala similarity index 88% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/AnyRefMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/AnyRefMapping.scala index 39adbafdb..bee47e4f5 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/AnyRefMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/AnyRefMapping.scala @@ -1,10 +1,10 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings -import com.google.protobuf.ByteString import akka.serialization.{Serializer, SerializerWithStringManifest} -import com.ing.baker.runtime.akka.actor.serialization.{ProtoMap, SerializersProvider} -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.versioned +import com.google.protobuf.ByteString +import com.ing.baker.runtime.serialization.ProtoMap.versioned import com.ing.baker.runtime.akka.actor.protobuf +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider} import scala.util.{Failure, Success, Try} diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerExceptionMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerExceptionMapping.scala new file mode 100644 index 000000000..a055b4aed --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerExceptionMapping.scala @@ -0,0 +1,26 @@ +package com.ing.baker.runtime.serialization.protomappings + +import com.ing.baker.runtime.akka.actor.protobuf +import com.ing.baker.runtime.serialization.ProtoMap.versioned +import com.ing.baker.runtime.common.BakerException +import com.ing.baker.runtime.serialization.ProtoMap + +import scala.util.Try + +class BakerExceptionMapping extends ProtoMap[BakerException, protobuf.BakerException] { + + val companion: protobuf.BakerException.type = protobuf.BakerException + + override def toProto(a: BakerException): protobuf.BakerException = + BakerException.encode(a) match { + case (message, enum) => + protobuf.BakerException(Some(message), Some(enum)) + } + + override def fromProto(message: protobuf.BakerException): Try[BakerException] = + for { + msg <- versioned(message.message, "message") + enum <- versioned(message.enum, "enum") + decoded <- BakerException.decode(msg, enum) + } yield decoded + } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/BakerTypesMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerTypesMapping.scala similarity index 95% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/BakerTypesMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerTypesMapping.scala index 833dab6f2..5f5359bb2 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/BakerTypesMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerTypesMapping.scala @@ -1,13 +1,13 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import cats.implicits._ -import com.ing.baker.types -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap import com.ing.baker.runtime.akka.actor.protobuf -import com.ing.baker.runtime.akka.actor.protobuf._ -import Type.OneofType._ -import PrimitiveType._ +import com.ing.baker.runtime.akka.actor.protobuf.PrimitiveType._ import com.ing.baker.runtime.akka.actor.protobuf.Type.OneofType +import com.ing.baker.runtime.akka.actor.protobuf.Type.OneofType.Primitive +import com.ing.baker.runtime.akka.actor.protobuf._ +import com.ing.baker.runtime.serialization.ProtoMap +import com.ing.baker.types import scala.util.{Failure, Success, Try} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/BakerValuesMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerValuesMapping.scala similarity index 94% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/BakerValuesMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerValuesMapping.scala index d2ef70c92..2479a2911 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/BakerValuesMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/BakerValuesMapping.scala @@ -1,15 +1,13 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings -import cats.instances.list._ -import cats.instances.try_._ -import cats.syntax.traverse._ +import cats.implicits._ import com.google.protobuf.ByteString -import com.ing.baker.types -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap import com.ing.baker.runtime.akka.actor.protobuf -import protobuf.Value.OneofValue._ -import org.joda.time.{LocalDate, LocalDateTime, LocalTime} +import com.ing.baker.runtime.akka.actor.protobuf.Value.OneofValue._ +import com.ing.baker.runtime.serialization.ProtoMap +import com.ing.baker.types import org.joda.time.format.ISODateTimeFormat +import org.joda.time.{LocalDate, LocalDateTime, LocalTime} import scala.util.{Failure, Success, Try} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/CompiledRecipeMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/CompiledRecipeMapping.scala similarity index 96% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/CompiledRecipeMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/CompiledRecipeMapping.scala index 7f535fa25..60f78cf26 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/CompiledRecipeMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/CompiledRecipeMapping.scala @@ -1,19 +1,15 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import java.util.concurrent.TimeUnit -import cats.syntax.traverse._ -import cats.instances.list._ -import cats.instances.option._ -import cats.instances.try_._ +import cats.implicits._ import com.ing.baker.il import com.ing.baker.petrinet.api._ -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceSerialization.tokenIdentifier import com.ing.baker.runtime.akka.actor.protobuf -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{versioned, ctxFromProto, ctxToProto} +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} import com.ing.baker.il.petrinet.{Node, Place, RecipePetriNet, Transition} import com.ing.baker.petrinet.api.Marking +import com.ing.baker.runtime.serialization.{ProtoMap, TokenIdentifier} import com.ing.baker.types.Value import scalax.collection.GraphEdge import scalax.collection.edge.WLDiEdge @@ -127,7 +123,7 @@ class CompiledRecipeMapping(anyMapping: ProtoMap[AnyRef, protobuf.SerializedData case (place, tokens) ⇒ tokens.toSeq.map { case (value, count) ⇒ protobuf.ProducedToken( placeId = Option(place.id), - tokenId = Option(tokenIdentifier(value)), + tokenId = Option(TokenIdentifier(value)), count = Option(count), tokenData = Option(anyMapping.toProto(value.asInstanceOf[AnyRef])) ) diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventDescriptorMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventDescriptorMapping.scala similarity index 77% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventDescriptorMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventDescriptorMapping.scala index 0a531a3a3..1385dcce3 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventDescriptorMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventDescriptorMapping.scala @@ -1,10 +1,10 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import cats.implicits._ import com.ing.baker.il -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ ctxToProto, ctxFromProto, versioned } +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} import com.ing.baker.runtime.akka.actor.protobuf +import com.ing.baker.runtime.serialization.ProtoMap import scala.util.Try diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventMomentMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventMomentMapping.scala similarity index 73% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventMomentMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventMomentMapping.scala index 3fb0f51a7..54439f4c9 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventMomentMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventMomentMapping.scala @@ -1,9 +1,9 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import com.ing.baker.runtime.akka.actor.protobuf -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.versioned +import com.ing.baker.runtime.serialization.ProtoMap.versioned import com.ing.baker.runtime.scaladsl.EventMoment +import com.ing.baker.runtime.serialization.ProtoMap import scala.util.Try diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventOutputTransformerMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventOutputTransformerMapping.scala similarity index 78% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventOutputTransformerMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventOutputTransformerMapping.scala index f50949288..2f7785079 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/EventOutputTransformerMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/EventOutputTransformerMapping.scala @@ -1,10 +1,10 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import com.ing.baker.il import com.ing.baker.il.EventOutputTransformer -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap import com.ing.baker.runtime.akka.actor.protobuf -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.versioned +import com.ing.baker.runtime.serialization.ProtoMap.versioned +import com.ing.baker.runtime.serialization.ProtoMap import scala.util.Try diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/IngredientDescriptorMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/IngredientDescriptorMapping.scala similarity index 76% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/IngredientDescriptorMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/IngredientDescriptorMapping.scala index 4d9d98276..ff7c84163 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/IngredientDescriptorMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/IngredientDescriptorMapping.scala @@ -1,9 +1,9 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import com.ing.baker.il -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ ctxToProto, ctxFromProto, versioned } +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} import com.ing.baker.runtime.akka.actor.protobuf +import com.ing.baker.runtime.serialization.ProtoMap import scala.util.Try diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/IngredientInstanceMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/IngredientInstanceMapping.scala new file mode 100644 index 000000000..bed54700a --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/IngredientInstanceMapping.scala @@ -0,0 +1,24 @@ +package com.ing.baker.runtime.serialization.protomappings + +import com.ing.baker.runtime.akka.actor.protobuf +import com.ing.baker.runtime.scaladsl.IngredientInstance +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.ProtoMap + +import scala.util.Try + +class IngredientInstanceMapping extends ProtoMap[IngredientInstance, protobuf.Ingredient] { + + val companion = protobuf.Ingredient + + override def toProto(data: IngredientInstance): protobuf.Ingredient = + protobuf.Ingredient(Option(data.name), None, Some(ctxToProto(data.value))) + + override def fromProto(message: protobuf.Ingredient): Try[IngredientInstance] = + for { + name <- versioned(message.name, "name") + valueProto <- versioned(message.value, "value") + value <- ctxFromProto(valueProto) + } yield IngredientInstance(name, value) + +} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/InteractionFailureStrategyMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/InteractionFailureStrategyMapping.scala similarity index 93% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/InteractionFailureStrategyMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/InteractionFailureStrategyMapping.scala index dff5c3eb1..293dd4327 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/InteractionFailureStrategyMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/InteractionFailureStrategyMapping.scala @@ -1,13 +1,13 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import java.util.concurrent.TimeUnit import cats.implicits._ -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap import com.ing.baker.il import com.ing.baker.il.failurestrategy.InteractionFailureStrategy import com.ing.baker.runtime.akka.actor.protobuf -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{versioned, ctxFromProto, ctxToProto} +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.ProtoMap import scala.util.{Failure, Success, Try} import scala.concurrent.duration._ diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/ProcessStateMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/ProcessStateMapping.scala similarity index 79% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/ProcessStateMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/ProcessStateMapping.scala index 3e18a8d8f..6b48224c7 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/ProcessStateMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/ProcessStateMapping.scala @@ -1,13 +1,11 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings -import cats.instances.list._ -import cats.instances.try_._ -import cats.syntax.traverse._ +import cats.implicits._ import com.ing.baker.types.Value import com.ing.baker.runtime.akka.actor.{protobuf => proto} -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} import com.ing.baker.runtime.scaladsl.{EventMoment, RecipeInstanceState} +import com.ing.baker.runtime.serialization.ProtoMap import scala.util.Try diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeEventMetadataMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeEventMetadataMapping.scala new file mode 100644 index 000000000..8c7ba06b5 --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeEventMetadataMapping.scala @@ -0,0 +1,24 @@ +package com.ing.baker.runtime.serialization.protomappings + +import com.ing.baker.runtime.akka.actor.{protobuf => proto} +import com.ing.baker.runtime.scaladsl.RecipeEventMetadata +import com.ing.baker.runtime.serialization.ProtoMap +import com.ing.baker.runtime.serialization.ProtoMap.versioned + +import scala.util.Try + +class RecipeEventMetadataMapping extends ProtoMap[RecipeEventMetadata, proto.RecipeEventMetadata] { + + val companion = proto.RecipeEventMetadata + + def toProto(a: RecipeEventMetadata): proto.RecipeEventMetadata = { + proto.RecipeEventMetadata(Some(a.recipeId), Some(a.recipeName), Some(a.recipeInstanceId)) + } + + def fromProto(message: proto.RecipeEventMetadata): Try[RecipeEventMetadata] = + for { + recipeId <- versioned(message.recipeId, "recipeId") + recipeName <- versioned(message.recipeName, "recipeName") + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + } yield RecipeEventMetadata(recipeId, recipeName, recipeInstanceId) +} diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeInformationMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeInformationMapping.scala new file mode 100644 index 000000000..f295d2a90 --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeInformationMapping.scala @@ -0,0 +1,24 @@ +package com.ing.baker.runtime.serialization.protomappings + +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.akka.actor.{protobuf => proto} +import com.ing.baker.runtime.scaladsl.RecipeInformation +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider} + +import scala.util.Try + +class RecipeInformationMapping(implicit ev0: SerializersProvider) extends ProtoMap[RecipeInformation, proto.RecipeInformation] { + + val companion = proto.RecipeInformation + + def toProto(a: RecipeInformation): proto.RecipeInformation = { + proto.RecipeInformation(Some(ctxToProto(a.compiledRecipe)), Some(a.recipeCreatedTime), a.errors.toSeq) + } + + def fromProto(message: proto.RecipeInformation): Try[RecipeInformation] = + for { + compiledRecipeProto <- versioned(message.compiledRecipe, "compiledRecipe") + compiledRecipe <- ctxFromProto(compiledRecipeProto) + recipeCreatedTime <- versioned(message.recipeCreatedTime, "recipeCreatedTime") + } yield RecipeInformation(compiledRecipe, recipeCreatedTime, message.errors.toSet) +} diff --git a/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeInstanceMetadataMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeInstanceMetadataMapping.scala new file mode 100644 index 000000000..da3614a5b --- /dev/null +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RecipeInstanceMetadataMapping.scala @@ -0,0 +1,24 @@ +package com.ing.baker.runtime.serialization.protomappings + +import com.ing.baker.runtime.serialization.ProtoMap.versioned +import com.ing.baker.runtime.akka.actor.{protobuf => proto} +import com.ing.baker.runtime.scaladsl.RecipeInstanceMetadata +import com.ing.baker.runtime.serialization.ProtoMap + +import scala.util.Try + +class RecipeInstanceMetadataMapping extends ProtoMap[RecipeInstanceMetadata, proto.RecipeInstanceMetadata] { + + val companion = proto.RecipeInstanceMetadata + + def toProto(a: RecipeInstanceMetadata): proto.RecipeInstanceMetadata = { + proto.RecipeInstanceMetadata(Some(a.recipeId), Some(a.recipeInstanceId), Some(a.createdTime)) + } + + def fromProto(message: proto.RecipeInstanceMetadata): Try[RecipeInstanceMetadata] = + for { + recipeId <- versioned(message.recipeId, "recipeId") + recipeInstanceId <- versioned(message.recipeInstanceId, "recipeInstanceId") + createdTime <- versioned(message.createdTime, "createdTime") + } yield RecipeInstanceMetadata(recipeId, recipeInstanceId, createdTime) + } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/RuntimeEventMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RuntimeEventMapping.scala similarity index 76% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/RuntimeEventMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RuntimeEventMapping.scala index 810e4a9e6..f71e6a0b1 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/RuntimeEventMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/RuntimeEventMapping.scala @@ -1,12 +1,10 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings -import cats.instances.list._ -import cats.instances.try_._ -import cats.syntax.traverse._ +import cats.implicits._ import com.ing.baker.runtime.akka.actor.protobuf -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} import com.ing.baker.runtime.scaladsl.EventInstance +import com.ing.baker.runtime.serialization.ProtoMap import com.ing.baker.types.Value import scala.util.Try diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/SensoryEventResultMapping.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/SensoryEventResultMapping.scala similarity index 78% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/SensoryEventResultMapping.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/SensoryEventResultMapping.scala index db2764b77..f5a400799 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/SensoryEventResultMapping.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/SensoryEventResultMapping.scala @@ -1,12 +1,10 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings -import cats.instances.list._ -import cats.instances.try_._ -import cats.syntax.traverse._ +import cats.implicits._ import com.ing.baker.runtime.akka.actor.protobuf -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} import com.ing.baker.runtime.scaladsl.SensoryEventResult +import com.ing.baker.runtime.serialization.ProtoMap import com.ing.baker.types.Value import scalapb.GeneratedMessageCompanion diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/SensoryEventStatusMappingHelper.scala b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/SensoryEventStatusMappingHelper.scala similarity index 96% rename from runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/SensoryEventStatusMappingHelper.scala rename to baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/SensoryEventStatusMappingHelper.scala index 3186e24d2..8d145df13 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/protomappings/SensoryEventStatusMappingHelper.scala +++ b/baker-interface/src/main/scala/com/ing/baker/runtime/serialization/protomappings/SensoryEventStatusMappingHelper.scala @@ -1,4 +1,4 @@ -package com.ing.baker.runtime.akka.actor.serialization.protomappings +package com.ing.baker.runtime.serialization.protomappings import com.ing.baker.runtime.akka.actor.protobuf import com.ing.baker.runtime.common.SensoryEventStatus diff --git a/build.sbt b/build.sbt index 68d91bc7f..04c907135 100644 --- a/build.sbt +++ b/build.sbt @@ -23,6 +23,7 @@ val commonSettings = Defaults.coreDefaultSettings ++ Seq( s"-target:jvm-$jvmV", "-Xfatal-warnings" ), + coverageExcludedPackages := ";.*.javadsl;.*.scaladsl;.*.common;.*.protobuf", packageOptions in (Compile, packageBin) += Package.ManifestAttributes( "Build-Time" -> new java.util.Date().toString, @@ -31,11 +32,10 @@ val commonSettings = Defaults.coreDefaultSettings ++ Seq( ) val dependencyOverrideSettings = Seq( - // note that this does NOT add the dependencies, just forces the version dependencyOverrides ++= Seq( catsCore, akkaActor, - "com.github.jnr" % "jnr-constants" % "0.9.9" + jnrConstants ) ) @@ -77,6 +77,18 @@ lazy val intermediateLanguage = project.in(file("intermediate-language")) ) ++ testDeps(scalaTest, scalaCheck, logback) ).dependsOn(bakertypes) +lazy val `baker-interface` = project.in(file("baker-interface")) + .settings(defaultModuleSettings) + .settings(scalaPBSettings) + .settings( + moduleName := "baker-interface", + libraryDependencies ++= Seq( + akkaActor, + catsCore, + scalaJava8Compat + ) ++ providedDeps(findbugs) + ) + .dependsOn(intermediateLanguage) lazy val runtime = project.in(file("runtime")) .settings(defaultModuleSettings) @@ -90,6 +102,9 @@ lazy val runtime = project.in(file("runtime")) akkaActor, akkaPersistence, akkaPersistenceQuery, + akkaCluster, + akkaClusterTools, + akkaDistributedData, akkaClusterSharding, akkaBoostrap, akkaSlf4j, @@ -118,7 +133,14 @@ lazy val runtime = project.in(file("runtime")) logback) ++ providedDeps(findbugs) ) - .dependsOn(intermediateLanguage, testScope(recipeDsl), testScope(recipeCompiler), testScope(bakertypes)) + .dependsOn( + intermediateLanguage, + `baker-interface`, + `baas-protocol-interaction-scheduling`, + `baas-protocol-recipe-event-publishing`, + testScope(recipeDsl), + testScope(recipeCompiler), + testScope(bakertypes)) .enablePlugins(MultiJvmPlugin) .configs(MultiJvm) @@ -177,17 +199,104 @@ lazy val recipeCompiler = project.in(file("compiler")) ) .dependsOn(recipeDsl, intermediateLanguage, testScope(recipeDsl)) -lazy val baas = project.in(file("baas")) +lazy val `baas-protocol-baker` = project.in(file("baas-protocol-baker")) .settings(defaultModuleSettings) .settings(scalaPBSettings) .settings( - moduleName := "baker-baas", - libraryDependencies ++= + moduleName := "baas-protocol-baker", + libraryDependencies ++= Seq( + akkaStream, + akkaHttp + ) + ) + .dependsOn(`baker-interface`) + +lazy val `baas-protocol-interaction-scheduling` = project.in(file("baas-protocol-interaction-scheduling")) + .settings(defaultModuleSettings) + .settings(scalaPBSettings) + .settings( + moduleName := "baas-protocol-interaction-scheduling" + ) + .dependsOn(`baker-interface`) + +lazy val `baas-protocol-recipe-event-publishing` = project.in(file("baas-protocol-recipe-event-publishing")) + .settings(defaultModuleSettings) + .settings(scalaPBSettings) + .settings( + moduleName := "baas-protocol-recipe-event-publishing" + ) + .dependsOn(`baker-interface`) + +lazy val `baas-node-client` = project.in(file("baas-node-client")) + .settings(defaultModuleSettings) + .settings( + moduleName := "baas-node-client", + libraryDependencies ++= Seq( + akkaStream, + akkaHttp + ) + ) + .dependsOn(`baker-interface`, `baas-protocol-baker`) + +lazy val `baas-node-state` = project.in(file("baas-node-state")) + .enablePlugins(JavaAppPackaging) + .settings(commonSettings) + .settings( + moduleName := "baas-node-state", + scalacOptions ++= Seq( + "-Ypartial-unification" + ), + libraryDependencies ++= Seq( + akkaHttp, + akkaPersistenceCassandra, + akkaManagementHttp, + akkaClusterBoostrap, + akkaDiscoveryKube + ) + ) + .settings( + maintainer in Docker := "The Apollo Squad", + packageSummary in Docker := "The core node", + packageName in Docker := "baas-node-state", + dockerExposedPorts := Seq(8080) + ) + .dependsOn(runtime, `baas-protocol-baker`, `baas-protocol-interaction-scheduling`) + +lazy val `baas-node-interaction` = project.in(file("baas-node-interaction")) + .settings(defaultModuleSettings) + .settings( + moduleName := "baas-node-interaction", + libraryDependencies ++= Seq( + akkaCluster, + akkaClusterTools, + slf4jApi + ) + ) + .dependsOn(`baas-protocol-interaction-scheduling`, `baker-interface`) + +lazy val `baas-node-event-listener` = project.in(file("baas-node-event-listener")) + .settings(defaultModuleSettings) + .settings( + moduleName := "baas-node-event-listener", + libraryDependencies ++= Seq( + akkaCluster, + akkaClusterTools, + slf4jApi + ) + ) + .dependsOn(`baas-protocol-recipe-event-publishing`, `baker-interface`) + +lazy val `baas-tests` = project.in(file("baas-tests")) + .settings(defaultModuleSettings) + .settings(noPublishSettings) + .settings( + moduleName := "baas-tests", + libraryDependencies ++= Seq() ++ testDeps( akkaSlf4j, akkaTestKit, + akkaInmemoryJournal, logback, - mockito, scalaTest, junitInterface, levelDB, @@ -195,13 +304,13 @@ lazy val baas = project.in(file("baas")) scalaCheck ) ) - .dependsOn(recipeDsl, recipeCompiler, intermediateLanguage, runtime, testScope(runtime)) + .dependsOn(`baas-node-client`, `baas-node-state`, `baas-node-interaction`, `baas-node-event-listener`, recipeCompiler) + .aggregate(`baas-node-client`, `baas-node-state`, `baas-node-interaction`, `baas-node-event-listener`) -lazy val baker = project - .in(file(".")) +lazy val baker = project.in(file(".")) .settings(defaultModuleSettings) .settings(noPublishSettings) - .aggregate(bakertypes, runtime, recipeCompiler, recipeDsl, intermediateLanguage, splitBrainResolver, baas) + .aggregate(bakertypes, runtime, recipeCompiler, recipeDsl, intermediateLanguage, splitBrainResolver, `baas-tests`) lazy val integration = project.in(file("integration")) .dependsOn(testScope(runtime)) @@ -225,13 +334,42 @@ lazy val integration = project.in(file("integration")) .enablePlugins(MultiJvmPlugin) .configs(MultiJvm) -lazy val examples = project - .in(file("examples")) +lazy val playground = project + .in(file("playground")) + .settings( + name := "baker-playground", + version := "0.1.0", + organization := "com.ing.baker", + scalaVersion := "2.12.4", + libraryDependencies ++= Seq( + catsEffect, + console4Cats, + scalaTest, + scalaCheck, + scalaLogging + ), + scalacOptions := Seq( + "-unchecked", + "-deprecation", + "-feature", + "-Ywarn-dead-code", + "-language:higherKinds", + "-language:existentials", + "-language:implicitConversions", + "-language:postfixOps", + "-encoding", "utf8", + s"-target:jvm-$jvmV", + "-Xfatal-warnings" + ) + ) + +lazy val `baker-example` = project + .in(file("examples/baker-example")) .enablePlugins(JavaAppPackaging) .settings(commonSettings) .settings(noPublishSettings) .settings( - moduleName := "examples", + moduleName := "baker-example", scalacOptions ++= Seq( "-Ypartial-unification" ), @@ -261,7 +399,173 @@ lazy val examples = project .settings( maintainer in Docker := "The Apollo Squad", packageSummary in Docker := "A web-shop checkout service example running baker", - packageName in Docker := "apollo.docker.ing.net/baker-example-app", + packageName in Docker := "baker-example-app", dockerExposedPorts := Seq(8080) ) .dependsOn(bakertypes, runtime, recipeCompiler, recipeDsl, intermediateLanguage) + +lazy val `baas-client-example` = project + .in(file("examples/baas-client-example")) + .enablePlugins(JavaAppPackaging) + .settings(commonSettings) + .settings(noPublishSettings) + .settings( + moduleName := "baas-client-example", + scalacOptions ++= Seq( + "-Ypartial-unification" + ), + libraryDependencies ++= + compileDeps( + slf4jApi, + slf4jSimple, + http4s, + http4sDsl, + http4sServer, + http4sCirce, + circe, + circeGeneric + ) ++ testDeps( + scalaTest, + scalaCheck + ) + ) + .settings( + maintainer in Docker := "The Apollo Squad", + packageSummary in Docker := "A web-shop checkout service example running on baas", + packageName in Docker := "baas-client-example", + dockerExposedPorts := Seq(8080) + ) + .dependsOn(bakertypes, `baas-node-client`, recipeCompiler, recipeDsl) + +lazy val `baas-interactions-example` = project + .in(file("examples/baas-interactions-example")) + .enablePlugins(JavaAppPackaging) + .settings(commonSettings) + .settings(noPublishSettings) + .settings( + moduleName := "baas-interactions-example", + scalacOptions ++= Seq( + "-Ypartial-unification" + ), + libraryDependencies ++= + compileDeps( + slf4jApi, + slf4jSimple, + catsEffect + ) ++ testDeps( + scalaTest, + scalaCheck + ) + ) + .settings( + maintainer in Docker := "The Apollo Squad", + packageSummary in Docker := "A web-shop checkout service interaction instances example running on baas", + packageName in Docker := "baas-interactions-example", + dockerExposedPorts := Seq(2551) + ) + .dependsOn(`baas-node-interaction`) + +lazy val `baas-event-listener-example` = project + .in(file("examples/baas-event-listener-example")) + .enablePlugins(JavaAppPackaging) + .settings(commonSettings) + .settings(noPublishSettings) + .settings( + moduleName := "baas-event-listener-example", + scalacOptions ++= Seq( + "-Ypartial-unification" + ), + libraryDependencies ++= + compileDeps( + slf4jApi, + slf4jSimple, + catsEffect + ) ++ testDeps( + scalaTest, + scalaCheck + ) + ) + .settings( + maintainer in Docker := "The Apollo Squad", + packageSummary in Docker := "A web-shop checkout service interaction instances example running on baas", + packageName in Docker := "baas-event-listener-example", + dockerExposedPorts := Seq(2551) + ) + .dependsOn(`baas-node-event-listener`) + +lazy val `baas-minikube-state` = project.in(file("examples/baas-minikube-setup/baas-minikube-state")) + .enablePlugins(JavaAppPackaging) + .settings(commonSettings) + .settings( + moduleName := "baas-minikube-state", + scalacOptions ++= Seq( + "-Ypartial-unification" + ), + javaOptions in Universal ++= Seq("-Dconfig.resource=kubernetes.conf"), + mainClass in Compile := Some("com.ing.baker.baas.state.Main") + ) + .settings( + maintainer in Docker := "The Apollo Squad", + packageSummary in Docker := "The core node", + packageName in Docker := "baas-minikube-state", + dockerExposedPorts := Seq(8080) + ) + .dependsOn(`baas-node-state`) + +lazy val `baas-minikube-event-listener` = project.in(file("examples/baas-minikube-setup/baas-minikube-event-listener")) + .enablePlugins(JavaAppPackaging) + .settings(commonSettings) + .settings( + moduleName := "baas-minikube-event-listener", + scalacOptions ++= Seq( + "-Ypartial-unification" + ), + libraryDependencies ++= + compileDeps( + slf4jApi, + slf4jSimple, + catsEffect, + akkaManagementHttp, + akkaClusterBoostrap, + akkaDiscoveryKube + ) ++ testDeps( + scalaTest, + scalaCheck + ) + ) + .settings( + maintainer in Docker := "The Apollo Squad", + packageSummary in Docker := "The event listener node", + packageName in Docker := "baas-minikube-event-listener", + dockerExposedPorts := Seq() + ) + .dependsOn(`baas-node-event-listener`) + +lazy val `baas-minikube-interactions` = project.in(file("examples/baas-minikube-setup/baas-minikube-interactions")) + .enablePlugins(JavaAppPackaging) + .settings(commonSettings) + .settings( + moduleName := "baas-minikube-interactions", + scalacOptions ++= Seq( + "-Ypartial-unification" + ), + libraryDependencies ++= + compileDeps( + slf4jApi, + slf4jSimple, + catsEffect, + akkaManagementHttp, + akkaClusterBoostrap, + akkaDiscoveryKube + ) ++ testDeps( + scalaTest, + scalaCheck + ) + ) + .settings( + maintainer in Docker := "The Apollo Squad", + packageSummary in Docker := "The interactions node", + packageName in Docker := "baas-minikube-interactions", + dockerExposedPorts := Seq() + ) + .dependsOn(`baas-node-interaction`) diff --git a/docs/sections/versions/baker-3-release-notes.md b/docs/sections/versions/baker-3-release-notes.md index 13d93a7e2..59d961519 100644 --- a/docs/sections/versions/baker-3-release-notes.md +++ b/docs/sections/versions/baker-3-release-notes.md @@ -139,7 +139,7 @@ case class SensoryEventResult( sensoryEventStatus: SensoryEventStatus, eventNames: Seq[String], ingredients: Map[String, Value] -) extends common.SensoryEventResult with ScalaApi +) extends com.ing.baker.runtime.common.SensoryEventResult with ScalaApi ``` ```java tab="Java" @@ -147,7 +147,7 @@ case class SensoryEventResult( sensoryEventStatus: SensoryEventStatus, eventNames: java.util.List[String], ingredients: java.util.Map[String, Value] -) extends common.SensoryEventResult with JavaApi { +) extends com.ing.baker.runtime.common.SensoryEventResult with JavaApi { def getSensoryEventStatus: SensoryEventStatus = sensoryEventStatus diff --git a/docs/specs/GuildsAndAdventurers.tla b/docs/specs/GuildsAndAdventurers.tla new file mode 100644 index 000000000..871a61802 --- /dev/null +++ b/docs/specs/GuildsAndAdventurers.tla @@ -0,0 +1,116 @@ +---------------------- MODULE InteractionManagerAgent ---------------------- + +CONSTANTS GUILDS, ADVENTURERS + +VARIABLES guildsState, adventurersState + +---------------------------------------------------------------------------- + +TypeOk == TRUE + /\ guildsState \in [GUILDS -> UNION {{ <<"requesting", "">>, <<"completed", "">>, <<"committed", adv>>} : adv \in ADVENTURERS}] + /\ adventurersState \in [ADVENTURERS -> UNION {{<<"looking-for-quest", "">>, <<"considering", guild>>, <<"committed", guild>>} : guild \in GUILDS}] + +Init == + /\ guildsState = [guild \in GUILDS |-> <<"requesting", "">>] + /\ adventurersState = [adventurers \in ADVENTURERS |-> <<"looking-for-quest", "">>] + +AdventurerIsConsideringGuild(guild, adv) == + adventurersState[adv] = <<"considering", guild>> + +AdventurerIsCommittedWithGuild(guild, adv) == + adventurersState[adv] = <<"committed", guild>> + +GuildIsCommittedWithAdventurer(guild, adv) == + guildsState[guild] = <<"committed", adv>> + +AreCommitted(guild, adv) == + /\ GuildIsCommittedWithAdventurer(guild, adv) + /\ AdventurerIsCommittedWithGuild(guild, adv) + +MatchExistsBetween(guild, adv) == + /\ guildsState[guild] = <<"requesting", "">> + /\ adventurersState[adv] = <<"looking-for-quest", "">> + +AdventurerIsReadyOk(guild, adv) == + /\ guildsState[guild] = <<"requesting", "">> + /\ adventurersState[adv] = <<"considering", guild>> + +GuildIsReadyOk(guild, adv) == + /\ guildsState[guild] = <<"committed", adv>> + /\ adventurersState[adv] = <<"considering", guild>> + +QuestInProgress(guild, adv) == + AreCommitted(guild, adv) + +QuestWasAlreadyTakenByAdv1(guild, adv1, adv2) == + /\ adv1 # adv2 + /\ ( guildsState[guild] = <<"committed", adv1>> \/ guildsState[guild] = <<"completed", "">> ) + /\ adventurersState[adv2] = <<"considering", guild>> + +---------------------------------------------------------------------------- + +AdventurerConsidersAQuest == + /\ \E <> \in GUILDS \X ADVENTURERS : MatchExistsBetween(guild, adv) + /\ LET guildAdv == CHOOSE <> \in GUILDS \X ADVENTURERS : MatchExistsBetween(guild, adv) + guild == guildAdv[1] + adv == guildAdv[2] + IN /\ adventurersState' = [adventurersState EXCEPT ![adv] = <<"considering", guild>>] + /\ UNCHANGED guildsState + +GuildCommitsToAnAdventurer == + /\ \E <> \in GUILDS \X ADVENTURERS : AdventurerIsReadyOk(guild, adv) + /\ LET guildAdv == CHOOSE <> \in GUILDS \X ADVENTURERS : AdventurerIsReadyOk(guild, adv) + guild == guildAdv[1] + adv == guildAdv[2] + IN /\ guildsState' = [guildsState EXCEPT ![guild] = <<"committed", adv>>] + /\ UNCHANGED adventurersState + +AdventurerCommitsToAGuild == + /\ \E <> \in GUILDS \X ADVENTURERS : GuildIsReadyOk(guild, adv) + /\ LET guildAdv == CHOOSE <> \in GUILDS \X ADVENTURERS : GuildIsReadyOk(guild, adv) + guild == guildAdv[1] + adv == guildAdv[2] + IN /\ adventurersState' = [adventurersState EXCEPT ![adv] = <<"committed", guild>>] + /\ UNCHANGED guildsState + +AdventurerDropsTakenQuest == + /\ \E <> \in GUILDS \X ADVENTURERS \X ADVENTURERS: QuestWasAlreadyTakenByAdv1(guild, adv1, adv2) + /\ LET guildAdv == CHOOSE <> \in GUILDS \X ADVENTURERS \X ADVENTURERS : QuestWasAlreadyTakenByAdv1(guild, adv1, adv2) + guild == guildAdv[1] + adv == guildAdv[3] + IN /\ adventurersState' = [adventurersState EXCEPT ![adv] = <<"looking-for-quest", "">>] + /\ UNCHANGED guildsState + +AdventurerFinishesTheQuest == + /\ \E <> \in GUILDS \X ADVENTURERS : QuestInProgress(guild, adv) + /\ LET guildAdv == CHOOSE <> \in GUILDS \X ADVENTURERS : QuestInProgress(guild, adv) + guild == guildAdv[1] + adv == guildAdv[2] + IN /\ adventurersState' = [adventurersState EXCEPT ![adv] = <<"looking-for-quest", "">>] + /\ guildsState' = [guildsState EXCEPT ![guild] = <<"completed", "">>] + +Next == + \/ AdventurerConsidersAQuest + \/ GuildCommitsToAnAdventurer + \/ AdventurerCommitsToAGuild + \/ AdventurerFinishesTheQuest + \/ AdventurerDropsTakenQuest + +Spec == + Init /\ [][Next]_<> + +Consistent == + /\ \A <> \in GUILDS \X ADVENTURERS : + /\ GuildIsCommittedWithAdventurer(guild, adv) => ( AdventurerIsCommittedWithGuild(guild, adv) \/ AdventurerIsConsideringGuild(guild, adv) ) + /\ AdventurerIsCommittedWithGuild(guild, adv) => GuildIsCommittedWithAdventurer(guild, adv) + /\ \A <> \in ADVENTURERS \X ADVENTURERS \X GUILDS : + ~ AreCommitted(guild, adv1) \/ ~ AreCommitted(guild, adv2) \/ adv1 = adv2 + +---------------------------------------------------------------------------- + +THEOREM Spec => [](TypeOk /\ Consistent) + +============================================================================= +\* Modification History +\* Last modified Mon Sep 09 16:41:00 CEST 2019 by atfm0 +\* Created Thu Sep 05 16:24:49 CEST 2019 by atfm0 diff --git a/examples/baas-client-example/src/main/resources/application.conf b/examples/baas-client-example/src/main/resources/application.conf new file mode 100644 index 000000000..64b47302c --- /dev/null +++ b/examples/baas-client-example/src/main/resources/application.conf @@ -0,0 +1,5 @@ +baas.hostname = "http://baker-haproxy:8080" +baas.hostname = ${?BAAS_HOSTNAME} + +service.httpServerPort = 8080 +service.httpServerPort = ${?HTTP_PORT} \ No newline at end of file diff --git a/examples/src/main/resources/logback.xml b/examples/baas-client-example/src/main/resources/logback.xml similarity index 100% rename from examples/src/main/resources/logback.xml rename to examples/baas-client-example/src/main/resources/logback.xml diff --git a/examples/baas-client-example/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala b/examples/baas-client-example/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala new file mode 100644 index 000000000..4f75e8d80 --- /dev/null +++ b/examples/baas-client-example/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala @@ -0,0 +1,122 @@ +package webshop.webservice + +import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff +import com.ing.baker.recipe.scaladsl.{Event, Ingredient, Interaction, Recipe} +import CheckoutFlowIngredients._ +import CheckoutFlowEvents._ +import CheckoutFlowInteractions._ +import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff.UntilMaximumRetries + +import scala.concurrent.Future +import scala.concurrent.duration._ + +object CheckoutFlowIngredients { + + case class OrderId(orderId: String) + + case class Item(itemId: String) + + case class ReservedItems(items: List[Item], data: Array[Byte]) + + case class ShippingAddress(address: String) + + case class PaymentInformation(info: String) + + case class ShippingOrder(items: List[Item], data: Array[Byte], address: ShippingAddress) +} + +object CheckoutFlowEvents { + + case class OrderPlaced(orderId: OrderId, items: List[Item]) + + case class PaymentInformationReceived(paymentInformation: PaymentInformation) + + case class ShippingAddressReceived(shippingAddress: ShippingAddress) + + sealed trait ReserveItemsOutput + + case class OrderHadUnavailableItems(unavailableItems: List[Item]) extends ReserveItemsOutput + + case class ItemsReserved(reservedItems: ReservedItems) extends ReserveItemsOutput + + sealed trait MakePaymentOutput + + case class PaymentSuccessful(shippingOrder: ShippingOrder) extends MakePaymentOutput + + case class PaymentFailed() extends MakePaymentOutput + + case class ShippingConfirmed() +} + +object CheckoutFlowInteractions { + + trait ReserveItems { + + def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] + } + + def ReserveItemsInteraction = Interaction( + name = "ReserveItems", + inputIngredients = Seq( + Ingredient[OrderId]("orderId"), + Ingredient[List[Item]]("items") + ), + output = Seq( + Event[OrderHadUnavailableItems], + Event[ItemsReserved] + ) + ) + + trait MakePayment { + + def apply(processId: String, items: ReservedItems, address: ShippingAddress, payment: PaymentInformation): Future[MakePaymentOutput] + } + + def MakePaymentInteraction = Interaction( + name = "MakePayment", + inputIngredients = Seq( + com.ing.baker.recipe.scaladsl.recipeInstanceId, + Ingredient[ReservedItems]("reservedItems"), + Ingredient[ShippingAddress]("shippingAddress"), + Ingredient[PaymentInformation]("paymentInformation") + ), + output = Seq( + Event[PaymentSuccessful], + Event[PaymentFailed] + ) + ) + + trait ShipItems { + + def apply(order: ShippingOrder): Future[ShippingConfirmed] + } + + def ShipItemsInteraction = Interaction( + name = "ShipItems", + inputIngredients = Seq( + Ingredient[ShippingOrder]("shippingOrder") + ), + output = Seq( + Event[ShippingConfirmed] + ) + ) +} + +object CheckoutFlowRecipe { + + def recipe: Recipe = Recipe("Webshop") + .withSensoryEvents( + Event[OrderPlaced], + Event[PaymentInformationReceived], + Event[ShippingAddressReceived]) + .withInteractions( + ReserveItemsInteraction, + MakePaymentInteraction, + ShipItemsInteraction) + .withDefaultFailureStrategy( + RetryWithIncrementalBackoff + .builder() + .withInitialDelay(1.second) + .withUntil(Some(UntilMaximumRetries(5))) + .build()) +} diff --git a/examples/baas-client-example/src/main/scala/webshop/webservice/Main.scala b/examples/baas-client-example/src/main/scala/webshop/webservice/Main.scala new file mode 100644 index 000000000..ab66742c8 --- /dev/null +++ b/examples/baas-client-example/src/main/scala/webshop/webservice/Main.scala @@ -0,0 +1,57 @@ +package webshop.webservice + +import akka.actor.ActorSystem +import akka.http.scaladsl.model.Uri +import akka.stream.ActorMaterializer +import cats.effect.concurrent.Ref +import cats.effect.{ExitCode, IO, IOApp, Resource} +import cats.implicits._ +import com.ing.baker.baas.scaladsl.BakerClient +import com.ing.baker.runtime.scaladsl._ +import com.typesafe.config.ConfigFactory +import org.http4s.server.blaze.BlazeServerBuilder +import org.log4s.Logger + +object Main extends IOApp { + + case class SystemResources(actorSystem: ActorSystem, baker: Baker, app: WebShopService, port: Int, shuttingDown: Ref[IO, Boolean]) + + val logger: Logger = org.log4s.getLogger + + val system: Resource[IO, SystemResources] = + Resource.make( + for { + actorSystem <- IO { ActorSystem("CheckoutService") } + config <- IO { ConfigFactory.load() } + materializer = ActorMaterializer()(actorSystem) + baasHostname = config.getString("baas.hostname") + baker <- IO { BakerClient(Uri.parseAbsolute(baasHostname))(actorSystem, materializer) } + checkoutRecipeId <- WebShopBaker.initRecipes(baker)(timer, actorSystem.dispatcher) + sd <- Ref.of[IO, Boolean](false) + webShopBaker = new WebShopBaker(baker, checkoutRecipeId)(actorSystem.dispatcher) + httpPort = config.getInt("service.httpServerPort") + app = new WebShopService(webShopBaker) + resources = SystemResources(actorSystem, baker, app, httpPort, sd) + } yield resources + )(resources => + IO(logger.info("Shutting down the Checkout Service...")) *> + terminateActorSystem(resources) + ) + + def terminateActorSystem(resources: SystemResources): IO[Unit] = + IO.fromFuture(IO { resources.actorSystem.terminate() }).void + + override def run(args: List[String]): IO[ExitCode] = { + system.flatMap { r => + + println(Console.GREEN + "Example client app started..." + Console.RESET) + sys.addShutdownHook(r.baker.gracefulShutdown()) + + BlazeServerBuilder[IO] + .bindHttp(r.port, "0.0.0.0") + .withHttpApp(r.app.buildHttpService(r.shuttingDown)) + .resource + }.use(_ => IO.never).as(ExitCode.Success) + } + +} diff --git a/examples/src/main/scala/webshop/webservice/OrderStatus.scala b/examples/baas-client-example/src/main/scala/webshop/webservice/OrderStatus.scala similarity index 100% rename from examples/src/main/scala/webshop/webservice/OrderStatus.scala rename to examples/baas-client-example/src/main/scala/webshop/webservice/OrderStatus.scala diff --git a/examples/src/main/scala/webshop/webservice/WebShop.scala b/examples/baas-client-example/src/main/scala/webshop/webservice/WebShop.scala similarity index 100% rename from examples/src/main/scala/webshop/webservice/WebShop.scala rename to examples/baas-client-example/src/main/scala/webshop/webservice/WebShop.scala diff --git a/examples/baas-client-example/src/main/scala/webshop/webservice/WebShopBaker.scala b/examples/baas-client-example/src/main/scala/webshop/webservice/WebShopBaker.scala new file mode 100644 index 000000000..48acb8f2e --- /dev/null +++ b/examples/baas-client-example/src/main/scala/webshop/webservice/WebShopBaker.scala @@ -0,0 +1,90 @@ +package webshop.webservice + +import java.util.UUID + +import cats.effect.{IO, Timer} +import com.ing.baker.compiler.RecipeCompiler +import com.ing.baker.il.CompiledRecipe +import com.ing.baker.runtime.scaladsl.{Baker, EventInstance} +import com.typesafe.scalalogging.LazyLogging +import webshop.webservice.CheckoutFlowIngredients.{Item, OrderId, PaymentInformation, ShippingAddress} + +import scala.concurrent.{ExecutionContext, Future} + +object WebShopBaker extends LazyLogging { + + val checkoutFlowCompiledRecipe: CompiledRecipe = + RecipeCompiler.compileRecipe(CheckoutFlowRecipe.recipe) + + def initRecipes(baker: Baker)(implicit time: Timer[IO], ec: ExecutionContext): IO[String] = { + IO.fromFuture(IO(for { + checkoutRecipeId <- baker.addRecipe(checkoutFlowCompiledRecipe) + _ = println(Console.GREEN + "V3 Checkout Recipe ID :: " + checkoutRecipeId + Console.RESET) + } yield checkoutRecipeId)) + } +} + +class WebShopBaker(baker: Baker, checkoutRecipeId: String)(implicit ec: ExecutionContext) extends WebShop { + + import WebShopBaker.logger + + override def createCheckoutOrder(items: List[String]): IO[String] = + IO.fromFuture(IO { + val orderId: String = UUID.randomUUID().toString + val event = EventInstance.unsafeFrom( + CheckoutFlowEvents.OrderPlaced(OrderId(orderId), items.map(Item))) + for { + _ <- baker.bake(checkoutRecipeId, orderId) + status <- baker.fireEventAndResolveWhenReceived(orderId, event) + _ = logger.info(s"${event.name}[$orderId]: $status") + } yield orderId + }) + + override def addCheckoutAddressInfo(orderId: String, address: String): IO[Option[String]] = + IO.fromFuture(IO { + fireAndInformEvent(orderId, EventInstance.unsafeFrom( + CheckoutFlowEvents.ShippingAddressReceived(ShippingAddress(address)))) + }) + + override def addCheckoutPaymentInfo(orderId: String, paymentInfo: String): IO[Option[String]] = + IO.fromFuture(IO { + fireAndInformEvent(orderId, EventInstance.unsafeFrom( + CheckoutFlowEvents.PaymentInformationReceived(PaymentInformation(paymentInfo)))) + }) + + private def fireAndInformEvent(orderId: String, event: EventInstance): Future[Option[String]] = { + for { + status <- baker.fireEventAndResolveWhenReceived(orderId, event) + _ = logger.info(s"${event.name}[$orderId]: $status") + } yield None + } + + override def pollOrderStatus(orderId: String): IO[OrderStatus] = + IO.fromFuture(IO { + for { + state <- baker.getRecipeInstanceState(orderId) + eventNames = state.events.map(_.name) + status = { + if (eventNames.contains("ShippingConfirmed")) + OrderStatus.Complete + else if (eventNames.contains("PaymentFailed")) + OrderStatus.PaymentFailed + else if (eventNames.contains("OrderHadUnavailableItems")) + OrderStatus.UnavailableItems(state.ingredients("unavailableItems").as[List[Item]].map(_.itemId)) + else if (eventNames.containsSlice(List("ShippingAddressReceived", "PaymentInformationReceived"))) + OrderStatus.ProcessingPayment + else if (eventNames.contains("PaymentSuccessful")) + OrderStatus.ShippingItems + else + OrderStatus.InfoPending(List("ShippingAddressReceived", "PaymentInformationReceived") + .filterNot(eventNames.contains) + .map(_.replace("Received", ""))) + } + } yield status + }) + + override def gracefulShutdown: IO[Unit] = + IO { + baker.gracefulShutdown() + } +} diff --git a/examples/baas-client-example/src/main/scala/webshop/webservice/WebShopService.scala b/examples/baas-client-example/src/main/scala/webshop/webservice/WebShopService.scala new file mode 100644 index 000000000..6e03544f5 --- /dev/null +++ b/examples/baas-client-example/src/main/scala/webshop/webservice/WebShopService.scala @@ -0,0 +1,113 @@ +package webshop.webservice + +import java.io.File +import java.util.concurrent.Executors + +import cats.data.Kleisli +import cats.effect.concurrent.Ref +import cats.effect.{ContextShift, IO, Timer} +import cats.implicits._ +import io.circe.generic.auto._ +import io.circe.syntax._ +import org.http4s._ +import org.http4s.circe._ +import org.http4s.dsl.io._ +import org.http4s.implicits._ +import org.http4s.server.Router + +import scala.concurrent.ExecutionContext + +object WebShopService { + + case class PlaceOrderRequest(items: List[String]) + + case class PlaceOrderResponse(orderId: String) + + case class AddAddressRequest(address: String) + + case class AddPaymentRequest(payment: String) + + case class PollPaymentStatusResponse(status: String) + +} + +class WebShopService(webshop: WebShop)(implicit timer: Timer[IO], cs: ContextShift[IO]) { + + import WebShopService._ + + implicit val placeOrderRequestDecoder: EntityDecoder[IO, PlaceOrderRequest] = + jsonOf[IO, PlaceOrderRequest] + implicit val addAddressRequestDecoder: EntityDecoder[IO, AddAddressRequest] = + jsonOf[IO, AddAddressRequest] + implicit val addPaymentRequestDecoder: EntityDecoder[IO, AddPaymentRequest] = + jsonOf[IO, AddPaymentRequest] + + val blockingEc = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(4)) + + def buildHttpService(shuttingDown: Ref[IO, Boolean]): Kleisli[IO, Request[IO], Response[IO]] = + (Router("/" -> HttpRoutes.of[IO] { + + case GET -> Root => Ok("Ok") + + case HEAD -> Root => + shuttingDown.get.flatMap { + case true => NotFound() + case false => Ok() + } + + }) <+> + Router("/admin" -> HttpRoutes.of[IO] { + + case GET -> Root / "shutdown" => + for { + _ <- shuttingDown.modify(_ => (true, ())) + _ <- webshop.gracefulShutdown + response <- Ok("down") + } yield response + }) <+> + Router("/api" -> HttpRoutes.of[IO] { + + case GET -> Root => + Ok("Ok") + + case req@POST -> Root / "order" => + for { + request <- req.as[PlaceOrderRequest] + orderId <- webshop.createCheckoutOrder(request.items) + response <- Ok(PlaceOrderResponse(orderId).asJson) + } yield response + + case req@PUT -> Root / "order" / orderId / "address" => + for { + request <- req.as[AddAddressRequest] + _ <- webshop.addCheckoutAddressInfo(orderId, request.address) + response <- Ok() + } yield response + + case req@PUT -> Root / "order" / orderId / "payment" => + for { + request <- req.as[AddPaymentRequest] + _ <- webshop.addCheckoutPaymentInfo(orderId, request.payment) + response <- Ok() + } yield response + + case GET -> Root / "order" / orderId => + for { + status <- webshop.pollOrderStatus(orderId) + response <- Ok(PollPaymentStatusResponse(status.toString).asJson) + } yield response + + })).orNotFound + + import java.lang.management.ManagementFactory + + import com.sun.management.HotSpotDiagnosticMXBean + + def dumpHeap(filePath: String, live: Boolean): IO[Unit] = IO { + val file = new File(filePath) + if (file.exists()) file.delete() + val server = ManagementFactory.getPlatformMBeanServer + val mxBean = ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", classOf[HotSpotDiagnosticMXBean]) + mxBean.dumpHeap(filePath, live) + } +} diff --git a/examples/baas-event-listener-example/src/main/resources/application.conf b/examples/baas-event-listener-example/src/main/resources/application.conf new file mode 100644 index 000000000..790ddda09 --- /dev/null +++ b/examples/baas-event-listener-example/src/main/resources/application.conf @@ -0,0 +1,47 @@ + +service { + + actorSystemName = "BaaS" + actorSystemName = ${?ACTOR_SYSTEM_NAME} + + clusterHost = "127.0.0.1" + clusterHost = ${?CLUSTER_HOST} + + clusterPort = 2551 + clusterPort = ${?CLUSTER_PORT} + + seedHost = "127.0.0.1" + seedHost = ${?CLUSTER_SEED_HOST} + + seedPort = 2551 + seedPort = ${?CLUSTER_SEED_PORT} +} + +akka.actor.allow-java-serialization = on +akka.cluster.configuration-compatibility-check.enforce-on-join = off + +akka { + + actor { + provider = "cluster" + } + + remote { + log-remote-lifecycle-events = off + netty.tcp { + hostname = ${service.clusterHost} + port = ${service.clusterPort} + } + } + + cluster { + + seed-nodes = [ + "akka.tcp://"${service.actorSystemName}"@"${service.seedHost}":"${service.seedPort}] + + # auto downing is NOT safe for production deployments. + # you may want to use it during development, read more about it in the docs. + # + # auto-down-unreachable-after = 10s + } +} diff --git a/examples/baas-event-listener-example/src/main/scala/webshop/webservice/Main.scala b/examples/baas-event-listener-example/src/main/scala/webshop/webservice/Main.scala new file mode 100644 index 000000000..ae3815f3c --- /dev/null +++ b/examples/baas-event-listener-example/src/main/scala/webshop/webservice/Main.scala @@ -0,0 +1,15 @@ +package webshop.webservice + +import akka.actor.ActorSystem +import com.ing.baker.baas.scaladsl.BaaSEventListener +import com.typesafe.scalalogging.LazyLogging + +object Main extends App with LazyLogging { + + val actorSystem = ActorSystem("BaaS") // TODO: This should be done by the BaaSInteractionInstance ecosystem to ease the configuration and improve the UX + val ecosystem = BaaSEventListener(actorSystem) + + ecosystem.registerEventListener("Webshop", (metadata, event) => { + logger.info("%s [%s] %s", metadata.recipeName, metadata.recipeInstanceId, event.name) + }) +} diff --git a/examples/baas-interactions-example/src/main/resources/application.conf b/examples/baas-interactions-example/src/main/resources/application.conf new file mode 100644 index 000000000..790ddda09 --- /dev/null +++ b/examples/baas-interactions-example/src/main/resources/application.conf @@ -0,0 +1,47 @@ + +service { + + actorSystemName = "BaaS" + actorSystemName = ${?ACTOR_SYSTEM_NAME} + + clusterHost = "127.0.0.1" + clusterHost = ${?CLUSTER_HOST} + + clusterPort = 2551 + clusterPort = ${?CLUSTER_PORT} + + seedHost = "127.0.0.1" + seedHost = ${?CLUSTER_SEED_HOST} + + seedPort = 2551 + seedPort = ${?CLUSTER_SEED_PORT} +} + +akka.actor.allow-java-serialization = on +akka.cluster.configuration-compatibility-check.enforce-on-join = off + +akka { + + actor { + provider = "cluster" + } + + remote { + log-remote-lifecycle-events = off + netty.tcp { + hostname = ${service.clusterHost} + port = ${service.clusterPort} + } + } + + cluster { + + seed-nodes = [ + "akka.tcp://"${service.actorSystemName}"@"${service.seedHost}":"${service.seedPort}] + + # auto downing is NOT safe for production deployments. + # you may want to use it during development, read more about it in the docs. + # + # auto-down-unreachable-after = 10s + } +} diff --git a/examples/baas-interactions-example/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala b/examples/baas-interactions-example/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala new file mode 100644 index 000000000..c2df108fa --- /dev/null +++ b/examples/baas-interactions-example/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala @@ -0,0 +1,62 @@ +package webshop.webservice + +import CheckoutFlowIngredients._ +import CheckoutFlowEvents._ + +import scala.concurrent.Future + +object CheckoutFlowIngredients { + + case class OrderId(orderId: String) + + case class Item(itemId: String) + + case class ReservedItems(items: List[Item], data: Array[Byte]) + + case class ShippingAddress(address: String) + + case class PaymentInformation(info: String) + + case class ShippingOrder(items: List[Item], data: Array[Byte], address: ShippingAddress) +} + +object CheckoutFlowEvents { + + case class OrderPlaced(orderId: OrderId, items: List[Item]) + + case class PaymentInformationReceived(paymentInformation: PaymentInformation) + + case class ShippingAddressReceived(shippingAddress: ShippingAddress) + + sealed trait ReserveItemsOutput + + case class OrderHadUnavailableItems(unavailableItems: List[Item]) extends ReserveItemsOutput + + case class ItemsReserved(reservedItems: ReservedItems) extends ReserveItemsOutput + + sealed trait MakePaymentOutput + + case class PaymentSuccessful(shippingOrder: ShippingOrder) extends MakePaymentOutput + + case class PaymentFailed() extends MakePaymentOutput + + case class ShippingConfirmed() +} + +object CheckoutFlowInteractions { + + trait ReserveItems { + + def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] + } + + trait MakePayment { + + def apply(processId: String, items: ReservedItems, address: ShippingAddress, payment: PaymentInformation): Future[MakePaymentOutput] + } + + trait ShipItems { + + def apply(order: ShippingOrder): Future[ShippingConfirmed] + } +} diff --git a/examples/baas-interactions-example/src/main/scala/webshop/webservice/Main.scala b/examples/baas-interactions-example/src/main/scala/webshop/webservice/Main.scala new file mode 100644 index 000000000..a08b8d447 --- /dev/null +++ b/examples/baas-interactions-example/src/main/scala/webshop/webservice/Main.scala @@ -0,0 +1,23 @@ +package webshop.webservice + +import akka.actor.ActorSystem +import cats.effect.IO +import com.ing.baker.baas.scaladsl.BaaSInteractionInstance +import com.ing.baker.runtime.scaladsl.InteractionInstance + +object Main extends App { + + val actorSystem = ActorSystem("BaaS") // This should be done by the BaaSInteractionInstance ecosystem to ease the configuration and improve the UX + val ecosystem = BaaSInteractionInstance(actorSystem) + val timer = IO.timer(actorSystem.dispatcher) + + import actorSystem.dispatcher + + val instances = InteractionInstance.unsafeFromList(List( + new MakePaymentInstance()(timer), + new ReserveItemsInstance()(timer), + new ShipItemsInstance()(timer) + )) + + ecosystem.load(instances: _*) +} diff --git a/examples/baas-interactions-example/src/main/scala/webshop/webservice/MakePaymentInstance.scala b/examples/baas-interactions-example/src/main/scala/webshop/webservice/MakePaymentInstance.scala new file mode 100644 index 000000000..a78c49088 --- /dev/null +++ b/examples/baas-interactions-example/src/main/scala/webshop/webservice/MakePaymentInstance.scala @@ -0,0 +1,20 @@ +package webshop.webservice + +import cats.effect.{IO, Timer} +import webshop.webservice.CheckoutFlowEvents.MakePaymentOutput +import webshop.webservice.CheckoutFlowIngredients.{PaymentInformation, ReservedItems, ShippingAddress, ShippingOrder} +import webshop.webservice.CheckoutFlowInteractions.MakePayment + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class MakePaymentInstance(implicit timer: Timer[IO]) extends MakePayment { + + override def apply(processId: String, items: ReservedItems, address: ShippingAddress, payment: PaymentInformation): Future[MakePaymentOutput] = { + IO.sleep(5.second) + .map(_ => println(Console.GREEN + processId + Console.RESET)) + .map(_ => CheckoutFlowEvents.PaymentSuccessful(ShippingOrder(items.items, items.data, address))) + .unsafeToFuture() + } +} + diff --git a/examples/src/main/scala/webshop/webservice/ReserveItemsInstance.scala b/examples/baas-interactions-example/src/main/scala/webshop/webservice/ReserveItemsInstance.scala similarity index 96% rename from examples/src/main/scala/webshop/webservice/ReserveItemsInstance.scala rename to examples/baas-interactions-example/src/main/scala/webshop/webservice/ReserveItemsInstance.scala index 140f6e003..b6d1634ac 100644 --- a/examples/src/main/scala/webshop/webservice/ReserveItemsInstance.scala +++ b/examples/baas-interactions-example/src/main/scala/webshop/webservice/ReserveItemsInstance.scala @@ -1,7 +1,7 @@ package webshop.webservice -import cats.implicits._ import cats.effect.{IO, Timer} +import cats.implicits._ import webshop.webservice.CheckoutFlowEvents.ReserveItemsOutput import webshop.webservice.CheckoutFlowIngredients.{Item, OrderId, ReservedItems} import webshop.webservice.CheckoutFlowInteractions.ReserveItems @@ -12,7 +12,7 @@ import scala.concurrent.duration._ class ReserveItemsInstance(implicit timer: Timer[IO]) extends ReserveItems { override def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] = { - IO.sleep(1 second) + IO.sleep(1.second) .as(CheckoutFlowEvents.ItemsReserved(ReservedItems(items, Array.fill(1000)(Byte.MaxValue)))) .unsafeToFuture() } diff --git a/examples/src/main/scala/webshop/webservice/ShipItemsInstance.scala b/examples/baas-interactions-example/src/main/scala/webshop/webservice/ShipItemsInstance.scala similarity index 95% rename from examples/src/main/scala/webshop/webservice/ShipItemsInstance.scala rename to examples/baas-interactions-example/src/main/scala/webshop/webservice/ShipItemsInstance.scala index 3dea936ad..c9424a151 100644 --- a/examples/src/main/scala/webshop/webservice/ShipItemsInstance.scala +++ b/examples/baas-interactions-example/src/main/scala/webshop/webservice/ShipItemsInstance.scala @@ -1,7 +1,7 @@ package webshop.webservice -import cats.implicits._ import cats.effect.{IO, Timer} +import cats.implicits._ import webshop.webservice.CheckoutFlowEvents.ShippingConfirmed import webshop.webservice.CheckoutFlowIngredients.ShippingOrder import webshop.webservice.CheckoutFlowInteractions.ShipItems @@ -12,7 +12,7 @@ import scala.concurrent.duration._ class ShipItemsInstance(implicit timer: Timer[IO]) extends ShipItems { override def apply(order: ShippingOrder): Future[ShippingConfirmed] = { - IO.sleep(500 millis) + IO.sleep(500.millis) .as(ShippingConfirmed()) .unsafeToFuture() } diff --git a/examples/baas-minikube-setup/README.md b/examples/baas-minikube-setup/README.md new file mode 100644 index 000000000..0e4e20b90 --- /dev/null +++ b/examples/baas-minikube-setup/README.md @@ -0,0 +1,22 @@ +# Running minikube locally + +## Install docker +https://docs.docker.com/install/ + +##Install VirtualBox +https://www.virtualbox.org/wiki/Downloads + +##Install Minikube +https://kubernetes.io/docs/tasks/tools/install-minikube/ + +##Create and start minikube cluster via VirtualBox +Execute `/baas-openshift-scripts/minikube/createMinikubeNamespace.sh` + +##[Optional] Run minikube dashboard +Execute `minikube dashboard` + +## Build example Docker images +`/examples/baas-minikube-setup/build-and-publish-images.sh` + +##Deploy PODs to minikube +`kubectl apply -f /examples/baas-minikube-setup/baas-kubernetes-example.yml` \ No newline at end of file diff --git a/examples/baas-minikube-setup/baas-kubernetes-example.yml b/examples/baas-minikube-setup/baas-kubernetes-example.yml new file mode 100644 index 000000000..6f1388dc7 --- /dev/null +++ b/examples/baas-minikube-setup/baas-kubernetes-example.yml @@ -0,0 +1,233 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: baas-state + name: baas-state +spec: + replicas: 2 + selector: + matchLabels: + app: baas-state + template: + metadata: + labels: + app: baas-state + actorSystemName: BaaS + spec: + containers: + - name: baas-state + image: baas-minikube-state:3.0.2-SNAPSHOT + imagePullPolicy: Never + readinessProbe: + httpGet: + path: /health/ready + port: 8558 + livenessProbe: + httpGet: + path: /health/alive + port: 8558 + ports: + # akka remoting + - name: remoting + containerPort: 2552 + protocol: TCP + # akka-management and bootstrap + - name: management + containerPort: 8558 + protocol: TCP + - name: webaccess + containerPort: 8080 + protocol: TCP + #namespace + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: baas-interactions + name: baas-interactions +spec: + replicas: 1 + selector: + matchLabels: + app: baas-interactions + template: + metadata: + labels: + app: baas-interactions + actorSystemName: BaaS + spec: + containers: + - name: baas-interactions + image: baas-minikube-interactions:3.0.2-SNAPSHOT + imagePullPolicy: Never + readinessProbe: + httpGet: + path: /health/ready + port: 8558 + livenessProbe: + httpGet: + path: /health/alive + port: 8558 + ports: + # akka remoting + - name: remoting + containerPort: 2552 + protocol: TCP + # akka-management and bootstrap + - name: management + containerPort: 8558 + protocol: TCP + #namespace + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: baas-event-listener + name: baas-event-listener +spec: + replicas: 1 + selector: + matchLabels: + app: baas-event-listener + template: + metadata: + labels: + app: baas-event-listener + actorSystemName: BaaS + spec: + containers: + - name: baas-event-listener + image: baas-minikube-event-listener:3.0.2-SNAPSHOT + imagePullPolicy: Never + readinessProbe: + httpGet: + path: /health/ready + port: 8558 + livenessProbe: + httpGet: + path: /health/alive + port: 8558 + ports: + # akka remoting + - name: remoting + containerPort: 2552 + protocol: TCP + # akka-management and bootstrap + - name: management + containerPort: 8558 + protocol: TCP + #namespace + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: client-app + name: client-app +spec: + replicas: 1 + selector: + matchLabels: + app: client-app + template: + metadata: + labels: + app: client-app + spec: + containers: + - name: client-app + image: baas-client-example:3.0.2-SNAPSHOT + imagePullPolicy: Never + readinessProbe: + httpGet: + path: /api + port: 8080 + livenessProbe: + httpGet: + path: /api + port: 8080 + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + - name: BAAS_HOSTNAME + value: http://baas-state-service:8080/ +--- + +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: pod-reader +rules: + - apiGroups: [""] # "" indicates the core API group + resources: ["pods"] + verbs: ["get", "watch", "list"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: read-pods +subjects: + # Note the `name` line below. The first default refers to the namespace. The second refers to the service account name. + # For instance, `name: system:serviceaccount:myns:default` would refer to the default service account in namespace `myns` + - kind: User + name: system:serviceaccount:default:default +roleRef: + kind: Role + name: pod-reader + apiGroup: rbac.authorization.k8s.io +--- + +apiVersion: v1 +kind: Service +metadata: + name: baas-state-service + labels: + run: baas-state-service +spec: + selector: + app: baas-state + ports: + - port: 8080 + protocol: TCP + type: LoadBalancer +--- + +apiVersion: v1 +kind: Service +metadata: + name: client-app-service + labels: + run: client-app-service +spec: + selector: + app: client-app + ports: + - port: 8080 + protocol: TCP + type: LoadBalancer + +# Run this command to expose the service to minikube +# minikube service baas-state-service diff --git a/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/resources/application.conf b/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/resources/application.conf new file mode 100644 index 000000000..d8fa215b6 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/resources/application.conf @@ -0,0 +1,46 @@ + +akka { + + actor { + provider = "cluster" + } + + cluster { + + roles = ["event-listener-node"] + + configuration-compatibility-check.enforce-on-join = off + } + + discovery { + kubernetes-api { + pod-label-selector = "actorSystemName=%s" + } + } + + management { + + http.routes { + cluster-management = "" + } + + cluster.bootstrap { + contact-point-discovery { + # For the kubernetes API this value is substituted in the %s in pod-label-selector + service-name = "BaaS" + + # pick the discovery method you'd like to use: + discovery-method = kubernetes-api + } + } + + health-checks { + readiness-path = "health/ready" + liveness-path = "health/alive" + + liveness-checks { + cluster-health = "webshop.webservice.ClusterHealthCheck" + } + } + } +} diff --git a/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/scala/webshop/webservice/ClusterHealthCheck.scala b/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/scala/webshop/webservice/ClusterHealthCheck.scala new file mode 100644 index 000000000..a6ae41f89 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/scala/webshop/webservice/ClusterHealthCheck.scala @@ -0,0 +1,13 @@ +package webshop.webservice + +import akka.actor.ActorSystem +import akka.cluster.{Cluster, MemberStatus} + +import scala.concurrent.Future + +class ClusterHealthCheck(system: ActorSystem) extends (() => Future[Boolean]) { + private val cluster = Cluster(system) + override def apply(): Future[Boolean] = { + Future.successful(cluster.selfMember.status == MemberStatus.Up) + } +} diff --git a/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/scala/webshop/webservice/Main.scala b/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/scala/webshop/webservice/Main.scala new file mode 100644 index 000000000..b70b4a782 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-event-listener/src/main/scala/webshop/webservice/Main.scala @@ -0,0 +1,19 @@ +package webshop.webservice + +import akka.actor.ActorSystem +import akka.management.cluster.bootstrap.ClusterBootstrap +import akka.management.scaladsl.AkkaManagement +import com.ing.baker.baas.scaladsl.BaaSEventListener +import com.typesafe.scalalogging.LazyLogging + +object Main extends App with LazyLogging { + + val actorSystem = ActorSystem("BaaS") // This should be done by the BaaSInteractionInstance ecosystem to ease the configuration and improve the UX + AkkaManagement(actorSystem).start() + ClusterBootstrap(actorSystem).start() + val ecosystem = BaaSEventListener(actorSystem) + + ecosystem.registerEventListener("Webshop", (metadata, event) => { + logger.info(metadata.recipeName + " [" + metadata.recipeInstanceId + "] " + event.name) + }) +} diff --git a/examples/baas-minikube-setup/baas-minikube-interactions/src/main/resources/application.conf b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/resources/application.conf new file mode 100644 index 000000000..ffe992895 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/resources/application.conf @@ -0,0 +1,46 @@ + +akka { + + actor { + provider = "cluster" + } + + cluster { + + roles = ["interactions-node"] + + configuration-compatibility-check.enforce-on-join = off + } + + discovery { + kubernetes-api { + pod-label-selector = "actorSystemName=%s" + } + } + + management { + + http.routes { + cluster-management = "" + } + + cluster.bootstrap { + contact-point-discovery { + # For the kubernetes API this value is substituted in the %s in pod-label-selector + service-name = "BaaS" + + # pick the discovery method you'd like to use: + discovery-method = kubernetes-api + } + } + + health-checks { + readiness-path = "health/ready" + liveness-path = "health/alive" + + liveness-checks { + cluster-health = "webshop.webservice.ClusterHealthCheck" + } + } + } +} diff --git a/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala new file mode 100644 index 000000000..c2df108fa --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala @@ -0,0 +1,62 @@ +package webshop.webservice + +import CheckoutFlowIngredients._ +import CheckoutFlowEvents._ + +import scala.concurrent.Future + +object CheckoutFlowIngredients { + + case class OrderId(orderId: String) + + case class Item(itemId: String) + + case class ReservedItems(items: List[Item], data: Array[Byte]) + + case class ShippingAddress(address: String) + + case class PaymentInformation(info: String) + + case class ShippingOrder(items: List[Item], data: Array[Byte], address: ShippingAddress) +} + +object CheckoutFlowEvents { + + case class OrderPlaced(orderId: OrderId, items: List[Item]) + + case class PaymentInformationReceived(paymentInformation: PaymentInformation) + + case class ShippingAddressReceived(shippingAddress: ShippingAddress) + + sealed trait ReserveItemsOutput + + case class OrderHadUnavailableItems(unavailableItems: List[Item]) extends ReserveItemsOutput + + case class ItemsReserved(reservedItems: ReservedItems) extends ReserveItemsOutput + + sealed trait MakePaymentOutput + + case class PaymentSuccessful(shippingOrder: ShippingOrder) extends MakePaymentOutput + + case class PaymentFailed() extends MakePaymentOutput + + case class ShippingConfirmed() +} + +object CheckoutFlowInteractions { + + trait ReserveItems { + + def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] + } + + trait MakePayment { + + def apply(processId: String, items: ReservedItems, address: ShippingAddress, payment: PaymentInformation): Future[MakePaymentOutput] + } + + trait ShipItems { + + def apply(order: ShippingOrder): Future[ShippingConfirmed] + } +} diff --git a/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ClusterHealthCheck.scala b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ClusterHealthCheck.scala new file mode 100644 index 000000000..a6ae41f89 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ClusterHealthCheck.scala @@ -0,0 +1,13 @@ +package webshop.webservice + +import akka.actor.ActorSystem +import akka.cluster.{Cluster, MemberStatus} + +import scala.concurrent.Future + +class ClusterHealthCheck(system: ActorSystem) extends (() => Future[Boolean]) { + private val cluster = Cluster(system) + override def apply(): Future[Boolean] = { + Future.successful(cluster.selfMember.status == MemberStatus.Up) + } +} diff --git a/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/Main.scala b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/Main.scala new file mode 100644 index 000000000..1a597bc56 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/Main.scala @@ -0,0 +1,27 @@ +package webshop.webservice + +import akka.actor.ActorSystem +import akka.management.cluster.bootstrap.ClusterBootstrap +import akka.management.scaladsl.AkkaManagement +import cats.effect.IO +import com.ing.baker.baas.scaladsl.BaaSInteractionInstance +import com.ing.baker.runtime.scaladsl.InteractionInstance + +object Main extends App { + + val actorSystem = ActorSystem("BaaS") // This should be done by the BaaSInteractionInstance ecosystem to ease the configuration and improve the UX + AkkaManagement(actorSystem).start() + ClusterBootstrap(actorSystem).start() + val ecosystem = BaaSInteractionInstance(actorSystem) + val timer = IO.timer(actorSystem.dispatcher) + + import actorSystem.dispatcher + + val instances = InteractionInstance.unsafeFromList(List( + new MakePaymentInstance()(timer), + new ReserveItemsInstance()(timer), + new ShipItemsInstance()(timer) + )) + + ecosystem.load(instances: _*) +} diff --git a/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/MakePaymentInstance.scala b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/MakePaymentInstance.scala new file mode 100644 index 000000000..a78c49088 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/MakePaymentInstance.scala @@ -0,0 +1,20 @@ +package webshop.webservice + +import cats.effect.{IO, Timer} +import webshop.webservice.CheckoutFlowEvents.MakePaymentOutput +import webshop.webservice.CheckoutFlowIngredients.{PaymentInformation, ReservedItems, ShippingAddress, ShippingOrder} +import webshop.webservice.CheckoutFlowInteractions.MakePayment + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class MakePaymentInstance(implicit timer: Timer[IO]) extends MakePayment { + + override def apply(processId: String, items: ReservedItems, address: ShippingAddress, payment: PaymentInformation): Future[MakePaymentOutput] = { + IO.sleep(5.second) + .map(_ => println(Console.GREEN + processId + Console.RESET)) + .map(_ => CheckoutFlowEvents.PaymentSuccessful(ShippingOrder(items.items, items.data, address))) + .unsafeToFuture() + } +} + diff --git a/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ReserveItemsInstance.scala b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ReserveItemsInstance.scala new file mode 100644 index 000000000..b6d1634ac --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ReserveItemsInstance.scala @@ -0,0 +1,19 @@ +package webshop.webservice + +import cats.effect.{IO, Timer} +import cats.implicits._ +import webshop.webservice.CheckoutFlowEvents.ReserveItemsOutput +import webshop.webservice.CheckoutFlowIngredients.{Item, OrderId, ReservedItems} +import webshop.webservice.CheckoutFlowInteractions.ReserveItems + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class ReserveItemsInstance(implicit timer: Timer[IO]) extends ReserveItems { + + override def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] = { + IO.sleep(1.second) + .as(CheckoutFlowEvents.ItemsReserved(ReservedItems(items, Array.fill(1000)(Byte.MaxValue)))) + .unsafeToFuture() + } +} diff --git a/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ShipItemsInstance.scala b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ShipItemsInstance.scala new file mode 100644 index 000000000..c9424a151 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-interactions/src/main/scala/webshop/webservice/ShipItemsInstance.scala @@ -0,0 +1,20 @@ +package webshop.webservice + +import cats.effect.{IO, Timer} +import cats.implicits._ +import webshop.webservice.CheckoutFlowEvents.ShippingConfirmed +import webshop.webservice.CheckoutFlowIngredients.ShippingOrder +import webshop.webservice.CheckoutFlowInteractions.ShipItems + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class ShipItemsInstance(implicit timer: Timer[IO]) extends ShipItems { + + override def apply(order: ShippingOrder): Future[ShippingConfirmed] = { + IO.sleep(500.millis) + .as(ShippingConfirmed()) + .unsafeToFuture() + } +} + diff --git a/examples/baas-minikube-setup/baas-minikube-state/src/main/resources/kubernetes.conf b/examples/baas-minikube-setup/baas-minikube-state/src/main/resources/kubernetes.conf new file mode 100644 index 000000000..acf0cdcc1 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-state/src/main/resources/kubernetes.conf @@ -0,0 +1,82 @@ +include "baker.conf" + +service { + + actorSystemName = "BaaS" + actorSystemName = ${?ACTOR_SYSTEM_NAME} + + httpServerPort = 8080 + httpServerPort = ${?HTTP_SERVER_PORT} +} + + +baker { + + interaction-manager = "remote" + + actor { + provider = "cluster-sharded" + idle-timeout = 1 minute + } +} + +#cassandra-journal.contact-points.0 = "127.0.0.1" +#cassandra-journal.contact-points.0 = ${?CASSANDRA_CONTACT_POINTS_0} + +#cassandra-snapshot-store.contact-points.0 = "127.0.0.1" +#cassandra-snapshot-store.contact-points.0 = ${?CASSANDRA_CONTACT_POINTS_0} + +#akka.actor.allow-java-serialization = on + +akka { + + actor { + provider = "cluster" + } + + cluster { + + roles = ["state-node"] + + configuration-compatibility-check.enforce-on-join = off + } + + #persistence { + # See https://doc.akka.io/docs/akka-persistence-cassandra/current/journal.html#configuration + #journal.plugin = "cassandra-journal" + # See https://doc.akka.io/docs/akka-persistence-cassandra/current/snapshots.html#configuration + #snapshot-store.plugin = "cassandra-snapshot-store" + #} + + discovery { + kubernetes-api { + pod-label-selector = "actorSystemName=%s" + } + } + + management { + + http.routes { + cluster-management = "" + } + + cluster.bootstrap { + contact-point-discovery { + # For the kubernetes API this value is substituted into the %s in pod-label-selector + service-name = "BaaS" + + # pick the discovery method you'd like to use: + discovery-method = kubernetes-api + } + } + + health-checks { + readiness-path = "health/ready" + liveness-path = "health/alive" + + liveness-checks { + cluster-health = "webshop.webservice.ClusterHealthCheck" + } + } + } +} diff --git a/examples/baas-minikube-setup/baas-minikube-state/src/main/scala/webshop/webservice/ClusterHealthCheck.scala b/examples/baas-minikube-setup/baas-minikube-state/src/main/scala/webshop/webservice/ClusterHealthCheck.scala new file mode 100644 index 000000000..a6ae41f89 --- /dev/null +++ b/examples/baas-minikube-setup/baas-minikube-state/src/main/scala/webshop/webservice/ClusterHealthCheck.scala @@ -0,0 +1,13 @@ +package webshop.webservice + +import akka.actor.ActorSystem +import akka.cluster.{Cluster, MemberStatus} + +import scala.concurrent.Future + +class ClusterHealthCheck(system: ActorSystem) extends (() => Future[Boolean]) { + private val cluster = Cluster(system) + override def apply(): Future[Boolean] = { + Future.successful(cluster.selfMember.status == MemberStatus.Up) + } +} diff --git a/examples/baas-minikube-setup/build-and-publish-images.sh b/examples/baas-minikube-setup/build-and-publish-images.sh new file mode 100755 index 000000000..66ea56a82 --- /dev/null +++ b/examples/baas-minikube-setup/build-and-publish-images.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +echo "Run this command from the baker root" + +sbt baas-minikube-state/docker:publish +sbt baas-minikube-interactions/docker:publish +sbt baas-minikube-event-listener/docker:publish +sbt baas-client-example/docker:publish diff --git a/examples/baas-minikube-setup/build-and-publish-local-images.sh b/examples/baas-minikube-setup/build-and-publish-local-images.sh new file mode 100755 index 000000000..03f78fe79 --- /dev/null +++ b/examples/baas-minikube-setup/build-and-publish-local-images.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +echo "Run this command from the baker root" + +sbt baas-minikube-state/docker:publishLocal +sbt baas-minikube-interactions/docker:publishLocal +sbt baas-minikube-event-listener/docker:publishLocal +sbt baas-client-example/docker:publishLocal diff --git a/examples/baas-minikube-setup/create-minikube-namespace.sh b/examples/baas-minikube-setup/create-minikube-namespace.sh new file mode 100755 index 000000000..57403d082 --- /dev/null +++ b/examples/baas-minikube-setup/create-minikube-namespace.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +########################################################################### +#Script Name : create-minikube-example.sh +#Description : Creates and runs new minikube instance using preproxy +#Args : Not supported +#Bash Version : GNU bash, version 3.x.XX +########################################################################### + +set -o errexit +set -eo pipefail +set -o nounset +#set -o xtrace + +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "Creating minikube cluster locally ${RED}(takes up to 10 minutes)${NC}" +minikube delete || echo "Ignoring delete for non existed minikube cluster" + +minikube --vm-driver virtualbox --memory 8192 --cpus 4 start + +kubectl api-resources \ No newline at end of file diff --git a/examples/src/main/java/webshop/simple/JMain.java b/examples/baker-example/src/main/java/webshop/simple/JMain.java similarity index 93% rename from examples/src/main/java/webshop/simple/JMain.java rename to examples/baker-example/src/main/java/webshop/simple/JMain.java index 58ca3859a..8c8f1143f 100644 --- a/examples/src/main/java/webshop/simple/JMain.java +++ b/examples/baker-example/src/main/java/webshop/simple/JMain.java @@ -3,7 +3,9 @@ import akka.actor.ActorSystem; import com.ing.baker.compiler.RecipeCompiler; import com.ing.baker.il.CompiledRecipe; +import com.ing.baker.runtime.akka.AkkaBaker; import com.ing.baker.runtime.javadsl.*; +import com.typesafe.config.ConfigFactory; import java.util.ArrayList; import java.util.List; @@ -16,7 +18,7 @@ public class JMain { static public void main_ignore(String[] args) { ActorSystem actorSystem = ActorSystem.create("WebshopSystem"); - Baker baker = Baker.akkaLocalDefault(actorSystem); + Baker baker = AkkaBaker.java(ConfigFactory.load(), actorSystem); List items = new ArrayList<>(2); items.add("item1"); diff --git a/examples/src/main/java/webshop/simple/JWebshopRecipe.java b/examples/baker-example/src/main/java/webshop/simple/JWebshopRecipe.java similarity index 100% rename from examples/src/main/java/webshop/simple/JWebshopRecipe.java rename to examples/baker-example/src/main/java/webshop/simple/JWebshopRecipe.java diff --git a/examples/src/main/java/webshop/simple/ReserveItemsInstance.java b/examples/baker-example/src/main/java/webshop/simple/ReserveItemsInstance.java similarity index 100% rename from examples/src/main/java/webshop/simple/ReserveItemsInstance.java rename to examples/baker-example/src/main/java/webshop/simple/ReserveItemsInstance.java diff --git a/examples/src/main/resources/application.conf b/examples/baker-example/src/main/resources/application.conf similarity index 96% rename from examples/src/main/resources/application.conf rename to examples/baker-example/src/main/resources/application.conf index 873a3c06b..60aab476d 100644 --- a/examples/src/main/resources/application.conf +++ b/examples/baker-example/src/main/resources/application.conf @@ -25,6 +25,9 @@ service { } baker { + + interaction-manager = "remote" + actor { provider = "cluster-sharded" idle-timeout = 1 minute @@ -43,6 +46,8 @@ cassandra-journal.contact-points.0 = ${?CASSANDRA_CONTACT_POINTS_0} cassandra-snapshot-store.contact-points.0 = "127.0.0.1" cassandra-snapshot-store.contact-points.0 = ${?CASSANDRA_CONTACT_POINTS_0} +akka.actor.allow-java-serialization = on + akka { actor { diff --git a/examples/src/main/resources/docker-compose.yaml b/examples/baker-example/src/main/resources/docker-compose.yaml similarity index 83% rename from examples/src/main/resources/docker-compose.yaml rename to examples/baker-example/src/main/resources/docker-compose.yaml index 674ae8b65..e42c4dc89 100644 --- a/examples/src/main/resources/docker-compose.yaml +++ b/examples/baker-example/src/main/resources/docker-compose.yaml @@ -1,7 +1,7 @@ version: '3' services: node1: - image: "checkout-service-baker-example:3.0.0-SNAPSHOT" + image: "checkout-service-baker-example:3.0.1-SNAPSHOT" ports: - "8081:8080" - "5261:5266" @@ -14,26 +14,26 @@ services: CLUSTER_SEED_HOST: node1 CLUSTER_SEED_PORT: 2551 node2: - image: "checkout-service-baker-example:3.0.0-SNAPSHOT" + image: "checkout-service-baker-example:3.0.1-SNAPSHOT" ports: - "8082:8080" - "5262:5266" - "9092:9095" - logging: - driver: none + #logging: + # driver: none environment: CLUSTER_HOST: node2 CLUSTER_PORT: 2551 CLUSTER_SEED_HOST: node1 CLUSTER_SEED_PORT: 2551 node3: - image: "checkout-service-baker-example:3.0.0-SNAPSHOT" + image: "checkout-service-baker-example:3.0.1-SNAPSHOT" ports: - "8083:8080" - "5263:526" - "9093:9095" - logging: - driver: none + #logging: + # driver: none environment: CLUSTER_HOST: node3 CLUSTER_PORT: 2551 diff --git a/examples/src/main/resources/gatling/webshop/webservice/CheckoutFlowSimulation.scala b/examples/baker-example/src/main/resources/gatling/webshop/webservice/CheckoutFlowSimulation.scala similarity index 100% rename from examples/src/main/resources/gatling/webshop/webservice/CheckoutFlowSimulation.scala rename to examples/baker-example/src/main/resources/gatling/webshop/webservice/CheckoutFlowSimulation.scala diff --git a/examples/src/main/resources/grafana/Dockerfile b/examples/baker-example/src/main/resources/grafana/Dockerfile similarity index 100% rename from examples/src/main/resources/grafana/Dockerfile rename to examples/baker-example/src/main/resources/grafana/Dockerfile diff --git a/examples/src/main/resources/grafana/config.ini b/examples/baker-example/src/main/resources/grafana/config.ini similarity index 100% rename from examples/src/main/resources/grafana/config.ini rename to examples/baker-example/src/main/resources/grafana/config.ini diff --git a/examples/src/main/resources/grafana/dashboards/akka_metrics.json b/examples/baker-example/src/main/resources/grafana/dashboards/akka_metrics.json similarity index 100% rename from examples/src/main/resources/grafana/dashboards/akka_metrics.json rename to examples/baker-example/src/main/resources/grafana/dashboards/akka_metrics.json diff --git a/examples/src/main/resources/grafana/provisioning/dashboards/all.yaml b/examples/baker-example/src/main/resources/grafana/provisioning/dashboards/all.yaml similarity index 100% rename from examples/src/main/resources/grafana/provisioning/dashboards/all.yaml rename to examples/baker-example/src/main/resources/grafana/provisioning/dashboards/all.yaml diff --git a/examples/src/main/resources/grafana/provisioning/datasources/all.yaml b/examples/baker-example/src/main/resources/grafana/provisioning/datasources/all.yaml similarity index 100% rename from examples/src/main/resources/grafana/provisioning/datasources/all.yaml rename to examples/baker-example/src/main/resources/grafana/provisioning/datasources/all.yaml diff --git a/examples/src/main/resources/haproxy/Dockerfile b/examples/baker-example/src/main/resources/haproxy/Dockerfile similarity index 100% rename from examples/src/main/resources/haproxy/Dockerfile rename to examples/baker-example/src/main/resources/haproxy/Dockerfile diff --git a/examples/src/main/resources/haproxy/haproxy.cfg b/examples/baker-example/src/main/resources/haproxy/haproxy.cfg similarity index 85% rename from examples/src/main/resources/haproxy/haproxy.cfg rename to examples/baker-example/src/main/resources/haproxy/haproxy.cfg index cbe29a7df..70a1b0d7b 100644 --- a/examples/src/main/resources/haproxy/haproxy.cfg +++ b/examples/baker-example/src/main/resources/haproxy/haproxy.cfg @@ -36,7 +36,7 @@ backend nodes option forwardfor http-request set-header X-Forwarded-Port %[dst_port] http-request add-header X-Forwarded-Proto https if { ssl_fc } - option httpchk HEAD / HTTP/1.1 - server web01 host.docker.internal:8081 check - server web02 host.docker.internal:8082 check - server web03 host.docker.internal:8083 check + option httpchk GET / HTTP/1.1\r\nHost:localhost + server web01 state-node-1:8080 check + server web02 state-node-2:8080 check + server web03 state-node-3:8080 check diff --git a/examples/baker-example/src/main/resources/logback.xml b/examples/baker-example/src/main/resources/logback.xml new file mode 100644 index 000000000..cee31bf6b --- /dev/null +++ b/examples/baker-example/src/main/resources/logback.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + \ No newline at end of file diff --git a/examples/src/main/resources/prometheus/Dockerfile b/examples/baker-example/src/main/resources/prometheus/Dockerfile similarity index 100% rename from examples/src/main/resources/prometheus/Dockerfile rename to examples/baker-example/src/main/resources/prometheus/Dockerfile diff --git a/examples/src/main/resources/prometheus/prometheus.yaml b/examples/baker-example/src/main/resources/prometheus/prometheus.yaml similarity index 100% rename from examples/src/main/resources/prometheus/prometheus.yaml rename to examples/baker-example/src/main/resources/prometheus/prometheus.yaml diff --git a/examples/src/main/scala/webshop/simple/SimpleWebshopInstances.scala b/examples/baker-example/src/main/scala/webshop/simple/SimpleWebshopInstances.scala similarity index 100% rename from examples/src/main/scala/webshop/simple/SimpleWebshopInstances.scala rename to examples/baker-example/src/main/scala/webshop/simple/SimpleWebshopInstances.scala diff --git a/examples/src/main/scala/webshop/simple/SimpleWebshopInstancesReflection.scala b/examples/baker-example/src/main/scala/webshop/simple/SimpleWebshopInstancesReflection.scala similarity index 100% rename from examples/src/main/scala/webshop/simple/SimpleWebshopInstancesReflection.scala rename to examples/baker-example/src/main/scala/webshop/simple/SimpleWebshopInstancesReflection.scala diff --git a/examples/src/main/scala/webshop/simple/SimpleWebshopRecipe.scala b/examples/baker-example/src/main/scala/webshop/simple/SimpleWebshopRecipe.scala similarity index 100% rename from examples/src/main/scala/webshop/simple/SimpleWebshopRecipe.scala rename to examples/baker-example/src/main/scala/webshop/simple/SimpleWebshopRecipe.scala diff --git a/examples/src/main/scala/webshop/simple/SimpleWebshopRecipeReflection.scala b/examples/baker-example/src/main/scala/webshop/simple/SimpleWebshopRecipeReflection.scala similarity index 100% rename from examples/src/main/scala/webshop/simple/SimpleWebshopRecipeReflection.scala rename to examples/baker-example/src/main/scala/webshop/simple/SimpleWebshopRecipeReflection.scala diff --git a/examples/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala b/examples/baker-example/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala similarity index 100% rename from examples/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala rename to examples/baker-example/src/main/scala/webshop/webservice/CheckoutFlowRecipe.scala diff --git a/examples/src/main/scala/webshop/webservice/Main.scala b/examples/baker-example/src/main/scala/webshop/webservice/Main.scala similarity index 95% rename from examples/src/main/scala/webshop/webservice/Main.scala rename to examples/baker-example/src/main/scala/webshop/webservice/Main.scala index 0b4455ce1..be79ed900 100644 --- a/examples/src/main/scala/webshop/webservice/Main.scala +++ b/examples/baker-example/src/main/scala/webshop/webservice/Main.scala @@ -5,6 +5,7 @@ import akka.cluster.Cluster import cats.effect.concurrent.Ref import cats.effect.{ExitCode, IO, IOApp, Resource} import cats.implicits._ +import com.ing.baker.runtime.akka.AkkaBaker import com.ing.baker.runtime.scaladsl._ import com.typesafe.config.ConfigFactory import kamon.Kamon @@ -24,7 +25,7 @@ object Main extends IOApp { for { actorSystem <- IO { ActorSystem("CheckoutService") } config <- IO { ConfigFactory.load() } - baker <- IO { Baker.akka(config, actorSystem) } + baker <- IO { AkkaBaker(config, actorSystem) } checkoutRecipeId <- WebShopBaker.initRecipes(baker)(timer, actorSystem.dispatcher) sd <- Ref.of[IO, Boolean](false) webShopBaker = new WebShopBaker(baker, checkoutRecipeId)(actorSystem.dispatcher) diff --git a/examples/src/main/scala/webshop/webservice/MakePaymentInstance.scala b/examples/baker-example/src/main/scala/webshop/webservice/MakePaymentInstance.scala similarity index 96% rename from examples/src/main/scala/webshop/webservice/MakePaymentInstance.scala rename to examples/baker-example/src/main/scala/webshop/webservice/MakePaymentInstance.scala index 81ca19cb8..9206f9280 100644 --- a/examples/src/main/scala/webshop/webservice/MakePaymentInstance.scala +++ b/examples/baker-example/src/main/scala/webshop/webservice/MakePaymentInstance.scala @@ -11,7 +11,7 @@ import scala.concurrent.duration._ class MakePaymentInstance(implicit timer: Timer[IO]) extends MakePayment { override def apply(processId: String, items: ReservedItems, address: ShippingAddress, payment: PaymentInformation): Future[MakePaymentOutput] = { - IO.sleep(5 second) + IO.sleep(5.second) .map(_ => println(Console.GREEN + processId + Console.RESET)) .map(_ => CheckoutFlowEvents.PaymentSuccessful(ShippingOrder(items.items, items.data, address))) .unsafeToFuture() diff --git a/examples/baker-example/src/main/scala/webshop/webservice/OrderStatus.scala b/examples/baker-example/src/main/scala/webshop/webservice/OrderStatus.scala new file mode 100644 index 000000000..6a89e4bef --- /dev/null +++ b/examples/baker-example/src/main/scala/webshop/webservice/OrderStatus.scala @@ -0,0 +1,30 @@ +package webshop.webservice + +import webshop.webservice.OrderStatus._ + +trait OrderStatus { + + override def toString: String = this match { + case InfoPending(pending) => "info-pending:" + pending.mkString(",") + case UnavailableItems(items) => "unavailable-items:" + items.length + case PaymentFailed => "payment-failed" + case ShippingItems => "shipping-items" + case ProcessingPayment => "processing-payment" + case Complete => "complete" + } +} + +object OrderStatus { + + case class InfoPending(pending: List[String]) extends OrderStatus + + case class UnavailableItems(items: List[String]) extends OrderStatus + + case object PaymentFailed extends OrderStatus + + case object ShippingItems extends OrderStatus + + case object ProcessingPayment extends OrderStatus + + case object Complete extends OrderStatus +} diff --git a/examples/baker-example/src/main/scala/webshop/webservice/ReserveItemsInstance.scala b/examples/baker-example/src/main/scala/webshop/webservice/ReserveItemsInstance.scala new file mode 100644 index 000000000..84001a0b4 --- /dev/null +++ b/examples/baker-example/src/main/scala/webshop/webservice/ReserveItemsInstance.scala @@ -0,0 +1,19 @@ +package webshop.webservice + +import cats.implicits._ +import cats.effect.{IO, Timer} +import webshop.webservice.CheckoutFlowEvents.ReserveItemsOutput +import webshop.webservice.CheckoutFlowIngredients.{Item, OrderId, ReservedItems} +import webshop.webservice.CheckoutFlowInteractions.ReserveItems + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class ReserveItemsInstance(implicit timer: Timer[IO]) extends ReserveItems { + + override def apply(orderId: OrderId, items: List[Item]): Future[ReserveItemsOutput] = { + IO.sleep(1.second) + .as(CheckoutFlowEvents.ItemsReserved(ReservedItems(items, Array.fill(1000)(Byte.MaxValue)))) + .unsafeToFuture() + } +} diff --git a/examples/baker-example/src/main/scala/webshop/webservice/ShipItemsInstance.scala b/examples/baker-example/src/main/scala/webshop/webservice/ShipItemsInstance.scala new file mode 100644 index 000000000..d4951eaed --- /dev/null +++ b/examples/baker-example/src/main/scala/webshop/webservice/ShipItemsInstance.scala @@ -0,0 +1,20 @@ +package webshop.webservice + +import cats.implicits._ +import cats.effect.{IO, Timer} +import webshop.webservice.CheckoutFlowEvents.ShippingConfirmed +import webshop.webservice.CheckoutFlowIngredients.ShippingOrder +import webshop.webservice.CheckoutFlowInteractions.ShipItems + +import scala.concurrent.Future +import scala.concurrent.duration._ + +class ShipItemsInstance(implicit timer: Timer[IO]) extends ShipItems { + + override def apply(order: ShippingOrder): Future[ShippingConfirmed] = { + IO.sleep(500.millis) + .as(ShippingConfirmed()) + .unsafeToFuture() + } +} + diff --git a/examples/baker-example/src/main/scala/webshop/webservice/WebShop.scala b/examples/baker-example/src/main/scala/webshop/webservice/WebShop.scala new file mode 100644 index 000000000..aa2d4b459 --- /dev/null +++ b/examples/baker-example/src/main/scala/webshop/webservice/WebShop.scala @@ -0,0 +1,17 @@ +package webshop.webservice + +import cats.effect.IO + +trait WebShop { + + def createCheckoutOrder(items: List[String]): IO[String] + + def addCheckoutAddressInfo(orderId: String, address: String): IO[Option[String]] + + def addCheckoutPaymentInfo(orderId: String, paymentInfo: String): IO[Option[String]] + + def pollOrderStatus(orderId: String): IO[OrderStatus] + + def gracefulShutdown: IO[Unit] +} + diff --git a/examples/src/main/scala/webshop/webservice/WebShopBaker.scala b/examples/baker-example/src/main/scala/webshop/webservice/WebShopBaker.scala similarity index 100% rename from examples/src/main/scala/webshop/webservice/WebShopBaker.scala rename to examples/baker-example/src/main/scala/webshop/webservice/WebShopBaker.scala diff --git a/examples/src/main/scala/webshop/webservice/WebShopService.scala b/examples/baker-example/src/main/scala/webshop/webservice/WebShopService.scala similarity index 100% rename from examples/src/main/scala/webshop/webservice/WebShopService.scala rename to examples/baker-example/src/main/scala/webshop/webservice/WebShopService.scala diff --git a/examples/src/test/java/webshop/JWebshopRecipeTests.java b/examples/baker-example/src/test/java/webshop/JWebshopRecipeTests.java similarity index 100% rename from examples/src/test/java/webshop/JWebshopRecipeTests.java rename to examples/baker-example/src/test/java/webshop/JWebshopRecipeTests.java diff --git a/examples/src/test/scala/webshop/simple/WebshopRecipeSpec.scala b/examples/baker-example/src/test/scala/webshop/simple/WebshopRecipeSpec.scala similarity index 100% rename from examples/src/test/scala/webshop/simple/WebshopRecipeSpec.scala rename to examples/baker-example/src/test/scala/webshop/simple/WebshopRecipeSpec.scala diff --git a/integration/src/multi-jvm/scala/com/ing/baker/HappyPathSpec.scala b/integration/src/multi-jvm/scala/com/ing/baker/HappyPathSpec.scala index 5e0ab024a..e2e7a74a6 100644 --- a/integration/src/multi-jvm/scala/com/ing/baker/HappyPathSpec.scala +++ b/integration/src/multi-jvm/scala/com/ing/baker/HappyPathSpec.scala @@ -123,7 +123,7 @@ object HappyPath extends MockitoSugar { def seedNodeConfig(path: ActorPath): Config = ConfigFactory.parseString(s"""baker.cluster.seed-nodes = ["$path"]""") - def implementations(implicit ec: ExecutionContext): Seq[InteractionInstance] = Seq( + def implementations: Seq[InteractionInstance] = Seq( ValidateOrder, ManufactureGoods, SendInvoice, diff --git a/playground/src/main/resources/haproxy-state-nodes/Dockerfile b/playground/src/main/resources/haproxy-state-nodes/Dockerfile new file mode 100644 index 000000000..ebf71cf99 --- /dev/null +++ b/playground/src/main/resources/haproxy-state-nodes/Dockerfile @@ -0,0 +1,14 @@ +FROM haproxy:1.7 + +ENV HAPROXY_USER haproxy + +RUN groupadd --system ${HAPROXY_USER} && \ + useradd --system --gid ${HAPROXY_USER} ${HAPROXY_USER} && \ + mkdir --parents /var/lib/${HAPROXY_USER} && \ + chown -R ${HAPROXY_USER}:${HAPROXY_USER} /var/lib/${HAPROXY_USER} + +RUN mkdir /run/haproxy/ + +COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg + +CMD ["haproxy", "-db", "-f", "/usr/local/etc/haproxy/haproxy.cfg"] \ No newline at end of file diff --git a/playground/src/main/resources/haproxy-state-nodes/haproxy.cfg b/playground/src/main/resources/haproxy-state-nodes/haproxy.cfg new file mode 100644 index 000000000..42e31d398 --- /dev/null +++ b/playground/src/main/resources/haproxy-state-nodes/haproxy.cfg @@ -0,0 +1,42 @@ +global + log /dev/log local0 + log /dev/log local1 notice + chroot /var/lib/haproxy + stats socket /run/haproxy/admin.sock mode 660 level admin + stats timeout 30s + user haproxy + group haproxy + daemon + + # Default SSL material locations + ca-base /etc/ssl/certs + crt-base /etc/ssl/private + + # Default ciphers to use on SSL-enabled listening sockets. + # For more information, see ciphers(1SSL). + ssl-default-bind-ciphers kEECDH+aRSA+AES:kRSA+AES:+AES256:RC4-SHA:!kEDH:!LOW:!EXP:!MD5:!aNULL:!eNULL + +defaults + log global + mode http + option httplog + option dontlognull + timeout connect 5000 + timeout client 50000 + timeout server 50000 + +frontend localnodes + bind *:8080 + mode http + default_backend nodes + +backend nodes + mode http + balance roundrobin + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + option httpchk GET / HTTP/1.1\r\nHost:localhost + server web01 state-node-1:8080 check + #server web02 state-node-2:8080 check + #server web03 state-node-3:8080 check diff --git a/playground/src/main/scala/com/ing/baker/playground/AppUtils.scala b/playground/src/main/scala/com/ing/baker/playground/AppUtils.scala new file mode 100644 index 000000000..0335fdf7f --- /dev/null +++ b/playground/src/main/scala/com/ing/baker/playground/AppUtils.scala @@ -0,0 +1,104 @@ +package com.ing.baker.playground + +import cats.Show +import cats.data.StateT +import cats.effect.IO +import cats.implicits._ +import cats.effect.Console.{io => console} + +import scala.util.Random + +object AppUtils { + + case class Environment(runningImages: List[String], bakerLocation: Option[String]) + + object Environment { + + def empty: Environment = Environment(List.empty, None) + } + + type App[A] = StateT[IO, Environment, A] + + implicit class AppIO[A](io: IO[A]) { + + def app: App[A] = StateT.liftF(io) + } + + implicit class AppOps[A](app: App[A]) { + + def tryForget: App[Unit] = + AppUtils.tryForget(app) + } + + def pure[A](a: A): App[A] = StateT.pure(a) + + def fail[A](e: Throwable): App[A] = IO.raiseError(e).app + + def fail[A](e: String): App[A] = IO.raiseError(new Exception(e)).app + + def tryForget[A](program: App[A]): App[Unit] = + StateT { state => program.run(state).attempt.map(_ => (state, ())) } + + def modify(f: Environment => Environment): App[Unit] = + StateT.modify[IO, Environment](f) + + def getState: App[Environment] = + StateT.get[IO, Environment] + + def addRunningImage(imageName: String): App[Unit] = + modify(state => state.copy(runningImages = imageName :: state.runningImages)) + + def addBakerLocation(location: String): App[Unit] = + modify(state => state.copy(bakerLocation = Some(location))) + + def doNothing: App[Unit] = + StateT.pure[IO, Environment, Unit](()) + + def print(message: String): App[Unit] = + console.putStr(message).app + + def printLn(message: String): App[Unit] = + console.putStrLn(message).app + + def printLnA[A: Show](a: A): App[Unit] = + print(a.show) + + def readLn: App[String] = + console.readLn.app + + private val colors: Array[String] = Array( + Console.MAGENTA, + Console.RED, + Console.YELLOW, + Console.GREEN, + Console.CYAN, + Console.BLUE + ) + + implicit class PrettyColorPrint[A](a: A) { + + def print: IO[Unit] = + IO { println(a.toString) } + + def magenta: String = + Console.MAGENTA + a.toString + Console.RESET + + def green: String = + Console.GREEN + a.toString + Console.RESET + + def red: String = + Console.RED + a.toString + Console.RESET + + def yellow: String = + Console.YELLOW + a.toString + Console.RESET + + def randomColor: String = + colors(Random.nextInt(colors.length)) + a.toString + Console.RESET + + def prompt(prepend: String): String = + a.toString.lines.map(prepend + _).mkString("\n") + + def notice: String = + " [>>>] " + a.toString + " [<<<] " + } +} diff --git a/playground/src/main/scala/com/ing/baker/playground/Command.scala b/playground/src/main/scala/com/ing/baker/playground/Command.scala new file mode 100644 index 000000000..93da53268 --- /dev/null +++ b/playground/src/main/scala/com/ing/baker/playground/Command.scala @@ -0,0 +1,61 @@ +package com.ing.baker.playground + +import cats.implicits._ +import com.ing.baker.playground.AppUtils._ +import com.ing.baker.playground.Command.RunCommand +import com.ing.baker.playground.commands.{BaaS, Terminal} + +trait Command { + def name: String + def help: String = "No help for this command" + def run: RunCommand +} + +object Command { + + type RunCommand = PartialFunction[String, App[Unit]] + + val commands: List[Command] = + List( + Help, + StartBaaS, + BuildImage + ) + + case object Help extends Command { + + override def name: String = "help" + + override def help: String = "Displays this help menu" + + override def run: RunCommand = { + case "help" => + printLn("") *> + commands.traverse { command => + val spaces = List.fill(20 - command.name.length)(".").mkString + printLn(command.name + " " + spaces + " " + command.help) + } *> + printLn("") + } + } + + case object StartBaaS extends Command { + + override def name: String = "start-baas" + + override def help: String = "Starts Cassandra, Haproxy and a cluster of 3 baas state nodes" + + override def run: RunCommand = { case "start-baas" => BaaS.startBaaS } + } + + case object BuildImage extends Command { + + override def name: String = "build" + + override def help: String = "Builds all playground required images from the baker repository" + + override def run: RunCommand = { + case "build" => BaaS.buildStateNodesHAProxyImage + } + } +} diff --git a/playground/src/main/scala/com/ing/baker/playground/Main.scala b/playground/src/main/scala/com/ing/baker/playground/Main.scala new file mode 100644 index 000000000..1244091a2 --- /dev/null +++ b/playground/src/main/scala/com/ing/baker/playground/Main.scala @@ -0,0 +1,16 @@ +package com.ing.baker.playground + +import cats.effect.{ExitCode, IO, IOApp} +import com.typesafe.scalalogging.LazyLogging + +object Main extends IOApp with LazyLogging { + + override def run(args: List[String]): IO[ExitCode] = { + PlaygroundApp.loop + .run(AppUtils.Environment.empty) + .map { case (finalState, _) => + logger.info(s"Finishing with final state $finalState") + ExitCode.Success + } + } +} diff --git a/playground/src/main/scala/com/ing/baker/playground/PlaygroundApp.scala b/playground/src/main/scala/com/ing/baker/playground/PlaygroundApp.scala new file mode 100644 index 000000000..9f372f52e --- /dev/null +++ b/playground/src/main/scala/com/ing/baker/playground/PlaygroundApp.scala @@ -0,0 +1,40 @@ +package com.ing.baker.playground + +import cats.implicits._ +import com.ing.baker.playground.AppUtils._ +import com.ing.baker.playground.Command.RunCommand +import com.ing.baker.playground.commands.Docker + +object PlaygroundApp { + + def loop: App[Unit] = + for { + _ <- print("playground> ") + line <- readLn + _ <- exec(line) + _ <- if (line == "exit") doNothing else loop + } yield () + + def exec(raw: String): App[Unit] = + tryOneCommand + .applyOrElse(raw, (other: String) => printLn(s"Unknown command '$other'")) + .attempt + .flatMap { + case Left(e) => printLn(e.getMessage) + case Right(_) => doNothing + } + + def tryOneCommand: RunCommand = + Command.commands.foldRight[RunCommand]({ + case "exit" => + cleanup *> printLn("Bye bye! I hope you had fun :D") + case "clean" => + cleanup *> printLn("Clean") + case "" => + doNothing + })(_.run.orElse(_)) + + def cleanup: App[Unit] = + Docker.terminateAllImages *> + Docker.deleteDockerNetwork +} diff --git a/playground/src/main/scala/com/ing/baker/playground/commands/BaaS.scala b/playground/src/main/scala/com/ing/baker/playground/commands/BaaS.scala new file mode 100644 index 000000000..6a8d14426 --- /dev/null +++ b/playground/src/main/scala/com/ing/baker/playground/commands/BaaS.scala @@ -0,0 +1,109 @@ +package com.ing.baker.playground.commands + +import cats.implicits._ +import com.ing.baker.playground.AppUtils._ +import Docker.{createDockerNetwork, networkName} + +object BaaS { + + val baasVersion = "3.0.2-SNAPSHOT" + + val haproxyStateNodesImage = "playground-haproxy-state-nodes:latest" + + def startBaaS: App[Unit] = + for { + _ <- createDockerNetwork + _ <- EnvSystems.runCassandra + node1 <- runStateNode(baasVersion, 1, "self") + _ <- EnvSystems.runHaproxy + _ <- runInteractionNode(baasVersion, 1, node1) + _ <- runEventListenerNode(baasVersion, 1, node1) + _ <- runClientApp(baasVersion, 1, "http://" + EnvSystems.haproxyName + ":8080") + } yield () + + def runStateNode(version: String, node: Int, seedHost: String): App[String] = { + val containerName: String = s"state-node-$node" + val seedHostname: String = if(seedHost == "self") containerName else seedHost + val envVars = Map( + "CLUSTER_HOST" -> containerName, + "CLUSTER_SEED_HOST" -> seedHostname, + "CASSANDRA_CONTACT_POINTS_0" -> "baker-cassandra" + ) + .map { case (env, value) => s"-e $env=$value"} + .mkString(" ") + val cmd = s"docker run --name $containerName --network $networkName $envVars baas-node-state:$version" + for { + _ <- Terminal.execAndWait( + command = cmd, + prompt = s"state-node:$version:$node", + condition = _.contains(s"State Node started...") + ) + _ <- printLn(s"Node: $containerName successfully started") + _ <- addRunningImage(containerName) + } yield containerName + } + + def runInteractionNode(version: String, node: Int, seedHost: String): App[String] = { + val containerName: String = s"interaction-node-$node" + val seedHostname: String = if(seedHost == "self") containerName else seedHost + val envVars = Map( + "CLUSTER_HOST" -> containerName, + "CLUSTER_SEED_HOST" -> seedHostname + ) + .map { case (env, value) => s"-e $env=$value"} + .mkString(" ") + val cmd = s"docker run --name $containerName --network $networkName $envVars baas-interactions-example:$version" + for { + _ <- Terminal.execAndWait( + command = cmd, + prompt = s"interactions-node:$version:$node", + condition = _ => true + ) + _ <- addRunningImage(containerName) + } yield containerName + } + + def runEventListenerNode(version: String, node: Int, seedHost: String): App[String] = { + val containerName: String = s"event-listener-node-$node" + val seedHostname: String = if(seedHost == "self") containerName else seedHost + val envVars = Map( + "CLUSTER_HOST" -> containerName, + "CLUSTER_SEED_HOST" -> seedHostname + ) + .map { case (env, value) => s"-e $env=$value"} + .mkString(" ") + val cmd = s"docker run --name $containerName --network $networkName $envVars baas-event-listener-example:$version" + for { + _ <- Terminal.execAndWait( + command = cmd, + prompt = s"event-listener-node:$version:$node", + condition = _ => true + ) + _ <- addRunningImage(containerName) + } yield containerName + } + + def runClientApp(version: String, node: Int, baasHostname: String): App[String] = { + val containerName: String = s"client-app-$node" + val envVars = Map( + "BAAS_HOSTNAME" -> baasHostname + ) + .map { case (env, value) => s"-e $env=$value"} + .mkString(" ") + val cmd = s"docker run --name $containerName --network $networkName $envVars -p 8080:8080 baas-client-example:$version" + for { + _ <- Terminal.execAndWait( + command = cmd, + prompt = s"client-app:$version:$node", + condition = _ => true + ) + _ <- addRunningImage(containerName) + } yield containerName + } + + def buildStateNodesHAProxyImage: App[Unit] = + Terminal.moveToBakerLocation *> Docker.buildImage( + "./playground/src/main/resources/haproxy-state-nodes", + haproxyStateNodesImage + ) +} diff --git a/playground/src/main/scala/com/ing/baker/playground/commands/Docker.scala b/playground/src/main/scala/com/ing/baker/playground/commands/Docker.scala new file mode 100644 index 000000000..6165816db --- /dev/null +++ b/playground/src/main/scala/com/ing/baker/playground/commands/Docker.scala @@ -0,0 +1,46 @@ +package com.ing.baker.playground.commands + +import cats.implicits._ +import com.ing.baker.playground.AppUtils._ + +import scala.util.matching.Regex + +object Docker { + + def networkName: String = "baker-playground-network" + + def buildImage(path: String, tag: String): App[Unit] = + Terminal.exec(s"docker build $path -t $tag", s"Build $tag") + + def checkForDockerVersion: App[Unit] = { + val DockerVersionReg: Regex = """Docker version (\d\d).*, build .+""".r + val requiredVersion: Int = 19 + Terminal.execBlock("docker --version").flatMap { + case DockerVersionReg(version) => + if (version.toInt >= requiredVersion) doNothing + else fail(s"Docker version is $version but $requiredVersion or greater is required.") + case _ => + fail("Bad input for function isRequiredVersion") + } + } + + def terminateAllImages: App[Unit] = + for { + env <- getState + _ <- env.runningImages.traverse(terminate) + _ <- modify(_.copy(runningImages = List.empty)) + } yield () + + def terminate(name: String): App[Unit] = + Terminal.exec(s"docker kill $name", s"Terminate $name").tryForget *> + Terminal.execBlock(s"docker rm $name").tryForget + + def createDockerNetwork: App[Unit] = + Terminal.exec(s"docker network create $networkName", "Docker Network").tryForget + + def deleteDockerNetwork: App[Unit] = + Terminal.exec(s"docker network rm $networkName", "Remove Docker Network").tryForget + + def dockerPull(image: String): App[Unit] = + Terminal.exec(s"docker pull $image", s"Pull $image Image").void +} diff --git a/playground/src/main/scala/com/ing/baker/playground/commands/EnvSystems.scala b/playground/src/main/scala/com/ing/baker/playground/commands/EnvSystems.scala new file mode 100644 index 000000000..d1df706c2 --- /dev/null +++ b/playground/src/main/scala/com/ing/baker/playground/commands/EnvSystems.scala @@ -0,0 +1,28 @@ +package com.ing.baker.playground.commands + +import cats.implicits._ +import com.ing.baker.playground.AppUtils.{App, addRunningImage} +import com.ing.baker.playground.commands.Docker.networkName + +object EnvSystems { + + def cassandraName: String = "baker-cassandra" + + def haproxyName: String = "baker-haproxy" + + def runCassandra: App[Unit] = + Terminal.execAndWait( + command = s"docker run --name $cassandraName --network $networkName -p 9042:9042 -p 9160:9160 cassandra:latest", + prompt = "Cassandra", + condition = _.matches("""INFO \[OptionalTasks:1\] (.+) CassandraRoleManager\.java:372 - Created default superuser role 'cassandra'""") + ) *> + addRunningImage(cassandraName) + + def runHaproxy: App[Unit] = + Terminal.execAndWait( + command = s"docker run --name $haproxyName --network $networkName ${BaaS.haproxyStateNodesImage}", + prompt = "HAProxy", + condition = _ => true + ) *> + addRunningImage(haproxyName) +} diff --git a/playground/src/main/scala/com/ing/baker/playground/commands/Terminal.scala b/playground/src/main/scala/com/ing/baker/playground/commands/Terminal.scala new file mode 100644 index 000000000..1db6e7673 --- /dev/null +++ b/playground/src/main/scala/com/ing/baker/playground/commands/Terminal.scala @@ -0,0 +1,92 @@ +package com.ing.baker.playground.commands + +import cats.implicits._ +import cats.effect.IO + +import scala.sys.process._ +import com.ing.baker.playground.AppUtils._ + +object Terminal { + + def moveToBakerLocation: App[Unit] = { + for { + state <- getState + location <- state.bakerLocation match { + case None => confirmBakerProjectLocation + case Some(location0) => pure(location0) + } + _ <- cd(location) + } yield () + } + + private def confirmBakerProjectLocation: App[String] = { + def confirm(path: String): App[String] = + for { + answer <- query(s"Is $path the baker project location? input yes or the correct absolute path") + realPath <- answer match { + case "yes" | "y" | "ye" => + pure(path) + case path0 => + confirm(path0) + } + } yield realPath + pwd >>= confirm + } + + def pwd: App[String] = + execBlock("pwd") + + def cd(location: String): App[Unit] = + execBlock(s"cd $location").attempt.flatMap { + case Left(e) => + fail(s"Could not move to directory $location... reason: ${e.getMessage}") + case Right(_) => + doNothing + } + + def query(question: String): App[String] = + printLn(question + ": ") *> readLn + + def exec(command: String, prompt: String): App[Unit] = { + val p = prompt.randomColor + execWithLogger(command, ProcessLogger( + _.prompt(p + " | ").print.unsafeRunSync(), + _.prompt((prompt + " [ERROR]").red + " | ").print.unsafeRunSync() + )).flatMap { running => + val exitValue = running.exitValue() + if (exitValue == 0) doNothing + else fail(new Exception(s"Command $command exited with non zero value $exitValue")) + } + } + + def execAndWait(command: String, prompt: String, condition: String => Boolean): App[Process] = { + val process = Process(command) + IO.async[Process] { callback => + val p = prompt.randomColor + var running: Process = null + var matched: Boolean = false + running = process.run(ProcessLogger( + { line => + line.prompt(p + " | ").print.unsafeRunSync() + if(condition(line) && !matched) { + matched = true + callback(Right(running)) + } + }, + { line => + line.prompt((prompt + " [ERROR]").red + " | ").print.unsafeRunSync() + if(condition(line) && !matched) { + matched = true + callback(Right(running)) + } + } + )) + } + }.app + + def execBlock(command: String): App[String] = + IO { Process(command).!!<.trim }.app + + def execWithLogger(command: String, logger: ProcessLogger): App[Process] = + IO { Process(command).run(logger) }.app +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e495c85ec..8923b939d 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -16,6 +16,7 @@ object Dependencies { .exclude("com.typesafe.akka", "akka-persistence-query") .exclude("com.typesafe.akka", "akka-stream") + val scalaJava8Compat = "org.scala-lang.modules" %% "scala-java8-compat" % "0.8.0" val scalaTest = "org.scalatest" %% "scalatest" % "3.0.8" val mockito = "org.mockito" % "mockito-all" % "1.10.19" val junitInterface = "com.novocode" % "junit-interface" % "0.11" @@ -28,12 +29,16 @@ object Dependencies { val akkaPersistenceCassandra = "com.typesafe.akka" %% "akka-persistence-cassandra" % "0.101" val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % akkaVersion val akkaClusterSharding = "com.typesafe.akka" %% "akka-cluster-sharding" % akkaVersion + val akkaDistributedData = "com.typesafe.akka" %% "akka-distributed-data" % akkaVersion + val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % akkaVersion val akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % akkaVersion val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % akkaVersion val akkaStreamTestKit = "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion val akkaMultiNodeTestkit = "com.typesafe.akka" %% "akka-multi-node-testkit" % akkaVersion - - val akkaHttp = "com.typesafe.akka" %% "akka-http" % "10.0.15" + val akkaManagementHttp = "com.lightbend.akka.management" %% "akka-management-cluster-http" % "1.0.5" + val akkaClusterBoostrap = "com.lightbend.akka.management" %% "akka-management-cluster-bootstrap" % "1.0.5" + val akkaDiscoveryKube = "com.lightbend.akka.discovery" %% "akka-discovery-kubernetes-api" % "1.0.5" + val akkaHttp = "com.typesafe.akka" %% "akka-http" % "10.1.11" val akkaBoostrap = "com.lightbend.akka.management" %% "akka-management-cluster-bootstrap" % "1.0.5" val levelDB = "org.iq80.leveldb" % "leveldb" % "0.12" @@ -60,6 +65,9 @@ object Dependencies { val catsEffect = "org.typelevel" %% "cats-effect" % "2.0.0" val catsCore = "org.typelevel" %% "cats-core" % "2.0.0" + val console4Cats = "dev.profunktor" %% "console4cats" % "0.8.0" + + val jnrConstants = "com.github.jnr" % "jnr-constants" % "0.9.9" def scalaReflect(scalaV: String): ModuleID = "org.scala-lang"% "scala-reflect" % scalaV val javaxInject = "javax.inject" % "javax.inject" % "1" diff --git a/project/plugins.sbt b/project/plugins.sbt index d44dc3ec0..9ccb8769c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -14,6 +14,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.5.1") -libraryDependencies += "org.slf4j" % "slf4j-nop" % "1.7.29" - -addSbtPlugin("io.kamon" % "sbt-kanela-runner" % "2.0.3") \ No newline at end of file +libraryDependencies += "org.slf4j" % "slf4j-nop" % "1.7.29" \ No newline at end of file diff --git a/recipe-dsl/src/test/scala/com/ing/baker/recipe/common/InteractionFailureStrategySpec.scala b/recipe-dsl/src/test/scala/com/ing/baker/recipe/common/InteractionFailureStrategySpec.scala index 43939c3e5..d87e89ad0 100644 --- a/recipe-dsl/src/test/scala/com/ing/baker/recipe/common/InteractionFailureStrategySpec.scala +++ b/recipe-dsl/src/test/scala/com/ing/baker/recipe/common/InteractionFailureStrategySpec.scala @@ -34,7 +34,7 @@ class InteractionFailureStrategySpec extends WordSpecLike with Matchers { "derive the correct parameters when deadline is specified2" in { val deadline = 16 seconds - val initialDelay = 1 seconds + val initialDelay = 1.seconds val backoffFactor: Double = 2.0 val actual = RetryWithIncrementalBackoff.builder() @@ -55,7 +55,7 @@ class InteractionFailureStrategySpec extends WordSpecLike with Matchers { "derive the correct parameters when deadline is specified and max time between retries set" in { val deadline = 22 seconds - val initialDelay = 1 seconds + val initialDelay = 1.seconds val backoffFactor: Double = 2.0 val maxDurationBetweenRetries = 4 seconds @@ -78,7 +78,7 @@ class InteractionFailureStrategySpec extends WordSpecLike with Matchers { "verify that deadline is greater than initial delay" in { - val deadline = 1 seconds + val deadline = 1.seconds val initialDelay = 2 seconds intercept[IllegalArgumentException] { diff --git a/runtime/src/main/protobuf/process_instance.proto b/runtime/src/main/protobuf/process_instance.proto index 81576e04d..886a8e670 100644 --- a/runtime/src/main/protobuf/process_instance.proto +++ b/runtime/src/main/protobuf/process_instance.proto @@ -18,14 +18,14 @@ message ConsumedToken { message Initialized { - option (scalapb.message).extends = "com.ing.baker.runtime.akka.actor.serialization.BakerSerializable"; + option (scalapb.message).extends = "com.ing.baker.runtime.serialization.BakerSerializable"; repeated ProducedToken initial_marking = 1; optional SerializedData initial_state = 2; } message TransitionFired { - option (scalapb.message).extends = "com.ing.baker.runtime.akka.actor.serialization.BakerSerializable"; + option (scalapb.message).extends = "com.ing.baker.runtime.serialization.BakerSerializable"; optional int64 job_id = 1; optional string correlation_id = 9; @@ -50,7 +50,7 @@ message FailureStrategy { message TransitionFailed { - option (scalapb.message).extends = "com.ing.baker.runtime.akka.actor.serialization.BakerSerializable"; + option (scalapb.message).extends = "com.ing.baker.runtime.serialization.BakerSerializable"; optional int64 job_id = 1; optional string correlation_id = 10; diff --git a/runtime/src/main/resources/reference.conf b/runtime/src/main/resources/reference.conf index 9b18cd939..a51a4dcf2 100644 --- a/runtime/src/main/resources/reference.conf +++ b/runtime/src/main/resources/reference.conf @@ -52,6 +52,18 @@ baker { # if enabled = on, a secret should be set # secret = ??? } + + # use "local" unless you are configuring a BaaS environment, then you will need "remote" + interaction-manager = "local" + + remote-interaction-manager { + + # amount of time to wait before failing an interaction when the remote interaction manager can't find an interaction node to run on + post-timeout = 10 seconds + + # amount of time to wait before failing an interaction when the interaction node is not responding with the computation result + computation-timeout = 60 seconds + } } akka { @@ -70,13 +82,14 @@ akka { serialization-bindings { - "com.ing.baker.runtime.akka.actor.serialization.BakerSerializable" = baker-typed-protobuf + "com.ing.baker.runtime.serialization.BakerSerializable" = baker-typed-protobuf "com.ing.baker.types.Value" = baker-typed-protobuf "com.ing.baker.types.Type" = baker-typed-protobuf "com.ing.baker.il.CompiledRecipe" = baker-typed-protobuf "com.ing.baker.runtime.scaladsl.EventInstance" = baker-typed-protobuf "com.ing.baker.runtime.scaladsl.RecipeInstanceState" = baker-typed-protobuf + "com.ing.baker.runtime.scaladsl.RecipeEventMetadata" = baker-typed-protobuf } } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/AkkaBaker.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/AkkaBaker.scala index 50b407519..f6892d39c 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/AkkaBaker.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/AkkaBaker.scala @@ -1,29 +1,60 @@ package com.ing.baker.runtime.akka -import akka.actor.{ Actor, ActorRef, Props } -import akka.pattern.{ FutureRef, ask } +import akka.actor.{Actor, ActorRef, ActorSystem, Address, Props} +import akka.pattern.{FutureRef, ask} import akka.util.Timeout +import cats.data.NonEmptyList import com.ing.baker.il._ import com.ing.baker.il.failurestrategy.ExceptionStrategyOutcome import com.ing.baker.runtime.akka.actor._ import com.ing.baker.runtime.akka.actor.process_index.ProcessIndexProtocol._ -import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol.{ Initialized, InstanceState, Uninitialized } +import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol.{Initialized, InstanceState, Uninitialized} import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProtocol import com.ing.baker.runtime.common.BakerException._ import com.ing.baker.runtime.common.SensoryEventStatus +import com.ing.baker.runtime.{javadsl, scaladsl} import com.ing.baker.runtime.scaladsl._ import com.ing.baker.types.Value +import com.typesafe.config.Config import com.typesafe.scalalogging.LazyLogging + import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.language.postfixOps import scala.util.Try +object AkkaBaker { + + def apply(config: Config, actorSystem: ActorSystem): scaladsl.Baker = + new AkkaBaker(AkkaBakerConfig.from(config, actorSystem)) + + def withConfig(config: AkkaBakerConfig): scaladsl.Baker = + new AkkaBaker(config) + + def localDefault(actorSystem: ActorSystem): scaladsl.Baker = + new AkkaBaker(AkkaBakerConfig.localDefault(actorSystem)) + + def clusterDefault(seedNodes: NonEmptyList[Address], actorSystem: ActorSystem): scaladsl.Baker = + new AkkaBaker(AkkaBakerConfig.clusterDefault(seedNodes, actorSystem)) + + def javaWithConfig(config: AkkaBakerConfig): javadsl.Baker = + new javadsl.Baker(withConfig(config)) + + def java(config: Config, actorSystem: ActorSystem): javadsl.Baker = + new javadsl.Baker(apply(config, actorSystem)) + + def javaLocalDefault(actorSystem: ActorSystem): javadsl.Baker = + new javadsl.Baker(new AkkaBaker(AkkaBakerConfig.localDefault(actorSystem))) + + def javaOther(baker: scaladsl.Baker): javadsl.Baker = + new javadsl.Baker(baker) +} + /** * The Baker is the component of the Baker library that runs one or multiples recipes. * For each recipe a new instance can be baked, sensory events can be send and state can be inquired upon */ -class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with LazyLogging { +class AkkaBaker private[runtime](config: AkkaBakerConfig) extends scaladsl.Baker with LazyLogging { import config.system @@ -47,10 +78,10 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz val implementationErrors = getImplementationErrors(compiledRecipe) if (implementationErrors.nonEmpty) - Future.failed(new ImplementationsException(implementationErrors.mkString(", "))) + Future.failed(ImplementationsException(implementationErrors.mkString(", "))) else if (compiledRecipe.validationErrors.nonEmpty) - Future.failed(new RecipeValidationException(compiledRecipe.validationErrors.mkString(", "))) + Future.failed(RecipeValidationException(compiledRecipe.validationErrors.mkString(", "))) else recipeManager.ask(RecipeManagerProtocol.AddRecipe(compiledRecipe))(config.defaultAddRecipeTimeout) flatMap { case RecipeManagerProtocol.AddRecipeResponse(recipeId) => Future.successful(recipeId) @@ -58,7 +89,7 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz } private def getImplementationErrors(compiledRecipe: CompiledRecipe): Set[String] = { - compiledRecipe.interactionTransitions.filterNot(config.interactionManager.getImplementation(_).isDefined) + compiledRecipe.interactionTransitions.filterNot(config.interactionManager.hasImplementation) .map(s => s"No implementation provided for interaction: ${s.originalInteractionName}") } @@ -74,7 +105,7 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz case RecipeManagerProtocol.RecipeFound(compiledRecipe, timestamp) => Future.successful(RecipeInformation(compiledRecipe, timestamp, getImplementationErrors(compiledRecipe))) case RecipeManagerProtocol.NoRecipeFound(_) => - Future.failed(new IllegalArgumentException(s"No recipe found for recipe with id: $recipeId")) + Future.failed(NoSuchRecipeException(recipeId)) } } @@ -102,9 +133,9 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz case _: Initialized => Future.successful(()) case ProcessAlreadyExists(_) => - Future.failed(new IllegalArgumentException(s"Process with id '$recipeInstanceId' already exists.")) + Future.failed(ProcessAlreadyExistsException(recipeInstanceId)) case RecipeManagerProtocol.NoRecipeFound(_) => - Future.failed(new IllegalArgumentException(s"Recipe with id '$recipeId' does not exist.")) + Future.failed(NoSuchRecipeException(recipeId)) } } @@ -118,9 +149,9 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz ))(config.defaultProcessEventTimeout).flatMap { // TODO MOVE THIS TO A FUNCTION case FireSensoryEventRejection.InvalidEvent(_, message) => - Future.failed(new IllegalArgumentException(message)) + Future.failed(IllegalEventException(message)) case FireSensoryEventRejection.NoSuchRecipeInstance(recipeInstanceId0) => - Future.failed(new NoSuchProcessException(s"Process with id $recipeInstanceId0 does not exist in the index")) + Future.failed(NoSuchProcessException(recipeInstanceId0)) case _: FireSensoryEventRejection.FiringLimitMet => Future.successful(SensoryEventStatus.FiringLimitMet) case _: FireSensoryEventRejection.AlreadyReceived => @@ -142,9 +173,9 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz reaction = FireSensoryEventReaction.NotifyWhenCompleted(waitForRetries = true) ))(config.defaultProcessEventTimeout).flatMap { case FireSensoryEventRejection.InvalidEvent(_, message) => - Future.failed(new IllegalArgumentException(message)) + Future.failed(IllegalEventException(message)) case FireSensoryEventRejection.NoSuchRecipeInstance(recipeInstanceId0) => - Future.failed(new NoSuchProcessException(s"Process with id $recipeInstanceId0 does not exist in the index")) + Future.failed(NoSuchProcessException(recipeInstanceId0)) case _: FireSensoryEventRejection.FiringLimitMet => Future.successful(SensoryEventResult(SensoryEventStatus.FiringLimitMet, Seq.empty, Map.empty)) case _: FireSensoryEventRejection.AlreadyReceived => @@ -166,9 +197,9 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz reaction = FireSensoryEventReaction.NotifyOnEvent(waitForRetries = true, onEvent) ))(config.defaultProcessEventTimeout).flatMap { case FireSensoryEventRejection.InvalidEvent(_, message) => - Future.failed(new IllegalArgumentException(message)) + Future.failed(IllegalEventException(message)) case FireSensoryEventRejection.NoSuchRecipeInstance(recipeInstanceId0) => - Future.failed(new NoSuchProcessException(s"Process with id $recipeInstanceId0 does not exist in the index")) + Future.failed(NoSuchProcessException(recipeInstanceId0)) case _: FireSensoryEventRejection.FiringLimitMet => Future.successful(SensoryEventResult(SensoryEventStatus.FiringLimitMet, Seq.empty, Map.empty)) case _: FireSensoryEventRejection.AlreadyReceived => @@ -194,9 +225,9 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz completeReceiver = futureRef.ref) ))(config.defaultProcessEventTimeout).flatMap { case FireSensoryEventRejection.InvalidEvent(_, message) => - Future.failed(new IllegalArgumentException(message)) + Future.failed(IllegalEventException(message)) case FireSensoryEventRejection.NoSuchRecipeInstance(recipeInstanceId0) => - Future.failed(new NoSuchProcessException(s"Process with id $recipeInstanceId0 does not exist in the index")) + Future.failed(NoSuchProcessException(recipeInstanceId0)) case _: FireSensoryEventRejection.FiringLimitMet => Future.successful(SensoryEventStatus.FiringLimitMet) case _: FireSensoryEventRejection.AlreadyReceived => @@ -211,9 +242,9 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz val futureCompleted = futureRef.future.flatMap { case FireSensoryEventRejection.InvalidEvent(_, message) => - Future.failed(new IllegalArgumentException(message)) + Future.failed(IllegalEventException(message)) case FireSensoryEventRejection.NoSuchRecipeInstance(recipeInstanceId0) => - Future.failed(new NoSuchProcessException(s"Process with id $recipeInstanceId0 does not exist in the index")) + Future.failed(NoSuchProcessException(recipeInstanceId0)) case _: FireSensoryEventRejection.FiringLimitMet => Future.successful(SensoryEventResult(SensoryEventStatus.FiringLimitMet, Seq.empty, Map.empty)) case _: FireSensoryEventRejection.AlreadyReceived => @@ -284,8 +315,8 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz .ask(GetProcessState(recipeInstanceId))(Timeout.durationToTimeout(config.defaultInquireTimeout)) .flatMap { case instance: InstanceState => Future.successful(instance.state.asInstanceOf[RecipeInstanceState]) - case NoSuchProcess(id) => Future.failed(new NoSuchProcessException(s"No such process with: $id")) - case ProcessDeleted(id) => Future.failed(new ProcessDeletedException(s"Process $id is deleted")) + case NoSuchProcess(id) => Future.failed(NoSuchProcessException(id)) + case ProcessDeleted(id) => Future.failed(ProcessDeletedException(id)) } /** @@ -335,21 +366,21 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz eventNames = processState.eventNames.toSet, ingredientNames = processState.ingredients.keySet)) case ProcessDeleted(_) => - Future.failed(new ProcessDeletedException(s"Process $recipeInstanceId is deleted")) + Future.failed(ProcessDeletedException(recipeInstanceId)) case Uninitialized(_) => - Future.failed(new NoSuchProcessException(s"Process $recipeInstanceId is not found")) + Future.failed(NoSuchProcessException(recipeInstanceId)) } } yield response } - private def doRegisterEventListener(listenerFunction: (String, EventInstance) => Unit, processFilter: String => Boolean): Future[Unit] = { + private def doRegisterEventListener(listenerFunction: (RecipeEventMetadata, EventInstance) => Unit, processFilter: String => Boolean): Future[Unit] = { registerBakerEventListener { - case EventReceived(_, recipeName, _, recipeInstanceId, _, event) if processFilter(recipeName) => - listenerFunction.apply(recipeInstanceId, event) - case InteractionCompleted(_, _, recipeName, _, recipeInstanceId, _, Some(event)) if processFilter(recipeName) => - listenerFunction.apply(recipeInstanceId, event) - case InteractionFailed(_, _, recipeName, _, recipeInstanceId, _, _, _, ExceptionStrategyOutcome.Continue(eventName)) if processFilter(recipeName) => - listenerFunction.apply(recipeInstanceId, EventInstance(eventName, Map.empty)) + case EventReceived(_, recipeName, recipeId, recipeInstanceId, _, event) if processFilter(recipeName) => + listenerFunction.apply(RecipeEventMetadata(recipeId = recipeId, recipeName = recipeName, recipeInstanceId = recipeInstanceId), event) + case InteractionCompleted(_, _, recipeName, recipeId, recipeInstanceId, _, Some(event)) if processFilter(recipeName) => + listenerFunction.apply(RecipeEventMetadata(recipeId = recipeId, recipeName = recipeName, recipeInstanceId = recipeInstanceId), event) + case InteractionFailed(_, _, recipeName, recipeId, recipeInstanceId, _, _, _, ExceptionStrategyOutcome.Continue(eventName)) if processFilter(recipeName) => + listenerFunction.apply(RecipeEventMetadata(recipeId = recipeId, recipeName = recipeName, recipeInstanceId = recipeInstanceId), EventInstance(eventName, Map.empty)) case _ => () } } @@ -359,7 +390,7 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz * * Note that the delivery guarantee is *AT MOST ONCE*. Do not use it for critical functionality */ - override def registerEventListener(recipeName: String, listenerFunction: (String, EventInstance) => Unit): Future[Unit] = + override def registerEventListener(recipeName: String, listenerFunction: (RecipeEventMetadata, EventInstance) => Unit): Future[Unit] = doRegisterEventListener(listenerFunction, _ == recipeName) /** @@ -368,7 +399,7 @@ class AkkaBaker private[runtime](config: AkkaBakerConfig) extends Baker with Laz * Note that the delivery guarantee is *AT MOST ONCE*. Do not use it for critical functionality */ // @deprecated("Use event bus instead", "1.4.0") - override def registerEventListener(listenerFunction: (String, EventInstance) => Unit): Future[Unit] = + override def registerEventListener(listenerFunction: (RecipeEventMetadata, EventInstance) => Unit): Future[Unit] = doRegisterEventListener(listenerFunction, _ => true) /** diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/AkkaBakerConfig.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/AkkaBakerConfig.scala index b904bddaa..e2dd6e562 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/AkkaBakerConfig.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/AkkaBakerConfig.scala @@ -1,15 +1,16 @@ package com.ing.baker.runtime.akka -import akka.actor.{ ActorSystem, Address, AddressFromURIString } +import akka.actor.{ActorSystem, Address, AddressFromURIString} import akka.persistence.query.PersistenceQuery -import akka.persistence.query.scaladsl.{ CurrentEventsByPersistenceIdQuery, CurrentPersistenceIdsQuery, PersistenceIdsQuery } +import akka.persistence.query.scaladsl.{CurrentEventsByPersistenceIdQuery, CurrentPersistenceIdsQuery, PersistenceIdsQuery} import cats.data.NonEmptyList import com.ing.baker.runtime.akka.AkkaBakerConfig.BakerPersistenceQuery -import com.ing.baker.runtime.akka.actor.serialization.Encryption -import com.ing.baker.runtime.akka.actor.{ BakerActorProvider, ClusterBakerActorProvider, LocalBakerActorProvider } -import com.ing.baker.runtime.akka.internal.InteractionManager +import com.ing.baker.runtime.akka.actor.{BakerActorProvider, ClusterBakerActorProvider, LocalBakerActorProvider} +import com.ing.baker.runtime.akka.internal.{InteractionManager, InteractionManagerDis, InteractionManagerLocal} +import com.ing.baker.runtime.serialization.Encryption import com.typesafe.config.Config import net.ceedubs.ficus.Ficus._ + import scala.concurrent.duration._ case class AkkaBakerConfig( @@ -58,7 +59,7 @@ object AkkaBakerConfig { defaultShutdownTimeout = 30.seconds, defaultAddRecipeTimeout = 10.seconds, bakerActorProvider = provider, - interactionManager = new InteractionManager(), + interactionManager = new InteractionManagerLocal(), readJournal = PersistenceQuery(actorSystem) .readJournalFor[BakerPersistenceQuery]("inmemory-read-journal") )(actorSystem) @@ -106,7 +107,14 @@ object AkkaBakerConfig { case Some(other) => throw new IllegalArgumentException(s"Unsupported actor provider: $other") } }, - interactionManager = new InteractionManager, + interactionManager = config.as[Option[String]]("baker.interaction-manager") match { + case Some("remote") => + val postTimeout = config.as[FiniteDuration]("baker.remote-interaction-manager.post-timeout") + val computationTimeout = config.as[FiniteDuration]("baker.remote-interaction-manager.computation-timeout") + new InteractionManagerDis(actorSystem, postTimeout, computationTimeout) + case _ => + new InteractionManagerLocal() + }, readJournal = PersistenceQuery(actorSystem) .readJournalFor[BakerPersistenceQuery](config.as[String]("baker.actor.read-journal-plugin")) )(actorSystem) diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/ClusterBakerActorProvider.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/ClusterBakerActorProvider.scala index 7e9b4d3c0..004eff171 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/ClusterBakerActorProvider.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/ClusterBakerActorProvider.scala @@ -15,8 +15,8 @@ import com.ing.baker.runtime.akka.actor.process_index.ProcessIndex.ActorMetadata import com.ing.baker.runtime.akka.actor.process_index.ProcessIndexProtocol._ import com.ing.baker.runtime.akka.actor.process_index._ import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManager -import com.ing.baker.runtime.akka.actor.serialization.{BakerSerializable, Encryption} import com.ing.baker.runtime.akka.internal.InteractionManager +import com.ing.baker.runtime.serialization.{BakerSerializable, Encryption} import com.typesafe.scalalogging.LazyLogging import scala.concurrent.duration._ import scala.concurrent.{Await, TimeoutException} @@ -91,10 +91,16 @@ class ClusterBakerActorProvider( override def createProcessIndexActor(interactionManager: InteractionManager, recipeManager: ActorRef)(implicit actorSystem: ActorSystem): ActorRef = { + val roles = Cluster(actorSystem).selfRoles ClusterSharding(actorSystem).start( typeName = "ProcessIndexActor", entityProps = ProcessIndex.props(actorIdleTimeout, Some(retentionCheckInterval), configuredEncryption, interactionManager, recipeManager, ingredientsFilter), - settings = ClusterShardingSettings.create(actorSystem), + settings = { + if(roles.contains("state-node")) + ClusterShardingSettings(actorSystem).withRole("state-node") + else + ClusterShardingSettings(actorSystem) + }, extractEntityId = ClusterBakerActorProvider.entityIdExtractor(nrOfShards), extractShardId = ClusterBakerActorProvider.shardIdExtractor(nrOfShards) ) @@ -108,12 +114,18 @@ class ClusterBakerActorProvider( RecipeManager.props(), terminationMessage = PoisonPill, settings = ClusterSingletonManagerSettings(actorSystem)) + val roles = Cluster(actorSystem).selfRoles actorSystem.actorOf(props = singletonManagerProps, name = recipeManagerName) val singletonProxyProps = ClusterSingletonProxy.props( singletonManagerPath = s"/user/$recipeManagerName", - settings = ClusterSingletonProxySettings(actorSystem)) + settings = { + if (roles.contains("state-node")) + ClusterSingletonProxySettings(actorSystem).withRole("state-node") + else + ClusterSingletonProxySettings(actorSystem) + }) actorSystem.actorOf(props = singletonProxyProps, name = "RecipeManagerProxy") } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/LocalBakerActorProvider.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/LocalBakerActorProvider.scala index 9e6967fcd..04c446f52 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/LocalBakerActorProvider.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/LocalBakerActorProvider.scala @@ -1,12 +1,13 @@ package com.ing.baker.runtime.akka.actor -import akka.actor.{ ActorRef, ActorSystem } +import akka.actor.{ActorRef, ActorSystem} import com.ing.baker.runtime.akka.actor.process_index.ProcessIndex import com.ing.baker.runtime.akka.actor.process_index.ProcessIndex.ActorMetadata -import com.ing.baker.runtime.akka.actor.process_index.ProcessIndexProtocol.{ GetIndex, Index } +import com.ing.baker.runtime.akka.actor.process_index.ProcessIndexProtocol.{GetIndex, Index} import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManager -import com.ing.baker.runtime.akka.actor.serialization.Encryption import com.ing.baker.runtime.akka.internal.InteractionManager +import com.ing.baker.runtime.serialization.Encryption + import scala.concurrent.Await import scala.concurrent.duration._ diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/interaction_scheduling/QuestMandated.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/interaction_scheduling/QuestMandated.scala new file mode 100644 index 000000000..4c68d2814 --- /dev/null +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/interaction_scheduling/QuestMandated.scala @@ -0,0 +1,92 @@ +package com.ing.baker.runtime.akka.actor.interaction_scheduling + +import java.util.UUID + +import akka.actor.{Actor, ActorRef, PoisonPill, Props} +import akka.cluster.pubsub.{DistributedPubSub, DistributedPubSubMediator} +import akka.util.Timeout +import com.ing.baker.runtime.akka.actor.interaction_scheduling.QuestMandated.{ComputationTimeout, PostTimeout, Start} +import com.ing.baker.runtime.scaladsl.IngredientInstance +import org.slf4j.LoggerFactory +import QuestMandated._ +import com.ing.baker.baas.protocol.{ProtocolInteractionExecution, ProtocolPushPullMatching, ProtocolQuestCommit} + +object QuestMandated { + + case object Start + + case object PostTimeout + + case object ComputationTimeout + + def apply(ingredients: Seq[IngredientInstance], interactionName: String, postTimeout: Timeout, computationTimeout: Timeout): Props = + Props(new QuestMandated(UUID.randomUUID(), ingredients, interactionName, postTimeout, computationTimeout)) + + private val log = LoggerFactory.getLogger(classOf[QuestMandated]) +} + +class QuestMandated(uuid: UUID, ingredients: Seq[IngredientInstance], interactionName: String, postTimeout: Timeout, computationTimeout: Timeout) extends Actor { + + val mediator: ActorRef = DistributedPubSub(context.system).mediator + + val pullTopic: String = + ProtocolPushPullMatching.pullTopic(interactionName) + + val pushTopic: String = + ProtocolPushPullMatching.pushTopic(interactionName) + + def push(): Unit = + mediator ! DistributedPubSubMediator.Publish(pushTopic, ProtocolPushPullMatching.Push(self, uuid)) + + def start(): Unit = { + mediator ! DistributedPubSubMediator.Subscribe(pullTopic, self) + push() + context.system.scheduler.scheduleOnce(postTimeout.duration, self, PostTimeout)(context.dispatcher, self) + } + + + def receive: Receive = { + case Start => + log.info(s"$interactionName:$uuid: Starting Quest for interaction") + start() + context.become(running(sender)) + } + + def running(manager: ActorRef): Receive = { + case ProtocolPushPullMatching.Pull(agent) => + // respond with available quest + log.info(s"$interactionName:$uuid: responding to pull of available agent") + agent ! ProtocolPushPullMatching.AvailableQuest(self, uuid) + + case ProtocolQuestCommit.Considering(agent) => + log.info(s"$interactionName:$uuid: Mandate Quest for agent: $agent") + // start the interaction execution protocol by responding with a commit message + agent ! ProtocolQuestCommit.Commit(self, ProtocolInteractionExecution.ExecuteInstance(ingredients)) + context.system.scheduler.scheduleOnce(computationTimeout.duration, self, ComputationTimeout)(context.dispatcher, self) + context.become(committed(manager)) + + case PostTimeout => + log.info(s"$interactionName:$uuid: Timed out on waiting for response when trying to find agent") + manager ! ProtocolInteractionExecution.NoInstanceFound + self ! PoisonPill + } + + def committed(manager: ActorRef): Receive = { + + case message: ProtocolInteractionExecution => + log.info(s"$interactionName:$uuid: Quest executed") + // report and kill himself + manager ! message + self ! PoisonPill + + case ProtocolQuestCommit.Considering(agent) => + log.info(s"$interactionName:$uuid: rejecting other agent: $agent") + agent ! ProtocolQuestCommit.QuestTaken + + case ComputationTimeout => + log.info(s"$interactionName:$uuid: Timed out on waiting for response of agent after commited") + manager ! ProtocolInteractionExecution.InstanceExecutionTimedOut + self ! PoisonPill + } + +} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndex.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndex.scala index 2f47a9b15..eb18e9f36 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndex.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndex.scala @@ -1,31 +1,32 @@ package com.ing.baker.runtime.akka.actor.process_index -import akka.actor.{ ActorRef, NoSerializationVerificationNeeded, Props, Terminated } -import akka.event.{ DiagnosticLoggingAdapter, Logging } +import akka.actor.{ActorRef, NoSerializationVerificationNeeded, Props, Terminated} +import akka.event.{DiagnosticLoggingAdapter, Logging} import akka.pattern.ask -import akka.persistence.{ PersistentActor, RecoveryCompleted } -import cats.data.{ EitherT, OptionT } +import akka.persistence.{PersistentActor, RecoveryCompleted} +import cats.data.{EitherT, OptionT} import cats.effect.IO import cats.instances.future._ -import com.ing.baker.il.petrinet.{ InteractionTransition, Place, Transition } -import com.ing.baker.il.{ CompiledRecipe, EventDescriptor } +import com.ing.baker.il.petrinet.{InteractionTransition, Place, Transition} +import com.ing.baker.il.{CompiledRecipe, EventDescriptor} import com.ing.baker.petrinet.api._ import com.ing.baker.runtime.akka.actor.Util.logging._ import com.ing.baker.runtime.akka.actor.process_index.ProcessIndex._ import com.ing.baker.runtime.akka.actor.process_index.ProcessIndexProtocol._ -import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol.ExceptionStrategy.{ BlockTransition, Continue, RetryWithDelay } +import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol.ExceptionStrategy.{BlockTransition, Continue, RetryWithDelay} import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol._ -import com.ing.baker.runtime.akka.actor.process_instance.{ ProcessInstance, ProcessInstanceRuntime } +import com.ing.baker.runtime.akka.actor.process_instance.{ProcessInstance, ProcessInstanceRuntime} import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProtocol._ -import com.ing.baker.runtime.akka.actor.serialization.{ BakerSerializable, Encryption } -import com.ing.baker.runtime.akka.internal.{ InteractionManager, RecipeRuntime } -import com.ing.baker.runtime.akka.{ namedCachedThreadPool, _ } -import com.ing.baker.runtime.scaladsl.{ EventInstance, RecipeInstanceCreated, RecipeInstanceState } +import com.ing.baker.runtime.akka.internal.{InteractionManager, RecipeRuntime} +import com.ing.baker.runtime.akka.{namedCachedThreadPool, _} +import com.ing.baker.runtime.scaladsl.{EventInstance, RecipeInstanceCreated, RecipeInstanceState} +import com.ing.baker.runtime.serialization.{BakerSerializable, Encryption} import com.ing.baker.types.Value + import scala.collection.mutable import scala.concurrent.duration._ -import scala.concurrent.{ Await, ExecutionContext, Future } -import scala.util.{ Failure, Success } +import scala.concurrent.{Await, ExecutionContext, Future} +import scala.util.{Failure, Success} object ProcessIndex { diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexProto.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexProto.scala index f2595d70c..65ebfe316 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexProto.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexProto.scala @@ -9,9 +9,10 @@ import com.ing.baker.runtime.akka.actor.ClusterBakerActorProvider.GetShardIndex import com.ing.baker.runtime.akka.actor.process_index.ProcessIndex._ import com.ing.baker.runtime.akka.actor.process_index.ProcessIndexProtocol.FireSensoryEventReaction.{NotifyBoth, NotifyOnEvent, NotifyWhenCompleted, NotifyWhenReceived} import com.ing.baker.runtime.akka.actor.process_index.ProcessIndexProtocol.{ProcessEventReceivedResponse, _} -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} -import com.ing.baker.runtime.akka.actor.serialization.protomappings.SensoryEventStatusMappingHelper -import com.ing.baker.runtime.akka.actor.serialization.{ProtoMap, SerializersProvider} +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.protomappings.SensoryEventStatusMappingHelper +import com.ing.baker.runtime.serialization.SerializersProvider +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider} import scala.concurrent.duration.FiniteDuration import scala.util.{Failure, Success, Try} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexProtocol.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexProtocol.scala index ff1f70537..73494b2ed 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexProtocol.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexProtocol.scala @@ -2,9 +2,9 @@ package com.ing.baker.runtime.akka.actor.process_index import akka.actor.ActorRef import com.ing.baker.runtime.akka.actor.process_index.ProcessIndex.ActorMetadata -import com.ing.baker.runtime.akka.actor.serialization.BakerSerializable import com.ing.baker.runtime.scaladsl.{EventInstance, SensoryEventResult} import com.ing.baker.runtime.common.{RejectReason, SensoryEventStatus} +import com.ing.baker.runtime.serialization.BakerSerializable import scala.concurrent.duration.FiniteDuration diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstance.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstance.scala index d4fe81db4..17d7d217f 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstance.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstance.scala @@ -15,8 +15,8 @@ import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol import com.ing.baker.runtime.akka.actor.process_instance.internal.ExceptionStrategy.{Continue, RetryWithDelay} import com.ing.baker.runtime.akka.actor.process_instance.internal._ import com.ing.baker.runtime.akka.actor.process_instance.{ProcessInstanceProtocol => protocol} -import com.ing.baker.runtime.akka.actor.serialization.Encryption import com.ing.baker.runtime.scaladsl.RecipeInstanceState +import com.ing.baker.runtime.serialization.Encryption import com.ing.baker.types.PrimitiveValue import scala.concurrent.ExecutionContext diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceEventSourcing.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceEventSourcing.scala index adc432c23..4fa383530 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceEventSourcing.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceEventSourcing.scala @@ -8,8 +8,8 @@ import akka.NotUsed import akka.actor.{ActorSystem, NoSerializationVerificationNeeded} import akka.persistence.query.scaladsl.CurrentEventsByPersistenceIdQuery import akka.stream.scaladsl.Source -import com.ing.baker.runtime.akka.actor.serialization.Encryption -import com.ing.baker.runtime.akka.actor.serialization.SerializersProvider +import com.ing.baker.runtime.serialization.SerializersProvider +import com.ing.baker.runtime.serialization.Encryption object ProcessInstanceEventSourcing { @@ -92,12 +92,13 @@ object ProcessInstanceEventSourcing { readJournal: CurrentEventsByPersistenceIdQuery, eventSourceFn: T ⇒ (S ⇒ E ⇒ S))(implicit actorSystem: ActorSystem): Source[(Instance[P, T, S], Event), NotUsed] = { - val serializer = new ProcessInstanceSerialization[P, T, S, E](SerializersProvider(actorSystem, null, encryption)) + val serializer = new ProcessInstanceSerialization[P, T, S, E](SerializersProvider(actorSystem, encryption)) val persistentId = ProcessInstance.recipeInstanceId2PersistenceId(processTypeName, recipeInstanceId) val src = readJournal.currentEventsByPersistenceId(persistentId, 0, Long.MaxValue) val eventSource = ProcessInstanceEventSourcing.apply[P, T, S, E](eventSourceFn) + // TODO: remove null value src.scan[(Instance[P, T, S], Event)]((Instance.uninitialized[P, T, S](topology), null.asInstanceOf[Event])) { case ((instance, _), e) ⇒ val serializedEvent = e.event.asInstanceOf[AnyRef] @@ -113,11 +114,12 @@ abstract class ProcessInstanceEventSourcing[P : Identifiable, T : Identifiable, encryption: Encryption, eventSourceFn: T => (S => E => S)) extends PersistentActor { - implicit val system = context.system + protected implicit val system: ActorSystem = context.system - val eventSource = ProcessInstanceEventSourcing.apply[P, T, S, E](eventSourceFn) + protected val eventSource: Instance[P, T, S] => Event => Instance[P, T, S] = + ProcessInstanceEventSourcing.apply[P, T, S, E](eventSourceFn) - private val serializer = new ProcessInstanceSerialization[P, T, S, E](SerializersProvider(system, null, encryption)) + private val serializer = new ProcessInstanceSerialization[P, T, S, E](SerializersProvider(system, encryption)) def onRecoveryCompleted(state: Instance[P, T, S]) @@ -128,7 +130,7 @@ abstract class ProcessInstanceEventSourcing[P : Identifiable, T : Identifiable, private var recoveringState: Instance[P, T, S] = Instance.uninitialized[P, T, S](petriNet) - private def applyToRecoveringState(e: AnyRef) = { + private def applyToRecoveringState(e: AnyRef): Unit = { val deserializedEvent = serializer.deserializeEvent(e)(recoveringState) recoveringState = eventSource(recoveringState)(deserializedEvent) } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceProto.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceProto.scala index e9c7700ec..be7ec4c52 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceProto.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceProto.scala @@ -7,9 +7,9 @@ import cats.syntax.traverse._ import com.ing.baker.petrinet.api.{Id, Marking, MultiSet} import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol._ import com.ing.baker.runtime.akka.actor.process_instance.protobuf.FailureStrategyMessage.StrategyTypeMessage -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} -import com.ing.baker.runtime.akka.actor.serialization.SerializersProvider +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.SerializersProvider +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider} import scalapb.GeneratedMessageCompanion import scala.util.{Failure, Success, Try} diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceProtocol.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceProtocol.scala index 8d0376c38..0572b73fc 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceProtocol.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceProtocol.scala @@ -1,7 +1,7 @@ package com.ing.baker.runtime.akka.actor.process_instance import com.ing.baker.petrinet.api._ -import com.ing.baker.runtime.akka.actor.serialization.BakerSerializable +import com.ing.baker.runtime.serialization.BakerSerializable /** * Describes the messages to and from a PetriNetInstance actor. diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceSerialization.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceSerialization.scala index 07c9f664c..20c77d729 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceSerialization.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceSerialization.scala @@ -1,40 +1,17 @@ package com.ing.baker.runtime.akka.actor.process_instance -import java.security.MessageDigest - import com.ing.baker.petrinet.api._ import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceEventSourcing._ -import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceSerialization._ import com.ing.baker.runtime.akka.actor.process_instance.internal.ExceptionStrategy.{BlockTransition, RetryWithDelay} import com.ing.baker.runtime.akka.actor.process_instance.internal.Instance import com.ing.baker.runtime.akka.actor.process_instance.protobuf.FailureStrategy.StrategyType import com.ing.baker.runtime.akka.actor.process_instance.protobuf._ import com.ing.baker.runtime.akka.actor.protobuf.{ProducedToken, SerializedData} -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ctxFromProto, ctxToProto} -import com.ing.baker.runtime.akka.actor.serialization.SerializersProvider +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto} +import com.ing.baker.runtime.serialization.{SerializersProvider, TokenIdentifier} import scala.util.{Failure, Success} -object ProcessInstanceSerialization { - - /** - * TODO: - * - * This approach is fragile, the identifier function cannot change ever or recovery breaks - * a more robust alternative is to generate the ids and persist them - */ - def tokenIdentifier(tokenValue: Any): Long = tokenValue match { - case null => -1 - case str: String => sha256(str) - case obj => obj.hashCode() - } - - def sha256(str: String) = { - val sha256Digest: MessageDigest = MessageDigest.getInstance("SHA-256") - BigInt(1, sha256Digest.digest(str.getBytes("UTF-8"))).toLong - } -} - /** * This class is responsible for translating the EventSourcing.Event to and from the protobuf.Event * @@ -93,7 +70,7 @@ class ProcessInstanceSerialization[P : Identifiable, T : Identifiable, S, E](pro case (placeId, tokens) ⇒ tokens.toSeq.map { case (value, count) ⇒ ProducedToken( placeId = Some(placeId), - tokenId = Some(tokenIdentifier(value)), + tokenId = Some(TokenIdentifier(value)), count = Some(count), tokenData = serializeObject(value) ) @@ -106,7 +83,7 @@ class ProcessInstanceSerialization[P : Identifiable, T : Identifiable, S, E](pro case (placeId, tokens) ⇒ tokens.toSeq.map { case (value, count) ⇒ protobuf.ConsumedToken( placeId = Some(placeId), - tokenId = Some(tokenIdentifier(value)), + tokenId = Some(TokenIdentifier(value)), count = Some(count) ) } @@ -117,8 +94,8 @@ class ProcessInstanceSerialization[P : Identifiable, T : Identifiable, S, E](pro case (accumulated, protobuf.ConsumedToken(Some(placeId), Some(tokenId), Some(count))) ⇒ val place = instance.petriNet.places.getById(placeId, "place in the petrinet") val keySet = instance.marking(place).keySet - val value = keySet.find(e ⇒ tokenIdentifier(e) == tokenId).getOrElse( - throw new IllegalStateException(s"Missing token with id $tokenId, keySet = ${keySet.map(tokenIdentifier)}") + val value = keySet.find(e ⇒ TokenIdentifier(e) == tokenId).getOrElse( + throw new IllegalStateException(s"Missing token with id $tokenId, keySet = ${keySet.map(TokenIdentifier(_))}") ) accumulated.add(placeId, value, count) case _ ⇒ throw new IllegalStateException("Missing data in persisted ConsumedToken") diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManager.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManager.scala index da55a4ca8..b094687e2 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManager.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManager.scala @@ -5,7 +5,7 @@ import akka.persistence.PersistentActor import com.ing.baker.il.CompiledRecipe import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManager._ import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProtocol._ -import com.ing.baker.runtime.akka.actor.serialization.BakerSerializable +import com.ing.baker.runtime.serialization.BakerSerializable import scala.collection.mutable diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProto.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProto.scala index 8ae106721..a5137d3d9 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProto.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProto.scala @@ -5,9 +5,9 @@ import cats.instances.try_._ import cats.syntax.traverse._ import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManager.RecipeAdded import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProtocol._ -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{versioned, ctxFromProto, ctxToProto} -import com.ing.baker.runtime.akka.actor.serialization.SerializersProvider +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto, versioned} +import com.ing.baker.runtime.serialization.SerializersProvider +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider} import scala.util.Try diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProtocol.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProtocol.scala index f64fdfb73..0d84f1c65 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProtocol.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProtocol.scala @@ -1,7 +1,7 @@ package com.ing.baker.runtime.akka.actor.recipe_manager import com.ing.baker.il.CompiledRecipe -import com.ing.baker.runtime.akka.actor.serialization.BakerSerializable +import com.ing.baker.runtime.serialization.BakerSerializable sealed trait RecipeManagerProtocol extends BakerSerializable diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/BakerSerializable.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/BakerSerializable.scala deleted file mode 100644 index 1408c9119..000000000 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/BakerSerializable.scala +++ /dev/null @@ -1,3 +0,0 @@ -package com.ing.baker.runtime.akka.actor.serialization - -trait BakerSerializable diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/BakerTypedProtobufSerializer.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/BakerTypedProtobufSerializer.scala index ebbc293bd..abb8955d4 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/BakerTypedProtobufSerializer.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/actor/serialization/BakerTypedProtobufSerializer.scala @@ -1,40 +1,42 @@ package com.ing.baker.runtime.akka.actor.serialization import akka.actor.ExtendedActorSystem -import akka.serialization.SerializerWithStringManifest +import com.ing.baker.il import com.ing.baker.runtime.akka.actor.ClusterBakerActorProvider import com.ing.baker.runtime.akka.actor.process_index.ProcessIndexProto._ -import com.ing.baker.runtime.akka.actor.process_index.{ ProcessIndex, ProcessIndexProtocol } +import com.ing.baker.runtime.akka.actor.process_index.{ProcessIndex, ProcessIndexProtocol} import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProto._ import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProto._ -import com.ing.baker.runtime.akka.actor.recipe_manager.{ RecipeManager, RecipeManagerProtocol } -import com.ing.baker.runtime.akka.actor.serialization.BakerTypedProtobufSerializer.BinarySerializable -import com.ing.baker.{ il, runtime } -import com.typesafe.scalalogging.LazyLogging -import scala.reflect.ClassTag -import scala.util.Try +import com.ing.baker.runtime.akka.actor.recipe_manager.{RecipeManager, RecipeManagerProtocol} +import com.ing.baker.runtime.scaladsl.{EventInstance, RecipeEventMetadata, RecipeInstanceState} +import com.ing.baker.runtime.serialization.TypedProtobufSerializer.{BinarySerializable, forType} +import com.ing.baker.runtime.serialization.{ProtoMap, SerializersProvider, TypedProtobufSerializer} object BakerTypedProtobufSerializer { + def entries(ev0: SerializersProvider): List[BinarySerializable] = { + implicit val ev = ev0 + commonEntries ++ processIndexEntries ++ processInstanceEntries ++ recipeManagerEntries + } + /** Hardcoded serializerId for this serializer. This should not conflict with other serializers. * Values from 0 to 40 are reserved for Akka internal usage. */ val identifier = 101 - def entries(implicit ev0: SerializersProvider): List[BinarySerializable] = - commonEntries ++ processIndexEntries ++ processInstanceEntries ++ recipeManagerEntries - def commonEntries(implicit ev0: SerializersProvider): List[BinarySerializable] = List( forType[com.ing.baker.types.Value] .register("baker.types.Value"), forType[com.ing.baker.types.Type] .register("baker.types.Type"), - forType[runtime.scaladsl.EventInstance] + forType[EventInstance] .register("core.RuntimeEvent"), - forType[runtime.scaladsl.RecipeInstanceState] + forType[RecipeInstanceState] .register("core.ProcessState"), + forType[RecipeEventMetadata] + .register("core.RecipeEventMetadata"), forType[il.CompiledRecipe] .register("il.CompiledRecipe") ) @@ -127,12 +129,12 @@ object BakerTypedProtobufSerializer { .register("ProcessInstanceProtocol.TransitionFailed"), forType[ProcessInstanceProtocol.TransitionFired] .register("ProcessInstanceProtocol.TransitionFired"), - forType[runtime.akka.actor.process_instance.protobuf.TransitionFired] - .register("TransitionFired")(ProtoMap.identityProtoMap(runtime.akka.actor.process_instance.protobuf.TransitionFired)), - forType[runtime.akka.actor.process_instance.protobuf.TransitionFailed] - .register("TransitionFailed")(ProtoMap.identityProtoMap(runtime.akka.actor.process_instance.protobuf.TransitionFailed)), - forType[runtime.akka.actor.process_instance.protobuf.Initialized] - .register("Initialized")(ProtoMap.identityProtoMap(runtime.akka.actor.process_instance.protobuf.Initialized)) + forType[com.ing.baker.runtime.akka.actor.process_instance.protobuf.TransitionFired] + .register("TransitionFired")(ProtoMap.identityProtoMap(com.ing.baker.runtime.akka.actor.process_instance.protobuf.TransitionFired)), + forType[com.ing.baker.runtime.akka.actor.process_instance.protobuf.TransitionFailed] + .register("TransitionFailed")(ProtoMap.identityProtoMap(com.ing.baker.runtime.akka.actor.process_instance.protobuf.TransitionFailed)), + forType[com.ing.baker.runtime.akka.actor.process_instance.protobuf.Initialized] + .register("Initialized")(ProtoMap.identityProtoMap(com.ing.baker.runtime.akka.actor.process_instance.protobuf.Initialized)) ) def recipeManagerEntries(implicit ev0: SerializersProvider): List[BinarySerializable] = @@ -154,90 +156,6 @@ object BakerTypedProtobufSerializer { forType[RecipeManager.RecipeAdded] .register("RecipeManager.RecipeAdded") ) - - def forType[A <: AnyRef](implicit tag: ClassTag[A]): RegisterFor[A] = new RegisterFor[A](tag) - - class RegisterFor[A <: AnyRef](classTag: ClassTag[A]) { - - def register[P <: scalapb.GeneratedMessage with scalapb.Message[P]](implicit protoMap: ProtoMap[A, P]): BinarySerializable = - register[P](None) - - def register[P <: scalapb.GeneratedMessage with scalapb.Message[P]](overrideName: String)(implicit protoMap: ProtoMap[A, P]): BinarySerializable = - register[P](Some(overrideName)) - - def register[P <: scalapb.GeneratedMessage with scalapb.Message[P]](overrideName: Option[String])(implicit protoMap: ProtoMap[A, P]): BinarySerializable = { - new BinarySerializable { - - override type Type = A - - override val tag: Class[_] = classTag.runtimeClass - - override val manifest: String = overrideName.getOrElse(classTag.runtimeClass.getName) - - override def toBinary(a: Type): Array[Byte] = protoMap.toByteArray(a) - - override def fromBinary(binary: Array[Byte]): Try[Type] = protoMap.fromByteArray(binary) - } - } - } - - trait BinarySerializable { - - type Type <: AnyRef - - val tag: Class[_] - - def manifest: String - - def toBinary(a: Type): Array[Byte] - - // The actor resolver is commented for future Akka Typed implementation - def fromBinary(binary: Array[Byte] /*, resolver: ActorRefResolver*/): Try[Type] - - def isInstance(o: AnyRef): Boolean = - tag.isInstance(o) - - def unsafeToBinary(a: AnyRef): Array[Byte] = - toBinary(a.asInstanceOf[Type]) - - // The actor resolver is commented for future Akka Typed implementation - def fromBinaryAnyRef(binary: Array[Byte] /*, resolver: ActorRefResolver*/): Try[AnyRef] = - fromBinary(binary) - - } - -} - -class BakerTypedProtobufSerializer(system: ExtendedActorSystem) extends SerializerWithStringManifest with LazyLogging { - - implicit def serializersProvider: SerializersProvider = SerializersProvider(system, system.provider) - - lazy val entriesMem: List[BinarySerializable] = BakerTypedProtobufSerializer.entries - - override def identifier: Int = - BakerTypedProtobufSerializer.identifier - - override def manifest(o: AnyRef): String = { - entriesMem - .find(_.isInstance(o)) - .map(_.manifest) - .getOrElse(throw new IllegalStateException(s"Unsupported object of type: ${o.getClass}")) - } - - override def toBinary(o: AnyRef): Array[Byte] = - entriesMem - .find(_.isInstance(o)) - .map(_.unsafeToBinary(o)) - .getOrElse(throw new IllegalStateException(s"Unsupported object of type: ${o.getClass}")) - - override def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = - entriesMem - .find(_.manifest == manifest) - .map(_.fromBinaryAnyRef(bytes)) - .getOrElse(throw new IllegalStateException(s"Unsupported object with manifest $manifest")) - .fold( - {e => logger.error(s"Failed to deserialize bytes with manifest $manifest", e); throw e}, - identity - ) } +class BakerTypedProtobufSerializer(system: ExtendedActorSystem) extends TypedProtobufSerializer(system, BakerTypedProtobufSerializer.identifier, BakerTypedProtobufSerializer.entries) diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/internal/InteractionManager.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/internal/InteractionManager.scala index 19a0c9a82..97b06ebc9 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/internal/InteractionManager.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/internal/InteractionManager.scala @@ -2,10 +2,48 @@ package com.ing.baker.runtime.akka.internal import java.util.concurrent.ConcurrentHashMap +import akka.actor.ActorSystem +import akka.pattern.ask +import akka.util.Timeout +import com.ing.baker.baas.protocol.ProtocolInteractionExecution import com.ing.baker.il.petrinet.InteractionTransition -import com.ing.baker.runtime.scaladsl.InteractionInstance +import com.ing.baker.runtime.akka.actor.interaction_scheduling.QuestMandated.Start +import com.ing.baker.runtime.akka.actor.interaction_scheduling.QuestMandated +import com.ing.baker.runtime.scaladsl.{EventInstance, IngredientInstance, InteractionInstance} import scala.compat.java8.FunctionConverters._ +import scala.concurrent.Future + + + +trait InteractionManager { + def hasImplementation(interaction: InteractionTransition): Boolean + + def executeImplementation(interaction: InteractionTransition, input: Seq[IngredientInstance]): Future[Option[EventInstance]] + + def addImplementation(interaction: InteractionInstance): Unit +} + +class InteractionManagerDis(system: ActorSystem, postTimeout: Timeout, computationTimeout: Timeout) extends InteractionManager { + + import system.dispatcher + + override def executeImplementation(interaction: InteractionTransition, input: Seq[IngredientInstance]): Future[Option[EventInstance]] = { + val a = system.actorOf(QuestMandated(input, interaction.originalInteractionName, postTimeout, computationTimeout)) + a.ask(Start)(Timeout.durationToTimeout(postTimeout.duration + computationTimeout.duration)).flatMap { + case ProtocolInteractionExecution.InstanceExecutedSuccessfully(result) => Future.successful(result) + case ProtocolInteractionExecution.InstanceExecutionFailed() => Future.failed(new RuntimeException("Remote execution of interaction failed")) + case ProtocolInteractionExecution.NoInstanceFound => executeImplementation(interaction, input) + case ProtocolInteractionExecution.InstanceExecutionTimedOut() => Future.failed(new RuntimeException("Execution of interaction timed out")) + case ProtocolInteractionExecution.InvalidExecution() => Future.failed(new RuntimeException("Execution of interaction failed because of invalid ingredient input")) + } + } + + override def hasImplementation(interaction: InteractionTransition): Boolean = true + + override def addImplementation(interaction: InteractionInstance): Unit = + throw new NotImplementedError("addImplementation is not implemented for the distributed interaction manager, please deploy interactions using the baas-node-interaction library") +} /** * The InteractionManager is responsible for all implementation of interactions. @@ -13,7 +51,7 @@ import scala.compat.java8.FunctionConverters._ * * @param interactionImplementations All */ -class InteractionManager(private var interactionImplementations: Seq[InteractionInstance] = Seq.empty) { +class InteractionManagerLocal(private var interactionImplementations: Seq[InteractionInstance] = Seq.empty) extends InteractionManager { private val implementationCache: ConcurrentHashMap[InteractionTransition, InteractionInstance] = new ConcurrentHashMap[InteractionTransition, InteractionInstance] @@ -53,6 +91,16 @@ class InteractionManager(private var interactionImplementations: Seq[Interaction * @param interaction The interaction to check * @return An option containing the implementation if available */ - def getImplementation(interaction: InteractionTransition): Option[InteractionInstance] = + private[internal] def getImplementation(interaction: InteractionTransition): Option[InteractionInstance] = Option(implementationCache.computeIfAbsent(interaction, (findInteractionImplementation _).asJava)) + + def hasImplementation(interaction: InteractionTransition): Boolean = + getImplementation(interaction).isDefined + + override def executeImplementation(interaction: InteractionTransition, input: Seq[IngredientInstance]): Future[Option[EventInstance]] = { + this.getImplementation(interaction) match { + case Some(implementation) => implementation.run(input) + case None => Future.failed(new FatalInteractionException("No implementation available for interaction")) + } + } } diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/internal/RecipeRuntime.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/internal/RecipeRuntime.scala index 454f5c2c7..9e6f96aa5 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/internal/RecipeRuntime.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/internal/RecipeRuntime.scala @@ -194,10 +194,6 @@ class RecipeRuntime(recipe: CompiledRecipe, interactionManager: InteractionManag try { - // obtain the interaction implementation - val implementation = interactionManager.getImplementation(interaction).getOrElse { - throw new FatalInteractionException("No implementation available for interaction") - } // create the interaction input val input = createInteractionInput(interaction, processState) @@ -208,7 +204,7 @@ class RecipeRuntime(recipe: CompiledRecipe, interactionManager: InteractionManag eventStream.publish(InteractionStarted(timeStarted, recipe.name, recipe.recipeId, processState.recipeInstanceId, interaction.interactionName)) // executes the interaction and obtain the (optional) output event - implementation.execute(input).map { interactionOutput => + interactionManager.executeImplementation(interaction, input).map { interactionOutput => // validates the event, throws a FatalInteraction exception if invalid RecipeRuntime.validateInteractionOutput(interaction, interactionOutput).foreach { validationError => diff --git a/runtime/src/main/scala/com/ing/baker/runtime/akka/package.scala b/runtime/src/main/scala/com/ing/baker/runtime/akka/package.scala index b0aa8889e..0a34163b6 100644 --- a/runtime/src/main/scala/com/ing/baker/runtime/akka/package.scala +++ b/runtime/src/main/scala/com/ing/baker/runtime/akka/package.scala @@ -31,29 +31,6 @@ package object akka { def toScala: FiniteDuration = FiniteDuration(duration.toMillis, TimeUnit.MILLISECONDS) } - /** - * Mockito breaks reflection when mocking classes, for example: - * - * interface A { } - * class B extends A - * val b = mock[B] - * - * When inspecting b, the fact that it extends from A can no longer be reflected. - * - * Here we obtain the original class that was mocked. - * - * @param clazz The (potentially mocked) class - * @return The original class - */ - def unmock(clazz: Class[_]) = { - - if (clazz.getName.contains("$$EnhancerByMockitoWithCGLIB$$")) { - val originalName: String = clazz.getName.split("\\$\\$EnhancerByMockitoWithCGLIB\\$\\$")(0) - clazz.getClassLoader.loadClass(originalName) - } else - clazz - } - def namedCachedThreadPool(threadNamePrefix: String): ExecutionContext = ExecutionContext.fromExecutorService(Executors.newCachedThreadPool(daemonThreadFactory(threadNamePrefix))) diff --git a/runtime/src/main/scala/com/ing/baker/runtime/common/BakerException.scala b/runtime/src/main/scala/com/ing/baker/runtime/common/BakerException.scala deleted file mode 100644 index 9a582d105..000000000 --- a/runtime/src/main/scala/com/ing/baker/runtime/common/BakerException.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.ing.baker.runtime.common - -sealed abstract class BakerException(message: String = "An exception occurred at Baker", cause: Throwable = null) - extends RuntimeException(message, cause) - -object BakerException { - - class NoSuchProcessException(message: String) extends BakerException(message) - - class ProcessDeletedException(message: String) extends BakerException(message) - - class RecipeValidationException(message: String) extends BakerException(message) - - class ImplementationsException(message: String) extends BakerException(message) -} diff --git a/runtime/src/test/java/com/ing/baker/BakerTest.java b/runtime/src/test/java/com/ing/baker/BakerTest.java index eb69bafeb..d6eea421d 100644 --- a/runtime/src/test/java/com/ing/baker/BakerTest.java +++ b/runtime/src/test/java/com/ing/baker/BakerTest.java @@ -5,6 +5,7 @@ import com.ing.baker.compiler.JavaCompiledRecipeTest; import com.ing.baker.compiler.RecipeCompiler; import com.ing.baker.il.CompiledRecipe; +import com.ing.baker.runtime.akka.AkkaBaker; import com.ing.baker.runtime.common.BakerException; import com.ing.baker.runtime.common.SensoryEventStatus; import com.ing.baker.runtime.javadsl.*; @@ -56,7 +57,7 @@ public void shouldSetupJBakerWithDefaultActorFramework() throws BakerException, CompiledRecipe compiledRecipe = RecipeCompiler.compileRecipe(JavaCompiledRecipeTest.setupSimpleRecipe()); String recipeInstanceId = UUID.randomUUID().toString(); - Baker jBaker = Baker.akka(config, actorSystem); + Baker jBaker = AkkaBaker.java(config, actorSystem); java.util.Map ingredients = jBaker.addInteractionInstances(implementationsList) .thenCompose(x -> jBaker.addRecipe(compiledRecipe)) .thenCompose(recipeId -> { @@ -80,7 +81,7 @@ public void shouldSetupJBakerWithGivenActorFramework() throws BakerException, Ex assertEquals(compiledRecipe.getValidationErrors().size(), 0); - Baker jBaker = Baker.akka(config, actorSystem); + Baker jBaker = AkkaBaker.java(config, actorSystem); jBaker.addInteractionInstances(implementationsList); String recipeId = jBaker.addRecipe(compiledRecipe).get(); @@ -100,7 +101,7 @@ public void shouldFailWhenMissingImplementations() throws BakerException, Execut exception.expect(ExecutionException.class); CompiledRecipe compiledRecipe = RecipeCompiler.compileRecipe(JavaCompiledRecipeTest.setupComplexRecipe()); - Baker jBaker = Baker.akka(config, actorSystem); + Baker jBaker = AkkaBaker.java(config, actorSystem); jBaker.addRecipe(compiledRecipe).get(); } @@ -108,7 +109,7 @@ public void shouldFailWhenMissingImplementations() throws BakerException, Execut @Test public void shouldExecuteCompleteFlow() throws BakerException, ExecutionException, InterruptedException { - Baker jBaker = Baker.akka(config, actorSystem); + Baker jBaker = AkkaBaker.java(config, actorSystem); List bakerEvents = new LinkedList<>(); jBaker.registerBakerEventListener(bakerEvents::add); diff --git a/runtime/src/test/java/com/ing/baker/Webshop.java b/runtime/src/test/java/com/ing/baker/Webshop.java index f67198caa..9ac3b2e70 100644 --- a/runtime/src/test/java/com/ing/baker/Webshop.java +++ b/runtime/src/test/java/com/ing/baker/Webshop.java @@ -10,6 +10,7 @@ import com.ing.baker.recipe.javadsl.Interaction; import com.ing.baker.recipe.javadsl.InteractionFailureStrategy; import com.ing.baker.recipe.javadsl.Recipe; +import com.ing.baker.runtime.akka.AkkaBaker; import com.ing.baker.runtime.javadsl.Baker; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -151,7 +152,7 @@ public void testWebshop() throws ExecutionException, InterruptedException { when(validateOrderMock.apply(any(), any())).thenReturn(new ValidateOrder.Valid()); ActorSystem system = ActorSystem.create("webshop"); - Baker baker = Baker.akka(config, system); + Baker baker = AkkaBaker.java(config, system); baker.addInteractionInstances(ImmutableList.of(shipGoodsMock, sendInvoiceMock, manufactureGoodsMock, validateOrderMock)); diff --git a/runtime/src/test/resources/application.conf b/runtime/src/test/resources/application.conf index e2f6863b6..57233a150 100644 --- a/runtime/src/test/resources/application.conf +++ b/runtime/src/test/resources/application.conf @@ -27,6 +27,8 @@ akka { logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" } +baker.interaction-manager = local + inmemory-read-journal { write-plugin = "inmemory-journal" offset-mode = "sequence" diff --git a/runtime/src/test/scala/com/ing/baker/BakerRuntimeTestBase.scala b/runtime/src/test/scala/com/ing/baker/BakerRuntimeTestBase.scala index 0d42c5459..b219438df 100644 --- a/runtime/src/test/scala/com/ing/baker/BakerRuntimeTestBase.scala +++ b/runtime/src/test/scala/com/ing/baker/BakerRuntimeTestBase.scala @@ -12,11 +12,20 @@ import com.ing.baker.recipe.{CaseClassIngredient, common} import com.ing.baker.runtime.scaladsl.{Baker, EventInstance, InteractionInstance} import com.ing.baker.types.{Converters, Value} import com.typesafe.config.{Config, ConfigFactory} +import java.nio.file.Paths +import java.util.UUID + +import com.ing.baker.recipe.common.Recipe +import com.ing.baker.runtime.akka.AkkaBaker +import com.ing.baker.recipe.TestRecipe.{fireTwoEventsInteraction, _} +import com.ing.baker.recipe.{CaseClassIngredient, common} +import com.ing.baker.runtime.scaladsl.{Baker, EventInstance, InteractionInstance} +import com.ing.baker.types.{Converters, Value} +import com.typesafe.config.{Config, ConfigFactory} import org.mockito.Matchers._ import org.mockito.Mockito._ import org.scalatest._ import org.scalatestplus.mockito.MockitoSugar - import scala.concurrent.Future import scala.concurrent.duration._ import scala.language.postfixOps @@ -204,9 +213,9 @@ trait BakerRuntimeTestBase setupBakerWithRecipe(recipe, mockImplementations)(actorSystem) } - protected def setupBakerWithRecipe(recipe: common.Recipe, implementations: Seq[InteractionInstance]) + protected def setupBakerWithRecipe(recipe: Recipe, implementations: Seq[InteractionInstance]) (implicit actorSystem: ActorSystem): Future[(Baker, String)] = { - val baker = Baker.akka(ConfigFactory.load(), actorSystem) + val baker = AkkaBaker(ConfigFactory.load(), actorSystem) baker.addInteractionInstances(implementations).flatMap { _ => baker.addRecipe(RecipeCompiler.compileRecipe(recipe)).map(baker -> _)(actorSystem.dispatcher) } @@ -214,7 +223,7 @@ trait BakerRuntimeTestBase protected def setupBakerWithNoRecipe()(implicit actorSystem: ActorSystem): Future[Baker] = { setupMockResponse() - val baker = Baker.akka(ConfigFactory.load(), actorSystem) + val baker = AkkaBaker(ConfigFactory.load(), actorSystem) baker.addInteractionInstances(mockImplementations).map { _ => baker } } diff --git a/runtime/src/test/scala/com/ing/baker/runtime/ExamplesSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/ExamplesSpec.scala index 060d995c3..8b3e90e51 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/ExamplesSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/ExamplesSpec.scala @@ -7,6 +7,9 @@ import com.ing.baker.runtime.ScalaDSLRuntime._ import com.ing.baker.runtime.scaladsl.Baker import com.typesafe.config.ConfigFactory import java.util.UUID + +import com.ing.baker.runtime.akka.AkkaBaker + import scala.concurrent.Future class ExamplesSpec extends BakerRuntimeTestBase { @@ -63,7 +66,7 @@ class ExamplesSpec extends BakerRuntimeTestBase { val implementations = Seq(validateOrderImpl, manufactureGoodsImpl, sendInvoiceImpl, shipGoodsImpl) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) for { _ <- Future.traverse(implementations)(baker.addInteractionInstance) diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/BakerExecutionSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/BakerExecutionSpec.scala index 9fd1c1187..9f0b33271 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/BakerExecutionSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/BakerExecutionSpec.scala @@ -16,9 +16,9 @@ import com.ing.baker.recipe.common.InteractionFailureStrategy.FireEventAfterFail import com.ing.baker.recipe.scaladsl.{Event, Ingredient, Interaction, Recipe} import com.ing.baker.runtime.common.BakerException._ import com.ing.baker.runtime.common._ -import com.ing.baker.runtime.scaladsl.{Baker, EventInstance, InteractionInstance} +import com.ing.baker.runtime.scaladsl.{Baker, EventInstance, InteractionInstance, RecipeEventMetadata} import com.ing.baker.types.{CharArray, Int32, PrimitiveValue} -import com.typesafe.config.ConfigFactory +import com.typesafe.config.{Config, ConfigFactory} import org.mockito.Matchers.{eq => mockitoEq, _} import org.mockito.Mockito._ import org.mockito.invocation.InvocationOnMock @@ -70,7 +70,7 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { for { exception <- Future.successful { intercept[IllegalArgumentException] { - Baker.akka(config, setupActorSystem) + AkkaBaker(config, setupActorSystem) } } _ <- setupActorSystem.terminate() @@ -92,7 +92,7 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { val setupActorSystem = ActorSystem("setup-actor-system", config) for { exception <- Future.successful { - intercept[MalformedURLException](Baker.akka(config, setupActorSystem)) + intercept[MalformedURLException](AkkaBaker(config, setupActorSystem)) } _ <- setupActorSystem.terminate() } yield assert(exception.getMessage contains "wrong-address") @@ -111,7 +111,7 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { val setupActorSystem = ActorSystem("setup-actor-system", config) for { exception <- Future.successful { - intercept[IllegalArgumentException](Baker.akka(config, setupActorSystem)) + intercept[IllegalArgumentException](AkkaBaker(config, setupActorSystem)) } _ <- setupActorSystem.terminate() } yield assert(exception.getMessage contains "No default service discovery implementation configured in `akka.discovery.method`") @@ -128,12 +128,12 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { } yield succeed } - "throw an IllegalArgumentException if baking a process with the same identifier twice" in { + "throw an ProcessAlreadyExistsException if baking a process with the same identifier twice" in { for { (baker, recipeId) <- setupBakerWithRecipe("DuplicateIdentifierRecipe") id = UUID.randomUUID().toString _ <- baker.bake(recipeId, id) - _ <- recoverToSucceededIf[IllegalArgumentException] { + _ <- recoverToSucceededIf[ProcessAlreadyExistsException] { baker.bake(recipeId, id) } } yield succeed @@ -164,12 +164,12 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { } } - "throw a IllegalArgumentException if the event fired is not a valid sensory event" in { + "throw a IllegalEventException if the event fired is not a valid sensory event" in { for { (baker, recipeId) <- setupBakerWithRecipe("NonExistingProcessEventTest") recipeInstanceId = UUID.randomUUID().toString _ <- baker.bake(recipeId, recipeInstanceId) - intercepted <- recoverToExceptionIf[IllegalArgumentException] { + intercepted <- recoverToExceptionIf[IllegalEventException] { baker.fireEventAndResolveWhenCompleted(recipeInstanceId, EventInstance.unsafeFrom(SomeNotDefinedEvent("bla"))) } _ = intercepted.getMessage should startWith("No event with name 'SomeNotDefinedEvent' found in recipe 'NonExistingProcessEventTest") @@ -197,7 +197,55 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { "interactionOneOriginalIngredient" -> interactionOneIngredientValue) } - "correctly notify on event" in { + "execute an interaction when its ingredient is provided in cluster" in { + val recipe = + Recipe("IngredientProvidedRecipeCluster") + .withInteraction(interactionOne) + .withSensoryEvent(initialEvent) + + + val config: Config = ConfigFactory.parseString( + """ + |include "baker.conf" + | + |akka { + | actor { + | provider = "cluster" + | } + | remote { + | log-remote-lifecycle-events = off + | netty.tcp { + | hostname = "127.0.0.1" + | port = 2555 + | } + | } + | + | cluster { + | seed-nodes = ["akka.tcp://remoteTest@127.0.0.1:2555"] + | auto-down-unreachable-after = 10s + | } + |} + """.stripMargin).withFallback(ConfigFactory.load()) + + val baker = AkkaBaker(config, ActorSystem.apply("remoteTest", config)) + + for { + _ <- baker.addInteractionInstances(mockImplementations) + recipeId <- baker.addRecipe(RecipeCompiler.compileRecipe(recipe)) + _ = when(testInteractionOneMock.apply(anyString(), anyString())).thenReturn(Future.successful(InteractionOneSuccessful(interactionOneIngredientValue))) + recipeInstanceId = UUID.randomUUID().toString + _ <- baker.bake(recipeId, recipeInstanceId) + _ <- baker.fireEventAndResolveWhenCompleted(recipeInstanceId, EventInstance.unsafeFrom(EventInstance.unsafeFrom(InitialEvent(initialIngredientValue)))) + _ = verify(testInteractionOneMock).apply(recipeInstanceId.toString, "initialIngredient") + state <- baker.getRecipeInstanceState(recipeInstanceId) + } yield + state.ingredients shouldBe + ingredientMap( + "initialIngredient" -> initialIngredientValue, + "interactionOneOriginalIngredient" -> interactionOneIngredientValue) + } + + "Correctly notify on event" in { val sensoryEvent = Event( name = "sensory-event", @@ -511,7 +559,7 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { .withPredefinedIngredients(("missingJavaOptional", ingredientValue))) .withSensoryEvent(initialEvent) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) for { _ <- baker.addInteractionInstances(mockImplementations) @@ -550,7 +598,7 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { } "notify a registered event listener of events" in { - val listenerMock = mock[(String, EventInstance) => Unit] + val listenerMock = mock[(RecipeEventMetadata, EventInstance) => Unit] when(testInteractionOneMock.apply(anyString(), anyString())).thenReturn(Future.successful(InteractionOneSuccessful(interactionOneIngredientValue))) val recipe = Recipe("EventListenerRecipe") @@ -563,8 +611,8 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { recipeInstanceId = UUID.randomUUID().toString _ <- baker.bake(recipeId, recipeInstanceId) _ <- baker.fireEventAndResolveWhenCompleted(recipeInstanceId, EventInstance.unsafeFrom(InitialEvent(initialIngredientValue))) - _ = verify(listenerMock).apply(mockitoEq(recipeInstanceId.toString), argThat(new RuntimeEventMatcher(EventInstance.unsafeFrom(InitialEvent(initialIngredientValue))))) - _ = verify(listenerMock).apply(mockitoEq(recipeInstanceId.toString), argThat(new RuntimeEventMatcher(EventInstance.unsafeFrom(InteractionOneSuccessful(interactionOneIngredientValue))))) + _ = verify(listenerMock).apply(mockitoEq(RecipeEventMetadata(recipeId, recipe.name, recipeInstanceId.toString)), argThat(new RuntimeEventMatcher(EventInstance.unsafeFrom(InitialEvent(initialIngredientValue))))) + _ = verify(listenerMock).apply(mockitoEq(RecipeEventMetadata(recipeId, recipe.name, recipeInstanceId.toString)), argThat(new RuntimeEventMatcher(EventInstance.unsafeFrom(InteractionOneSuccessful(interactionOneIngredientValue))))) } yield succeed } @@ -920,9 +968,6 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { _ <- baker.bake(recipeId, recipeInstanceId) //Handle first event _ <- baker.fireEventAndResolveWhenCompleted(recipeInstanceId, EventInstance.unsafeFrom(InitialEvent(initialIngredientValue))) - _ <- Future { - Thread.sleep(50) - } state <- baker.getRecipeInstanceState(recipeInstanceId) } yield state.eventNames should contain(interactionOne.retryExhaustedEventName) } @@ -941,9 +986,6 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { _ <- baker.bake(recipeId, recipeInstanceId) //Handle first event _ <- baker.fireEventAndResolveWhenCompleted(recipeInstanceId, EventInstance.unsafeFrom(InitialEvent(initialIngredientValue))) - _ <- Future { - Thread.sleep(50) - } //Since the defaultEventExhaustedName is set the retryExhaustedEventName of interactionOne will be picked. state <- baker.getRecipeInstanceState(recipeInstanceId) } yield state.eventNames should not contain interactionOne.retryExhaustedEventName @@ -960,7 +1002,7 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { for { (baker, recipeId) <- setupBakerWithRecipe(recipe, mockImplementations) - listenerMock = mock[(String, EventInstance) => Unit] + listenerMock = mock[(RecipeEventMetadata, EventInstance) => Unit] _ <- baker.registerEventListener("ImmediateFailureEvent", listenerMock) recipeInstanceId = UUID.randomUUID().toString @@ -968,11 +1010,8 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { //Handle first event _ <- baker.fireEventAndResolveWhenCompleted(recipeInstanceId, EventInstance.unsafeFrom(InitialEvent(initialIngredientValue))) - _ <- Future { - Thread.sleep(50) - } - _ = verify(listenerMock).apply(mockitoEq(recipeInstanceId.toString), argThat(new RuntimeEventMatcher(EventInstance.unsafeFrom(InitialEvent(initialIngredientValue))))) - _ = verify(listenerMock).apply(mockitoEq(recipeInstanceId.toString), argThat(new RuntimeEventMatcher(EventInstance(interactionOne.retryExhaustedEventName, Map.empty)))) + _ = verify(listenerMock).apply(mockitoEq(RecipeEventMetadata(recipeId, recipe.name, recipeInstanceId.toString)), argThat(new RuntimeEventMatcher(EventInstance.unsafeFrom(InitialEvent(initialIngredientValue))))) + _ = verify(listenerMock).apply(mockitoEq(RecipeEventMetadata(recipeId, recipe.name, recipeInstanceId.toString)), argThat(new RuntimeEventMatcher(EventInstance(interactionOne.retryExhaustedEventName, Map.empty)))) state <- baker.getRecipeInstanceState(recipeInstanceId) } yield state.eventNames should contain(interactionOne.retryExhaustedEventName) @@ -1113,7 +1152,7 @@ class BakerExecutionSpec extends BakerRuntimeTestBase { def second(recipeId: String) = { val system2 = ActorSystem("persistenceTest2", localLevelDBConfig("persistenceTest2")) - val baker2 = Baker.akka(ConfigFactory.load(), system2) + val baker2 = AkkaBaker(ConfigFactory.load(), system2) (for { _ <- baker2.addInteractionInstances(mockImplementations) state <- baker2.getRecipeInstanceState(recipeInstanceId) diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/BakerSetupSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/BakerSetupSpec.scala index 83edec81d..9c26b1dd3 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/BakerSetupSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/BakerSetupSpec.scala @@ -33,7 +33,7 @@ class BakerSetupSpec extends BakerRuntimeTestBase { .withInteraction(interactionOne) .withSensoryEvent(initialEvent)) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) for { _ <- baker.addInteractionInstances(mockImplementations) @@ -46,12 +46,12 @@ class BakerSetupSpec extends BakerRuntimeTestBase { } "providing implementations in a sequence" in { - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) baker.addInteractionInstances(mockImplementations).map(_ => succeed) } "providing an implementation with the class simplename same as the interaction" in { - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) baker.addInteractionInstance(InteractionInstance.unsafeFrom(new implementations.InteractionOne())).map(_ => succeed) } @@ -61,7 +61,7 @@ class BakerSetupSpec extends BakerRuntimeTestBase { .withInteraction(interactionOne.withName("interactionOneRenamed")) .withSensoryEvent(initialEvent) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) for { _ <- baker.addInteractionInstance(InteractionInstance.unsafeFrom(new implementations.InteractionOne())) @@ -75,7 +75,7 @@ class BakerSetupSpec extends BakerRuntimeTestBase { .withInteraction(interactionOne) .withSensoryEvent(initialEvent) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) for { _ <- baker.addInteractionInstance(InteractionInstance.unsafeFrom(new InteractionOneFieldName())) @@ -89,7 +89,7 @@ class BakerSetupSpec extends BakerRuntimeTestBase { .withInteraction(interactionOne) .withSensoryEvent(initialEvent) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) for { _ <- baker.addInteractionInstance(InteractionInstance.unsafeFrom(new InteractionOneInterfaceImplementation())) @@ -102,7 +102,7 @@ class BakerSetupSpec extends BakerRuntimeTestBase { .withInteraction(interactionWithAComplexIngredient) .withSensoryEvent(initialEvent) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) for { _ <- baker.addInteractionInstance(InteractionInstance.unsafeFrom(mock[ComplexIngredientInteraction])) @@ -118,7 +118,7 @@ class BakerSetupSpec extends BakerRuntimeTestBase { .withInteraction(interactionOne) .withSensoryEvent(secondEvent) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) baker.addInteractionInstances(mockImplementations) @@ -133,7 +133,7 @@ class BakerSetupSpec extends BakerRuntimeTestBase { .withInteraction(interactionOne) .withSensoryEvent(initialEvent) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) recoverToExceptionIf[ImplementationsException] { baker.addRecipe(RecipeCompiler.compileRecipe(recipe)) @@ -146,7 +146,7 @@ class BakerSetupSpec extends BakerRuntimeTestBase { .withInteraction(interactionOne) .withSensoryEvent(initialEvent) - val baker = Baker.akka(ConfigFactory.load(), defaultActorSystem) + val baker = AkkaBaker(ConfigFactory.load(), defaultActorSystem) baker.addInteractionInstance(InteractionInstance.unsafeFrom(new InteractionOneWrongApply())) diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/UtilSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/UtilSpec.scala index c25d890e7..314380ad3 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/UtilSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/UtilSpec.scala @@ -18,7 +18,7 @@ class UtilSpec extends AkkaTestBase("UtilSpec") { val futures = fastFutures :+ slowFuture - val collected = Util.collectFuturesWithin(futures, 1 second, system.scheduler) + val collected = Util.collectFuturesWithin(futures, 1.second, system.scheduler) val expectedResult = List.fill(5)(true) diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexSpec.scala index 89d6f1396..4d6c258b2 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_index/ProcessIndexSpec.scala @@ -14,15 +14,16 @@ import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol._ import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProtocol import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProtocol.{AllRecipes, GetAllRecipes, RecipeInformation} -import com.ing.baker.runtime.akka.actor.serialization.Encryption -import com.ing.baker.runtime.akka.internal.InteractionManager +import com.ing.baker.runtime.akka.internal.InteractionManagerLocal import com.ing.baker.runtime.scaladsl.{EventInstance, RecipeInstanceState} +import com.ing.baker.runtime.serialization.Encryption import com.ing.baker.types import com.ing.baker.types.Value import com.typesafe.config.{Config, ConfigFactory} import org.mockito.Mockito import org.mockito.Mockito.when import org.scalatest.concurrent.Eventually +import org.scalatestplus.mockito.MockitoSugar import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll, Matchers, WordSpecLike} import org.scalatestplus.mockito.MockitoSugar import scalax.collection.immutable.Graph @@ -102,7 +103,7 @@ class ProcessIndexSpec extends TestKit(ActorSystem("ProcessIndexSpec", ProcessIn } "delete a process if a retention period is defined, stop command is received" in { - val recipeRetentionPeriod = 500 milliseconds + val recipeRetentionPeriod = 500.milliseconds val processProbe = TestProbe() val recipeManagerProbe = TestProbe() val actorIndex = createActorIndex(processProbe.ref, recipeManagerProbe.ref) @@ -118,7 +119,7 @@ class ProcessIndexSpec extends TestKit(ActorSystem("ProcessIndexSpec", ProcessIn Thread.sleep(recipeRetentionPeriod.toMillis) // inform the index to check for processes to be cleaned up actorIndex ! CheckForProcessesToBeDeleted - processProbe.expectMsg(15 seconds, Stop(delete = true)) + processProbe.expectMsg(15.seconds, Stop(delete = true)) } "Forward the FireTransition command when a valid HandleEvent is sent" in { @@ -177,7 +178,7 @@ class ProcessIndexSpec extends TestKit(ActorSystem("ProcessIndexSpec", ProcessIn "reply with an InvalidEvent rejection message when attempting to fire an event that is now know in the compiledRecipe" in { - val receivePeriodTimeout = 500 milliseconds + val receivePeriodTimeout = 500.milliseconds val petriNetActorProbe = TestProbe("petrinet-probe") val recipeManagerProbe = TestProbe("recipe-manager-probe") @@ -206,7 +207,7 @@ class ProcessIndexSpec extends TestKit(ActorSystem("ProcessIndexSpec", ProcessIn "reply with an InvalidEvent rejection message when attempting to fire an event that does not comply to the recipe" in { - val receivePeriodTimeout = 500 milliseconds + val receivePeriodTimeout = 500.milliseconds val petriNetActorProbe = TestProbe("petrinet-probe") val recipeManagerProbe = TestProbe("recipe-manager-probe") @@ -283,7 +284,7 @@ class ProcessIndexSpec extends TestKit(ActorSystem("ProcessIndexSpec", ProcessIn recipeInstanceIdleTimeout = None, retentionCheckInterval = None, configuredEncryption = Encryption.NoEncryption, - interactionManager = new InteractionManager(), + interactionManager = new InteractionManagerLocal(), recipeManager = recipeManager, Seq.empty) { override def createProcessActor(id: String, compiledRecipe: CompiledRecipe) = petriNetActorRef diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceEventSourcingSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceEventSourcingSpec.scala index 6bcecc828..20a83a3b8 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceEventSourcingSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceEventSourcingSpec.scala @@ -1,6 +1,6 @@ package com.ing.baker.runtime.akka.actor.process_instance -import akka.persistence.inmemory.extension.{ InMemoryJournalStorage, StorageExtension } +import akka.persistence.inmemory.extension.{InMemoryJournalStorage, StorageExtension} import akka.persistence.query.PersistenceQuery import akka.persistence.query.scaladsl._ import akka.stream.ActorMaterializer @@ -13,21 +13,23 @@ import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceEventSou import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceProtocol._ import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceSpec._ import com.ing.baker.runtime.akka.actor.process_instance.dsl._ -import com.ing.baker.runtime.akka.actor.serialization.Encryption.NoEncryption import java.util.UUID + +import com.ing.baker.runtime.serialization.Encryption.NoEncryption import org.scalatest.BeforeAndAfterEach import org.scalatest.Matchers._ + import scala.concurrent.ExecutionContext import scala.concurrent.duration._ class ProcessInstanceEventSourcingSpec extends AkkaTestBase("ProcessQuerySpec") with BeforeAndAfterEach { - implicit val akkaTimout = Timeout(2 seconds) - val timeOut: Duration = akkaTimout.duration + private implicit val akkaTimout: Timeout = Timeout(2 seconds) + private val timeOut: Duration = akkaTimout.duration - implicit def materializer = ActorMaterializer() + private implicit val materializer: ActorMaterializer = ActorMaterializer() - implicit def ec: ExecutionContext = system.dispatcher + private implicit val ec: ExecutionContext = system.dispatcher override protected def beforeEach(): Unit = { // Clean the journal before each test @@ -57,16 +59,16 @@ class ProcessInstanceEventSourcingSpec extends AkkaTestBase("ProcessQuerySpec") instance ! Initialize(p1.markWithN(1), ()) expectMsg(Initialized(p1.markWithN(1), ())) - expectMsgPF(timeOut) {case TransitionFired(_, 1, _, _, _, _, _) ⇒} - expectMsgPF(timeOut) {case TransitionFired(_, 2, _, _, _, _, _) ⇒} + expectMsgPF(timeOut) { case TransitionFired(_, 1, _, _, _, _, _) ⇒ } + expectMsgPF(timeOut) { case TransitionFired(_, 2, _, _, _, _, _) ⇒ } ProcessInstanceEventSourcing.eventsForInstance[Place, Transition, Unit, Unit]( - "test", - recipeInstanceId, - petriNet, - NoEncryption, - readJournal, - t ⇒ eventSourceFunction) + processTypeName = "test", + recipeInstanceId = recipeInstanceId, + topology = petriNet, + encryption = NoEncryption, + readJournal = readJournal, + eventSourceFn = t ⇒ eventSourceFunction) .map(_._2) // Get the event from the tuple .runWith(TestSink.probe) .request(3) @@ -80,7 +82,6 @@ class ProcessInstanceEventSourcingSpec extends AkkaTestBase("ProcessQuerySpec") consumed shouldBe p2.markWithN(1).marshall produced shouldBe p3.markWithN(1).marshall } - } } } diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceSpec.scala index e34322908..4a62a7363 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/process_instance/ProcessInstanceSpec.scala @@ -18,7 +18,7 @@ import com.ing.baker.runtime.akka.actor.process_instance.ProcessInstanceSpec._ import com.ing.baker.runtime.akka.actor.process_instance.dsl._ import com.ing.baker.runtime.akka.actor.process_instance.internal.ExceptionStrategy.RetryWithDelay import com.ing.baker.runtime.akka.actor.process_instance.{ProcessInstanceProtocol => protocol} -import com.ing.baker.runtime.akka.actor.serialization.Encryption.NoEncryption +import com.ing.baker.runtime.serialization.Encryption.NoEncryption import com.ing.baker.runtime.akka.namedCachedThreadPool import org.mockito.Matchers._ import org.mockito.Mockito._ diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProtoSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProtoSpec.scala index 27b966abe..94f0c4a12 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProtoSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/recipe_manager/RecipeManagerProtoSpec.scala @@ -2,8 +2,8 @@ package com.ing.baker.runtime.akka.actor.recipe_manager import com.ing.baker.BakerRuntimeTestBase import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManager.RecipeAdded -import com.ing.baker.runtime.akka.actor.serialization.{Encryption, SerializationSpec, SerializersProvider} - +import com.ing.baker.runtime.akka.actor.serialization.SerializationSpec +import com.ing.baker.runtime.serialization.{Encryption, SerializersProvider} import org.scalatest.TryValues._ class RecipeManagerProtoSpec extends BakerRuntimeTestBase { diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/serialization/EncryptionPropertiesSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/serialization/EncryptionPropertiesSpec.scala index ea7f9a34c..cc85ddaf5 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/serialization/EncryptionPropertiesSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/serialization/EncryptionPropertiesSpec.scala @@ -4,7 +4,7 @@ import org.scalacheck.Gen._ import org.scalacheck.Prop.forAll import org.scalacheck._ import org.scalatest.FunSuite -import com.ing.baker.runtime.akka.actor.serialization.Encryption._ +import com.ing.baker.runtime.serialization.Encryption._ import org.scalatestplus.scalacheck.Checkers class EncryptionPropertiesSpec extends FunSuite with Checkers { diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/serialization/SerializationSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/serialization/SerializationSpec.scala index c95e1a188..a41f94b7f 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/serialization/SerializationSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/actor/serialization/SerializationSpec.scala @@ -17,10 +17,11 @@ import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManager.RecipeAdded import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProto._ import com.ing.baker.runtime.akka.actor.recipe_manager.RecipeManagerProtocol.GetRecipe import com.ing.baker.runtime.akka.actor.recipe_manager.{RecipeManager, RecipeManagerProtocol} -import com.ing.baker.runtime.akka.actor.serialization.Encryption.{AESEncryption, NoEncryption} -import com.ing.baker.runtime.akka.actor.serialization.ProtoMap.{ctxFromProto, ctxToProto} +import com.ing.baker.runtime.serialization.Encryption.{AESEncryption, NoEncryption} +import com.ing.baker.runtime.serialization.ProtoMap.{ctxFromProto, ctxToProto} import com.ing.baker.runtime.common.SensoryEventStatus import com.ing.baker.runtime.scaladsl.{EventInstance, EventMoment, RecipeInstanceState, SensoryEventResult} +import com.ing.baker.runtime.serialization.ProtoMap import com.ing.baker.types.modules.PrimitiveModuleSpec._ import com.ing.baker.types.{Value, _} import com.ing.baker.{AllTypeRecipe, types} diff --git a/runtime/src/test/scala/com/ing/baker/runtime/akka/internal/InteractionManagerSpec.scala b/runtime/src/test/scala/com/ing/baker/runtime/akka/internal/InteractionManagerSpec.scala index 4a1b274a1..7dbf1bfb9 100644 --- a/runtime/src/test/scala/com/ing/baker/runtime/akka/internal/InteractionManagerSpec.scala +++ b/runtime/src/test/scala/com/ing/baker/runtime/akka/internal/InteractionManagerSpec.scala @@ -9,7 +9,7 @@ import org.mockito.Mockito.when import org.scalatest.{Matchers, WordSpecLike} import org.scalatestplus.mockito.MockitoSugar -class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSugar { +class InteractionManagerLocalSpec extends WordSpecLike with Matchers with MockitoSugar { "getImplementation" should { "return Some" when { "an interaction implementation is available" in { @@ -17,7 +17,7 @@ class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSuga when(interactionImplementation.name).thenReturn("InteractionName") when(interactionImplementation.input).thenReturn(Seq(types.Int32)) - val interactionManager: InteractionManager = new InteractionManager(Seq(interactionImplementation)) + val interactionManager: InteractionManagerLocal = new InteractionManagerLocal(Seq(interactionImplementation)) val interactionTransition = mock[InteractionTransition] when(interactionTransition.originalInteractionName).thenReturn("InteractionName") val ingredientDescriptor: IngredientDescriptor = IngredientDescriptor("ingredientName", types.Int32) @@ -35,7 +35,7 @@ class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSuga when(interactionImplementation2.name).thenReturn("InteractionName2") when(interactionImplementation2.input).thenReturn(Seq(types.Int32)) - val interactionManager: InteractionManager = new InteractionManager(Seq(interactionImplementation1, interactionImplementation2)) + val interactionManager: InteractionManagerLocal = new InteractionManagerLocal(Seq(interactionImplementation1, interactionImplementation2)) val interactionTransition = mock[InteractionTransition] when(interactionTransition.originalInteractionName).thenReturn("InteractionName") val ingredientDescriptor: IngredientDescriptor = IngredientDescriptor("ingredientName", types.Int32) @@ -53,7 +53,7 @@ class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSuga when(interactionImplementation2.name).thenReturn("InteractionName") when(interactionImplementation2.input).thenReturn(Seq(types.Int32)) - val interactionManager: InteractionManager = new InteractionManager(Seq(interactionImplementation1, interactionImplementation2)) + val interactionManager: InteractionManagerLocal = new InteractionManagerLocal(Seq(interactionImplementation1, interactionImplementation2)) val interactionTransition = mock[InteractionTransition] when(interactionTransition.originalInteractionName).thenReturn("InteractionName") val ingredientDescriptor: IngredientDescriptor = IngredientDescriptor("ingredientName", types.Int32) @@ -69,7 +69,7 @@ class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSuga when(interactionImplementation.name).thenReturn("InteractionName") when(interactionImplementation.input).thenReturn(Seq(types.Int32)) - val interactionManager: InteractionManager = new InteractionManager(Seq(interactionImplementation)) + val interactionManager: InteractionManagerLocal = new InteractionManagerLocal(Seq(interactionImplementation)) val interactionTransition = mock[InteractionTransition] when(interactionTransition.originalInteractionName).thenReturn("WrongInteractionName") val ingredientDescriptor: IngredientDescriptor = IngredientDescriptor("ingredientName", types.Int32) @@ -83,7 +83,7 @@ class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSuga when(interactionImplementation.name).thenReturn("InteractionName") when(interactionImplementation.input).thenReturn(Seq(types.Int32)) - val interactionManager: InteractionManager = new InteractionManager(Seq(interactionImplementation)) + val interactionManager: InteractionManagerLocal = new InteractionManagerLocal(Seq(interactionImplementation)) val interactionTransition = mock[InteractionTransition] when(interactionTransition.originalInteractionName).thenReturn("InteractionName") val ingredientDescriptor: IngredientDescriptor = IngredientDescriptor("ingredientName", types.CharArray) @@ -97,7 +97,7 @@ class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSuga when(interactionImplementation.name).thenReturn("InteractionName") when(interactionImplementation.input).thenReturn(Seq(types.Int32, types.CharArray)) - val interactionManager: InteractionManager = new InteractionManager(Seq(interactionImplementation)) + val interactionManager: InteractionManagerLocal = new InteractionManagerLocal(Seq(interactionImplementation)) val interactionTransition = mock[InteractionTransition] when(interactionTransition.originalInteractionName).thenReturn("InteractionName") val ingredientDescriptor: IngredientDescriptor = IngredientDescriptor("ingredientName", types.Int32) @@ -111,7 +111,7 @@ class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSuga when(interactionImplementation.name).thenReturn("InteractionName") when(interactionImplementation.input).thenReturn(Seq(types.Int32)) - val interactionManager: InteractionManager = new InteractionManager(Seq(interactionImplementation)) + val interactionManager: InteractionManagerLocal = new InteractionManagerLocal(Seq(interactionImplementation)) val interactionTransition = mock[InteractionTransition] when(interactionTransition.originalInteractionName).thenReturn("InteractionName") val ingredientDescriptor: IngredientDescriptor = IngredientDescriptor("ingredientName", types.Int32) @@ -122,7 +122,7 @@ class InteractionManagerSpec extends WordSpecLike with Matchers with MockitoSuga } "empty interaction seq" in { - val interactionManager: InteractionManager = new InteractionManager(Seq.empty) + val interactionManager: InteractionManagerLocal = new InteractionManagerLocal(Seq.empty) val interactionTransition: InteractionTransition = mock[InteractionTransition] interactionManager.getImplementation(interactionTransition) should equal(None) diff --git a/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverHickUpNodeSpec.scala b/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverHickUpNodeSpec.scala index f47642b21..0a08ae254 100644 --- a/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverHickUpNodeSpec.scala +++ b/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverHickUpNodeSpec.scala @@ -45,8 +45,8 @@ abstract class SplitBrainResolverHickUpNodeSpec(splitBrainResolverConfig: SplitB enterBarrier("cluster-is-up") runOn(nodeA) { - scheduleReachable = Some(system.scheduler.schedule(0 seconds, 2 second, () => statusNodeE(reachable = true))) - scheduleUnreachable = Some(system.scheduler.schedule(1 seconds, 2 second, () => statusNodeE(reachable = false))) + scheduleReachable = Some(system.scheduler.schedule(0.seconds, 2 second, () => statusNodeE(reachable = true))) + scheduleUnreachable = Some(system.scheduler.schedule(1.seconds, 2 second, () => statusNodeE(reachable = false))) } Thread.sleep(10 * 1000) @@ -66,8 +66,8 @@ abstract class SplitBrainResolverHickUpNodeSpec(splitBrainResolverConfig: SplitB runOn(nodeD) { awaitAssert( clusterView.isTerminated should be(true), - 20 seconds, - 1 second + 20.seconds, + 1.second ) enterBarrier("fourth-node-down") } diff --git a/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverNodeCrashSpec.scala b/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverNodeCrashSpec.scala index 27112790f..668f8c23a 100644 --- a/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverNodeCrashSpec.scala +++ b/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverNodeCrashSpec.scala @@ -102,7 +102,7 @@ abstract class SplitBrainResolverNodeCrashSpec(splitBrainResolverConfig: SplitBr awaitAssert( clusterView.isTerminated should be(true), 20 seconds, - 1 second + 1.second ) } @@ -112,7 +112,7 @@ abstract class SplitBrainResolverNodeCrashSpec(splitBrainResolverConfig: SplitBr awaitAssert( clusterView.isTerminated should be(true), 20 seconds, - 1 second + 1.second ) } diff --git a/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverWithNetworkSplitSpec.scala b/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverWithNetworkSplitSpec.scala index b2aadca42..e4e0cbf00 100644 --- a/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverWithNetworkSplitSpec.scala +++ b/split-brain-resolver/src/multi-jvm/scala/com/ing/baker/runtime/akka/actor/downing/SplitBrainResolverWithNetworkSplitSpec.scala @@ -49,7 +49,7 @@ abstract class SplitBrainResolverWithNetworkSplitSpec(splitBrainResolverConfig: awaitAssert( clusterView.isTerminated should be(true), 20 seconds, - 1 second + 1.second ) } @@ -85,7 +85,7 @@ abstract class SplitBrainResolverWithNetworkSplitSpec(splitBrainResolverConfig: awaitAssert( clusterView.isTerminated should be(true), 20 seconds, - 1 second + 1.second ) } @@ -124,7 +124,7 @@ abstract class SplitBrainResolverWithNetworkSplitSpec(splitBrainResolverConfig: awaitAssert( clusterView.isTerminated should be(true), 20 seconds, - 1 second + 1.second ) enterBarrier("unreachable-fourth-node") } @@ -158,7 +158,7 @@ abstract class SplitBrainResolverWithNetworkSplitSpec(splitBrainResolverConfig: awaitAssert( clusterView.isTerminated should be(true), 20 seconds, - 1 second + 1.second ) enterBarrier("split-between-remaining-nodes") }