/** * @param WorkflowMessage $workflowMessage * @return void */ public function handleWorkflowMessage(WorkflowMessage $workflowMessage) { if (!MessageNameUtils::isProcessingCommand($workflowMessage->messageName())) { $this->workflowEngine->dispatch(LogMessage::logUnsupportedMessageReceived($workflowMessage)); } try { if ($workflowMessage->messageType() === MessageNameUtils::COLLECT_DATA) { $processingMessage = $this->handleCollectData($workflowMessage); if (!$processingMessage instanceof ProcessingMessage) { throw new \RuntimeException(sprintf("%s::handleCollectData method returned %s instead of a ProcessingMessage", get_called_class(), is_object($processingMessage) ? get_class($processingMessage) : gettype($processingMessage))); } } else { if ($workflowMessage->messageType() === MessageNameUtils::PROCESS_DATA) { $processingMessage = $this->handleProcessData($workflowMessage); if (!$processingMessage instanceof ProcessingMessage) { throw new \RuntimeException(sprintf("%s::handleProcessData method returned %s instead of a ProcessingMessage", get_called_class(), is_object($processingMessage) ? get_class($processingMessage) : gettype($processingMessage))); } } else { $this->workflowEngine->dispatch(LogMessage::logUnsupportedMessageReceived($workflowMessage)); return; } } $this->workflowEngine->dispatch($processingMessage); return; } catch (\Exception $ex) { $this->workflowEngine->dispatch(LogMessage::logException($ex, $workflowMessage)); return; } }
/** * If workflow message handler receives a process-data message it forwards the message to this * method and uses the returned ProcessingMessage as response * * @param WorkflowMessage $workflowMessage * @return ProcessingMessage */ protected function handleProcessData(WorkflowMessage $workflowMessage) { if (array_key_exists($workflowMessage->payload()->getTypeClass(), $this->getSupportedMessagesByTypeMap())) { $dataAsJsonString = json_encode($workflowMessage->payload()); $answer = $workflowMessage->answerWithDataProcessingCompleted(); try { \Zend\Stdlib\ErrorHandler::start(); if (!file_put_contents(__DIR__ . '/../../data/target-data.txt', $dataAsJsonString)) { \Zend\Stdlib\ErrorHandler::stop(true); } } catch (\Exception $ex) { $answer = \Prooph\Processing\Message\LogMessage::logException($ex, $workflowMessage); } return $answer; } else { return LogMessage::logErrorMsg(sprintf('%s: Unknown type %s received', __CLASS__, $workflowMessage->payload()->getTypeClass()), $workflowMessage); } }
/** * If workflow message handler receives a process-data message it forwards the message to this * method and uses the returned ProcessingMessage as response * * @param WorkflowMessage $workflowMessage * @return ProcessingMessage */ protected function handleProcessData(WorkflowMessage $workflowMessage) { try { return $this->processData($workflowMessage); } catch (\Exception $ex) { ErrorHandler::stop(); return LogMessage::logException($ex, $workflowMessage); } }
/** * @param TaskListPosition $taskListPosition * @param WorkflowMessage|LogMessage $lastAnswer * @throws \RuntimeException If process cannot be found * @throws \Exception If error occurs during processing */ private function continueProcessAt(TaskListPosition $taskListPosition, $lastAnswer) { $process = $this->processRepository->get($taskListPosition->taskListId()->processId()); if (is_null($process)) { throw new \RuntimeException(sprintf("Last received message %s (%s) contains unknown processId. A process with id %s cannot be found!", $lastAnswer->getMessageName(), $lastAnswer->uuid()->toString(), $taskListPosition->taskListId()->processId()->toString())); } $this->beginTransaction(); try { $process->receiveMessage($lastAnswer, $this->workflowEngine); $this->commitTransaction(); } catch (\Exception $ex) { $this->rollbackTransaction(); throw $ex; } if ($process->isFinished()) { $this->processorEventQueue->enqueue($this->events()->getNewActionEvent('process_did_finish', $this, ['process_id' => $process->processId()->toString(), 'finished_at' => $lastAnswer->createdAt()->format(\DateTime::ISO8601), 'succeed' => $process->isSuccessfulDone()])); } if ($process->isSubProcess() && $process->isFinished()) { if ($process->isSuccessfulDone()) { $this->informParentProcessAboutSubProcess($process, true, $lastAnswer); } else { if (!$lastAnswer instanceof LogMessage) { $lastAnswer = LogMessage::logException(new \RuntimeException("Sub process failed but last message was not a LogMessage"), $process->parentTaskListPosition()); } if (!$lastAnswer->isError()) { $lastAnswer = LogMessage::logErrorMsg($lastAnswer->technicalMsg(), $lastAnswer); } $this->informParentProcessAboutSubProcess($process, false, $lastAnswer); } } }
/** * The workflow engine provides access to the communication layer of the processing system. * Communication is based on ProophServiceBus. We use the idea of CQRS to decouple the * workflow processor from workflow message handlers which are responsible for processing * single tasks. A workflow message handler is normally the glue component which connects * Prooph\Processing with an external system. It receives commands from the processor like collect data or * process data and send events back to tell the processor what's happened. * * Each target (external system) gets a command bus and an event bus assigned. These are the communication * channels between the processor and the target. * You can use the full power of ProophServiceBus so a channel can be a local bus, a link to a messaging infrastructure, * a link to a worker queue, or a http remote interface. * * @return \Prooph\Processing\Processor\RegistryWorkflowEngine */ function _set_up_workflow_engine() { $commandBus = new \Prooph\ServiceBus\CommandBus(); $eventBus = new \Prooph\ServiceBus\EventBus(); $commandRouter = new \Prooph\ServiceBus\Router\CommandRouter(); //For our scenario it is enough to use a closure as workflow message handler //In a production system this should be a class loadable by Zend\ServiceManager //See the more complex scenarios to get an idea how such a set up can be look like. $commandRouter->route(\Prooph\Processing\Message\MessageNameUtils::getProcessDataCommandName('Prooph\\ProcessingExample\\Type\\SourceUser'))->to(function (\Prooph\Processing\Message\WorkflowMessage $message) use($eventBus) { $dataAsJsonString = json_encode($message->payload()); $answer = $message->answerWithDataProcessingCompleted(); try { \Zend\Stdlib\ErrorHandler::start(); if (!file_put_contents('data/target-data.txt', $dataAsJsonString)) { \Zend\Stdlib\ErrorHandler::stop(true); } } catch (\Exception $ex) { $answer = \Prooph\Processing\Message\LogMessage::logException($ex, $message); } $eventBus->dispatch($answer); }); $commandBus->utilize($commandRouter); $commandBus->utilize(new \Prooph\ServiceBus\InvokeStrategy\CallbackStrategy()); $workflowEngine = new \Prooph\Processing\Processor\RegistryWorkflowEngine(); $workflowEngine->registerCommandBus($commandBus, ['target-file-writer']); $workflowEngine->registerEventBus($eventBus, [\Prooph\Processing\Processor\Definition::SERVICE_WORKFLOW_PROCESSOR]); return $workflowEngine; }
/** * @param ManipulatePayload $task * @param TaskListPosition $taskListPosition * @param WorkflowEngine $workflowEngine * @param WorkflowMessage $previousMessage */ protected function performManipulatePayload(ManipulatePayload $task, TaskListPosition $taskListPosition, WorkflowEngine $workflowEngine, WorkflowMessage $previousMessage) { if (!MessageNameUtils::isProcessingEvent($previousMessage->messageName())) { $this->receiveMessage(LogMessage::logWrongMessageReceivedFor($task, $taskListPosition, $previousMessage), $workflowEngine); return; } $payload = $previousMessage->payload(); try { $task->performManipulationOn($payload); } catch (\Exception $ex) { $this->receiveMessage(LogMessage::logException($ex, $taskListPosition), $workflowEngine); return; } $newEvent = $previousMessage->prepareDataProcessing($taskListPosition, $this->taskList->taskListId()->nodeName())->answerWithDataProcessingCompleted(); $this->receiveMessage($newEvent, $workflowEngine); }
/** * @test */ public function it_only_accepts_error_code_greater_than_399_otherwise_it_uses_500_as_code() { $wfMessage = $this->getTestWorkflowMessage(); $exception = new \DomainException("Data cannot be found", 399); $message = LogMessage::logException($exception, $wfMessage); $this->assertEquals('Data cannot be found', $message->technicalMsg()); $this->assertTrue($message->isError()); $this->assertEquals(500, $message->msgCode()); $this->assertEquals($wfMessage->target(), $message->origin()); $this->assertTrue($wfMessage->processTaskListPosition()->equals($message->processTaskListPosition())); $this->assertTrue(isset($message->msgParams()['trace'])); $this->assertEquals(NodeName::defaultName()->toString(), $message->target()); }