Home > Mobile >  How to create read and write separate repos in CQRS architecture
How to create read and write separate repos in CQRS architecture

Time:05-22

I have this interace and repository

<?php

namespace App\Domain\Model\User;

interface UserRepositoryInterface
{
    public function findNextId(string $id = null): string;

    public function findOneById(string $id): ?User;

    public function findOneByEmail(string $email): ?User;

    public function save(User $user): void;

    public function remove(User $user): void;
}


final class DoctrineUserRepository implements UserRepositoryInterface {...}

Now as per CQRS I have to split my repo in 2 repos. One for read and one for write model, correct ? So splitting my interface in 2 interfaces.

    <?php

namespace App\Domain\Model\User;

interface UserRepositoryReadInterface
{
    public function findNextId(string $id = null): string;

    public function findOneById(string $id): ?User;

    public function findOneByEmail(string $email): ?User;
}

And write:
<?php

namespace App\Domain\Model\User;

interface UserRepositoryWriteInterface
{
    public function save(User $user): void;

    public function remove(User $user): void;
}

And should create DoctrineUserWriteRepository.php and DoctrineUserReadRepository.php. Is that correct? I am really lost in this. Red ton of articales but it is explained absolutelly not clear.

CodePudding user response:

Aright, so what you're doing here is using repository pattern to split your code to achieve CQRS architecture.

I don't know why you should do this using repository pattern because in most cases, the idea of using CQRS come with doing it with DDD and other architectures or etc.(I'm not going to say it should, but base on my experiences and working with different teams...).

In other hand If you're just trying to use repository pattern for better and organized code, then go ahead and do it, but in other hand, you should realize, there are most cases that, people don't like over engineering things like these cases, and these peoples are not just juniors.

Anyway, in this case, I would rather use Services to start with, so here's an example :

Let's start with installing Ecotone, which i personally used and seen in many projects (Big projects of-course). composer require ecotone/laravel

Then you can build your Service :

// I'll call this UserService since you've pointed to User in your repository pattern.

class UserService
{
    #[CommandHandler]
    public function save(SaveUserCommand $command) : void
    {
        // get user and save new info, create new one Or etc.
    }

    #[QueryHandler]
    public function findOneByEmail(GetUserEmaillAddressQuery $query)
    {
        $userFoundOut= User::where(... or find or etc. // use the query
        // to get user by email address or id or etc.;

        return $userFoundOut;
    }
}

In above code we've created UserService with query and command, which you might think why they're both in one class, which I should say, I worked with many peoples, and in many projects, I've saw people that they're doing this using one class, and in some other scenarios, they do it using two classes to separate query and command.

I'll rather to split them in general and do something like UserQueryServices Or UserCommandServices, but in other-hand you might work with people like lead-architectures, which in this case, they'll decide to what to do base on build context and etc (and there's not anything wrong with this at all, and do not consider this same class idea as a bad idea.).

Now in controller For Query Example see below:

class UserController
{
    public function __construct(private QueryBus $queryBus) {}


    public function getUserEmailAddress(Request $request)
    {
        $userId = $request->get('user_id');

        $user = $this->queryBus->send(new GetUserEmaillAddressQuery($userId));

        return new response($user->email);
    }
}

class GetUserEmaillAddressQuery 
{
    public function __construct(private string $user_Id) {}

    public function getUserId(): string
    {
        return $this->user_Id;
    }
}

And of-course for Command :

class UserController
{
    public function __construct(private CommandBus $commandBus) {}
    
    public function saveUser(Request $request)
    {
        $userId = $request->get('user_id');
        $name = $request->get('user_name');
        $email = $request->get('user_email');
        $something_cool= $request->get('something_cool');
        
        $this->commandBus->send(new SaveUserCommand($userId, $name $email ,$something_cool));
        
        return new response('done');
    }
}

class SaveUserCommand
{
    public function __construct(private string $user_id, private string $name,
private string $email, private string $something_cool) {}

    public function getUserId(): string
    {
        return $this->user_id;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function getSomethingCool(): string
    {
        return $this->something_cool;
    }


    public function getName(): string
    {
        return $this->name;
    }
}
  • Related