1

ドメイン駆動型の設計アプローチを使用して Web アプリケーションに取り組んでいますが、DDD に適していないアプリケーションの側面がいくつかあります。たとえば、従業員の給与を一括更新する必要があります。一度に何百もの給与が更新されることが多いため、従業員エンティティ全体をロードして各従業員に保存するのは効率的ではありません。さらに、古い給与の記録や新しい給与の発効日の記録など、同時に実行する必要がある他のタスクがあります。したがって、このタイプの操作は、コア ドメインの境界付けられたコンテキストの外側にあると言えます。さらに、この操作は手続き上の観点からアプローチするのが最善であることを理解しています。それはすべて問題なく、良いことです。

たとえば、次の構造を使用しています。

  • UI
  • 応用
  • モデル
  • インフラストラクチャー

コア ドメインの境界付けられたコンテキストの外側にあるものについても、この構造に固執したいと思います。現時点での私の懸念は、主にインフラストラクチャ レイヤーに関するものです。もともと私はインフラストラクチャ内で以下を使用していました:

  • リポジトリ
  • ファインダー (個別読み取りモデル用)
  • コマンド

アドホック読み取りクエリを Finders に配置し、アドホック コマンドを Commands に配置しました。これに関する問題は、一部の操作には一連のクエリとコマンドが必要であり、それらをすべて 1 つのユニットにグループ化する方が組織化されているように思われることです。リポジトリのようなものですが、ドメイン エンティティへのアクセスを提供する代わりに、特定の手順を構成する一連のクエリとコマンドをカプセル化します。

したがって、私が探しているのは、「コマンド」フォルダー/名前空間を、論理的に適合する一連のクエリ/コマンドをより適切に説明するものに変更するための命名規則に関する提案です。私が気付いていない名前/パターンが既にありますか?

アップデート:

現在、論理的に適合するこれらのクエリ/コマンドを説明する名前空間「手順」を検討しています。一方で、私が説明していることはストアド プロシージャに似ており、アプリケーションのこの部分が DDD アプローチではなく手続き型アプローチを使用していることを説明しているため、適切です。私の唯一の懸念は、この命名規則がストアド プロシージャの使用を暗示していることですが、そうではありません。

4

3 に答える 3

3

最初にこの記事をお読みください。一括更新に役立つ場合があります。IMO Salary は Employee 定義の一部ではありません。Salary が関連付けられているか、従業員が必要であると言えます (参照として)。

給与の変更はドメインのユースケースであり、古い給与を維持することはドメインのルールのようです。彼らはすべての古い給与を正しく維持したいですか?これは技術的な決定ではなく、ビジネス上の決定です。個人的には、手続き型の考え方には当てはまらないと思います。

クエリとコマンドの両方を必要とする操作について。コマンドはドメインを変更するものです。それらのコマンド/クエリが特定の手順を構成しているとあなたは言います。なにかの?ユースケースを説明しているようです。それはドメインに知られているものですか?ビジネスロジックが必要ですか?それとも、永続性またはインフラストラクチャに厳密に関連していますか (一部の読み取りモデルを更新し、他の読み取りモデルをクエリします)? ロジックがある場合は、サービスがある可能性があるという兆候です。ビジネス ロジックの場合は、ビジネス コンセプト、またはドメインに属するユース ケースである可能性があります。永続化ロジックの場合は DAL に属し、UI ロジックの場合は UI などに属します。

更新 コメントするには長すぎました

この記事の要点は、ドメイン エンティティに一括処理が必要な場合、99% のケースで間違ったモデルを使用しているということです。あなたの場合、従業員と給与の関係は重要です。特定の日付から始まる給与に従業員 (id) を関連付ける SalariesManager (悪い名前..) を持つことができると思います (非常に漠然とした提案です)。

 public interface IManageSalaries
 {
      void ChangeEmployeeSalary(Guid employee, Salary newSalary,DateTime startingFrom);

     //possible bulk operation
      void ChangeSalary(Guid[] employees, Salary newSalary,DateTime startingFrom);          

      SalaryInfo[] GetSalaryHistory(Guid employeeId);
      Salary GetCurrentSalary(Guid employeeId);
 }

実装は、新しい (employee_id、給与、日付) をテーブルに追加するだけです。現在の給与を変更しますが、古いエントリも保持します。インターフェイスはドメインの一部にすることができますが、実装は永続性の一部です (単純であり、ビジネス ロジックが含まれていないため)。考えてみると、リポジトリのように見えますが、集約ルートはありません。

しかし、これは単なる提案です。ドメインにより適した別のソリューションがあると感じています。それを識別するのは、あなたとドメインの専門家次第です。

于 2014-06-11T14:21:20.480 に答える
0

リポジトリにトランザクションを開始する機能を与え、TransactionCommand を関係するすべてのリポジトリに渡します。最終リポジトリの更新が完了したら、コミットします。このようにして、アドホック/カスタム クエリを記述しなくても、既存のリポジトリ (基本的にモデル/エンティティごとに 1 つ) を引き続き使用できます。

モデル/エンティティごとに個別のリポジトリを定義するのではなく、モデル/エンティティ自体にリポジトリ インターフェイスを実装してもらいたいと思います。前者は多態的なリポジトリ操作を許可しますが、後者は許可しません。生きて燃える。

これはトランザクション リポジトリの使用法です。このメソッドは Entity1Service クラスから取得されました (エンティティごとに 1 つのクラスである私のサービス レイヤーには、目的を達成するために 1 つ以上のリポジトリを使用する静的メソッドが含まれています)。

public class Entity1Service
{
    public static bool WriteEntity1(Entity1 entity1)
    {
        Entity1RepositorySQLite sqlRepo = new Entity1RepositorySQLite();

        sqlRepo.BeginTransaction(); 

        int entity1Id;

        if (!LocalIdService.ReadAndIncrementEntity1Id(out entity1Id, sqlRepo.TransactionCommand))
        {
            sqlRepo.RollbackTransaction();

            return false;
        }   

        int entity1IdOld = entity1.Id;  

        try
        {
            IdMapService.CreateMapEntity1Id(entity1Id, entity1Id, sqlRepo.TransactionCommand);

            entity1.Id = entity1Id;

            sqlRepo.Write(attachment);
        }
        catch (Exception)
        {
            sqlRepo.RollbackTransaction();

            attachment.Entity1Id = attachmentIdOld;     

            throw;
        }   

        sqlRepo.EndTransaction();

        return true;
    }
}

リポジトリ定義

public interface ITransaction
{
    SqliteCommand TransactionCommand { get; set; }
    bool InTransaction { get; }

    void BeginTransaction();
    void RollbackTransaction();
    void CloseTransaction();
    void EndTransaction();
}

public class RepositorySQLiteTransactional<T> : RepositorySQLite<T>, ITransaction where T : new()
{    
    public RepositorySQLiteTransactional(IDataMapperSQLite<T, string[]> dataMapper) : base(dataMapper) { }     

    public RepositorySQLiteTransactional(IDataMapperSQLite<T, string[]> dataMapper, SqliteCommand transactionCommand) : this(dataMapper)        
    {            
        _dbLayer.DblTransactionCommand = transactionCommand;
    }

    public SqliteCommand TransactionCommand
    {            
        get { return (SqliteCommand)_dbLayer.DblTransactionCommand; }
        set
        {
            _dbLayer.DblTransactionCommand = value;
        }
    }

    public bool InTransaction
    {
        get { return _dbLayer.DblInTransaction; }
    }       

    public void BeginTransaction()        
    {
        try
        {
            _dbLayer.DblBeginTransaction();
        }
        catch( Exception )           
        {
            _dbLayer.DblCloseTransaction();

            throw;
        }
    }

    public void RollbackTransaction()
    {
        _dbLayer.DblRollbackTransaction();
    }

    public void CloseTransaction()
    {
        _dbLayer.DblCloseTransaction();
    }

    public void EndTransaction()
    {
        _dbLayer.DblEndTransaction();         
    }
}    

public interface IRepository<T>
{        
    List<T> Read(IConditions conditions);     

    T FindOne(IQuery query);
    List<T> FindAll(IQuery query);

    void WriteOne(T obj);
    void WriteOne(T obj, out int newId);
    void WriteOne(IQuery query);
    void WriteAll(List<T> objs);

    void UpdateOne(T obj);
    void UpdateAll(List<T> objs);        
    void UpdateOne(IQuery query);

    void ReplaceAll(List<T> objs);

    void DeleteAll();
    void DeleteAll(List<T> objs);

    //void Add(T entity);
    //void Delete(T entity);
    //void Edit(T entity);
    //void Save();
}

public class RepositorySQLite<T> : IRepository<T> where T : new()
{
    protected AndroidDB _dbLayer;
    protected IDataMapperSQLite<T, string[]> _dataMapper;

    private RepositorySQLite()  // force data mapper init
    {

    }

    public RepositorySQLite(IDataMapperSQLite<T, string[]> dataMapper)
    {

    }

    public List<T> Read(IConditions conditions) { throw new NotImplementedException(); }
    public void WriteOne(T obj, out int newId) { throw new NotImplementedException(); }
    public void WriteOne(IQuery query) { throw new NotImplementedException(); }

    private void ClearMapState()
    {

    }

    public void ReplaceAll(List<T> objs)
    {

    }

    public void WriteAll(List<T> objList)
    {

    }

    public void WriteOne(T obj)
    {

    }

    public void UpdateOne(T obj)
    {

    }

    public void UpdateAll(List<T> objs)
    {

    }

    public void UpdateOne(IQuery query)
    {

    }

    public T FindOne(IQuery query)
    {

    }

    public List<T> FindAll(IQuery query)
    {

    }

    public void DeleteAll(List<T> objs)
    {  

    }

    public void DeleteAll()
    {           

    }

    public void DeleteAll( IQuery query )
    {            

    }    
}
于 2014-06-11T13:33:37.303 に答える