簡體   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