ecotoneframework / ecotone-dev

Ecotone Framework Development - This is Monorepo which contains all official public modules
https://docs.ecotone.tech
Other
37 stars 16 forks source link

Call command from event handler (command -> event -> command) #73

Closed SuperBuba closed 1 year ago

SuperBuba commented 1 year ago

Ecotone version(s) affected: 1.67.0

Description On chain:

  1. Create aggregate by command
  2. Set aggregate property by command and publish event
  3. Process event and create another aggregate

First command executed twice.

I am using Symfony with Doctrine ORM, may be i do something wrong.

How to reproduce
Create aggregate by command:

#[CommandHandler]
public static function create(MerchantCreateCommand $command): self
{
    return new self(new Ulid(), $command->name);
}

Set aggregate attribute and publish event:

#[CommandHandler]
public function setOwner(MerchantSetOwnerCommand $command, EventBus $bus) : void
{
    $this->owner    = new Owner(new Ulid(), $command->email);

    $bus->publish(new MerchantOwnerSetted($this->owner->email(), $this->owner->userId()));
}

Process event and send new command:

#[EventHandler]
public function onMerchantOwnerSetted(MerchantOwnerSetted $event, CommandBus $bus) : void
{
     $bus->send(new UserCreateCommand($event->email, $event->user_id));
}

Create another aggregate by command from event handler:

#[CommandHandler]
public static function create(UserCreateCommand $command) : self
{
    return new self(
         $command->user_id ?? new Ulid(),
         $command->email
    );
}

I made a repository to make it easier to reproduce the issue. After install just call php bin/console ecotone:test

Context
Query log from Doctrine. The second INSERT INTO merchants comes right after UPDATE merchants

+---+------------------------------------------------------------------------+----------------------------------------------------+
| # | SQL                                                                    | Params                                             |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 1 | "START TRANSACTION"                                                    |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 2 | INSERT INTO merchants (id, name, owner_user_id, owner_email) VALUES (? | 01GNVD0Q5M5Z08GMEVTANRGQA4, merchant 1, ,          |
|   | , ?, ?, ?)                                                             |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 3 | "COMMIT"                                                               |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 4 | "START TRANSACTION"                                                    |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 5 | SELECT t0.id AS id_1, t0.name AS name_2, t0.owner_user_id AS owner_use | 01GNVD0Q5M5Z08GMEVTANRGQA4                         |
|   | r_id_3, t0.owner_email AS owner_email_4 FROM merchants t0 WHERE t0.id  |                                                    |
|   | = ? LIMIT 1                                                            |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 6 | INSERT INTO users (id, email) VALUES (?, ?)                            | 01GNVD0Q66D1Q54SC2VG3VXGAH, merchant@test.com      |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 7 | UPDATE merchants SET owner_user_id = ?, owner_email = ? WHERE id = ?   | 01GNVD0Q66D1Q54SC2VG3VXGAH, merchant@test.com, 01G |
|   |                                                                        | NVD0Q5M5Z08GMEVTANRGQA4                            |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 8 | INSERT INTO merchants (id, name, owner_user_id, owner_email) VALUES (? | 01GNVD0Q5M5Z08GMEVTANRGQA4, merchant 1, 01GNVD0Q66 |
|   | , ?, ?, ?)                                                             | D1Q54SC2VG3VXGAH, merchant@test.com                |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 9 | "ROLLBACK"                                                             |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
SuperBuba commented 1 year ago

I did another test. It looks like the problem appears if you call a command from the event handler. Now I just call the command MerchantSetOwnerCommand, but after calling UPDATE, INSERT is still called:

+---+------------------------------------------------------------------------+----------------------------------------------------+
| # | SQL                                                                    | Params                                             |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 1 | "START TRANSACTION"                                                    |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 2 | SELECT t0.id AS id_1, t0.name AS name_2, t0.owner_user_id AS owner_use | 01GNVGQZ5GREB95ED6P8EQQ4C5                         |
|   | r_id_3, t0.owner_email AS owner_email_4 FROM merchants t0 WHERE t0.id  |                                                    |
|   | = ? LIMIT 1                                                            |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 3 | INSERT INTO users (id, email) VALUES (?, ?)                            | 01GNVGQZ78XBMVSA9632C3R0NY, merchant@test.com      |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 4 | UPDATE merchants SET owner_user_id = ?, owner_email = ? WHERE id = ?   | 01GNVGQZ78XBMVSA9632C3R0NY, merchant@test.com, 01G |
|   |                                                                        | NVGQZ5GREB95ED6P8EQQ4C5                            |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 5 | INSERT INTO merchants (id, name, owner_user_id, owner_email) VALUES (? | 01GNVGQZ5GREB95ED6P8EQQ4C5, merchant 1, 01GNVGQZ78 |
|   | , ?, ?, ?)                                                             | XBMVSA9632C3R0NY, merchant@test.com                |
+---+------------------------------------------------------------------------+----------------------------------------------------+
| 6 | "ROLLBACK"                                                             |                                                    |
+---+------------------------------------------------------------------------+----------------------------------------------------+
dgafka commented 1 year ago

When you use EventBus, you publish event right away, this means that setOwner will not finish yet, when User will be created.

You can use use WithAggregateEvents; trait to record events to be published after your aggregate is saved.

class Merchant
{
    use WithAggregateEvents;

    (...)

    #[CommandHandler]
    public function setOwner(MerchantSetOwnerCommand $command) : void
    {
        $this->owner    = new Owner(new Ulid(), $command->email);

        $this->recordThat(new MerchantOwnerSetted($this->owner->email(), $this->owner->userId()));
    }
} 

There was no double execution, however doctrine entity manager was cleaned after each command, which means due to previous EventBus based implementation, we have cleared info about Merchant and Doctrine wanted to insert it (because thought it was new entity).

Either way, this is now fixed with 1.67.1. Have fun :)

SuperBuba commented 1 year ago

Thanks a lot! Now everything works perfectly.