[英]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.