jorge07/symfony-6-es-cqrs-boilerplate

UserReadProjectionFactory

waeyy opened this issue · 15 comments

waeyy commented

Hi,

I have a question and in your case i don't understand the usage of class UserReadProjectionFactory.
It seems like it is never called. What's the point of this Factory?

In the query handler "FindByEmailHandler" you inject directly MysqlUserReadModelRepository.
But in my point is heavily coupled and when we want to change the way we get the data (Doctrine, Redis, etc) we must change this class.

Why don't we inject the factory here? Where the factory will handle the way to retrieve the data and then only working on our Infrastructure instead of working on Infrastructure and Application?

May be there's something here i don't understand well..

Thank you

Hi @waeyy
UserReadprojectionFwctory (quite redundant name to be honest...) extends Projector which is a type of event listener in the Broadway library this project use. It calls applyEVENT_NAME method with the event itself as argument as soon as the aggregateRoot is persisted in the event store.
So, yes, it's used and yes add the entire infrastructure class in the handler seems too much. Interface segregation seems way better.

Nice catch!

The reason why don't use the factory in the aggregate is that the factory just compose and store projections from the events but doesn't handle the query logic, just the write side.

waeyy commented

Thank you,
I misunderstood the way we use the UserReadProjectionFactory and its utility.

Concerning the events. Let's say when a user was created we want to send a welcome email to this user. The event here could be SendWelcomeEmailWhenUserWasCreated.
And then, in our AggreagateRoot, should we call the apply method with this event passed as parameter in the create function since we already have a call for apply the event UserWasCreated? Or should it be handled in a different way?

Another question, about adding the entire infrastructure class in the handler. We agree that interface segregation seems way better. But i see another issue, let's say we have an interface in our Infrastructure called UserReadModelRepositoryInterface and we add this interface to our handler.
So DoctrineUserReadModelRepository should implement this interface and everything will work properly.
But as soon as we want to add, let's say MongoContactReadModelRepository, RedisContactReadModelRepository, because we use the independency injection, Symfony will not know which one use.
And i am a little bit confused on how to handle that by keeping everything very simple as it is now and sure respect all of the rules of the DDD, CQRS/ES.

Thanks!

Hey @waeyy

  • SendWelcomeEmailWhenUserWasCreated: It doesn't sound like an event right? Sounds like an EventListener that consumes this UserWasCreated event. To do that you've 2 options: sync or async.

    • Sync: Extend this class and add a handle method like: handleUserWasCreated(UserWasCreated $event) and do the job inside. It will be called after this event persist in the event store.
    • Async: Follow the instructions here
  • About interface segregation: This interface in your example is too generic, too big. Think in something you use in the QueryHandler like GetOneUserByEmailInterface (you may no need this Interface suffix but is much clear for this example), this interface can be implemented in mysql, redis, or mongo with a low cost. You may need this interface just in one XReadModelRepository. Here and example of how we should do it in all places.

I hope you find this a little bit helpful

waeyy commented

Hey @jorge07,

Thank you, that was very helpful. Do you mind if i send you an email i have several questions that i would like to ask.

Thank you!

@waeyy I prefer github issues so we can share with others. Of course that's not always possible so feel free to email me if needed

waeyy commented

@jorge07 No problem!

I would like to discuss about Reset Password feature, i have some ideas but having external feedback would be great since i am not a pro in CQRS and Event Sourcing.

Should Reset Password Event be in User Aggregate? I'd probably say yes. Here is what i see:

  1. PasswordResetRequested: (Event) User has requested a password reset through the UI.
  2. PasswordResetEmailSent: (EventListener) It consumes the PasswordResetRequest event.
  3. PasswordResetProcess: (Event) User submits a new password and verification tokan.

Note: PasswordResetProcess: my brain is currently not able to find the right name for this event lol.

What are your thoughts and vision about that?

This workflow make sense, but yeah naming is hard. I prefer ForgottenPassword event, PasswordResetEmailSender for the EventListener, as doesn't live in the past like events. And PasswordChanged.
Keep in mind that to events are idempotent, so if you replay the state for some reason you don't spam all the people inbox

waeyy commented

And where do you putthe EventListener? For me, i think this one belongs to the Application Layer.

Here is how i did it:

My EventListener SendWelcomeEmailWhenUserSignUp where i inject an UserMailerServiceInterface (App\Domain\User\Service\Notification\Email) with a method sendWelcomeMailTo

Then in the Infrastructure Layer, UserMailerService implements that interface and extends an abstract class SwiftMailerService|MailjetService (App\Infrastructure\Common\Service\Notification\Email\Client).

I also have an Email (App\Domain\Common\Service\Notification\Email) object that represent an email.

And everything works properly.

Is the processor called when a event throw an exception or encountered an error? Because if not, this might answer the spamming issue.

IMHO notification channels are domain related most of the time. But it depends of your domain. I usually have a notification bounded context in domain and his implementation in the infrastructure layer. And then I just communicate this two bounded contexts by events. To do that define this use cases in application layer But instead of commands, are event listeners.

waeyy commented

Hi,

I am facing performance issues. I have a file (almost 1GB) that i am parsing and i would like to add each entry of this file in my database. There are like 1M rows in this file. I just a have a while that read each entry in the file and for each each 1 command. But it is SUPER SLOW.

Any suggestion on how to improve that?

Thank you

@waeyy I don't understand why this question is here, but I think it is the perfect problem to practice with yield 😉

waeyy commented

Oh when i created this thread it was about something i didn't understand well and then it turned on a thread where i can discuss and share problems/techniques about this boilerplate and those design pattern. That's why my question is here. I don't want to open a thread for each question.

And by using a generator i don't see a significative change, i mean it might be faster for sure but not too much

To process big batches I use to do it asynchronously so I can add more workers and speed up the process.

My suggestion is:

Read this file, send each row to a queue and continue with the next one. Then in the consumer side you can scale as much as you need.

This use case is a bit far from the usual modeling but this may work. You can loop at the end querying the queue size to reply to the user when it's done, or send a email or whatever

I'll close this as is already resolved. Fell free to comment any other question if needed