何处将Ad-hoc命令/查询放在主要是DDD的应用程序中

时间:2014-05-29 14:47:26

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

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

例如我使用以下结构。

  • UI
  • 申请
  • 模型
  • 基础设施

我想坚持这个结构,即使是在核心域的有限上下文之外的事情。我目前关注的主要是我的基础设施层。最初我在基础设施中使用以下内容:

  • 存储库
  • Finders(针对单独的阅读模型)
  • 命令

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

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

更新

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

3 个答案:

答案 0 :(得分:3)

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

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

关于执行需要查询和命令的操作。命令是改变域的东西。你说那些命令/查询构成了一个特定的过程。什么?看来你正在描述一个用例。这是领域已知的东西吗?它需要业务逻辑吗?或者严格持久性或基础架构相关(更新一些读取模型,查询其他读取模型)?如果有逻辑,则表示您可能拥有服务。如果它的业务逻辑可能是业务概念或用例,那么它们属于域。如果它的持久性逻辑属于DAL,如果它是UI逻辑,那么它属于UI等。

更新 评论时间过长

文章的重点是,如果你需要Domain实体上的大量内容,那么在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的一部分(因为它很简单,并且它不包含业务逻辑)。想想看,它看起来像一个存储库,但没有聚合根。

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

答案 1 :(得分:0)

应用程序责任中的命令/查询,请查看六边形体系结构。您可以将ad-hoc命令和查询作为适配器/端口添加到应用程序

答案 2 :(得分:0)

我为我的存储库提供了开始事务的能力,然后将TransactionCommand传递给所有涉及的存储库。最终存储库完成更新后,我提交了它。这样,我仍然可以使用我现有的存储库(基本上每个模型/实体一个),而无需编写ad-hoc /自定义查询。

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

以下是事务存储库的用法,此方法取自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 )
    {            

    }    
}