I have a scenario am trying to refactor to DDD. I have a Batch which is an aggregate and List of BatchEntries. After a Batch is created and BatchEntries added, an SMS is sent to the individuals in the batch and the status of the batch changes from running to posted.
Any ideas on how to make the design better? The domain has two aggregates Batch and BatchEntry with Batch being the aggregate root.
The code looks like this
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>{}
The domain and domain services are exposed via Application Services. The Code in the application services is as follows:
//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));
}
}
Questions:
- Where should the BatchStatus i.e Running, Posted be assigned?
- Should the MarkBatchAsPosted method be defined as a mehod in the Batch Entity?
- How best can this be redesigned for domain-driven design?