/** * @param StoryName $storyName * @param StoryTeller $storyTeller * @throws \Prooph\Done\Shared\Exception\StoryTeller */ public function registerStoryTeller(StoryName $storyName, StoryTeller $storyTeller) { if (isset($this->storyTellers[$storyName->toString()])) { throw Exception\StoryTeller::alreadyRegisteredForStoryName($storyName); } $this->storyTellers[$storyName->toString()] = $storyTeller; }
/** * @test */ public function it_can_be_started_with_a_message_and_provides_next_commands() { $startMessage = new FileWasUploaded(); $story = Story::start(StoryName::fromString('Test Story'), $startMessage, $this->getTestChapterTemplates(), new NoOpMessageConverter()); $events = $this->popRecordedEvents($story); $this->assertEquals(2, count($events)); /** @var $storyWasStarted Story\Event\StoryWasStarted */ $storyWasStarted = $events[0]; $this->assertInstanceOf(Story\Event\StoryWasStarted::class, $storyWasStarted); $this->assertEquals('Test Story', $storyWasStarted->storyName()->toString()); //StoryId should be the uuid of the start message $this->assertEquals($startMessage->uuid()->toString(), $storyWasStarted->storyId()->toString()); /** @var $chapterWasStarted Story\Event\ChapterWasStarted */ $chapterWasStarted = $events[1]; $this->assertInstanceOf(Story\Event\ChapterWasStarted::class, $chapterWasStarted); $this->assertTrue($storyWasStarted->storyId()->equals($chapterWasStarted->chapterStatus()->chapterNumber()->storyId())); $this->assertEquals(Story\ChapterStatus::STATUS_RUNNING, $chapterWasStarted->chapterStatus()->status()); $this->assertEquals(1, $chapterWasStarted->chapterStatus()->chapterNumber()->number()); $nextCommands = $story->getNextCommands(); $this->assertEquals(1, count($nextCommands)); $this->assertInstanceOf(ProcessFile::class, $nextCommands[0]); //Next commands can only be requested once $this->assertEquals(0, count($story->getNextCommands())); $this->applyPendingEvents($story, $events); return [$story, $nextCommands[0]]; }
/** * @return StoryName */ public function storyName() { if (null === $this->storyName) { $this->storyName = StoryName::fromString($this->payload['story_name']); } return $this->storyName; }
/** * @test */ public function it_starts_a_new_chapter_by_providing_the_chapter_command() { $fileWasUploaded = new FileWasUploaded(); $startCondition = $this->prophesize(MessageCondition::class); $startCondition->isTrueFor($fileWasUploaded)->willReturn(true); $isDoneCondition = $this->prophesize(MessageCondition::class); $payloadGenerator = $this->prophesize(PayloadGenerator::class); $payloadGenerator->__invoke(Argument::any(), Argument::any(), Argument::any())->will(function ($args) { list($previousMessage, $payload, $metadata) = $args; $payload['filename'] = $previousMessage->payload()['filename']; }); $commandTemplate = new MessageTemplate(ProcessFile::class, new FQCNMessageFactory(), [], [], $payloadGenerator->reveal()); $chapterTemplate = new ChapterTemplate(ChapterName::fromString('File Processor'), $commandTemplate, $startCondition->reveal(), $isDoneCondition->reveal()); $storyId = StoryId::generateNew(); $chapterNumber = ChapterNumber::withNumber($storyId, 2); $chapterCommand = $chapterTemplate->startChapter(StoryName::fromString('Test Story'), $chapterNumber, $fileWasUploaded); $this->assertInstanceOf(ProcessFile::class, $chapterCommand); $this->assertEquals('some-file.csv', $chapterCommand->payload()['filename']); $this->assertEquals($chapterNumber->toString(), $chapterCommand->metadata()[Metadata::STORY_CHAPTER]); $this->assertEquals('Test Story', $chapterCommand->metadata()[Metadata::STORY_NAME]); }
$storyLog = new \Prooph\Done\Infrastructure\EventStoreStoryLog($eventStore); $commandRouter = new \Prooph\ServiceBus\Plugin\Router\CommandRouter(); $commandBus = new \Prooph\ServiceBus\CommandBus(); $commandBus->utilize($commandRouter); //Enable transaction handling based on command dispatch $transactionManager = new \Prooph\EventStoreBusBridge\TransactionManager($eventStore); $commandBus->utilize($transactionManager); $delayedCommandBus = new \Prooph\Done\Infrastructure\DelayedCommandBus($commandBus); //The delayed command bus queues commands until next EventStore.commit.post event occurs $delayedCommandBus->setUp($eventStore); //As a Story can handle and dispatch any message implementing Prooph\Common\Messaging\Message //it requires a message factory and message converter to be able to create and log messages correctly $messageFactory = new \Prooph\Common\Messaging\FQCNMessageFactory(); $messageConverter = new \Prooph\Common\Messaging\NoOpMessageConverter(); //Set up the story $storyName = \Prooph\Done\Shared\StoryName::fromString('Json to CSV Conversion'); //Set up the StoryTeller - basically a StoryTeller is a generic message handler but specifically //designed to handle a single Story configured with so called ChapterTemplates $jsonToCSVStoryTeller = new \Prooph\Done\Story\StoryTeller($storyName, [new \Prooph\Done\Story\ChapterTemplate(\Prooph\Done\Story\ChapterName::fromString('Merge Json Files'), new \Prooph\Done\Message\MessageTemplate(\ProophExample\Done\JsonToCSVStory\MergeJsonFilesChapter\MergeJsonFiles::class, $messageFactory, ['json_files_directory' => __DIR__ . DIRECTORY_SEPARATOR . 'data', 'to_filename' => 'users.json', 'file_pattern' => '/^user[\\d]{1,}\\.json$/']), new \ProophExample\Done\JsonToCSVStory\IsStartStoryCommand(), new \ProophExample\Done\JsonToCSVStory\IsInfoSuccessEvent()), new \Prooph\Done\Story\ChapterTemplate(\Prooph\Done\Story\ChapterName::fromString('Convert Json to CSV'), new \Prooph\Done\Message\MessageTemplate(\ProophExample\Done\JsonToCSVStory\JsonToCSVChapter\ConvertJsonToCSV::class, $messageFactory, [], [], function (\Prooph\Done\ChapterLogger\Event\Info $previousMessage, \ArrayObject $payload, \ArrayObject $metadata) { //We extract the generated filename from the previous event //to use it as input file for the next chapter command $inputFile = $previousMessage->payload()['context']['to_file_path']; $outputFile = str_replace('.json', '.csv', $inputFile); //Payload and metadata for the next chapter command are passed as \ArrayObjects //to the PayloadGenerator callback so that the callback can manipulate them both $payload['input_file'] = $inputFile; $payload['output_file'] = $outputFile; }), new \ProophExample\Done\JsonToCSVStory\IsInfoSuccessEvent(), new \ProophExample\Done\JsonToCSVStory\IsInfoSuccessEvent())], $storyLog, $delayedCommandBus, $messageFactory, $messageConverter); //We route the start command of the story to the responsible story teller $commandRouter->route(\ProophExample\Done\JsonToCSVStory\StartStory::class)->to($jsonToCSVStoryTeller); //As we perform the chapter in the same process as handling the Story
/** * @param \Prooph\Done\Story\ChapterName $chapterName * @param StoryName $storyName * @return ChapterName */ public static function isDuplicateForStory(\Prooph\Done\Story\ChapterName $chapterName, StoryName $storyName) { return new self(sprintf('Duplicate Chapter name %s for story %s detected', $chapterName->toString(), $storyName->toString())); }
/** * @param StoryName $storyName * @param ChapterNumber $chapterNumber * @param Message $previousMessage * @return Message */ public function startChapter(StoryName $storyName, ChapterNumber $chapterNumber, Message $previousMessage) { $command = $this->chapterCommandTemplate->createMessageFromPreviousMessage($previousMessage); $command = $command->withAddedMetadata(Metadata::STORY_CHAPTER, $chapterNumber->toString()); return $command->withAddedMetadata(Metadata::STORY_NAME, $storyName->toString()); }
/** * @param ChapterName $chapterName * @param StoryName $storyName * @return Story */ public static function cannotFindMatchingTemplateForChapterName(ChapterName $chapterName, StoryName $storyName) { return new self(sprintf('Story %s cannot find matching template for chapter name %s', $storyName->toString(), $chapterName->toString())); }