繁体   English   中英

重构为域驱动设计

[英]Refactoring to Domain driven design

我有一种情况试图将其重构为DDD。 我有一个批处理,它是一个汇总和BatchEntries列表。 创建批次并添加BatchEntries之后,会向该批次中的个人发送一条SMS,并且批次的状态将从运行变为已发布。

关于如何使设计更好的任何想法? 该域具有两个聚合Batch和BatchEntry,其中Batch是聚合根。

代码看起来像这样

public class Batch : EntityBase, IValidatableObject
{
    public int BatchNumber { get; set; }
    public string Description { get; set; }
    public decimal TotalValue { get; set; }
    public bool SMSAlert { get; set; }
    public int Status { get; set; }

    private HashSet<BatchEntry> _batchEntries;
    public virtual ICollection<BatchEntry> BatchEntries
    {
        get{
            if (_batchEntries == null){
                _batchEntries = new HashSet<BatchEntry>();
            }
            return _batchEntries;
        }
        private set {
            _batchEntries = new HashSet<BatchEntry>(value);
        }
    }

    public static Batch Create(string description, decimal totalValue, bool smsAlert)
    {
        var batch = new Batch();
        batch.GenerateNewIdentity();
        batch.Description = description;
        batch.TotalValue = totalValue;
        batch.SMSAlert = smsAlert;
        return batch;
    }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        // 
    }
}

public interface IBatchRepository : IRepository<Batch>
{
    int NextBatchNumber();
}

public class BatchEntry : EntityBase, IValidatableObject
{
    public Guid BatchId { get; set; }
    public virtual Batch Batch { get; private set; }
    public decimal Amount { get; set; }
    public Guid CustomerAccountId { get; set; }
    public virtual CustomerAccount CustomerAccount { get; private set; }

    public static BatchEntry Create(Guid batchId, Guid customerAccountId, decimal amount)
    {
        var batchEntry = new BatchEntry();
        batchEntry.GenerateNewIdentity();
        batchEntry.BatchId = batchId;
        batchEntry.CustomerAccountId = customerAccountId;
        batchEntry.Amount = amount;
        return batchEntry;
    }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        //
    }
}

public interface IBatchEntryRepository : IRepository<BatchEntry>{}

域和域服务通过应用程序服务公开。 应用程序服务中的代码如下:

//Application Services Code

public class BatchApplicationService : IBatchApplicationService
{
    private readonly IBatchRepository _batchRepository;
    private readonly IBatchEntryRepository _batchEntryRepository;

    public BatchAppService(IBatchRepository batchRepository, IBatchEntryRepository batchEntryRepository)
    {
        if (batchRepository == null) throw new ArgumentNullException("batchRepository");

        if (batchEntryRepository == null) throw new ArgumentNullException("batchEntryRepository");

        _batchRepository = batchRepository;
        _batchEntryRepository = batchEntryRepository;
    }

    public BatchDTO AddNewBatch(BatchDto batchDto)
    {
        if (batchDto != null)
        {
            var batch = Batch.Create(batchDto.Description, batchDto.TotalValue, batchDto.SMSAlert);
            batch.BatchNumber = _batchRepository.NextBatchNumber();
            batch.Status = (int)BatchStatus.Running;
            SaveBatch(batch);
            return batch.Map<BatchDto>();
        }
        else
        {
            //
        }
    }

    public bool UpdateBatch(BatchDto batchDto)
    {
        if (batchDto == null || batchDto.Id == Guid.Empty)
        {
            //
        }

        var persisted = _batchRepository.Get(batchDto.Id);
        if (persisted != null)
        {
            var result = false;
            var current = Batch.Create(batchDto.Description, batchDto.TotalValue, batchDto.SMSAlert);
            current.ChangeCurrentIdentity(persisted.Id);
            current.BatchNumber = persisted.BatchNumber;
            current.Status = persisted.Status;

            _batchRepository.Merge(persisted, current);
            _batchRepository.UnitOfWork.Commit();

            if (persisted.BatchEntries.Count != 0){
                persisted.BatchEntries.ToList().ForEach(x => _batchEntryRepository.Remove(x));
                _batchEntryRepository.UnitOfWork.Commit();
            }

            if (batchDto.BatchEntries != null && batchDto.BatchEntries.Any())
            {
                List<BatchEntry> batchEntries = new List<BatchEntry>();
                int counter = default(int);
                batchDTO.BatchEntries.ToList().ForEach(x =>
                {
                    var batchEntry = BatchEntry.Create(persisted.Id, x.CustomerAccountId, x.Amount);
                    batchEntries.Add(batchEntry);
                });
            }
            else result = true;
            return result;
        }
        else
        {
            //
        }
    }

    public bool MarkBatchAsPosted(BatchDto batchDto, int authStatus)
    {
        var result = false;
        if (batchDto == null || batchDto.Id == Guid.Empty)
        {
            //
        }

        var persisted = _batchRepository.Get(batchDto.Id);
        if (persisted != null)
        {
            var current = Batch.Create(batchDto.Description, batchDto.TotalValue, batchDto.SMSAlert);
            current.ChangeCurrentIdentity(persisted.Id);
            current.BatchNumber = persisted.BatchNumber;
            current.Status = authStatus;
            _batchRepository.Merge(persisted, current);
            _batchRepository.UnitOfWork.Commit();
            result = true;
        }
        else
        {
            //
        }
        return result;
    }

    private void SaveBatch(Batch batch)
    {
        var validator = EntityValidatorFactory.CreateValidator();
        if (validator.IsValid<Batch>(batch))
        {
            _batchRepository.Add(batch);
            _batchRepository.UnitOfWork.Commit();
        }
        else throw new ApplicationValidationErrorsException(validator.GetInvalidMessages(batch));
    }
}

问题:

  1. BatchStatus(即运行,已过帐)应分配到哪里?
  2. 是否应将MarkBatchAsPosted方法定义为批处理实体中的方法?
  3. 如何最好地将其重新设计为域驱动设计?

尽管看起来很简单,但我不确定我是否真的了解您的领域。

诸如

“创建批次并添加BatchEntries之后,会向该批次中的个人发送一条SMS,并且批次的状态将从运行变为已发布”

对我来说毫无意义。 批次真的可以是没有任何输入的批次吗? 如果不是,为什么在添加条目时批处理会自动开始?

无论如何,我不冒险回答您的3个问题,但是您似乎违反了一些准则,理解这些准则将使您提出自己的答案:

  • 您的域患有贫血

  • 非根聚合不应该拥有自己的存储库,因为它们只能通过根进行访问。 总根的子代只能通过其根进行修改(告诉不要问)。 如果EntryRepository不是根目录,则不应具有BatchEntryRepository

  • 聚合根是事务处理边界,并且在同一事务中只能修改一个边界。 此外,聚合根应尽可能小,因此,您只保留在集群中实施不变式所需的部分。 在您的情况下,添加/删除批次条目似乎会影响Batch的状态,因此在Batch下有一个BatchEntry集合是有意义的,并可以保护事务不变式。

    注意:如果在Batch上存在很多争用,例如,多个人在同一个Batch实例上工作,添加和删除BatchEntry实例,那么您可能必须使BatchEntry成为其自身的聚合根,并使用BatchEntry的一致性将系统引入一致状态。

  • 通常应使用始终有效的方法来设计域对象,这意味着永远不能将它们置于无效状态。 UI通常应注意验证用户输入,以避免发送不正确的命令,但是域可能会惹上您。 因此, validator.IsValid<Batch>(batch)几乎没有任何意义,除非它正在验证Batch本身无法强制执行的操作。

  • 域逻辑不应在应用程序服务中泄漏,并且通常应尽可能封装在实体中(否则域服务)。 当前,您正在应用程序服务中执行很多业务逻辑,例如, if (persisted.BatchEntries.Count != 0){ ... }

  • DDD不是CRUD。 在CRUD中使用战术DDD模式不一定是错误的,但肯定不是DDD。 DDD是关于无处不在的语言和域建模的。 当您看到名为Update...方法或大量的getter/setters ,通常意味着您做错了。 DDD与基于任务的UI一起使用时效果最佳,该UI允许一次专注于一项业务操作。 您的UpdateBatch方法做的太多了,应该隔离到更有意义和更精细的业务操作中。

希望我的回答可以帮助您完善模型,但是我强烈建议您阅读EvansVernon ...或两者;)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM