Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populate Projection with multiple tables #59

Open
webdevilopers opened this issue Aug 10, 2021 · 2 comments
Open

Populate Projection with multiple tables #59

webdevilopers opened this issue Aug 10, 2021 · 2 comments

Comments

@webdevilopers
Copy link
Owner

Came from:

Here is a current example where we need to get guest data from a different stream before we insert a related (guestId) visit data - promoting the unique ID - at all.
What are the options beside event enriching? Separate repository calls sound like a bad idea.

projection
readmodel

As suggested by @prooph and @gquemener for SQL projections:

Instead of storing guest data in projection state, write it to a table that is only used by the projection.

IIUC, you could store your guest data (guestId, postalCode, ...) in a separate table and make a classic inner join when querying your "audience" table (as you have the guestId when inserting into this table, you can reference it).
You may even use your existing read model class, with specialized methods (eg. insertAudience, insertGuest, ...). There's no need to make a separate read model for the guest table, IMO. Only advice would be to limit join within the read model (as in a single-table read model).

I will post two possible solutions for this in Prooph soon.

@webdevilopers
Copy link
Owner Author

webdevilopers commented Aug 10, 2021

Solution 1: A single Manager and Read Model for Projection "audience"

Both tables are managed by a single read model.

// ...

final class PgsqlAudienceProjection implements ReadModelProjection
{
    public function project(ReadModelProjector $projector): ReadModelProjector
    {
        $projector->fromStreams('guest_profile_stream', 'visit_stream', 'guest_preference_stream')
            ->when([
                GuestProfileCreated::class => function ($state, GuestProfileCreated $event) {
                     /** @var PgsqlAudienceReadModel $readModel */
                    $readModel = $this->readModel();
                    $readModel->stack('insertGuest', [/*...*/]);
                },
                GuestSelfCheckedIn::class => function ($state, GuestSelfCheckedIn $event) {
                    /** @var PgsqlAudienceReadModel $readModel */
                    $readModel = $this->readModel();
                    $readModel->stack('insert', [/*...*/]);
                }
            ]);

        return $projector;
    }
}
final class PgsqlAudienceReadModel extends AbstractReadModel
{
    private \PDO $connection;

    public function __construct(\PDO $connection)
    {
        $this->connection = $connection;
    }

    public function init() : void
    {
        $this->connection->exec(sprintf('create table %1$s (
            visit_id uuid not null,
            guest_id uuid not null,
            // ...
            primary key (visit_id)
        );', ProjectionName::AUDIENCE));

        $this->connection->exec(sprintf('create table %1$s (
            guest_id uuid not null,
            // ...
            primary key (guest_id)
        );', ProjectionName::GUEST_DATA));
    }

    public function reset() : void
    {
        $this->connection->exec(sprintf('TRUNCATE TABLE %s', ProjectionName::AUDIENCE));
        $this->connection->exec(sprintf('TRUNCATE TABLE %s', ProjectionName::GUEST_DATA));
    }

    public function delete() : void
    {
        $this->connection->exec(sprintf('DROP TABLE %s', ProjectionName::AUDIENCE));
        $this->connection->exec(sprintf('DROP TABLE %s', ProjectionName::GUEST_DATA));
    }

    protected function insertGuest(array $data): void
    {
        // ...
    }

    protected function insert(array $data): void
    {
        // ...
    }

    // ...
}

Solution 2: A single Manager and separate Read Models for Projection "audience"

// ...

final class PgsqlAudienceProjection implements ReadModelProjection
{
    public function project(ReadModelProjector $projector): ReadModelProjector
    {
        $projector->fromStreams('guest_profile_stream', 'visit_stream', 'guest_preference_stream')
            ->when([
                GuestProfileCreated::class => function ($state, GuestProfileCreated $event) {
                     /** @var PgsqlGuestDataReadModel $readModel */
                    $readModel = $this->readModel();
                    $readModel->stack('insert', [/*...*/]);
                },
                GuestSelfCheckedIn::class => function ($state, GuestSelfCheckedIn $event) {
                    $guest = $state['guests'][$event->guestId()->toString()];

                    /** @var PgsqlAudienceReadModel $readModel */
                    $readModel = $this->readModel();
                    $readModel->stack('insert', [/*...*/]);
                }
            ]);

        return $projector;
    }
}

Which one would you prefer @codeliner @prolic?

@prolic
Copy link

prolic commented Aug 12, 2021

I have projectors per use case, not per table. It could be however, that your use case involves one projector per table.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants