【问题标题】:Where To Put Ad-hoc Commands/Queries in an Application That is Primarily DDD在主要是 DDD 的应用程序中放置 Ad-hoc 命令/查询的位置
【发布时间】:2014-07-19 03:01:00
【问题描述】:

我正在使用域驱动设计方法开发 Web 应用程序,但是我的应用程序的某些方面不适合 DDD。例如,我们需要批量更新员工工资。加载整个员工实体并为每个员工保存它是效率不高的,因为通常一次更新数百个薪水。此外,还需要同时执行其他任务,例如记录旧工资和记录新工资的生效日期。所以我会说这种类型的操作超出了我的核心域的有界上下文。此外,我了解,最好从程序的角度来处理此操作。这一切都很好,但是我仍然希望我的应用程序保持连贯和有条理,无论我在应用程序的特定部分使用什么方法。

例如我使用下面的结构。

  • 用户界面
  • 应用
  • 型号
  • 基础设施

即使对于我的核心领域的有界上下文之外的事情,我也想坚持这种结构。我现在主要关心的是我的基础设施层。最初我在基础架构中使用以下内容:

  • 存储库
  • 查找器(用于单独读取模型)
  • 命令

我将 ad-hoc 读取查询放在 Finders 中,将 ad-hoc 命令放在 Commands 中。这样做的问题是某些操作需要一系列查询和命令,对我来说,将它们全部组合在一个单元中似乎更有条理。有点像存储库,但它不是提供对域实体的访问,而是封装了一组构成特定过程的查询和命令。

所以我正在寻找的是重新命名约定的建议,以将“命令”文件夹/命名空间更改为更好地描述逻辑上组合在一起的一系列查询/命令。是否已经有我不知道的名称/模式?

更新:

目前正在考虑使用命名空间“Procedures”来描述这些逻辑上组合在一起的查询/命令。一方面它是合适的,因为我所描述的内容类似于存储过程,并且描述性的是应用程序的这一部分使用的是过程而不是 DDD 方法。我唯一的疑虑是这种命名约定意味着使用存储过程,但事实并非如此。

【问题讨论】:

  • 我让我的存储库能够开始事务,然后将 TransactionCommand 传递给所有涉及的存储库。一旦最终存储库完成更新,我就会提交它。这样,我仍然可以使用现有的存储库(基本上每个模型一个),而无需编写临时/自定义查询。

标签: architecture domain-driven-design ddd-repositories procedural-programming bounded-contexts


【解决方案1】:

阅读此article first 它可以帮助您进行批量更新。 IMO薪水不是员工定义的一部分,我会说薪水是相关的或需要员工(作为c的参考)。

更改薪水是域用例,而保持旧薪水似乎是域规则。他们确实想保留所有旧工资吗?这不是技术决策,而是商业决策。就我个人而言,我不认为这是程序性思维的情况。

关于同时需要查询和命令的操作。命令是改变域的东西。你说这些命令/查询构成了一个特定的过程。什么的?您似乎在描述一个用例。它是域已知的东西吗?它需要业务逻辑吗?还是 严格 持久性或基础设施相关(更新某些读取模型,查询其他读取模型)?如果有逻辑,则表明您可能拥有服务。如果是业务逻辑,它可能是业务概念或用例,因此它们属于域。如果它的持久化逻辑属于 DAL,如果是 UI 逻辑则属于 UI 等等。

更新 评论太长了

这篇文章的重点是,如果您需要对域实体进行批量处理,那么在 99% 的情况下,您使用的是错误的模型。在您的情况下,员工与工资之间的关系确实很重要。我会说(只是一个非常模糊的建议)你可以有一个 SalariesManager (坏名字..),它将员工(id)与从特定日期开始的薪水相关联。

 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、salary、date)添加到表中。您更改了当前工资,但也保留了旧条目。接口可以是 Domain 的一部分,但实现是 Persistence 的一部分(因为它很简单并且不包含业务逻辑)。想想看,它确实看起来像一个存储库,但没有聚合根。

但这只是一个建议。我觉得还有另一种更适合域的解决方案。由您和领域专家来识别它。

【讨论】:

  • 我之前读过同一篇文章,并从中得出结论,我们不应该使用 DDD 进行批量更新,而是继续进行 CRUDy。这篇文章看起来像是你写的,所以我可能误解了。
  • 我知道需要一个服务并且我确实实现了它,但是该服务调用了我在基础设施中的数据访问层。所以我要问的是如何组织数据层以将属于一起但同时包含命令和查询的数据访问方法组合在一起。我最终决定是否在基础设施层将它们组合在一起并不重要。只要服务将相关任务组合在一起,我认为在基础设施层将它们分开并不重要。
  • 我认为您是在建议我将 Salary 视为与员工分离的实体,并将 Salary 模型视为域的一部分。这是一个很好的建议。我会尝试,但我确实有更复杂的案例,似乎符合程序风格。看看this question 我问在哪里每个人都同意这是使用过程代码的一个案例。在这种情况下,我使用薪水作为批量更新示例,因为它更容易解释。
  • 通常程序风格适用于简单的情况。当您说复杂案例(从业务角度,而不是技术角度)时,这暗示您需要进行一些领域建模。顺便说一句,在这种情况下,Salary 不是一个实体,它是一个值对象。为什么有人需要确定薪水。但是,可以包含薪水、津贴、奖金等的 RemunerationPackage 可能是一个实体,因为您将根据职位、资历等分配薪酬包,因此一个包用于多个员工,您可能希望整体更改包。
  • 看看我链接到的问题。有时事情太复杂了,或者换句话说,不适合 DDD。
【解决方案2】:

应用程序责任中的命令/查询,看看六边形架构。您可以将临时命令和查询作为适配器/端口添加到您的应用程序

【讨论】:

  • 我希望我所有的持久性逻辑都在一个地方,而不是分散在整个应用程序中。此外,在我的情况下,命令/查询不是特定于应用程序的,因为有多个应用程序必须访问此逻辑。即网络和控制台。
  • 您可以为命令/查询和其他应用程序访问此服务创建 Web 服务。
  • 是的,我明白了。我已经有了。我在我的问题中想知道的是专门用于实现命令/查询的持久性方面的命名约定。即在我的基础设施中,我有用于域持久性的存储库,用于只读查询的查找器。我怎么称呼第三件事,即命令/查询的逻辑分组。
【解决方案3】:

我让我的存储库能够开始事务,然后将 TransactionCommand 传递给所有相关的存储库。一旦最终存储库完成更新,我就会提交它。这样,我仍然可以使用现有的存储库(基本上每个模型/实体一个),而无需编写临时/自定义查询。

我只希望我自己的模型/实体实现存储库接口,而不是为每个接口定义一个单独的存储库。前者将允许多态存储库操作,而后者则不允许。生活和燃烧。

这里是事务存储库的用法,该方法取自 Entity1Service 类(我的服务层,每个实体一个类,包含使用一个或多个存储库来完成某些目标的静态方法)

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 )
    {            

    }    
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-23
    • 1970-01-01
    • 2013-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-16
    相关资源
    最近更新 更多