【问题标题】:Best way of modelling a list within a component in NHibernate在 NHibernate 中的组件内建模列表的最佳方法
【发布时间】:2021-12-01 11:02:51
【问题描述】:

想象一下,我有一个房地产网站,您可以通过它向不同的房地产经纪人询问特定的房地产。不同的查询方法可能具有与之关联的不同计费计算,并且并非所有代理都会启用每种计费模型。

public class EmailEnquiryBillingModel : ValueObject
{
    public string EmailAddress { get; set; }
    public decimal CostPerEnquiry { get; set; }
}

public enum DayOfWeek
{
    Monday,
    Tuseday,
    // etc.
}

public class OpeningHours : ValueObject
{
    public DateTime OpeningTime { get; set; }
    public DateTime ClosingTime { get; set;} 
}

public class PhoneEnquiryBillingModel : ValueObject
{
    public PhoneEnquiryBillingModel()
    {
        OpeningHours = new Dictionary<DayOfWeek, OpeningHours>();
    }
    public int PhoneNumber { get; set; }
    public IDictionary<DayOfWeek, OpeningHours> OpeningHours { get; set; }
}

public class EstateAgent : Entity
{
    public string Name { get; set; }
    public EmailEnquiryBillingModel EmailEnquiryBillingModel { get; set; }
    public PhoneEnquiryBillingModel PhoneEnquiryBillingModel { get; set; }
}

NHibernate 具有组件(值对象)的语义,如果组件中的每个属性都为 null,则该组件也将为 null。

因此,通过适当的映射,您可以编写if(estateAgent.EmailEnquiryBillingModel != null),而不必检查电子邮件查询计费模型的每个单独属性,或者该模型是否有效:我们要么有模型,要么没有。这是一种检查是否启用了特定计费模式的简单而优雅的方法。

当您在一个组件中有一个集合时,问题就出现了,例如电话查询计费模式和不同的营业时间。 PhoneEnquiryBillingModelOpeningHours 都不是实体。这些是合法的价值对象:我们不关心房地产经纪人是在周一上午 9 点这个还是周一上午 9 点 那个 开门,只关心它在周一上午 9 点开门。星期一。

因此,这感觉像是用 C# 表示此领域模型的语义正确方式。

然而,PhoneEnquiryBillingModel 包含一个集合(ProviderOpenHours),并且集合在 NHibernate 中不能为空,只能为空,这意味着 ProviderOpenHours 将始终为非空,即使房地产代理没有有意义地启用该查询模型。 (有关详细信息,请参阅:https://ayende.com/blog/4685/those-are-the-rules-even-when-you-dont-like-them)。

这意味着您不能像if(estateAgent.PhoneEnquiryBillingModel != null) 这样进行简单的检查,因为该对象总是不为空。

因此,对于某些计费模型,您可以进行空值检查以查看它们是否已启用,但对于其他计费模型,您必须找到另一种检查方式,具体取决于这些计费模型是否包含一套。

实际上,您需要了解计费模型的内部结构才能知道是否可以进行这种比较,这感觉就像您正在打破封装并更改基于域模型的关于 ORM 的规则。

有没有更好的建模方法?或者如果 NHibernate 没有电话号码或任何营业时间,可以让 NHibernate 在 PhoneEnquiryBillingModel 中序列化为 null?

【问题讨论】:

    标签: nhibernate domain-driven-design


    【解决方案1】:

    因此,通过适当的映射,您可以编写 if(estateAgent.EmailEnquiryBillingModel != null)

    这本身并不是最好的封装。

    相反,你选择:

    if (estateAgent.DoesAcceptEmailEnquiries())
    

    if (estateAgent.DoesAcceptPhoneEnquiries())
    

    与查询 EstageAgent 聚合上的属性以对 EstateAgent 的能力进行假设相比,这将提供更好的封装。如果您决定更改 EstateAgent 在内部存储此信息的方式的实现,该怎么办?您需要更改所有客户端。

    EstateAgent 对其基础价值对象执行单个属性检查并没有什么特别不好的。

    但是,您可以更进一步,在 PhoneEnquiryBillingModel 上实现一个检查器方法,甚至可以使用静态方法来避免 EstateAgent 中的空检查。

    PhoneEnquiryBillingModel

    public class PhoneEnquiryBillingModel : ValueObject
    {
        public PhoneEnquiryBillingModel()
        {
            OpeningHours = new Dictionary<DayOfWeek, OpeningHours>();
        }
        public int PhoneNumber { get; set; }
        public IDictionary<DayOfWeek, OpeningHours> OpeningHours { get; set; }
    
        public static bool DoesAcceptEnquiries(PhoneEnquiryBillingModel phone)
        {
            if (phone == null) return false;
    
            if (phone.OpeningHours.Count == 0) return false;
     
            return true;
        }
    }
    

    地产代理

    public class EstateAgent : Entity
    {
        public string Name { get; set; }
        public EmailEnquiryBillingModel _emailEnquiryBillingModel { get; set; }
        public PhoneEnquiryBillingModel _phoneEnquiryBillingModel { get; set; }
    
        public bool DoesAcceptPhoneEnquiries()
        {
            return PhoneEnquiryBillingModel.DoesAcceptEnquiries(
                _phoneEnquiryBillingModel);
        }
    }
    

    【讨论】:

    • 是的。我可以看到这是一个解决方案。但我认为拥有可选字段并不少见,如果该字段存在,则假设您具有有效属性并使用该属性执行一些逻辑;如果它为空,那么它不适用,你做别的事情。有什么单独的逻辑来检查存在和有效性(如果电话!= null && phone.OpeningHours.Count > 0)那么它增加了更多的工作?此外,客户端程序员可能不知道他们必须在访问 PhoneEnquiryBillingModel 对象之前检查 DoesAcceptPhoneEnquiries() 方法。
    • 封装!如果客户端不知道如何处理它以及何时使用它,它真的应该访问 PhoneEnquiryBillingModel 吗?你的模型贫血。您的客户只是在类中导航数据结构。那不是DDD。是的,模型中的工作量更大,但对客户来说更容易。如果模型的工作量较少,那么客户需要做更多的工作,并且比他们真正需要的更多地了解模型。
    • 我明白你的意思。但我认为在某些情况下客户端程序员可能想了解业务模型。假设您有一个计费服务,它接受一个属性和一个计费模型,并计算出对该属性的查询应该花费多少,例如billingService.GetCostOfEnquiry(属性,enquiryBillingModel)。在这种情况下,我认为不需要将计费模型封装在房地产代理实体中。计费服务只关心计费模型。房地产经纪人的其他财产与该计算无关。
    • 在您的示例中:GetCostOfEnquiry,客户端仍然没有检查 enquiryBillingModel 的属性,所以这很好。您不必“隐藏”房地产经纪人背后的计费模型......如果您将行为放在客户可以使用的计费模型上。关键是,客户端应该在您的模型上调用行为,而不是枚举其属性来决定如何处理它。这意味着您的客户正在实现属于您的域模型的业务逻辑
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-02
    • 2015-08-03
    • 1970-01-01
    相关资源
    最近更新 更多