2013-02-27 7 views
4

Я использую доменный дизайн, и у меня есть довольно четкое представление о моей модели домена. Он содержит более 120 классов и довольно стабилен. Мы реализуем его в .NET 4 и C#. Дело в том, что модель должна быть многоязычной; некоторые атрибуты должны храниться на нескольких языках. Например, класс Person имеет свойство Position типа string, которое должно хранить значение на английском языке (например, «Библиотекарь») и испанский (например, «Bibliotecario»). Получатель для этого свойства должен возвращать английскую или испанскую версию в зависимости от какого-либо параметра языка.Как создать многоязычную модель домена

И вот начните мои вопросы. Я не уверен, как параметризовать это. Я выделил два основных способа:

  1. Использовать коллекцию недвижимости. Позиция не была бы string, а скорее Dictionary<Language, string>, что позволило бы клиентам получить позицию человека по языку.
  2. Сохраняйте простые, скалярные свойства, но заставляйте их возвращать (или задавать) значение для одного или другого языка в зависимости от общедоступной настройки «текущего языка». Клиентский код задает рабочий язык, а затем все объекты будут установлены и получат значения на этом языке.

Вариант 1 позволяет избежать глобального состояния, но он испортил интерфейс почти каждого класса в моей модели. С другой стороны, вариант 2 менее выразителен, поскольку вы не можете сказать, какой язык вы собираетесь получить, не глядя на глобальные настройки. Кроме того, он вводит зависимость в каждый класс в глобальной настройке.

Обратите внимание, что меня не интересуют реализация БД или ORM; Я работаю только на уровне модели домена.

У меня есть два конкретных вопроса, то:

  • Что является лучшим вариантом (1 или 2), чтобы достичь своей цели многоязычной модели предметной области?
  • Есть ли другие варианты, которые я не рассматривал, и какие они?

спасибо.

Редактировать. Некоторые из них предположили, что это проблема, связанная с пользовательским интерфейсом, и поэтому ее можно решить с помощью поддержки глобализации/локализации в .NET. Я не согласен. Локализация пользовательского интерфейса работает только в том случае, если вы знаете локализованные литералы, которые должны отображаться пользователю во время компиляции, но это не наш случай. Мой вопрос включает в себя многоязычные данные, которые неизвестны во время компиляции, поскольку они будут предоставлены в виде пользовательских данных во время выполнения. Это не проблема, связанная с пользовательским интерфейсом.

Редактировать 2. Пожалуйста, имейте в виду, что Person.Position - это просто игрушечный пример, иллюстрирующий вопрос. Это не часть реальной модели. Не пытайтесь критиковать его или улучшать его; в этом нет смысла. Наши бизнес-требования включают в себя множество атрибутов, которые не могут быть закодированы как типы перечислений или аналогичные, и должны оставаться свободным текстом. Отсюда и трудность.

+0

На какой платформе вы являетесь? Некоторые из них имеют хорошую (?) Поддержку локализации и интернационализации из коробки. – Oded

+0

.NET 4 и C#. Я знаком с поддержкой интернационализации .NET для WinForms, WPF и ASP.NET, но я не работаю над пользовательским интерфейсом. Речь идет только о модели домена: классы без визуального компонента. – CesarGon

+0

Должен признаться, это звучит странно для меня. Почему это для модели домена, если она не будет видна? – Oded

ответ

5

Учитывая следующее:

Некоторых Прецеденты включают установки значений для объекта во всех поддерживаемых языках; другие предполагают просмотр значений в одном заданном языке .

Я предлагаю перейти к обоим вариантам. Это означает, что лицо, и все классы, которые держат многоязычный контент должны держать, что содержание в их состоянии и:

  • Свойства Положения должны установить/получить положение человека в языке пользователя ток.

  • Должно быть соответствующее свойство или способ для все язык установка/получение.

  • Должен быть способ установки (или даже переключения при необходимости) на язык пользователя. Я бы создал абстрактный класс (например, BaseMultilingualEntity) с абстрактным методом SetLanguage (Language ) и получателем CurrentLanguage. Вам нужно сохранить дорожку всех объектов, которые происходят из BaseMultilingualEntity в каком-то реестре, который может выставить языковые настройки.

РЕДАКТИРОВАТЬ С НЕКОТОРЫХ КОД

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(); 

    } 
} 

+0

+1 Это звучит хорошо; Спасибо. Он точно соответствует моему варианту 2 в OP. Вы могли бы рассмотреть класс модели, например, для отслеживания языка, к которому могут иметь доступ все бизнес-объекты, а не абстрактные BaseMultilingualEntity? – CesarGon

+0

@ GesarGon Я добавил код на свой оригинальный ответ, который может пролить некоторый свет – gtourkas

+0

Спасибо. Так, действительно, это очень близко к предлагаемому варианту 2 в ОП. Это хорошо. ;-) – CesarGon

0

Он содержит более 120 классов и довольно стабилен.

Непосредственно связано с вопросом, но вы можете подумать о существовании множества ограниченных контекстов в вашем домене.

Я согласен с Одедом, что, похоже, в вашем сценарии язык является проблемой пользовательского интерфейса. Конечно, домен может быть объявлен посредством комбинации C# и английского языка, то, что он представляет, является абстрактным. Пользовательский интерфейс будет обрабатывать язык с CultureInfo.CurrentCulture - эффективно вариант № 2.

Объект Person, имеющий свойство Position, не определяет естественный язык, используемый для представления позиции. У вас может быть случай использования, когда вы хотите отображать позицию на одном языке, в то время как она первоначально хранится в другом. В этом случае у вас может быть переводчик как часть пользовательского интерфейса. Это похоже на представление денег как пары суммы и валюты, а затем конвертации валют.

EDIT

Поглотитель для этого свойства следует вернуть английский или испанский вариант в зависимости от некоторого параметра языка.

Что определяет этот параметр языка? Что несет ответственность за то, что, например, Position, хранится на нескольких языках? Или перевод выполняется «на лету»? Кто является клиентом собственности? Если клиент определяет параметр языка, почему клиент не может выполнить перевод без привлечения домена? Есть ли поведение, связанное с несколькими языками, или это только проблема для чтения? Точка DDD - это перераспределение вашего основного поведенческого домена и изменение аспектов, связанных с запросом данных, в другие области ответственности. Например, вы можете использовать read-model pattern для доступа к свойству Position совокупности с определенным языком.

+0

Мы рассмотрели, составляют ли 120 классов один или несколько ограниченных контекстов, но мы уверены, что он один. У нас есть несколько сложных иерархий специализации концепций, связанных с наукой; В целом модель не охватывает столько, сколько концептуальной ширины. – CesarGon

+0

Что касается вашего предложения о том, как обращаться с многоязычным аспектом, см. Мой комментарий к ответу Одеда. Благодарю. – CesarGon

3

Доменная модель - это абстракция - она ​​моделирует определенную часть мира, она захватывает концепции домена.

Модель существует, поэтому разработчики могут общаться в коде так, как сообщают специалисты домена, используя те же названия для тех же понятий.

Теперь испанский эксперт и английский эксперт могут использовать разные слова для одной и той же концепции, но сама концепция будет такой же (можно надеяться, хотя язык может быть двусмысленным, и люди не всегда понимают одну и ту же концепцию в то же самое, но я отвлекаюсь).

Код должен выбрать один человеческий язык для этих понятий и придерживаться его. Нет абсолютно никакой причины для того, чтобы модель состояла из разных языков, чтобы представлять единую концепцию.

Теперь вам может понадобиться показать пользователей данных приложения и метаданных на их языке, но концепция не меняется.

В связи с этим, вы вторым вариантом является, что вы должны делать - с .NET, обычно это делается, глядя на CurrentThread.CurrentCulture и/или CurrentThread.CurrentUICulture и с помощью satellite assemblies, который будет содержать локализованные ресурсы.

+0

Спасибо, что ответили. Может быть, я не слишком четко писал свой вопрос. Я согласен с вами в отношении концепций захвата модели * class * и относительно независим от языка. Однако многоязычный аспект появляется, когда мы переходим к * объектным * моделям, то есть к примерам модели класса. Класс Person, чтобы использовать пример, не изменяется. Но это могут быть случаи. В моем примере я могу установить p.Position = «Библиотекарь» или p.Position = «Bibliotecario»; концептуально они говорят одно и то же и представляют одну и ту же реальность, но я использую разные языки для этого. (TBC) – CesarGon

+0

В этой связи я не вижу в этом проблемы, связанной с пользовательским интерфейсом, совсем нет. Концептуальные модели - это лингвистические сущности (см., Например, работу Atkinson & Kühne, или Partridge, или даже Agerfalk), и как таковые они выражаются на языке (например, естественном языке). Я хочу, чтобы мои были выражены на нескольких языках. – CesarGon

+0

@CesarGon - Я не совсем понимаю. Пример 'p.Position' - это то, что вы бы локализовали. Получите строки из центрального ресурса, который возвращает разные локализованные строки в соответствии с языковой версией (или любыми критериями). Для меня это не касается дизайна, управляемого доменом. – Oded

0

Сделать пользователя Явный! Я уже сталкивался с доменами, где культура пользователя является гражданином первого класса в домене, но в таких ситуациях я моделирую правильный объект значения (в вашем примере я бы использовал класс позиции properly, реализующий IEquatable<Position>) и the User, который может чтобы выразить такие значения.

Вставлять к вашему примеру, что-то вроде:

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 } 
} 

И не забудьте документы и надлежащим образом разработаны exceptions!

ПРЕДУПРЕЖДЕНИЕ
Есть по крайней мере две огромные конструкции запахов в модели образца, которую вы указали:

  • публичный сеттер (свойство Position)
  • System.String холдинг стоимость бизнеса

Публичный сеттер означает, что ваш объект предоставляет свое собственное состояние клиентам независимо от его собственных инвариантов или что такое свойство не имеет бизнес-ценность для субъекта и, следовательно, не должны быть частью организации вообще. Действительно, изменяемые объекты всегда должны separate commands (that can change the state) and queries (that cannot).

A System.String с бизнес-семантикой всегда пахнет концепцией домена, оставленной неявной, обычно значением-объектом с операциями равенства (который реализует IEquatable, я имею в виду).

Имейте в виду, что модель многоразового использования довольно сложно получить, поскольку для этого требуется более двух экспертов домена и сильный опыт моделирования ddd.Худшая «модель домена», с которой я столкнулась в своем карьере, была разработана старшим программистом с огромными навыками ООП, но не имела предыдущего опыта моделирования: это было сочетание шаблонов GoF и структур данных, которые в надежде на то, что они будут действительно гибкими, быть бесполезным. После 200 тыс. Евро, потраченных на это, нам пришлось выбросить его и перезапустить с нуля.

Возможно, вам просто нужна хорошая модель данных, непосредственно сопоставленная с набором простых структур данных в C#: у вас никогда не будет никакого ROI от авансовых инвестиций в модель домена, если вам это действительно не нужно !

+0

Спасибо за ваш ответ, но не слишком увлекайтесь примером позиции. Это просто игрушка, чтобы проиллюстрировать этот вопрос. Это даже не часть реальной модели.Таким образом, ваши комментарии о запахах неприменимы. :-) – CesarGon

+0

Суть вопроса в том, что наши бизнес-требования включают обработку бесплатных атрибутов открытого текста на нескольких языках. Эти атрибуты не могут быть закодированы как типы перечислений или аналогичные. Отсюда и трудность. – CesarGon

+0

Ну, вы можете просто удалить исключение UnknownPositionNameException, созданное ParsePosition. Таким образом, вы можете перенести позицию (которая, очевидно, завершает строку), и позволить оператору backoffice связывать описание этой позиции, которое связано (например, «Bibliotecario» и «Librarian»). Однако, если такие тексты свободной формы не связаны с бизнес-правилами, вы должны переместить их за пределы модели домена. –

1

Мой вопрос связан с многоязычных данные

[...]

Пожалуйста, обратите внимание, что я не заинтересован в базе данных или ORM реализаций;

В этих двух утверждениях я вижу некоторое противоречие. Каким бы ни было окончательное решение, в любом случае у вас будут многоязычные структуры в вашей базе данных, а также механизм, который запрашивает их для перевода, не так ли?

Дело в том, если ваш домен действительно не о перевод, я стараюсь держать его подальше от многоязычных проблем, насколько это возможно, по той же причине, что вы пытаетесь сделать ваш домен живучесть невежественными или UI невежественны ,

Таким образом, я бы, по крайней мере, поставил логику многоязычного разрешения на уровне инфраструктуры. Затем можно, например, использовать aspects только прикрепить многоязычное поведение некоторых свойств, если вам действительно нужен след нескольких языков в ваших лицах и не хочет, чтобы ваша живучесть слой обрабатывать все, что прозрачно:

public class Person 
{ 
    [Multilingual] 
    public string Position { get; set; } 
} 
+0

Нет противоречия, если у вас нет очень ограниченного представления о том, какие данные или базы данных. :-) Я намерен использовать и управлять многоязычными данными в памяти, как сеть взаимосвязанных объектов домена. Я не храню их в базе данных в традиционном смысле, то есть, по крайней мере, к настоящему времени не будет реляционного или ненадежного хранилища. Итак, никакого противоречия. – CesarGon

+0

Что касается вашего предложения относиться к многоязычности как к аспекту, я согласен; это звучит как хорошая идея. Но, к сожалению, это не касается моего вопроса, который более конкретен. Спасибо, в любом случае. – CesarGon

+0

ОК, «некоторые атрибуты должны быть сохранены», просто нажал на меня как «в (постоянном) хранилище данных», вот почему. Я до сих пор не понимаю, чего вы пытаетесь достичь. Пример Person несколько вводит в заблуждение, я предлагаю вам детали сообщений и код, более специфичные для вашей проблемы. – guillaume31

0

Возможно, стоит упомянуть функцию Apache MultiViews и способ доставки различного контента на основе заголовка браузера Accept-Language.

Так что, если пользователь запрашивает «content.xml», например, Apache будет доставлять контент.en.xml или content.sp.xl или content.fr.xml или все, что доступно на основе некоторых правил приоритизации.

+1

Ну, сборщики .NET, как упоминалось @Oded, тоже работают.Но рассматривать это как проблему локализации было исключено из начала, поскольку строки, которые будут отображаться, не известны во время разработки. – CesarGon

0

Учитывая требования, я, вероятно, попытаюсь самостоятельно смоделировать позицию как объект/ценность. Этот объект не был бы словарем для переводов, а скорее использовался бы как ключ в domainDictionary.

// 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)); 

Теперь предполагая, что вам нужно динамически создавать новые позиции, когда подходящий синоним не существует, вероятно, можно сохранить данные несколько аккуратных при использовании IDomainDictionary в качестве источника для автозаполнения предложений в пользовательском интерфейсе.

Смежные вопросы