- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我面临一个设计问题,我想在两个不同的有界上下文中对同一物理对象进行建模。
为了尽可能准确地描述我的问题,甚至我意识到这只是一个实现细节,我将从事件源机制开始。
我的事件存储机制
以下内容受到Greg Young的CQRS文档https://cqrs.wordpress.com/documents/的广泛启发(请注意PDF“构建事件存储”部分)。
我有 2个表,一个表称为Aggregates
,另一个表称为Events
(注意,复数形式是表,而不是对象!)看起来像这样:
汇总表
我所有的汇总都存储在此表中;它具有3列(因此不支持md表格式,因此,抱歉,我会列出):
AggregateId
:基本上是该表的主键。我正在使用Guid,因为我的所有聚合都使用了Guid。AggregateType
:完全限定的聚合名称。 CurrentVersion
:当前的聚合版本。每次存储事件时都会增加的整数。 AggregateId
:聚集表的外键。 SerializedEvent
:聚集发布但以序列化形式(例如json)发布的域事件Version
:每次存储事件时(对于每个给定的聚合)递增的整数。 EventDate
:日期时间UserName
:发出事件的生产命令Product Entity
,其中包含的属性与Sales观点和Supply观点一样相关。
Product AR
namespace DomainModel/WriteSide;
abstract class AggregateRoot
{
protected $lastRecordedEvents = [];
protected function recordThat(DomainEvent $event)
{
$this->latestRecordedEvents[]=$event;
$this->apply($event);
}
protected function apply(DomainEvent $event)
{
$method = 'apply'.get_class($event);
$this->$method($event);
}
public function getUncommittedEvents()
{
return $this->lastestRecordedEvents;
}
public function markEventsAsCommitted()
{
$this->lastestRecordedEvents = [];
}
public static function reconstituteFrom(AggregateHistory $history)
{
foreach($history as $event) {
$this->apply($event);
}
return $this;
abstract public function getAggregateId();
}
namespace DomainModel/WriteSide/SupplyChain;
use DomainModel/WriteSide/AggregateRoot as BaseAggregate;
Class Product extends BaseAggregate
{
private $productId;
private $productName;
//some other attributes related to the supply chain BC...
public function getAggregateId()
{
return $this->productId;
}
private function __construct(ProductId $productId, $productName)
{
//private constructor allowing factory methods
}
public static function AddToCatalog(AddProductToCatalogCommand $command)
{
//some invariants protection stuff
$this->recordThat(new ProductWasAddedToCatalog($command->productId));
}
private function applyProductWasAddedToCatalog(DomainEvent $event)
{
$newProduct = new Product($event->productId);
return $newProduct;
}
//more methods there...
}
AddProductToCatalogCommand(/*...*/)
,Aggregates
表中插入新行:Events
表中插入新行:ProductWasAddedToCatalog($productId)
(当然是序列化形式)namespace DomainModel/WriteSide/Sales;
use DomainModel/WriteSide/AggregateRoot as BaseAggregate;
Class Product extends BaseAggregate
{
private $productId;
//some other attributes related to the Sales BC, like sales price, guarantees...
public static function AddAutomaticallyProductToCatalogSinceSupplyChainAddedIt(UpdateSalesCatalogCommand $command)
{
// some invariants' protection code here
$this->recordThat(new ProductWasAutomaticallyAddedToSalesCatalog($command->productId));
}
}
public static function reconstituteFrom(AggregateHistory $history)
时会收到另一个ID的事件
namespace DomainModel/WriteSide/Sales;
use DomainModel/WriteSide/AggregateRoot as BaseAggregate;
Class Product extends BaseAggregate
{
private $productId;
private $supplyChainProductId; //the reference to the supply chain BC Product AR...
public function getAggregateId()
{
return $this->productId;
}
//more methods there...
}
AggregateId
列作为主键,但是我可以同时使用
AggregateId
和
AggregateType
。
namespace DomainModel/WriteSide/Sales;
use DomainModel/WriteSide/AggregateRoot as BaseAggregate;
// **here i'd introduce my sub-entity**
use DomainModel/Sales/Product/Entities/Product as ProductEntity;
Class Product extends BaseAggregate
{
private $_Id;
private $product; //holds a ProductEntity instance
public function getAggregateId()
{
return $this->_Id;
}
public function getProductId()
{
return $this->product->getProductId();
}
//more methods there...
}
namespace DomainModel/QuerySide;
Class ProductMapping
{
private $productId;
private $salesAggregateId;
private $supplyChainAggregateId;
private $product; //holds a ProductEntity instance
public function getSalesAggregateId()
{
return $this->salesAggregateId;
}
public function getSupplyChainAggregateId()
{
return $this->supplyChainAggregateId();
}
}
Class ProductMappingRepository
{
public function findByproductId($productId)
{
//return the ProductMapping object
}
public function addFromEvent(DomainEvent $event)
{
//this repository is an event subscriber....
}
}
最佳答案
哇,这里有很多事。对AR建模既是一门艺术,又是一门科学!
第一点建议:设计AR时不要涉及数据库。为什么? CQRS,AR和事件源都是DDD的战略和战术模式。重点是消除建模过程中的干扰。数据库很分散注意力(在这种情况下)。这可能是您遇到困难的根本原因。
有限的上下文是简化建模的一种机制。它们应反映各个部门如何看待产品/物品等事物。实际上,该模型就是一个很好的例子。模型名称反映了企业在每种情况下使用的词。在某些方面,当他们谈论同一件事时,他们是不同的。它们在各自的上下文中具有不同的含义。因此,需要对它们分别建模。
那外部引用呢...
一个AR可以引用另一个AR,但只能以ID的形式(不一定是数据库密钥)。实际上,AR不得在其内部包含对另一个AR的引用。包含另一个AR的私有变量(具有a)。这是因为仅保证AR在其边界内保持一致。
这将我们带入问题中的问题。我们如何协调来自不同边界环境的这3个AR?
第一种方法是询问它们是否实际上处于不同的受限上下文中。有时,这些建模问题是引发对模型进行重新思考的有用方法。
让我们为您的域假设它们是正确的。我们如何协调他们?
在这种情况下,流程管理器和反腐败层似乎是一个不错的选择。流程经理将监听产品和/或项目创建的事件。然后它将生成用于创建其他实体的适当命令。每种情况有不同的机会。因此,需要ACL。 ACL负责将请求转换为在其域内有意义的内容。这可能很简单,只需将原始AR的外部ID添加到命令中以创建其AR。或者它可能只是将信息保存在暂存区域中,直到满足其他各种条件为止。
在较高级别上,听事件并在其他有界上下文中使用它们来触发相关过程。使用流程管理器(如果需要)和ACL。
最后是存储问题...
我会在这里选择一种简单的事件存储策略。将每个事件保持在流中。使用AR ID可以撤回任何单个AR的事件。
对于读取模型,我将使用一组侦听事件流的反规范器。然后,他们将生成针对UI定制的读取模型(在这种情况下)。这可能涉及合并来自不同BC的信息。对您的用户有意义的一切。
我在博客中的一篇文章4 Secrets to Inter Aggregate Communication中介绍了其中一些想法。
无论如何,我希望这会有所帮助。
关于domain-driven-design - DDD:建模聚合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29716944/
我遇到了搜索引擎优化问题,用户可以通过指定任何子域访问我的站点。这会导致 SEO 出现重复页面问题。 例如,如果用户错误输入“www”然后在论坛上发布了一个链接,则谷歌正在抓取“wwww.domain
这个问题在这里已经有了答案: 关闭10 年前。 Possible Duplicates: Can I change all my links to just //? Network-Path Ref
我正在玩 lighttpd在小型虚拟专用服务器上。我两个域指向服务器。我在 Ubuntu 8.10 上使用最新版本的 lighttpd 和 mod_evhost。 我正在尝试设置一个规则,这样如果任何
我正在开发一个由 domain.ext 提供的网络应用程序。此 Web 应用程序使用基于 cookie 的 session ,并为用户提供在子域上托管包含自定义 JavaScript 的网页的能力,例
我想重定向: 情况一、example.com到subdomain.example.com 情况2.www.example.com到subdomain.example.com 我已将这两段代码放入我的
我遇到了“领域对象”这个术语,并在 Google 上找到了几个定义,但我只是想验证我的理解是否正确。 这是否只是表示业务规则的任何类 - 因为“域”一词通常表示特定于某些本地问题集的规则,例如如何计算
将我们的应用程序静态内容托管在一个完全不同的域上而不是像其他域一样托管在我们主域的子域上有什么好处吗?例如微软使用 i.microsoft.com,谷歌使用 gstatic.com,雅虎使用 yimg
Orbited的客户端JS组件( cometd 服务器),要求如果服务器运行在与 JS 本身不同的域或端口上,则必须执行 document.domain = document.domain; 在加载任
我通读了另一篇文章,但似乎没有人回答我一直遇到的问题。是否可以使用通配符域的通配符子域(即使它仅用于子域而不是子子域),例如:foo.example.local。 我已经有 example.local
我在 Google Domains 上有一个域名,我想指向我在家里的 raspberry pi 服务器上运行的网站。我的公共(public) IP 地址是动态的,当 IP 更新时,我无法弄清楚如何保持
我有这个经典的 DDD 问题;我有一个做一些事情的域服务“DetectPriority”。 PM 要求我创建 2 个不同的服务;一个 INTERNAL(包含完整的业务规则并涉及许多其他领域模型)和另一
An interesting thread我刚才输入这个问题时就出现了。但我认为它没有回答我的问题。 我一直在使用 .NET MVC3 进行大量工作,因此需要一个贫乏的模型。 View 模型和编辑模型
我正在通过docker-compose运行最新版本的Artifactory OSS(如here和here所述)。我的docker-compose文件看起来像这样: version: '2' servi
我正在阅读 MDC entry for nsICookieManager2.add它讨论了域和非域 cookie。这两种类型的 cookie 有什么区别? 最佳答案 来自 RFC2109: Host
这是我的问题:http://regexr.com?2temn 我相信对于你们中的一些正则表达式大师来说这很简单。 干杯! 最佳答案 这也有效: (?<=\.|)\w+\.\w+$ 仅使用 PHP 进行
这应该很简单,但我无法理解。有许多好的和坏的正则表达式方法来匹配 URL,有或没有协议(protocol),有或没有 www。我遇到的问题是这个(在 javascript 中):如果我使用正则表达式来
我有一个网站 http://mywebsite.tld ,我想将 www.mywebsite.tld 重定向到 http://mywesite.tld (所以 www=>非 www) (防止重复内容)
TL;DR 我应该从 www.domain.tld 重定向到 domain.tld 还是相反? 我正在运行一个处理多个域的 CMS。到目前为止,CMS 负责将 www.domain.tld 重定向到
我目前有以下代码: string user = @"DOMAIN\USER"; string[] parts = user.Split(new string[] { "\\" }, StringSpl
使用 Go 和 AWS-SDK 我正在尝试查询 AWS 控制台中 Route53 -> Hosted Zones 下列出的 route53 CNAME 和 A 记录。我可以使用以下代码进行查询,但它需
我是一名优秀的程序员,十分优秀!