[英]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));
}
}
問題:
盡管看起來很簡單,但我不確定我是否真的了解您的領域。
諸如
“創建批次並添加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
方法做的太多了,應該隔離到更有意義和更精細的業務操作中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.