如何在 CQRS 架构中创建读写分离的 repos

How to create read and write separate repos in CQRS architecture

我有这个界面和存储库

<?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 {...}

现在,根据 CQRS,我必须将我的回购分成 2 个回购。一种用于读取,一种用于写入模型,对吗? 所以将我的界面拆分为 2 个界面。

    <?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;
}

并且应该创建 DoctrineUserWriteRepository.phpDoctrineUserReadRepository.php。那是对的吗?我真的迷失了。红吨文章,但它解释绝对不清楚。

好的,所以您在这里所做的是使用存储库模式拆分代码以实现 CQRS 架构。

我不知道你为什么要使用存储库模式来做到这一点,因为在大多数情况下,使用 CQRS 的想法与 DDD 和其他架构等一起使用(我我不会说它应该,但根据我的经验和与不同团队的合作......)。

另一方面,如果您只是想使用存储库模式来获得更好、更有条理的代码,那就继续去做吧,但另一方面,您应该意识到,在大多数情况下,人们不会这样做喜欢像这些案例这样的过度工程,而且这些人不仅仅是小辈。

无论如何,在这种情况下,我宁愿使用 Services 作为开始,所以这里有一个例子:

让我们从安装 Ecotone 开始,我个人在许多项目(大项目 of-course)中使用并看到了它。 composer require ecotone/laravel

然后你就可以构建你的服务了:

// 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;
    }
}

在上面的代码中,我们创建了 UserServicequery and command,您可能会想为什么它们会合二为一 class,我应该说,我和很多人一起工作过人们,在许多项目中,我看到人们使用一个 class 来做到这一点,而在其他一些情况下,他们使用两个 classes 来分隔 querycommand.

我宁愿将他们大体分开并做一些类似 UserQueryServices Or UserCommandServices 的事情,但在 other-hand 中你可能会与像 lead-architectures 这样的人一起工作,在这种情况下,他们'我会根据构建上下文等来决定要做什么(这根本没有任何问题,并且不要将同样的 class 想法视为一个坏主意。)。

现在在控制器中查询示例见下文:

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;
    }
}

和 of-course 命令:

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;
    }
}