Code Monkey home page Code Monkey logo

broadway's Introduction

Broadway

Broadway is a project providing infrastructure and testing helpers for creating CQRS and event sourced applications. Broadway tries hard to not get in your way. The project contains several loosely coupled components that can be used together to provide a full CQRS\ES experience.

build status

About

Read the blog post about this repository at:

Installation

$ composer require broadway/broadway

Documentation

You can find detailed documentation of the Broadway bundle on broadway.github.io/broadway.

Feel free to join #qandidate on freenode with questions and remarks!

Acknowledgements

The broadway project is heavily inspired by other open source project such as AggregateSource, Axon Framework and Ncqrs.

We also like to thank Benjamin, Marijn and Mathias for the conversations we had along the way that helped us shape the broadway project. In particular Marijn for giving us access to his in-house developed CQRS framework.

License

MIT, see LICENSE.

broadway's People

Contributors

0mars avatar abachmann avatar alessandrominoccheri avatar asm89 avatar boedah avatar boekkooi avatar cordoval avatar dawidmazurek avatar dependabot-preview[bot] avatar dependabot[bot] avatar e1379 avatar felixcarpena avatar fritsjanb avatar kelvinj avatar kimlai avatar maks3w avatar marcosh avatar matthiasnoback avatar mbadolato avatar miliooo avatar nicolopignatelli avatar othillo avatar raistlin avatar reenl avatar renedekat avatar rgeraads avatar ricbra avatar simensen avatar unkind avatar wjzijderveld avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

broadway's Issues

Incorrect timezone_type for DateTime toBeginningOfWeek()

Upon running the test I encountered some failures under DateTimeTest.php it_converts_to_the_beginning_of_week($dateTime, $expectedBeginningOfWeek)

Expected result:

Broadway\Domain\DateTime Object (
    'dateTime' => DateTime Object (
        'date' => '2015-03-09 00:00:00.000000'
        'timezone_type' => 1
        'timezone' => '+00:00'
    )
)

Actual result:

Broadway\Domain\DateTime Object (
    'dateTime' => DateTime Object (
        'date' => '2015-03-09 00:00:00.000000'
        'timezone_type' => 3
        'timezone' => 'UTC'
    )
)

Simple fix seems to be to change 'UTC' to '+00:00'

I've committed the change to my branch. I've been waiting for an open source project to contribute to and give back to the community and would definitely like to contribute to this once. I'll apologise in advance for anything incorrect I do in the process.

Would be hugely grateful if you think this change is worth applying to pull my commit, because I can then see end to end how contributing to an open source project works.

Provide an example for a saga

It would be very helpful to provide a simple example for sagas.
I'm just trying to figure out how this works. Thanks!

Loading by id of a different aggregate

Sorry if this is a silly question and I am missing something.

Since the same table is used for all events in the event store: if I have two aggregates classes and I call EventSourcingRepository::load() on a repository for one aggregate class with an id belonging to an instance of the other aggregate class then there is no exception. Instead the events are played back against the wring type of entity and silently do nothing as there are no methods to handle them.

Am I (a) doing something wrong to make this possible at all (b) missing something that should make this throw an exception (c) just needing to implement something that stops this within my aggregates?

(Re)move subscribe from CommandBusInterface

The CommandBusInterface allows to subscribe command handlers, but if your using a lazy loading method like the Service container you properly don't need this method.

Can we move the subscribe method to a SubscribableCommandBusInterface or something?

And secondly, why does the SimpleCommandBus calls ALL the handlers.
Shouldn't there be a 1 to 1 relationship?

Problem when trying to use DBALEventStore with PostgreSQL

This is a little strange, and it's likely an issue with how I'm using Doctrine DBAL. But, other users of this library may run into this issue, and it would be helpful if there's an answer here.

Everything works just fine if I use pdo_sqlite. The problem comes in when using the pdo_pgsql driver. There's apparently some column name case-sensitivity issues happening and I cannot figure out the way around it. Granted, I'm new to working directly with DBAL, but still... nothing I've tried helps.

I have a test class with the following set up:

$parameters = [
    'driver'        => 'pdo_pgsql',
    'user'          => 'test',
    'password'      => 'test',
    'database'      => 'test',
    'host'          => 'localhost',
    'port'          => 5433,
];

$connection     = DriverManager::getConnection($parameters);
$schemaManager  = $connection->getSchemaManager();
$schema         = $schemaManager->createSchema();
$eventStore     = new DBALEventStore($connection, new SimpleInterfaceSerializer(), new SimpleInterfaceSerializer(), 'events');

if ($table = $eventStore->configureSchema($schema)) {
    $schemaManager->createTable($table);
}

Yes, 5433 is the correct port on this machine (not the default 5432) and all of the connection info is correct. I can connect to the database.

Here's the problem:

When createTable($table) is called, the column names are getting normalized and thus lowercase. This is an issue with the recordedOn field. The column name is getting created as 'recordedon'. This means that the deserializeEvent is throwing an error on

DateTime::fromString($row['recordedOn'])

because it thinks the recordedOn doesn't exist (which it doesn't the database has it as 'recordedon').

The inserts into the table work just fine. The data is getting retrieved just fine. It just crashes in the deserializeEvent method.

If I manually change the column name in the database to recordedOn, then the insert commands in the event store no longer work.

Broadway\EventStore\DBALEventStoreException:

Caused by
Doctrine\DBAL\DBALException: An exception occurred while executing 'INSERT INTO events (uuid, playhead, metadata, payload, recordedOn, type) VALUES (?, ?, ?, ?, ?, ?)' with params ["07ebf76f-7ddf-4e57-969d-e271c560ebdd", 0, "{\"class\":\"Broadway\\\\Domain\\\\Metadata\",\"payload\":[]}", "{\"class\":\"Cybernox\\\\Theme\\\\Events\\\\ThemeCreatedEvent\",\"payload\":{\"themeId\":\"07ebf76f-7ddf-4e57-969d-e271c560ebdd\"}}", "2014-09-16T07:07:02.626363+00:00", "Cybernox.Theme.Events.ThemeCreatedEvent"]:

SQLSTATE[42703]: Undefined column: 7 ERROR:  column "recordedon" of relation "events" does not exist
LINE 1: ...T INTO events (uuid, playhead, metadata, payload, recordedOn...

I thought it might be simply a case of needing to configure the connection to use the PDO::CASE_NATURAL values, so I added a driverOptions to the connection. Still nothing (trying CASE_LOWER and CASE_UPPER didn't work either. In fact, UPPER caused a bigger crash where tables and column names were empty)

$parameters = [
    'driver'        => 'pdo_pgsql',
    'user'          => 'test',
    'password'      => 'test',
    'database'      => 'test',
    'host'          => 'localhost',
    'port'          => 5433,
    'driverOptions' => [\PDO::ATTR_CASE => \PDO::CASE_NATURAL],
];

The only way I could get a success (and it's totally wrong, I was just trying to see if I could get SOMETHING to work), was to add

$row['recordedOn'] = $row['recordedon'];

to DBALEventStore::deserializeEvent() just to keep the mismatch from happening.

Anyone know of the proper voodoo to use with the dbal connection to keep the column names as-is instead of getting lowercased?

Serialization Problems

Hi all,

I'm loving this framework, great job! One issue I am struggling with is the serialization of event payloads.
Some of our events carry Value Objects, and they get lost in serialization. Further, the current SerializerInterface works with arrays, even though serialization implies strings.. this makes it difficult to integrate more mature solutions which also want to work with string.

I suspect that your reasoning behind the array format is opportunity for backwards compatibility if event code evolves away from already recorded events.. is this the case?

Thank you for any help understanding this better.
Daniel

i wonder if we have a view on the bundle like an event store analyzer

the analyzer would just have a read only view of the event store and a button to analyze and tap on with a listener perhaps on specialized pluggable views to inspect events from the eventstream.

cc any thoughts on this, i think i would be nice to have ๐Ÿ‘ด since it is independent on the domain or particulars on one's implementation. It can thereby be a selling point to win the case for ES on a project.

Elastic search and findBy fields

Hi,

I was searching via findBy method https://github.com/qandidate-labs/broadway/blob/41a495d69fba2303622febfb3bc715a0f500c61d/src/Broadway/ReadModel/ElasticSearch/ElasticSearchRepository.php#L91 and was not getting any results.

$fields = [
    'name' => 'beta',
];
$this->repository->findBy($fields)

I tried with plain elastic search api and results are getting for below api .

<?php
$searchParams = array();
$searchParams['index'] = 'something';
$searchParams['type']  = 'Some\Namespaced\Class';
$searchParams['body']['query']['wildcard']['name'] = 'b*';
// $searchParams['body']['query']['match']['name'] = 'beta';

The find with id is working as expected.

Thanks!

Bundle command init should be idempotent

currently the command to init ~ php app/console broadway:event-store:schema:init is not idempotent

~ php app/console broadway:event-store:schema:init                      Luiss-MacBook-Pro-3 [3:13:21]
Created schema
                      Luiss-MacBook-Pro-3 [3:13:35]
Could not create schema
Catchable Fatal Error: Argument 1 passed to 
Doctrine\DBAL\Schema\AbstractSchemaManager::createTable() must be an instance of 
Doctrine\DBAL\Schema\Table, null given, called in 
/Users/cordoval/Sites/libs/pcna/vendor/broadway/broadway/src/Broadway/Bundle/BroadwayBundle/Com
mand/SchemaEventStoreCreateCommand.php on line 58 and defined in 
/Users/cordoval/Sites/libs/pcna/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager
.php line 439

will you take a PR turning this command in idempotent? cc @mbadolato

The bundle should not hardcode the name of the DB connection

In the bundle, the name default is hardcoded in several places using the connection.

But there is no guarantee that a connection named default will exist (or that it is the default one). DoctrineBundle does not give such guarantee (default is just the name used by the short syntax).

ParameterNotFoundException non-existent parameter "storage_suffix".

When trying to use the bundle I get the following error:

ParameterNotFoundException: The service "broadway.saga.state.mongodb_database" 
has a dependency on a non-existent parameter "storage_suffix".

The reason is in this file
https://github.com/qandidate-labs/broadway/blob/master/src/Broadway/Bundle/BroadwayBundle/Resources/config/saga.xml#L26

I solved it for now by adding a fake parameter to my config (I'm not using sagas at the moment)
Still I think this needs to be addressed

bootstrap aggregates that apear out of the blue into the ES system

I am working on a hybrid system and basically i want to bootstrap event streams as soon as there is any activity related to them, say for assignment:

protected function handleAssignPatient(AssignPatient $command)
    {
        try {
            $patient = $this->load($command->patientId->toNative());
        } catch (AggregateNotFoundException $e) {
            $patient = new Patient();
            $patient->apply(new PatientWasNotAssignedBefore(new PatientId()));
            $this->repository->add($patient);
        }
        $patient->assign($command->userId);
        $this->save($patient);
    }

whenever it cannot pull the stream, i create a Patient() out of nowhere, however this gives me an exception because the uuid is null and when doctrine tries to persist it it will fail of course.

Any hints on how to overcome this broadway limitation?

remove the requirement for getId on AggregateRoot

Split this out from this discussion on #18 between @simensen and @fritsjanb

@simensen wrote:

I'm wondering if we can remove the requirement for getId on AggregateRoot altogether. I haven't tried implementing this yet, but this is what I have in mind:

https://gist.github.com/simensen/d982fac5550d3b5f8698

Basically, adding something like getIdForAggregateRoot to the RepositoryInterface and removing getId from the AggregateRoot interface. The repository (that already needs to be extended? this might not be entirely true...) would be responsible for asking its aggregate root for its ID in a way that the aggregate root expects.

@fritsjanb replied:

The fact remains that your aggregate needs a function to expose its identity. There is only one reason for this: it allows us to store the events.

Of course you could move the responsibility for this to the RepositoryInterface (I do think this is an elegant solution), but you would still have to implement a function in your AggregateRoot with the sole purpose of exposing an id for storage.

True, it allows you to expose a getFooId() instead of a getId(), but is there really that much added value to this?

Regarding the extending of the Repository: this is not entirely true. In our application we do in fact extend it (for typehinting purposes and it makes testing easier), but at this point it is not yet required.

At the moment our repositories look like this:

<?php

use Broadway\EventHandling\EventBusInterface;
use Broadway\EventSourcing\EventSourcingRepository;
use Broadway\EventStore\EventStoreInterface;

class FooRepository extends EventSourcingRepository
{
    public function __construct(EventStoreInterface $eventStore, EventBusInterface $eventBus, array $eventStreamDecorators = array())
    {   
        parent::__construct($eventStore, $eventBus, '\Qandidate\Project\Foo\Foo', $eventStreamDecorators);
    }   
}

but you could just instantiate an EventSourcingRepository directly.

Anyways, it would be nice if you could create a separate issue for this :)

Add HHVM to travis build matrix

It is a bit more work then just adding it to the build matrix.

The mongo extension isn't available yet for hhvm, so we need to disable that and skip some tests.
Annoying part is that it is also part of the composer.json, so we might need to remove that in a before_script:

before_script:
  ..
  - sed -r 's/^.*mongodb.*$//' composer.json > composer.json.copy; mv composer.json.copy composer.json
  ..

RFC Improve scenario test case API with constrained assertions

The current Scenario implementations for test cases contains a then method asserting that the whole repository content equals the expected values.

When testing a ReadModel we've found it was a bit tedious to setup correct data for the then part of the scenario, and it damages test readability. We came up with the method below allowing us to only express constraints for each repository entry:

protected function thenAssertThat($constraints)
    {
        $valuesAndAssertions = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY);
        $valuesAndAssertions->attachIterator(new \ArrayIterator($this->repository->findAll()));
        $valuesAndAssertions->attachIterator(new \ArrayIterator($constraints));

        foreach ($valuesAndAssertions as $toAssert) {
            list($value, $hasConstraint) = $toAssert;
            $this->assertThat($value, $hasConstraint);
        }
    }

Usage:

$this->scenario
            ->given([
                $this->uneFicheIndicationCree(),
                new FicheIndicationEnvoyee($this->unIdIndication, $destinataireInitialId)
            ])
            ->when(
                new FicheIndicationTransmise($this->unIdIndication, $destinataireFinalId, '')
            )
            ->thenAssertThat([
               $this->attributeEqualTo('statut', Indication::STATUT_ENVOYEE)
           ]);

We can do a PR for bringing this API into Scenario test cases, but I wanted first to discuss the API you would prefer. So far I see 2 solutions:

  • creating a separate method like shown above
  • change the current then implementation to default to an equalTo constraint if the expectation row is not a constraint (for a kind of backwards compatibility - only the assertions count would be increased)

What are yourt thoughts about it?

Proper way to use the Metadata components?

There are a couple of simple examples and tests where you guys are directly callingDomainMessage::recordNow() and passing in data to be used as Metadata, but there's no real world examples, and in fact, the task looks to be impossible in the course of the normal flow.

Using EventSourcedAggregateRoot the only time DomainMessage::recordNow() gets triggered is when the AR methods of a class that extends EventSourcedAggregateRoot fire off a apply($myEvent) command. But apply() does not accept a parameter for any metadata, and furthermore, hardcodes new Metadata(array()) in the call to recordNow.

Is this intentional, or a bug/oversight?

We (or at least from what I understand to be the proper use of Broadway) are not making direct calls to recordNow() and should be using the library the way I described, via apply(). If that's the case, how do we apply metadata?

To me, it would seem appropriate to change apply() to something like

public function apply($event, $metadata = [])
{
    $this->handleRecursively($event);

    $this->playhead++;
    $this->uncommittedEvents[] = DomainMessage::recordNow(
        $this->getAggregateRootId(),
        $this->playhead,
        new Metadata($metadata),
        $event
    );
}

Also, while on the subject, EventSourcedAggregateRoot implements AggregateRoot. AggregateRoot does not define apply() as part of the interface. Shouldn't it? EventSourcedAggregateRoot is the only concrete implementation of AggregateRoot currently in the system, but it would seem to me that all AR's need a way to apply events to themselves.

Thoughts (on both issues)? I'd be happy to submit a PR tonight.

Contracts should be defined by interface rather than abstract classes

A good example is the EventDispatcher component (I haven't reviewed other components yet). The contact is defined by the AbstractEventDispatcher, which contains only public abstract methods.
Such abstract class does not have any benefit over an interface IMO (there is no implementation code in it, only a contract), but it imposes a strong constraint on the actual implementation (PHP does not support multiple inheritance).
why not using interfaces for the contracts ?

thus, using a typehint on AbstractEventDispatcher looks weird IMO, it is likely that some devs could end up typehinting the implementation instead (I would be in favor of using EventDispatcher as the name of the interface and finding a different name for the simple implementation provided, but this is a separate topic for which http://verraes.net/2013/09/sensible-interfaces/ explains it well)

Question about the RepositoryInterface

Hello,

I find it a bit awkward to call Repository::add() everytime I want to save an existing aggregate. Why is this method not called save() ? Am I using it wrong ?

[RFC] Possible flaw in projector scenario testing logic

It seems there may be a small flaw in the procedure of how Projector Scenarios get tested. Sorry for the length, I wanted to make sure I got all of the information across.

Overview

  • Test classes extend the abstract ProjectorScenarioTestCase
  • The ProjectorScenarioTestCase::createScenario() method instantiates InMemoryRepository
  • InMemoryRepository::save() requires passing in an object $model that implements ReadModelInterface
  • ReadModelInterface requires $model to have a getId() method
  • InMemoryRepository:::save() persists $model utilizing the getId() method ($this->data[$model->getId()] = $model)
  • Scenario tests do the given([..])/when()/then([..]) process, resulting in an assert() being performed

The problem lies within the then([..]) stage of test. We're required to pass in an array of objects to compare against the results of objects that got put into the repository via the executed projector. We can't, however, effectively do this because of the model object's $id.

When the projector instantiates (or loads and modifies) a read model object and then saves it, we (our test class) have no idea what that read model object's id is. Therefore, when we're constructing the array of objects to pass into then() we won't be able to determine and match what should be coming back from the read repository's findall() for the assertEquals($expected, $repository->findAll()) comparison.

The read model is required to have an id (and getId() method). This can be created one of the following ways:

  1. Generated in the read model object's constructor
  2. Auto-generated by an ORM
  3. Generated in the projector and passed in during instantiation
  4. Passed along via the original command and event process

Options 1 and 2 are the more proper ways to do it. Option 3 is ok, but not preferable. Regardless, those 3 options have no way to communicate that id back to the test, nor can they receive a pre-generated id from the test.

Option 4 is flat out wrong and not an option. The commands and events should know nothing about the listeners. And, there could be multiple projectors and read models listening to events and storing data, each which would need its own id(s).

Let's give an example, using the Basket and Product sample that @mathiasverraes used in his Practical Event Sourcing talk, rebuilt using Broadway.

The projector:

class BasketProjector extends Projector
{
    /** @var Broadway\ReadModel\RepositoryInterface\RepositoryInterface */
    private $repository;

    public function __construct(RepositoryInterface $repository)
    {
        $this->repository = $repository;
    }

    public function applyProductWasAddedToBasket(ProductWasAddedToBasket $event, DomainMessage $domainMessage)
    {
        $basket = new Basket($event->basketId, $event->productId, $event->productName);
        $this->repository->save($basket);
    }
}

The read model class:

class Basket implements ReadModelInterface
{
    private $basketId;
    private $id;
    private $productId;
    private $productName;

    public function __construct($basketId, $productId, $productName)
    {
        $this->id           = SomeIdGenerator::generate();
        $this->basketId     = $basketId;
        $this->productId    = $productId;
        $this->productName  = $productName;
    }

    public function getBasketId()
    {
        return $this->basketId;
    }

    /** {@inheritdoc} */
    public function getId()
    {
        return $this->id;
    }

    public function getProductId()
    {
        return $this->productId;
    }

    public function getProductName()
    {
        return $this->productName;
    }
}

So let's say the test I'm running is:

$this->scenario
    ->given([])
    ->when(new ProductWasAddedToBasket($this->basketId, $this->productId, 'Test Product'))
    ->then([new Basket($this->basketId, $this->productId, 'Test Product')]);

the then() clause will ALWAYS fail. I can instantiate Basket to pass in for comparison, but since the one in the repository has an $id associated with it I cannot create an identical object for comparison.

Let me also just say that while this may be somewhat contrived and silly: using $basketId for the id wouldn't work because there could be multiple rows, one for each $basketId/$productId combination. Using a combined id of $basketId . $productId wouldn't work because we could have more than one row containing both of those (perhaps the same product was added to the basket more than once, and we're not storing a quantity here; we're just recording each basket item as it's added)

Thoughts?

ElasticSearch should allow to create nested not analyzed fields

When creating an index, the ElasticSearchRepository only allows to create not analyzed fields at first level.

It would be great to allow nested not analyzed fields.

Example usage:

$readModelRepository->create(
            $prefix . 'indications',
            \Recommandations\Domain\ReadModel\Indication::class,
            ['statut', 'fiche_suivi.ficheDeSuiviId']
        );

Naive implementation (to be refactored)

    private function createNotAnalyzedFieldsMapping(array $notAnalyzedFields)
    {
        $fields = array();

        foreach ($notAnalyzedFields as $field) {
            $fieldPath = explode('.', $field);
            $fieldName = array_shift($fieldPath);
            if (empty($fieldPath)) {
                $fields[$fieldName] = array(
                    'type'  => 'string',
                    'index' => 'not_analyzed'
                );
            } else {
                if (empty($fields[$fieldName]['properties'])) {
                    $fields[$fieldName]['properties'] = [];
                }
                $fields[$fieldName]['properties'] = array_merge(
                    $fields[$fieldName]['properties'],
                    $this->createNotAnalyzedFieldsMapping([implode('.', $fieldPath)])
                );
            }
        }

        return $fields;
    }

(This is a note for self, and I hope to find time to come with a cleaner PR ;))

[Suggestion] Common base exception class or interface

It would be nice if all exceptions thrown had a common base, so something like this is possible to catch all exceptions from Broadway.

try {
    // some code here
} catch (\Broadway\Exception\BaseException $e) {
    //do something here, logging etc
}

Split components in separate composer packages

Given that components are fully decoupled (or mostly at least, as I haven't checked every classes), it would make sense to distribute them as separate Composer packages to allow users to depend only on the part they need.
For the bundle, it would also allow to define its real dependencies instead of listing them as suggest in the global composer.json and having the bundle break if they are not installed (it breaks if DoctrineBundle is not there btw, and DoctrineBundle is not even listed). Optional requirements are always a pain to manage, because you cannot put any constraint about the version of the other package to be used (well, you could do some stuff with conflicts, but it is not easy).

there is 2 alternatives for this:

  1. develop in the main repo and then provide subtree-splits for each component. This is the way Symfony does it. It requires having a hook updating the subtrees each time the main repo gets updated.

    Pros Cons
    All development in one place Complex to setup
    Easy to test/change coupled parts Release cycle is coupled
  2. develop each component in their own repo, dropping the main repo (after creating subtree splits).

    Pros Cons
    Easy to setup (no updating server needed) development is split in several places
    Each component can be released separately when needed harder to change coupled parts in non-BC ways
    Easy for contributors to contribute on the component they use

Given that there is almost no coupling between components, the second alternative probably wins in this case

Think about DomainEventStream decoration

With #71 merged, we now typehint to the interface.
But we still always return the concrete class.

This doesn't have to be a problem, but it might be a surprise for the first person that implements his/her own DomainEventStream.

This issue is mainly a reminder :)

Snapshotting support

It would be nice if there was snapshotting support so that we can snapshot and cache a representation of a domain so that we don't have to build it by replaying events.

A storage interface would be nice as well, so that implementers can implement a snapshot storage adapter for desired storages.

Add Release on master branch

Hi,

Can you add tags / release please ?

I use Broadway on a project into production and I would like to freeze the version in my composer.json.

Thank you in advance.

[RFC] Symfony configurations for the aggregate factories and event repository

In my test project, I created the following broadway.* service definitions (the first 3 listed) to allow me to easily configure a repository (4th service definition below) to pass into my command handler (5th definition below).

Any interest in me submitting that back to the Symfony bundle configs?

I put the broadway.aggregate_factory.named in as an example but haven't used it yet. It could use a little more fleshing out to allow for overriding the constructor name argument, but it's minimal effort to do so and I can add it into the PR.

I'd only be submitting back the broadway.* definitions and can include docs showing how I use them, like the example here.

<?xml version="1.0" encoding="UTF-8" ?>

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="broadway.aggregate_factory.public" class="Broadway\EventSourcing\AggregateFactory\PublicConstructorAggregateFactory" public="false" />
        <service id="broadway.aggregate_factory.named" class="Broadway\EventSourcing\AggregateFactory\NamedConstructorAggregateFactory" public="false">
            <argument>instantiateForReconstitution</argument>
        </service>

        <service id="broadway.event_repository.public_constructor" class="Broadway\EventSourcing\EventSourcingRepository" abstract="true">
            <argument type="service" id="broadway.event_store" />
            <argument type="service" id="broadway.event_handling.event_bus" />
            <argument /> <!-- Argument #3 to be replaced in child definitions -->
            <argument type="service" id="broadway.aggregate_factory.public" />
        </service>

        <service id="cybernox.event_repository.client_management.client" parent="broadway.event_repository.public_constructor" public="false">
            <argument index="2">Cybernox\ClientManagement\Model\Client</argument>
        </service>

        <service id="cybernox.command_handler.client_manager.client" class="Cybernox\ClientManagement\CommandHandler\ClientCommandHandler" public="false">
            <argument type="service" id="cybernox.event_repository.client_management.client" />
            <tag name="command_handler" />
        </service>
    </services>
</container>

Renaming the add method on the AggregateRoot

Currently, you have to call the add method to save the uncommitted events of your aggregate root to the event store. it is called add because you add the events to the event store, but that name isn't very obvious when you want to save your aggregate.

Ideas for a better name are welcome!

Dropping branch alias

I like to drop the branch alias, it doesn't add much value with all the changes we do now and can cause pain when we forget to update it.

Another option would be to just change it to 1.x and don't look at it until we tag 1.0.0 :-)

Anybody against?

How to manage Exception on execute Command

Hi,

My story :
In my batch, I execute many command over many aggregate. If a command throws an Exception, the batch stop for current aggregate but don't stop for all aggregate. The batch pass at next aggregate.

My problem :
If a command throws a Exception in aggregate execution, all next command are never executed. Is queued but never processed because the variable isDispatching is true in my SimpleCommandBus.

Have you another solution for my problem ?

Best regards,

[Suggestion] Add Laravel integration

I will start working on a broadway integration for Laravel this week / month.
Should I create a separate repository or, make a pull request once it's finished?

Bundle configuration should work out of the box

@rdohms pointed this out on IRC:
At this moment, the default configuration of the Bundle uses mongodb for Saga storage. but this also requires you to set the storage_suffix parameter, which is undocumented. As most people won't use Sagas, we should make sure the configuration works out of the box.

Proposed solution: change the default repository from mongodb to in_memory (see https://github.com/qandidate-labs/broadway/blob/3ce4f63131d23a2f5ba0be67a176d04febf5b783/src/Broadway/Bundle/BroadwayBundle/DependencyInjection/Configuration.php#L45)

Symfony Bundle broken due to invalid service definition

When the Symfony Bundle is enabled, Symfony attempts to build the service container, which includes loading the services.xml file.

That services.xml file contains a definition for broadway.auditing.command_logger:

<service id="broadway.auditing.command_logger" class="Broadway\Auditing\CommandLogger">
    <argument><!-- logger --></argument>
    <argument type="service" id="broadway.auditing.serializer" />
    <tag name="broadway.event_listener" event="broadway.command_handling.command_success" method="onCommandHandlingSuccess" />
    <tag name="broadway.event_listener" event="broadway.command_handling.command_failure" method="onCommandHandlingFailure" />
</service>

However, since CommandLogger.php requires an object that implements Psr\Log\LoggerInterface to be passed in as the first argument:

public function __construct(LoggerInterface $logger, CommandSerializerInterface $commandSerializer)

we get an error because the service definition has a string (the <!-- logger --> placeholder) defined.

The configuration

broadway:
    command_handling:
        logging: false

has no effect on this since this is simple Symfony service container building and not conditional based on the configs.

That placeholder string in the service definition needs to be replaced with an actual logger implementation.

A yml configuration option to define a PSR logger should be defined and the service definition could use that object definition. Ideally this config option could take a class name or a service definition

broadway:
    command_handling:
        logging: true
            logger: Acme\Path\To\MyPsrLogger

or

broadway:
    command_handling:
        logging: true
            logger: [@monolog.logger]

This doesn't solve the issue of a logger not being defined if logging is false so either

  1. the service definition should only be dynamically created if logging is true or
  2. (optimally) a default Broadway PSR Logger object needs to be defined and used if logging is false and/or no logger config is specified (logger: ~)

second bus projector via tags?

So I am having to create projectors that really fire a second command depending on some conditions.

They act as a second layer on top of my microservices aka BCs.

So my question is if projectors that act on replaying/rebuilding need to be called also, this seems like they will create double the events that they were firing.

Broadway does not seem to have examples or implementations for these cases? any insights cc @simensen @mbadolato @wjzijderveld would be appreciated

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.