Code Monkey home page Code Monkey logo

todosample-cqrs-es's Introduction

Todo sample application for cqrs-es for PHP7

This is a sample application to show how cqrs-es can be used in production.

Architecture

The application uses a zend-expressive skeleton for HTTP bindings, dependency injection and template rendering.

The architecture is based on CQRS and Event Sourcing using xprt64/cqrs-es library.

The basic ideea of CQRS with Event Sourcing is that in order to modify the state of the application commands must be executed. The result of the command are the events that are persisted in an Event Store. Those events are used to rehydrate the write models and to update the read models (the projections). Read more general documentation about this implementation of CQRS here.

Configuration

The application uses a dependency injection container (\Zend\ServiceManager\ServiceManager). The file config/autoload/dependencies.global.php contains the composition root. Here we must wire the dependencies to the CQRS library.

The abstract factory

An abstract factory is used by the application to create Read Models, Sagas and Command validators.

\Gica\Dependency\AbstractFactory::class                      => function (\Interop\Container\ContainerInterface $container) {
    return new \Gica\Dependency\ConstructorAbstractFactory($container);
},

The databases

The read model has an inverted dependency (by using an Interface) on the infrastructure:

\Domain\Read\Dependency\Database\ReadModelsDatabase::class => function (ContainerInterface $container) {
    return $container->get(\Infrastructure\Implementations\ReadModelsDatabase::class);
},

CQRS specific configuration

Event store

The event store has a MongoDB implementation. Future Events Store is the persistence for the scheduled events. If the yielded event is an instance of \Dudulina\Event\ScheduledEvent then this event is not persisted in the EventStore but in the FutureEventStore.

\Dudulina\EventStore::class =>  function (ContainerInterface $container) {
    return new MongoEventStore(
        $container->get(\Infrastructure\Implementations\EventStoreDatabase::class)->selectCollection('eventStore'),
        new EventSerializer()
    );
},

\Dudulina\FutureEventsStore::class =>  function (ContainerInterface $container) {
    return new FutureEventsStore(
        $container->get(\Infrastructure\Implementations\EventStoreDatabase::class)->selectCollection('futureEventStore'));
},

Event subscribers and command subscribers

\Dudulina\Command\CommandSubscriber::class => function (ContainerInterface $container) {
    return $container->get(\Infrastructure\Cqrs\CommandHandlerSubscriber::class);
},

\Dudulina\Event\EventSubscriber::class => function (ContainerInterface $container) {
    return $container->get(\Infrastructure\Cqrs\EventSubscriber::class);
},

CommandValidatorSubscriber::class => function (ContainerInterface $container) {
    return $container->get(\Infrastructure\Cqrs\CommandValidatorSubscriber::class);
},
\Dudulina\Event\EventDispatcher::class => function (ContainerInterface $container) {
    return new CompositeEventDispatcher(
        new EventDispatcherBySubscriber(
            $container->get(\Infrastructure\Cqrs\EventSubscriber::class)
        ),
        new EventDispatcherBySubscriber(
            $container->get(\Infrastructure\Cqrs\WriteSideEventSubscriber::class)
        )
    );
},

Command dispatcher

\Dudulina\Command\CommandDispatcher::class => function (ContainerInterface $container) {
    return new CommandDispatcherWithValidator(
        new DefaultCommandDispatcher(
            new CommandHandlerSubscriber(),
            $container->get(\Dudulina\Event\EventDispatcher::class),
            new CommandApplier(),
            $container->get(\Dudulina\Aggregate\AggregateRepository::class),
            new ConcurrentProofFunctionCaller(),
            new EventsApplierOnAggregate,
            new DefaultMetadataFactory(new AuthenticatedIdentityService()),
            new DefaultMetadataWrapper(),
            $container->get(\Dudulina\FutureEventsStore::class),
            $container->get(\Dudulina\Scheduling\CommandScheduler::class)
        ),
        $container->get(\Dudulina\Command\CommandValidator::class));
}

The command dispatcher has the possibility to detect and schedule future commands yielded by the Aggregate's command handlers. Scheduled commands are run in a cron job. See deploy/cron. See more about command scheduler here.

Automation tools

In order to speed up development, some tools exists in the bin/code directory. These tools parse the source code in the Domain directory and build the folowing maps:

  • command handlers map: create_cqrs_command_handlers_map.php
  • command validators map: create_cqrs_command_validators_map.php
  • event handlers map: create_cqrs_event_handlers_map.php
  • read models list map: create_cqrs_read_model_map.php
  • saga event processors map: create_cqrs_command_side_event_listener_map.php

You should run them all after any relevant modification of the source code by:

php -f bin/code/create_all_maps.php

A relevant modification is in any of the following cases:

  • a new command handler
  • a new event applier on the Aggregate is created
  • a new event handler is created
  • an existing command is renamed, moved
  • an existing event is renamed, moved

Testing

What is more important than testing? Nothing!

You can create unit-tests for the Aggregates using BddAggregateTestHelper. Here is a sample unit-test:

class TodoAggregateTest extends PHPUnit_Framework_TestCase
{

    public function test_handleAddNewTodo()
    {
        $command  = new AddNewTodo(
            123, 'test'
        );

        $expectedEvent = new ANewTodoWasAdded('test');

        $sut = new TodoAggregate();

        $helper = new BddAggregateTestHelper(
            new CommandHandlerSubscriber()
        );

        $helper->onAggregate($sut);
        $helper->given();
        $helper->when($command);
        $helper->then($expectedEvent);

        $this->assertTrue(true);//fake assertion
    }

    public function test_handleAddNewTodo_idempotent()
    {
        $command  = new AddNewTodo(
            123, 'test'
        );

        $priorEvent = new ANewTodoWasAdded('test');

        $sut = new TodoAggregate();

        $helper = new BddAggregateTestHelper(
            new CommandHandlerSubscriber()
        );

        $helper->onAggregate($sut);
        $helper->given($priorEvent);
        $helper->when($command);
        $helper->then();//no events must be yielded

        $this->assertTrue(true);//fake assertion
    }
}

Run it

To run this application you must clone this repository then use ./start-app.sh to start it.

git clone https://github.com/xprt64/todosample-cqrs-es todosample-cqrs-es
cd todosample-cqrs-es
./start-app.sh

Deploy to Docker Cloud

Then, in your browser, access http://localhost.

todosample-cqrs-es's People

Contributors

xprt64 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.