如何更新域聚合根对象的属性



在一个干净的体系结构项目中,领域层包含:DTO接口、事件、工厂、模型、异常等…

每个域对象都包含一个构造函数,其参数用于传递数据。

我正在使用接受DTO接口的工厂,从DTO接口创建域对象。

基础架构层的数据模型实现了域层的DTO接口。

DTO:

namespace Acme.Core.Domain.Identity.DTO
{
public interface IBuyerDto : IPersonDto
{
IAddressDto BillingAddress { get; set; }
IAddressDto ShippingAddress { get; set; }
}
}

域模型:

namespace Acme.Core.Domain.Identity.Models.BuyerAggregate
{
public sealed class Buyer : Aggregate<Buyer, BuyerId>, IPerson
{
public Buyer(BuyerId id, PersonName name, DateOfBirth dateOfBirth, Gender gender, string pictureUrl, Address billingAddress, Address shippingAddress, Account account) : base(id)
{
Name = name;
DateOfBirth = dateOfBirth;
Gender = gender;
BillingAddress = billingAddress;
ShippingAddress = shippingAddress;
Account = Guard.Against.Null(account, nameof(account));
PictureUrl = pictureUrl;
}
public Account Account { get; private set; }
public PersonName Name { get; private set; }
public DateOfBirth DateOfBirth { get; private set; }
public string PictureUrl { get; private set; }
public Gender Gender { get; private set; }
public Address BillingAddress { get; private set; }
public Address ShippingAddress { get; private set; }
public void UpdateName(PersonName personName)
{
Name = personName;
}
public void UpdateBillingAddress(Address billingAddress)
{
BillingAddress = billingAddress;
}
public void UpdateShippingAddress(Address shippingAddress)
{
ShippingAddress = shippingAddress;
}
}
}
namespace Acme.Core.Domain.Identity.Models
{
public class Account : Entity<Account, AccountId>
{
public Account(AccountId id, string userName, string normalizedUserName, string passwordHash, string concurrencyStamp, string securityStamp, string email, string normalizedEmail, bool emailConfirmed, string phoneNumber, bool phoneNumberConfirmed, bool twoFactorEnabled, DateTimeOffset? lockoutEnd, bool lockoutEnabled, int accessFailedCount, AccountStatus status, List<RoleId> roles, List<AccountClaim> accountClaims, List<AccountLogin> accountLogins, List<AccountToken> accountTokens) : base(id)
{
UserName = Guard.Against.NullOrWhiteSpace(userName, nameof(userName));
NormalizedUserName = Guard.Against.NullOrWhiteSpace(normalizedUserName, nameof(normalizedUserName));
PasswordHash = Guard.Against.NullOrWhiteSpace(passwordHash, nameof(passwordHash));
ConcurrencyStamp = concurrencyStamp;
SecurityStamp = securityStamp;
Email = Guard.Against.NullOrWhiteSpace(email, nameof(email));
NormalizedEmail = Guard.Against.NullOrWhiteSpace(normalizedEmail, nameof(normalizedEmail));
EmailConfirmed = emailConfirmed;
PhoneNumber = phoneNumber;
PhoneNumberConfirmed = phoneNumberConfirmed;
TwoFactorEnabled = twoFactorEnabled;
LockoutEnd = lockoutEnd;
LockoutEnabled = lockoutEnabled;
AccessFailedCount = accessFailedCount;
Status = Guard.Against.Null(status, nameof(status));
_roles = Guard.Against.Null(roles, nameof(roles));
_accountClaims = accountClaims;
_accountLogins = accountLogins;
_accountTokens = accountTokens;
}
public string UserName { get; private set; }
public string NormalizedUserName { get; private set; }
public string PasswordHash { get; private set; }
public string ConcurrencyStamp { get; private set; }
public string SecurityStamp { get; private set; }
public string Email { get; private set; }
public string NormalizedEmail { get; private set; }
public bool EmailConfirmed { get; private set; }
public string PhoneNumber { get; private set; }
public bool PhoneNumberConfirmed { get; private set; }
public bool TwoFactorEnabled { get; private set; }
public DateTimeOffset? LockoutEnd { get; private set; }
public bool LockoutEnabled { get; private set; }
public int AccessFailedCount { get; private set; }
public AccountStatus Status { get; private set; }
private List<RoleId> _roles;
public IReadOnlyCollection<RoleId> Roles
{
get
{
return _roles;
}
}
private List<AccountClaim> _accountClaims;
public IReadOnlyCollection<AccountClaim> AccountClaims
{
get
{
return _accountClaims;
}
}
private List<AccountLogin> _accountLogins;
public IReadOnlyCollection<AccountLogin> AccountLogins
{
get
{
return _accountLogins;
}
}
private List<AccountToken> _accountTokens;
public IReadOnlyCollection<AccountToken> AccountTokens
{
get
{
return _accountTokens;
}
}
public void AddRole(long roleId)
{
var role = _roles.Where(x => x.GetValue().Equals(roleId)).FirstOrDefault();
if (role == null)
{
_roles.Add(new RoleId(roleId));
}
}
public void RemoveRole(long roleId)
{
var role = _roles.Where(x => x.GetValue().Equals(roleId)).FirstOrDefault();
if (role == null)
{
_roles.Remove(role);
}
}
public void ActivateAccount()
{
Status = AccountStatus.Active;
}
public void BanAccount()
{
Status = AccountStatus.Banned;
}
public void CloseAccount()
{
Status = AccountStatus.Closed;
}
public void LockAccount()
{
Status = AccountStatus.Locked;
}
public void NewAccount()
{
Status = AccountStatus.New;
}
}
}
工厂:

namespace Acme.Core.Domain.Identity.Factories
{
public class BuyerAggregateFatory : IBuyerAggregateFactory
{
private readonly IPersonNameFactory _personNameFactory;
private readonly IDateOfBirthFactory _dateOfBirthFactory;
private readonly IGenderFactory _genderFactory;
private readonly IAccountFactory _accountFactory;
private readonly IAddressFactory _addressFactory;
public BuyerAggregateFatory(IPersonNameFactory personNameFactory,
IDateOfBirthFactory dateOfBirthFactory,
IGenderFactory genderFactory,
IAccountFactory accountFactory,
IAddressFactory addressFactory)
{
_personNameFactory = Guard.Against.Null(personNameFactory);
_dateOfBirthFactory = Guard.Against.Null(dateOfBirthFactory);
_genderFactory = Guard.Against.Null(genderFactory);
_accountFactory = Guard.Against.Null(accountFactory);
_addressFactory = Guard.Against.Null(addressFactory);
}
public Buyer Create(IBuyerDto dto)
{
BuyerId aggregateId = new BuyerId(dto.Id);
PersonName name = _personNameFactory.Create(dto.Name);
DateOfBirth dob = _dateOfBirthFactory.Create(dto.DateOfBirth);
Gender gender = _genderFactory.Create(dto.GenderId);
Address billingAddress = _addressFactory.Create(dto.BillingAddress);
Address shippingAddress = _addressFactory.Create(dto.ShippingAddress);
Account account = _accountFactory.Create(dto.Account);
return new Buyer(aggregateId, name, dob, gender, dto.PictureUrl, billingAddress, shippingAddress, account);
}
}
}

从应用程序层,服务类使用存储库接口和工厂接口为用例进行编排。

用例1:在更新操作期间,我使用存储库从数据库中获取聚合的现有数据。我需要更新域聚合根对象的一个或两个属性。示例:我需要更新帐单地址或收货地址。

用例2:在更新操作期间,我使用存储库从数据库中获取聚合的现有数据。我需要更新一下账户状态。我从域聚合根对象调用状态更新方法。例子:buyerAggregate.Account.ActivateAccount ()

我是否以正确的方式更新域聚合根对象及其属性?

在用例2中,您的汇总将是帐户,而不是买方。买方没有必要介入这笔交易。

因此,对于这种情况,您将从存储库检索Account,然后直接调用ActivateAccount()。

为用例设计的任何聚合都应该提供对聚合进行更改的完整接口。换句话说,您的应用层将只处理聚合根上的属性和方法。如果需要更改子实体,则应该在聚合根上实现该方法。不应该直接与聚合的子属性交互。聚合有责任避免其范围内的任何不变量。如果直接更改子对象,可能会使整个聚合处于无效状态,因为聚合无法强制执行控件。

最新更新