有一份申请书,写在
.net
,写着DDD
,CQRS+EventSourcing
。有一个要求 - 为某些值对象添加翻译。如何实现这样的功能?
例如
有聚合产品和活动:
public class ProductId
{
public long Id {get;set;}
}
public class ProductDescription
{
public string Description {get;set;}
}
public class ProductName
{
public string Name{get;set;}
}
public class ProductAggregate
{
public ProductId {get;set;}
public ProductDescription Description{get;set;}
public ProductName Name{get;set;}
public ProductPrice {get;set;} // no need to have translation for this field while
...
}
public class DescriptionUpdatedEvent
{
ProductId Id{ get;set;}
PeoductDescription Description {get;set;}
}
应该能够为某些聚合的某些属性提供多种语言支持。例如,当用户使用英语语言的 UI 时,他应该看到“产品描述”,当用户将 UI 中的语言切换到 Fr 时,他应该看到“产品描述”。问题不在于如何翻译 UI,而在于如何向数据添加翻译。
我的想法:
public class ProductAggregate
{
public ProductId {get;set;}
public ProductDescription Description{get;set;}
public Dictionary<string,ProductDescription> DescriptionTranslations{get;set;}
public ProductName Name{get;set;}
public Dictionary<string,ProductName> NameTranslations{get;set;}
...
}
public class DescriptionTrnslationUpdated // new event should be added
{
ProductId Id{ get;set;}
string Language {get;set;} // can be 'en','fr' etc.
ProductDescription {get;set;}
}
看起来这是最简单的方法,每个字段只需添加 1 个事件。我在这里看到的唯一一个问题 - 我们将实体与其翻译混合在一起,它使聚合的大小加倍
public class ProductDescription
{
public string Description {get;set;}
public Dictionary<string,ProductDescription> Translations{get;set;}
}
public class DescriptionTrnslationUpdated // new event should be added
{
ProductId Id{ get;set;}
string Language {get;set;} // can be 'en','fr' etc.
ProductDescription {get;set;}
}
public class DescriptionUpdatedEvent2 // since VO modified, old event will be broken and we need to convert old event to new type or create new type and use it
{
ProductId Id{ get;set;}
PeoductDescription Description {get;set;}
}
这种方法看起来很复杂。
ProductFr-1
: public class ProductFrAggregate
{
public ProductId {get;set;}
public ProductDescription Description{get;set;}
public ProductName Name{get;set;}
...
}
public class DescriptionFrUpdated // new event should be added
{
ProductFrId {get;set}
string Language {get;set;} // can be 'en','fr' etc.
ProductDescription {get;set;}
}
public class DescriptionEnUpdated // new event should be added
{
ProductFrId {get;set}
string Language {get;set;} // can be 'en','fr' etc.
ProductDescription {get;set;}
}
也很简单,但是我们应该添加很多事件类型,每种语言 1 个事件
ProductTranslations-1
: public class ProductTranslationsAggregate
{
public ProductTranslationId {get;set;}
public Dictionary<string,ProductDescription> DescriptionTranslations{get;set;}
public Dictionary<string,ProductName> NameTranslations{get;set;}
...
}
public class DescriptionTranslationUpdated // new event should be added
{
ProductTranslationId {get;set}
string Language {get;set;} // can be 'en','fr' etc.
ProductDescription {get;set;}
}
public class NameTranslationUpdated // new event should be added
{
ProductTranslationId {get;set}
string Language {get;set;} // can be 'en','fr' etc.
ProductDescription {get;set;}
看起来和第一个一样简单,不需要添加太多事件类型。但由于我们将使用 2 个聚合,因此每个聚合都将具有一致性。目前还不清楚如何使用它。如果产品将被删除怎么办?或者有一些逻辑 - 有点“如果价格发生变化,描述应该更新”,在两个聚合之间管理这种情况变得很困难
我认为您不会找到这个问题的权威答案。
但是非权威地说...
你的大部分代码可能不关心翻译来自哪里;代码只是想知道要使用什么值。例如,如果您要生成目录的 HTML 表示形式,我们可能会有一个产品标识符、一个语言代码和一个时间戳,然后我们会将这些传递到某个黑匣子以获取我们需要的文本(可能是包含在带有其他有趣信息的数据结构中)。
换句话说,翻译的集合,以及决定在哪种情况下使用哪些翻译的政策可能是参考数据。在最幼稚的版本中,我们只是使用翻译者最后写入数据库的任何值。
如果翻译和翻译政策是您工作中的关键区别因素,那么您可能有一堆聚合来协助创建/维护翻译的过程。因此,您需要坐下来与“领域专家”进行翻译,弄清楚流程是什么,以及自动化如何提供价值。
(亚历克斯向我们发送了一份新的英文产品描述。因此,首先,我们确定需要提前准备哪些翻译,这是我们确定的方法......然后,对于每个翻译,我们向谷歌翻译提交请求,在这些条件下,该翻译“足够好”,我们将在其他条件下使用它;我们将谷歌的建议发送给帕特和查理,他们将进行改进,然后将该文本发送给金。最终批准....)
但是,如果您除了复制翻译者提供给您的信息之外没有做任何其他事情,那么您可能是一个数据库,而不是域模型,并且让自己陷入一堆“聚合”仪式中是一种浪费时间和资源。