[英]DDD (Domain Driven Design) Application Layer
我一直在尝试基于DDD构建应用程序,但我有一些问题。
我有一些层: - 表示层 - MVC - 应用层 - 域层...
首先,我想知道我是否可以在ApplicationLayer中执行此操作(获取系列信息>获取默认消息信息>发送电子邮件>更新数据库):
public ApproveFamilyOutput ApproveFamily(ApproveFamilyInput input)
{
Family family = _familyRepository.GetFamily(input.Username);
family.Approve();
DefaultMessage defaultMessage = _defaultMessageRepository.GetDefaultMessage(MessageTypes.FamilyApproved);
_email.Send(family.GetEmail(), defaultMessage.Subject, defaultMessage.Message);
_familyRepository.Update(family);
bool isSaved = _familyRepository.Save();
return new ApproveFamilyOutput()
{
Errors = Helper.GetErrorIfNotSaved(isSaved)
};
}
我在想什么? Application层负责完成这项工作吗?
第二个问题是:我需要根据用户拥有的权限将一些数据发送到表示层。 这些权限在数据库中定义。 示例: - 对象族具有Name,LastName,PhoneNumber,Email属性,用户可以显示/隐藏每个值。 我怎么处理这个?
我可以在应用程序层中执行以下操作:
public GetFamilyOutput GetFamily(GetFamilyInput input)
{
Family family = _familyRepository.GetFamily(input.Username);
FamilyConfiguration familyConfiguration = _familyConfigurationRepository.GetConfigurations(family.Id);
//ProcessConfiguration will set to null the properties that I cannot show
family.ProcessConfiguration(familyConfiguration);
return new GetFamilyOutput
{
//Map Family Object to the GetFamilyOutput
};
}
注意:Family,DefaultMessage和FamilyConfiguration是在域层内创建的域对象。
你有什么意见?
谢谢 :)
编辑:注意:我喜欢下面的所有答案,我使用了一点点:)(我不能将所有答案都标记为可接受)
您的应用程序服务在#1中所做的是完全有效的:它以很少甚至没有业务逻辑知识来协调工作流。
然而,当然可以做的改进很少,例如:
我没有看到任何交易? 电子邮件只应在成功进行交易时发送。
发送电子邮件可能被视为家庭批准的副作用。 我想业务专家可以说: “ 当一个家庭获得批准后,通过电子邮件通知感兴趣的各方” 。 因此,发布FamilyApproved
域事件并在事件处理程序中移动电子邮件发送逻辑可能是明智之举。
请注意,只有在将域事件持久保存到磁盘并且您希望将事件保留在与聚合相同的事务中之后,才希望异步调用该处理程序。
您可以进一步将邮件发送过程抽象为emailService.send(MessageTypes.FamilyApproved, family.getEmail())
。 应用程序服务不必知道默认消息。
存储库通常是聚合根(AR)所独有的,如果DefaultMessage
不是AR,那么我会考虑以不同方式命名DefaultMessageRepository
服务。
对于#2,尽管可以在域中进行授权检查,但更常见的是从域中解除此类任务并在应用程序层中强制执行权限。 您甚至可以拥有支持有界上下文(BC)的专用身份和访问。
“// ProcessConfiguration将设置为null我无法显示的属性”
该解决方案不会那么好(就像实施IFamilyProperty
解决方案一样),因为您的域模型会受到技术授权问题的污染。 如果您正在寻求应用DDD,那么该模型应该尽可能忠实于IFamilyProperty
在语言(UL),我怀疑IFamilyProperty
是您的领域专家会提及甚至理解的东西。 允许属性变为null
可能也会违反某些不变量。
这种解决方案的另一个问题是域模型很少适用于查询(它是为命令构建的),因此通常更喜欢完全绕过它并且更倾向于直接进入数据库。 在域中实施授权会阻止您轻松地执行此操作。
至少出于这些原因,我认为最好在域外实施授权检查。 您可以自由使用您想要的任何实现并满足您的需求。 例如,我认为从DTO中剥离价值可能是合法的。
我也怀疑是否可以将一些逻辑放到应用程序服务上。 但是,一旦我阅读了Vladimir Khorikov的域名服务与应用服务文章,事情变得更加清晰。 它说明了这一点
域服务包含域逻辑,而应用服务则不包含域逻辑。
并通过很好的例子说明了这个想法。 因此,在您的情况下,我认为将这些方案放在Application Service中是完全没问题的,因为它不包含域逻辑。
至于#1:
从理论上讲,应用程序层可以执行您所描述的操作。 但是,我个人更愿意进一步分开关注:应该有一个持久层。 在您的情况下,开发人员需要知道:
我认为应该将2-3-4移到持久层,以使代码看起来像:
Family family = _familyRepository.GetFamily(input.Username);
family.Approve().Notify(_email);
这种方法在如何处理错误和一些业务逻辑改进方面提供了更多灵活性。 例如,如果遇到持久性错误,则不会发送电子邮件。
当然,您需要实现一些其他类型和扩展方法(例如“Notify()”)。
最后,我认为应该使用存储库模式实现电子邮件服务(因此您有两个存储库)并且具有持久性级别的实现。 我的观点:在应用程序之外持有的任何东西都需要存储库和持久性实现; 电子邮件在用户邮箱中的应用程序之外保留。
至于#2:
我强烈建议不要使用可空属性并清除它们。 它真的很混乱,非常快,非常难以进行单元测试,并且有很多“隐藏”的警告。 相反,为您的属性实现类。 例如:
public class UserPriviledge { //... your db-defined privileges }
public interface IFamilyProperty<T>
{
public string PropertyName { get; }
public T PropertyValue { get; }
public List<UserPriviledge> ReadPriviledges { get; }
public bool IsReadOnly { get; }
}
public class FamilyName : IFamilyProperty<string>
{
public static string PropertyName => "Name";
public string PropertyValue { get; }
public List<UserPriviledge> ReadPriviledges { get; }
public bool IsReadOnly { get; private set; }
public FamilyName(string familyName) {
this.PropertyValue = familyName;
this.ReadPriviledges.Add(someUserPrivilege);
this.IsReadOnly = false;
}
public void MakeReadOnly() {
this.IsReadOnly = true;
}
}
public class Family
{
public int Id { get; }
public List<IFamilyProperty> LimitedProperties { get; }
}
使用这种实现,您可以使用相同类型的方法来删除值,而不是模糊值或应用更复杂的逻辑:
public void ApplyFamilyPermissions(Family family, UserEntity user)
{
foreach (var property in family.LimitedProperties) {
if (property.ReadPriviledges.Intersect(user.Priviledges).Any() == false) {
family.LimitedProperties.Remove(property);
} else if (property.IsReadOnly == false && HasPropertyWriteAccess(property, user) == false) {
property.MakeReadOnly();
}
}
}
注意:代码未经过验证,我很确定会出现一些语法错误,但我相信它会清楚地传达这个想法。
广告1
我通常将该逻辑移动到域层 - 服务 。
那么应用层就是调用:
public ApproveFamilyOutput ApproveFamily(ApproveFamilyInput input)
{
var approveService = diContainer.Get<ApproveService>(); // Or correctly injected by constructor
var result = approveService.ApproveFamily(input);
// Convert to ouput
}
域服务(AppproveService类)看起来像:
public ApproveResult ApproveFamily(ApproveFamilyInput input)
{
var family = _familyRepository.GetFamily(input.Username);
family.Approve();
_familyRepository.Update(family);
bool isSaved = _familyRepository.Save();
if(isSaved)
_eventPublisher.Publish(family.raisedEvents);
// return result
}
为了使其工作(并遵循六边形/洋葱架构),域层定义其依赖关系的所有接口(IFamilyRepository,IDefaultMessageRepository等),并且应用层将特定实现注入域层。
说清楚:
1.域层是独立的
2.域对象是纯粹的 - 由实体,值对象组成
3.域对象不调用存储库,它取决于域服务
4.域对象引发事件
5.不相关的逻辑由事件(事件处理程序)处理 - 例如发送电子邮件,它遵循开放 - 封闭原则
class FamilyApprovedHandler : IHandle<FamilyApprovedEvent>
{
private readonly IDefaultMessageRepository _defaultMessageRepository;
private readonly IEmailSender _emailSender;
private readonly IEmailProvider _emailProvider;
// ctor
public Task Handle(FamilyApprovedEvent event)
{
var defaultMessage = _defaultMessageRepository.GetDefaultMessage(MessageTypes.FamilyApproved);
var email = _emailProvider.Generate(event.Family, defaultMessage.Subject, defaultMessage.Message);
_emailSender.Send(email);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.