Domain driven design 如何创建多语言域模型
我正在使用领域驱动设计,我对我的领域模型有一个非常清晰的了解。它包含120多个类,并且非常稳定。我们将在.NET4和C#中实现它。问题是,我们需要多语言的模型;有些属性需要以多种语言存储。例如,Person类有一个类型为Domain driven design 如何创建多语言域模型,domain-driven-design,multilingual,Domain Driven Design,Multilingual,我正在使用领域驱动设计,我对我的领域模型有一个非常清晰的了解。它包含120多个类,并且非常稳定。我们将在.NET4和C#中实现它。问题是,我们需要多语言的模型;有些属性需要以多种语言存储。例如,Person类有一个类型为string的Position属性,该属性应以英语(例如“Library”)和西班牙语(例如“Bibliotecario”)存储一个值。此属性的getter应返回英语或西班牙语版本,具体取决于某些语言参数 现在开始我的问题。我不知道如何将其参数化。我列举了两种主要的方法: 使用属
string
的Position属性,该属性应以英语(例如“Library”)和西班牙语(例如“Bibliotecario”)存储一个值。此属性的getter应返回英语或西班牙语版本,具体取决于某些语言参数
现在开始我的问题。我不知道如何将其参数化。我列举了两种主要的方法:
字符串
,而是一个字典
,它可以让客户通过语言检索此人的位置- 要实现我的多语言域模型目标,哪一个是最好的选择(1或2)
- 还有其他我没有考虑的选择吗?它们是什么
与问题不直接相关,但您可能需要考虑域中多个有界上下文的存在。
我同意Oded的观点,在您的场景中,语言是一个UI问题。当然,域可以通过C#和英语的组合来声明,它所表示的是抽象的。UI将使用-effective选项2处理语言 具有Position属性的Person实体不控制用于表示位置的自然语言。您可能有一个用例,您希望用一种语言显示一个位置,而它最初存储在另一种语言中。在这种情况下,可以将转换器作为UI的一部分。这类似于将货币表示为金额和货币的一对,然后在货币之间进行转换 编辑 此属性的getter应返回英语或西班牙语 版本取决于某些语言参数什么决定了这个语言参数?什么负责确保Position以多种语言存储?或者翻译是在飞行中进行的?谁是该物业的客户?如果客户机确定语言参数,为什么客户机不能在不涉及域的情况下执行翻译?是否存在与多种语言相关的行为,或者这只是出于阅读目的?DDD的要点是提取您的核心行为域,并将与查询数据相关的方面转移到其他职责领域。例如,您可以使用访问具有特定语言的聚合的Position属性。域模型是一种抽象-它为世界的特定部分建模,它捕获域的概念 该模型的存在使得开发人员可以用领域专家交流的方式进行代码交流——对相同的概念使用相同的名称 现在,西班牙语专家和英语专家可能会对同一个概念使用不同的词,但概念本身是相同的(人们希望,尽管语言可能会模棱两可,人们并不总是以相同的方式理解同一个概念,但我离题了) 代码应该为这些概念选择一种人类语言并坚持使用。模型完全没有理由为了表示一个概念而由不同的语言组成 现在,您可能需要用他们的语言向用户显示应用程序数据和元数据,但概念没有改变 在这方面,第二个选项是您应该做的事情-在.NET中,这通常是通过查看
CurrentThread.CurrentCulture
和/或CurrentThread.CurrentUICulture
来完成的,并且使用它将包含本地化的资源。给出以下内容:
一些用例涉及在所有的场景中设置对象的值
支持的语言;另一些则涉及到查看一个给定项目中的值
语言
我建议两种选择都去。这意味着持有多语言内容的个人和所有类别都应将该内容保持在其状态,并且:
- Position属性应设置/获取
public enum Language { English, German } // all multilingual entity classes should derive from this one; this is practically a partly implemented observer public abstract class BaseMultilingualEntity { public Language CurrentLanguage { get; private set; } public void SetCurrentLanguage(Language lang) { this.CurrentLanguage = lang; } } // this is practically an observable and perhaps SRP is not fully respected here but you got the point i think public class UserSettings { private List<BaseMultilingualEntity> _multilingualEntities; public void SetCurrentLanguage(Language lang) { if (_multilingualEntities == null) return; foreach (BaseMultilingualEntity multiLingualEntity in _multilingualEntities) multiLingualEntity.SetCurrentLanguage(lang); } public void TrackMultilingualEntity(BaseMultilingualEntity multiLingualEntity) { if (_multilingualEntities == null) _multilingualEntities = new List<BaseMultilingualEntity>(); _multilingualEntities.Add(multiLingualEntity); } } // the Person entity class is a multilingual entity; the intention is to keep the XXXX with the XXXXInAllLanguages property in sync public class Person : BaseMultilingualEntity { public string Position { set { _PositionInAllLanguages[this.CurrentLanguage] = value; } get { return _PositionInAllLanguages[this.CurrentLanguage]; } } private Dictionary<Language, string> _PositionInAllLanguages; public Dictionary<Language, string> PositionInAllLanguages { get { return _PositionInAllLanguages; } set { _PositionInAllLanguages = value; } } } public class Program { public static void Main() { UserSettings us = new UserSettings(); us.SetCurrentLanguage(Language.English); Person person1 = new Person(); us.TrackMultilingualEntity(person1); // use case: set position in all languages person1.PositionInAllLanguages = new Dictionary<Language, string> { { Language.English, "Software Developer" }, { Language.German, "Software Entwikcler" } }; // use case: display a person's position in the user language Console.WriteLine(person1.Position); // use case: switch language us.SetCurrentLanguage(Language.German); Console.WriteLine(person1.Position); // use case: set position in the current user's language person1.Position = "Software Entwickler"; // use case: display a person's position in all languages foreach (Language lang in person1.PositionInAllLanguages.Keys) Console.WriteLine(person1.PositionInAllLanguages[lang]); Console.ReadKey(); } }
public sealed class VATIN : IEquatable<VATIN> { // implementation here... } public sealed class Position : IEquatable<Position> { // implementation here... } public sealed class Person { // a few constructors here... // a Person's identifier from the domain expert, since it's an entity public VATIN Identifier { get { // implementation here } } // some more properties if you need them... public Position CurrentPosition { get { // implementation here } } // some commands public void PromoteTo(Position newPosition) { // implementation here } } public sealed class User { // <summary>Express the position provided according to the culture of the user.</summary> // <param name="position">Position to express.</param> // <exception cref="ArgumentNullException"><paramref name="position"/> is null.</exception> // <exception cref="UnknownPositionException"><paramref name="position"/> is unknown.</exception> public string Express(Position position) { // implementation here } // <summary>Returns the <see cref="Position"/> expressed from the user.</summary> // <param name="positionName">Name of the position in the culture of the user.</param> // <exception cref="ArgumentNullException"><paramref name="positionName"/> is null or empty.</exception> // <exception cref="UnknownPositionNameException"><paramref name="positionName"/> is unknown.</exception> public Position ParsePosition(string positionName) { // implementation here } }
// IDomainDictionary would be resolved based on CurrentThread.CurrentUICulture var domainDict = container.Resolve<IDomainDictionary<Position>>(); var position = person.Position; Debug.Writeline(domainDict.NameFor(position, pluralForm: 1));