C# 事件源增量int-id

C# 事件源增量int-id,c#,cqrs,event-sourcing,C#,Cqrs,Event Sourcing,我看了很多事件源教程,都使用简单的演示来关注教程主题(事件源) 这很好,直到您在实际的工作应用程序中遇到这些教程中未涉及的内容:) 我碰到了这样的东西。 我有两个数据库,一个事件存储和一个投影存储(读取模型) 所有聚合都有一个GUID Id,到目前为止这是100%正常的 现在,我创建了一个新的JobAggregate和一个jobProjection。 我的公司要求有一个唯一的增量int64作业Id 现在我看起来很傻:) 另一个问题是,每秒会创建多次作业! 这意味着,获取下一个数字的方法必须是真正

我看了很多事件源教程,都使用简单的演示来关注教程主题(事件源)

这很好,直到您在实际的工作应用程序中遇到这些教程中未涉及的内容:)

我碰到了这样的东西。 我有两个数据库,一个事件存储和一个投影存储(读取模型) 所有聚合都有一个GUID Id,到目前为止这是100%正常的

现在,我创建了一个新的
JobAggregate
和一个jobProjection。 我的公司要求有一个唯一的增量int64作业Id

现在我看起来很傻:) 另一个问题是,每秒会创建多次作业! 这意味着,获取下一个数字的方法必须是真正安全的

在过去(没有ES),我有一个表,将PK定义为自动递增int64,save Job,DB做这个工作,给我下一个数字,done

但如何在聚合或命令处理程序中做到这一点? 通常情况下,投影作业由事件处理程序创建,但这将推迟到流程的后期,因为聚合应该已经具有int64。(用于在空数据库上重放聚合,并具有相同的聚合Id->Job Id关系)

我应该如何解决这个问题

问候

在过去(没有ES),我有一个表,将PK定义为自动递增int64,save Job,DB做这个工作,给我下一个数字,done

在这个序列中有一件重要的事情需要注意,那就是唯一标识符的生成和记录簿中数据的持久性都共享一个事务

当您分离这些想法时,您基本上看到的是两个事务——一个使用id,因此没有其他聚合尝试共享它,另一个将该id写入存储

最好的答案是安排这两个部分都是同一事务的一部分——例如,如果您使用关系数据库作为事件存储,那么您可以在保存事件时在同一事务的“aggregate_id to long”表中创建一个条目

另一种可能是将聚合的“创建”视为
Prepare
,然后是
Created
;事件处理程序通过事后保留长标识符来响应准备事件,然后向聚合发送新命令以将长标识符分配给它。因此,
创建的所有使用者
都可以看到分配了long的聚合

值得注意的是,您正在为创建的每个聚合分配一个有效的随机长,因此您最好深入了解公司认为它从中获得了什么好处--如果他们期望标识符将提供订购保证或完整性保证,那你最好明白这一点

先留长的没有什么特别不好的;根据聚合保存失败的频率,最终可能会出现间隙。在大多数情况下,您应该期望能够保持较小的故障率(即,在实际运行命令之前检查以确保您期望命令成功)

在真正意义上,唯一标识符的生成属于;我们通常使用UUID“欺骗”,放弃任何排序的伪装,并假装冲突风险为零。关系数据库非常适合集合验证;活动商店可能没那么多。如果您需要由模型控制的唯一顺序标识符,那么您的“分配标识符集”需要位于聚合中


接下来的关键短语是“业务成本”——确保您理解为什么长标识符很有价值。

@SharpNoizy,非常简单

创建自己的Id生成器。假设一个字母数字字符串,例如“DB3U8DD12X”,它提供了数十亿个可能性。现在,您要做的是通过给每个字符一个有序值,按顺序生成这些ID

0 - 0
1 - 1
2 - 2
.....
10 - A
11 - B
明白了吗?因此,接下来要做的是创建函数,使用该矩阵增加“D74ERT3E4”字符串的每个索引

所以,“R43E4D”,“R43E4E”,“R43E4F”,“R43E4G”。。。明白了吗

然后在加载应用程序时,查看数据库并找到生成的最新Id。然后在内存中加载接下来的50000个组合(如果您想要超高速),并创建一个将返回该值的静态类/方法

Aggregate.Id = IdentityGenerator.Next();
通过这种方式,您可以控制ID的生成,因为它是唯一具有这种能力的类

我喜欢这种方法,因为它在web api中使用时更“可读”。guid很难(而且很乏味)阅读、记忆等

获取api/job/DF73比记住api/job/XXXX-XXXX-XXXX-XXXX更好


这有意义吗?

以下是我的方法

我同意Id生成器的想法,它是“业务Id”,而不是“技术Id”

这里的核心是要有一个处理所有基础设施服务的应用程序级
JobService
,以协调要做的事情

控制器(如web控制器或命令行)将直接使用应用程序级别的
JobService
,以控制/命令状态更改

它在PHP中类似于伪代码,但这里我们讨论的是体系结构和流程,而不是语法。根据C#语法调整它,事情是一样的

应用程序级别 注意:如果同步投影的成本太高,只需返回Id:

        // ...
        $entityId = $jobCreatedEvent->getId();
        $this->jobProjector->enqueueProjection( $entityId );

        return $entityId;
    }
}
基础架构级别(各种应用程序通用) 域级别 如果您不喜欢创建entityId的工厂,对某些人来说可能看起来很难看,只需将其作为参数传递给特定类型,然后pss就有责任创建一个新的entityId,并且不要
        // ...
        $entityId = $jobCreatedEvent->getId();
        $this->jobProjector->enqueueProjection( $entityId );

        return $entityId;
    }
}
class JobBusinessIdGenerator implements DomainLevelJobBusinessIdGeneratorInterface
{
    // In infrastructure because it accesses persistance layers.

    // In the creator, get persistence objects and so... database, files, whatever.

    public function getNextJobId() : int
    {
        $this->lockGlobalCounterMaybeAtDatabaseLevel();

        $current = $this->persistance->getCurrentJobCounter();
        $next = $current + 1;
        $this->persistence->setCurrentJobCounter( $next );

        $this->unlockGlobalCounterMaybeAtDatabaseLevel();

        return $next;
    }
}
class JobEventFactory
{
    // It's in this factory that we create the entity Id.

    private $idGenerator;

    public function __construct( EntityIdGenerator $idGenerator )
    {
        $this->idGenerator = $idGenerator;
    }
    
    public function createNewJobCreatedEvent( Id $applicationExecutionId, int $businessId, string $jobDescription, xxxx $otherData ); : JobCreatedEvent
    {
        $eventId = $this->idGenerator->createNewId();
        $entityId = $this->idGenerator->createNewId();
        
        // The only place where we allow "new" is in the factories. No other places should do a "new" ever.
        $event = new JobCreatedEvent( $eventId, $entityId, $applicationExecutionId, $businessId, $jobDescription, $otherData );

        return $event; 
    }
}
class JobCreatedEvent;
class JobEventStoreService;
class JobProjectorService;