DDD(域驱动设计)应用层

时间:2017-03-19 01:50:38

标签: c# .net domain-driven-design

我一直在尝试基于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是在域层内创建的域对象。

你有什么看法?

谢谢:)

编辑: 注意:我喜欢下面的所有答案,我使用了一点点:)(我不能将所有答案都标记为可接受)

4 个答案:

答案 0 :(得分:3)

您的应用程序服务在#1中所做的是完全有效的:它以很少甚至没有业务逻辑知识协调工作流。

然而,肯定很少有改进,例如:

  1. 我没有看到任何交易?只有在成功进行交易后才能发送电子邮件。

  2. 发送电子邮件可能被视为家庭批准的副作用。我想商业专家可以说:一个家庭获得批准时,然后通过电子邮件通知感兴趣的各方”。因此,发布FamilyApproved域事件并在事件处理程序中移动电子邮件发送逻辑可能是明智之举。

    请注意,您希望仅以异步方式调用处理程序 在域事件持久化到磁盘并且您想要保留之后 与聚合相同的交易中的事件。

  3. 您可以进一步将邮件流程抽象为emailService.send(MessageTypes.FamilyApproved, family.getEmail())之类的内容。应用程序服务不必知道默认消息。

  4. 存储库通常是聚合根(AR)所独有的,如果DefaultMessage不是AR,那么我会考虑以不同的方式命名DefaultMessageRepository服务。

  5. 对于#2,尽管可以在域中进行授权检查,但更常见的是从域中解除此类任务并在应用程序层中强制执行权限。你甚至可以拥有一个专门的身份和访问支持有界上下文(BC)。

      

    “// ProcessConfiguration将设置为null我不能的属性   秀“

    该解决方案不会那么好(就像实施IFamilyProperty解决方案一样),因为您的域模型会受到技术授权问题的污染。如果您正在寻求应用DDD,那么该模型应尽可能忠实于泛在语言(UL),我怀疑IFamilyProperty是您的领域专家提及甚至理解的内容。允许属性变为null也可能违反某些不变量。

    这种解决方案的另一个问题是域模型很少适用于查询(它是为命令构建的),因此通常更喜欢完全绕过它并且更倾向于直接进入数据库。在域中实施授权会阻止您轻松地执行此操作。

    至少出于这些原因,我认为最好在域外实施授权检查。您可以自由使用您想要的任何实现并满足您的需求。例如,我认为从DTO中剥离价值可能是合法的。

答案 1 :(得分:1)

我也怀疑是否可以将某些逻辑放到应用服务中。但是一旦我读到弗拉基米尔·霍里科夫的Domain services vs Application services article,事情变得更加清晰。它说明了

  

域服务保留域逻辑,而应用程序服务则不保存。

通过很好的例子来说明这个想法。因此,在您的情况下,我认为将这些方案放在Application Service中是完全没问题的,因为它不包含域逻辑。

答案 2 :(得分:0)

至于#1:

理论上,应用程序层可以执行您所描述的操作。但是,我个人更愿意进一步分离问题:应该有一个持久层。在您的情况下,开发人员需要知道:

  1. 从存储库中获取家人。
  2. 调用方法批准家庭对象。
  3. 更新存储库中的系列。
  4. 保留存储库。
  5. 如果存在持久性错误,请处理任何可能的错误。
  6. 我认为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();
            }
        }
    }
    

    注意:代码未经过验证,我确信有一些语法错误,但我相信它会清楚地传达这个想法。

答案 3 :(得分:0)

广告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);
    }
}