我目前正在开发具有登录页面的CQRS应用程序。在LoginCommand
中,从数据库中提取了一个用户,如果可以登录(加密的密码与给定的加密密码匹配),则会向该用户分配一个令牌。
在CQRS中,该命令通常接收该命令所针对的元素的ID,以便获取其标识的域聚合并对其执行逻辑。但是,在那种情况下,我从用户那里得到的是电子邮件。尽管这是一个唯一字段,但是尽管是一个唯一字段,但我不确定使用该字段获取聚合是否错误。
我还可以想到其他具有相同问题的情况,例如,尝试通过不包含帖子ID的给定语义URL识别Post
。
由于禁止在Command中执行查询,并且不太可能在登录表单中附加用户ID,在这种情况下,我必须提取哪些选项?我应该在命令(例如控制器)之外查询读取的模型吗?
我认为在这种特殊情况下,CQRS对您的设计没有任何影响。也许事件源可能会。
如果您有一些自然键,那么您有两个选择。一方面,它必须是唯一的。您的UserRepository
除了ID之外,可能还有一种使用电子邮件地址来提取用户的方法:
public interface IUserRepository
{
User Get(Guid id);
User Find(string email);
}
[当我可能返回Find
时我倾向于使用null
方法,因为Get
表示该实体应该存在,并且在找不到该实体时会抛出异常。
[如果只想按id
查找,则需要使用某些商店中的id
查找email
。根据您的一致性要求,最终的一致性查询/读取存储可能就足够了,但是没有什么可以阻止您访问具有电子邮件到ID映射的100%一致性存储。
从域的角度来讲,与ID
记录关联的User
就是surrogate key。它在现实世界中没有对应的表示形式,仅用于帮助您持久保存和检索数据。
因此,如果email
是您的User
记录的唯一字段,请务必像在命令中使用标识符一样使用它。
并不一定意味着您可以摆脱id
字段。
[您仍然希望在id
记录中有一个替代键,例如User
字段,因为您可能希望给用户提供更改其电子邮件地址的选项。即使更改了电子邮件地址,您仍需要能够在整个系统中唯一地标识用户,这就是代理密钥派上用场的地方。由于性能原因,您还需要代理密钥。最好总是使用Integer
或UUID
字段代替String
电子邮件地址作为主键,或在参考字段中使用。
您还应该在Command
及其对应的Command Handler
之间进行区分。 Command
只是一个DTO,它封装了外部环境中发生的更改或需要提交给数据库的更改。从这个意义上讲,它们是不可变的,不应以任何方式执行查询或更新自身。
A Command Handler
(本质上类似于应用程序服务,但具有后台功能)消耗命令中的数据。在这里,您可以查询存储库和检索记录。实际上,这将是进行任何重复或参考密钥验证的必要条件。
我强烈建议这不是'命令',而是从读取模型中读取。这是为什么:
您只是在检查提供的凭据是否与读取模型中的凭据匹配。此操作不会更改域的状态,因此与命令的使用不一致。
但是这里存在更严重的潜在问题。我不确定您是否正在使用事件源,但是如果您使用事件源,我将非常担心将密码放入事件源中。即使加密。使用当前密码和历史密码对事件存储进行数据泄露可能是一个现实问题。
还有更多...
我想尽可能限制密码在网络上的传输。与对成员数据库进行传统的凭据检查相比,将其添加到命令中(取决于您的基础结构)会增加额外的传输时间。
我确实知道,但是您可能想要记录某人已登录或登录失败的事实。为此,如果您的域需要此命令,请发出“ RecordSuccessfullLoggin”或“ RecordFailedLoginAttempt”之类的命令。
首先,我假设当您说您向用户分配令牌时,这意味着您在数据库中写入了该分配。否则,您的登录命令将不是命令。