我正在尝试运行Hossam Barakat的代码,以了解更多的DDD概念。注意:它使用MediatR
但不确定为什么发送POST请求/订阅,有一个错误说_subscriptions
是空的。看起来为了发布请求/订阅,我还必须同时创建一个新的Customer
。我认为创建客户和创建订阅是两个不同的过程,还是这些代码中有DDD缺陷?
代码
[ApiController]
[Route("[controller]")]
public class SubscriptionsController : ControllerBase
{
private readonly IMediator _mediator;
public SubscriptionsController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> Subscribe(SubscribeRequest request)
{
await _mediator.Send(request);
return Ok();
}
}
namespace Subscriptions.Commands
{
public class SubscribeRequest : IRequest
{
public Guid CustomerId { get; set; }
public Guid ProductId { get; set; }
}
public class SubscribeRequestHandler : IRequestHandler<SubscribeRequest>
{
private readonly SubscriptionContext _subscriptionContext;
private readonly ISubscriptionAmountCalculator _subscriptionAmountCalculator;
public SubscribeRequestHandler(SubscriptionContext subscriptionContext, ISubscriptionAmountCalculator subscriptionAmountCalculator)
{
_subscriptionContext = subscriptionContext;
_subscriptionAmountCalculator = subscriptionAmountCalculator;
}
public async Task<Unit> Handle(SubscribeRequest request, CancellationToken cancellationToken)
{
var customer = await _subscriptionContext.Customers.FindAsync(request.CustomerId);
var product = await _subscriptionContext.Products.FindAsync(request.ProductId);
if (customer == null || product == null) return Unit.Value;
customer.AddSubscription(product, _subscriptionAmountCalculator);
var result = await _subscriptionContext.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
}
namespace Subscriptions.Domain
{
public class Customer: Entity
{
private Customer()
{
}
public Customer(Email email, CustomerName customerName): this()
{
Id = Guid.NewGuid();
Email = email ?? throw new ArgumentNullException(nameof(email));
CustomerName = customerName ?? throw new ArgumentNullException(nameof(customerName));
_subscriptions = new List<Subscription>();
}
public Email Email { get; private set;}
public CustomerName CustomerName { get; private set;}
public decimal MoneySpent { get; private set; }
private readonly List<Subscription> _subscriptions;
public IReadOnlyCollection<Subscription> Subscriptions => _subscriptions.AsReadOnly();
public void AddSubscription(Product product, ISubscriptionAmountCalculator subscriptionAmountCalculator)
{
var subscriptionAmount = subscriptionAmountCalculator.Calculate(product, this);
var subscription = new Subscription(this, product, subscriptionAmount);
_subscriptions.Add(subscription);
MoneySpent += subscription.Amount;
AddDomainEvent(new CustomerSubscribedToProduct
{
CustomerId = Id,
ProductId = product.Id
});
}
}
}
namespace Subscriptions.Domain
{
public class Subscription : Entity
{
private Subscription()
{
}
public Subscription(Customer customer, Product product, decimal amount): this()
{
Id = Guid.NewGuid();
Customer = customer ?? throw new ArgumentNullException(nameof(customer));
Product = product ?? throw new ArgumentNullException(nameof(product));
Amount = amount >= 0 ? amount : throw new ArgumentOutOfRangeException(nameof(amount));
Status = SubscriptionStatus.Active;
CurrentPeriodEndDate = product.BillingPeriod.CalculateBillingPeriodEndDate();
}
public SubscriptionStatus Status { get; private set; }
public Customer Customer { get; private set; }
public Product Product { get; private set; }
public decimal Amount { get; private set; }
public DateTime CurrentPeriodEndDate { get; private set; }
}
}
问题来自下面的指令:
var customer = await _subscriptionContext.Customers.FindAsync(request.CustomerId);
该指令使实体框架从数据库中重新生成一个Customer。EF不调用构造函数,实体是通过反射实例化的。这意味着_subscriptions
字段没有初始化,并且为空,这解释了NullReferenceException
。在这种情况下,最好的解决方案是删除公共构造函数的第4行,并在字段级别进行初始化:
namespace Subscriptions.Domain
{
public class Customer: Entity
{
public Customer(Email email, CustomerName customerName) : this()
{
Id = Guid.NewGuid();
Email = email ?? throw new ArgumentNullException(nameof(email));
CustomerName = customerName ?? throw new ArgumentNullException(nameof(customerName));
}
private readonly List<Subscription> _subscriptions = new List<Subscription>();
// redacted
}
}