6

私はドメイン駆動設計を使用しており、ドメインモデルをかなり明確に把握しています。120以上のクラスが含まれており、非常に安定しています。.NET 4とC#で実装します。重要なのは、モデルが多言語である必要があるということです。一部の属性は、複数の言語で保存する必要があります。たとえば、Personクラスには、string英語(「Librarian」など)とスペイン語(「Bibliotecario」など)の値を格納するタイプのPositionプロパティがあります。このプロパティのゲッターは、言語パラメーターに応じて英語またはスペイン語のバージョンを返す必要があります。

そして、ここから私の質問を始めます。これをパラメータ化する方法がわかりません。私はそれを行うための2つの主要な方法を説明しました:

  1. プロパティコレクションを使用します。位置はではstringなく、Dictionary<Language, string>クライアントが言語によって人の位置を取得できるようにするためのものです。
  2. 単純なスカラープロパティを保持しますが、グローバルに知られている「現在の言語」設定に応じて、ある言語または別の言語の値を返す(または設定する)ようにします。クライアントコードが作業言語を設定し、次にすべてのオブジェクトがその言語で値を設定および取得します。

オプション1はグローバル状態を回避しますが、モデル内のほぼすべてのクラスのインターフェースを台無しにします。一方、オプション2は、グローバル設定を見ないと、どの言語を使用するかわからないため、表現力が低下します。また、グローバル設定のすべてのクラスに依存関係を導入します。

私はデータベースやORMの実装には興味がないことに注意してください。私はドメインモデルレベルでのみ作業しています。

次に、2つの具体的な質問があります。

  • 多言語ドメインモデルの目標を達成するための最良のオプション(1または2)はどれですか?
  • 私が考慮していない他のオプションはありますか、そしてそれらはどれですか?

ありがとうございました。

編集します。これはユーザーインターフェイス関連の問題であり、.NETのグローバリゼーション/ローカリゼーションサポートを通じて対処できるとの意見もあります。同意しません。UIローカリゼーションは、コンパイル時にユーザーに表示する必要があるローカライズされたリテラルを知っている場合にのみ機能しますが、これは私たちの場合ではありません。私の質問は、実行時にユーザーデータとして提供されるため、コンパイル時に不明な多言語データに関するものです。これはUI関連の問題ではありません。

編集2。Person.Positionは、質問を説明するための単なるおもちゃの例であることに注意してください。実際のモデルの一部ではありません。それを批判したり、改善したりしようとしないでください。それをする意味はありません。私たちのビジネス要件には、列挙型などとしてエンコードできない多くの属性が含まれ、フリーテキストのままにする必要があります。したがって、難しさ。

4

7 に答える 7

5

次の場合:

一部のユースケースでは、サポートされているすべての言語でオブジェクトの値を設定します。他の人は、1つの特定の言語で値を調べることを含みます。

両方のオプションを選択することをお勧めします。つまり、Personと多言語コンテンツを保持するすべてのクラスは、そのコンテンツをその状態に維持する必要があります。

  • Positionプロパティは、 現在のユーザーの言語で個人の位置を設定/取得する必要があります。

  • すべての 言語設定/取得 に対応するプロパティまたはメソッドが必要です。

  • ユーザー言語を設定する(または必要に応じて切り替える)方法があるはずです。抽象SetLanguage(Language lang)メソッドとCurrentLanguageゲッターを使用して抽象クラス(例:BaseMultilingualEntity)を作成します。言語設定を公開するある種のレジストリで、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();

    }
}

于 2013-03-03T09:23:33.207 に答える
3

ドメインモデルは抽象化であり、世界の特定の部分をモデル化し、ドメインの概念をキャプチャします。

このモデルは、開発者がドメインの専門家が通信する方法で、同じ概念に同じ名前を使用してコードで通信できるようにするために存在します。

現在、スペイン語の専門家と英語の専門家は同じ概念に異なる単語を使用する場合がありますが、概念自体は同じです(言語があいまいで、人々が同じ概念を常に同じように理解するとは限りませんが、希望しますが、私は逸脱します)。

コードは、これらの概念に対して1つの人間の言語を選択し、それに固執する必要があります。単一の概念を表すためにモデルが異なる言語で構成される理由はまったくありません。

ここで、アプリケーションデータとメタデータのユーザーをそれぞれの言語で表示する必要があるかもしれませんが、概念は変わりません。

この点で、2番目のオプションはあなたがしなければならないことです-.NETでは、これは通常、CurrentThread.CurrentCultureおよび/またはローカライズされたリソースを含む衛星アセンブリCurrentThread.CurrentUICultureを使用することによって行われます。

于 2013-02-27T20:47:48.007 に答える
1

私の質問には多言語データが含まれます

[...]

私はデータベースやORMの実装には興味がないことに注意してください。

これらの2つのステートメントには少し矛盾が見られます。最終的な解決策が何であれ、データベースには多言語固有の構造と、翻訳を行うためにそれらを照会するメカニズムがありますよね?

重要なのは、ドメインが本当に翻訳に関するものでない限り、ドメインの永続性を無視したりUIを無視したりするのと同じ理由で、多言語の懸念からできるだけ遠ざけるようにすることです。

したがって、少なくとも多言語解決ロジックをインフラストラクチャ層に配置します。たとえば、エンティティに多言語のトレースが本当に必要であり、永続化レイヤーですべてを透過的に処理したくない場合は、アスペクトを使用して、一部のプロパティに多言語の動作のみを付加できます。

public class Person
{
   [Multilingual]
   public string Position { get; set; }
}
于 2013-03-06T10:05:33.423 に答える
0

120以上のクラスが含まれており、非常に安定しています。

質問とは直接関係ありませんが、ドメイン内に複数の境界付きコンテキストが存在することを検討することをお勧めします。

私はOdedに同意します。あなたのシナリオでは、言語はUIの問題であるように思われます。確かに、ドメインはC#と英語の組み合わせで宣言できますが、それが表すのは抽象的です。UIはCultureInfo.CurrentCultureで言語を処理します-事実上オプション#2。

Positionプロパティを持つPersonエンティティは、位置を表すために使用される自然言語を管理しません。ある言語で位置を表示し、元々は別の言語で保存したいというユースケースがあるかもしれません。この場合、UIの一部としてトランスレータを使用できます。これは、金額と通貨のペアとしてお金を表し、通貨間で変換することに似ています。

編集

このプロパティのゲッターは、言語パラメーターに応じて英語またはスペイン語のバージョンを返す必要があります。

この言語パラメータを決定するものは何ですか?たとえば、Positionが複数の言語で保存されるようにする責任は何ですか?それとも、翻訳はその場で実行されますか?プロパティのクライアントは誰ですか?クライアントが言語パラメータを決定する場合、なぜクライアントはドメインを関与させずに翻訳を実行できないのですか?複数の言語に関連する行動はありますか、それともこれは読書目的のみの懸念事項ですか?DDDのポイントは、コアの動作ドメインを抽出し、データのクエリに関連する側面を他の責任領域に移すことです。たとえば、read-modelパターンを使用して、特定の言語で集計のPositionプロパティにアクセスできます。

于 2013-02-27T18:38:05.087 に答える
0

ユーザーを明示的にしてください!
ユーザーの文化がドメインの第一級市民であるドメインにすでに遭遇しましたが、そのような状況では、適切な値オブジェクトをモデル化し(この例では、適切に実装されたPositionクラスを使用しますIEquatable<Position>)、そのような表現が可能なユーザー値。

あなたの例に固執すると、次のようなものになります。

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

そして、ドキュメントと適切に設計された例外を忘れないでください!

警告
提供したサンプルモデルには、少なくとも2つの大きなデザインの匂いがあります。

  • パブリックセッター(Positionプロパティ)
  • ビジネス価値を保持するSystem.String

パブリックセッターとは、エンティティが自身の不変条件に関係なく、自身の状態をクライアントに公開すること、またはそのようなプロパティがエンティティにとってビジネス価値を持たないため、エンティティの一部であってはならないことを意味します。実際、可変エンティティは常にコマンド(状態を変更できる)とクエリ(できない)を分離する必要があります。

ビジネスセマンティックを備えたSystem.Stringは、常に暗黙的に残されたドメインコンセプトの匂いがします。通常は、等価演算を備えた値オブジェクトです(つまり、IEquatableを実装します)。

再利用可能なドメインモデルは、2人以上のドメインの専門家と、dddモデリングの豊富な経験が必要なため、取得が非常に難しいことに注意してください。私がキャリアで直面した最悪の「ドメインモデル」は、OOPのスキルは高いが、モデリングの経験がない上級プログラマーによって設計されました。これは、GoFパターンとデータ構造の組み合わせであり、非常に柔軟であることが証明されました。役に立たない。20万ユーロを費やした後、私たちはそれを捨てて最初からやり直さなければなりませんでした。

C#の単純なデータ構造のセットに直接マッピングされた優れたデータモデルが必要な場合もあります。本当に必要でない場合は、ドメインモデルへの先行投資によるROIはありません。

于 2013-03-04T23:57:06.447 に答える
0

ApacheのMultiViews機能と、ブラウザーのAccept-Languageヘッダーに基づいてさまざまなコンテンツを配信する方法について言及する価値があるかもしれません。

したがって、たとえばユーザーが「content.xml」を要求した場合、Apacheはcontent.en.xml、content.sp.xl、content.fr.xml、またはいくつかの優先順位付けルールに基づいて利用可能なものを配信します。

于 2013-03-08T01:19:37.847 に答える
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));

ここで、適切な同義語が存在しないときに動的に新しい位置を作成する必要があると仮定すると、UIのオートコンプリート候補のソースとしてIDomainDictionaryを使用することで、データをある程度整理できます。

于 2014-02-24T15:33:59.467 に答える