【问题标题】:Where to put object-oriented queries in a layered architecture?在分层架构中将面向对象的查询放在哪里?
【发布时间】:2011-04-16 07:22:48
【问题描述】:

给定:

  • 您拥有一个包含表示层、业务层和数据层的架构。
  • 您正在应用领域驱动设计。
  • 您正在使用可让您创建面向对象查询的对象关系映射器(例如,可让您创建 HQL 查询的 NHibernate)。

问题:

你应该把面向对象的查询放到哪一层?

我的想法:

我认为将它们放入表示层通常是没有意义的。但是不知道是放到业务层还是数据层。

示例(NHibernate):

假设您的业务逻辑需要一些方法 GetCustomersPossiblyInterestedIn(Product p)。然后,您可以创建一个复杂的 HQL 查询,用于选择客户已经购买了与 p 属于同一类别的产品的客户对象。

论据 a)

一方面,我会说这个查询显然是业务逻辑,因为根据客户是否购买了同一类别的产品而将其视为可能对产品感兴趣的决定是一个业务决策。不妨选择在同一类别中以相似价格购买过多个产品的客户,例如

论据 b)

另一方面,业务层不应该依赖于数据层,所以直接在业务层使用NHibernate敲响了警钟。

可能的解决方案 1)

创建您在业务层中使用的您自己的面向对象的查询语言,并在数据层中转换为 HQL。 我认为这会导致很多开销。如果您使用基于查询对象的查询语言而不是已解析的查询语言,您可能会付出一些努力,但我的反对意见仍然适用。

可能的解决方案 2)

在业务层直接使用 NHibernate 是可以的,因为 NHibernate 能够提供 HQL、ISession 等的抽象级别适合业务层。无需包装。

你怎么看?

编辑:

有关密切相关的讨论,请参阅 Ayende Rahien 的 "Repository is the new Singleton""The false myth of encapsulating data access in the DAL"

【问题讨论】:

  • 您是否将规范模式视为解决方案 3?
  • 到目前为止,没有。不过谢谢,我去看看。

标签: nhibernate hql ddd-repositories 3-tier


【解决方案1】:

绝对避免将面向对象的查询放入表示层。它应该只显示/使用从业务逻辑层 (BLL) 接收到的数据。没有任何询问。如果您需要查询从您的 BLL 收到的结果,那么您的 BLL 需要扩展以提供不需要查询的此类数据。

您使用“面向对象的查询语言”的想法很好。通常,这种“语言”就是你的 DAL :) 我想说“面向对象查询语言”的好例子是正确实现的数据访问层 (DAL)。

从我的角度来看,您 DAL 应该实现 80-90% 的所有功能并提供这样的一组功能:

  • 客户 GetCustomerById(int customerId);
  • List GetLastRegisteredCustomers(int count);
  • 等等……

这些函数提供了大部分不需要查询的必需功能。

对于所有其他 10-20% 的很少使用的查询(您将它们命名为“面向对象”),您的 DAL 应该实现返回 IQueryable 结果的方法/方法,我会说至少是 'GetAll()' 方法和可能很少的自定义:

  • IQueryable GetAll();
  • IQueryable GetCustomerByCountry(int countryId);

在这种情况下,如果您需要在今年注册 BLL 的国家/地区寻找客户,您将致电:

List<Customer> customers = GetCustomerRepository()
    .GetCustomerByCountry(countryId)
    .Where(customer=>customer.RegisterDate.Year==year)
    .ToList<Customer>()
    ;

猜猜看,你知道什么提供了 IQueryable 接口。

如何在 NHibernate 下使用 Linq:Linq to NHibernate

还有一个提示:我建议您使用“存储库”模式来实现 DAL。前段时间我用这个作为一般的想法:http://habrahabr.ru/blogs/net/52173/(如果你不能用谷歌阅读俄语翻译整个页面 - 它应该是可读的)。

希望对您有所帮助。

【讨论】:

  • 那么,您会在 DAL 和 BAL 之间拆分复杂的查询吗?我不太熟悉 Linq/Linq to NHibernate。 GetCustomerByCountry(int) 返回什么?内存中的所有客户对象?
  • var customersByCountry = "GetCustomerByCountry(int)" 将返回一个“查询对象”。这是一个表达式,当您调用customersByCountry.Fisrt()、customersByCountry.ToList() 等时,将由DATABASE 执行。但在调用.ToList() 之前,您可以调用'Where' 扩展方法,该方法将应用附加条件在您的查询上也将由 DB 执行。我建议使用您的数据库分析器来查看真正执行的内容和时间,以便更好地了解正在发生的事情。如果您还有其他问题,请告诉我,但这可能是另一个话题。 GL!
  • 谢谢!假设你不能使用 Linq(例如,在 Java 中),你怎么能完成类似的事情呢?您描述的 Linq to NHibernate 方法是否实现了命名设计模式?
  • 对不起,在这种情况下,我会问 Java 专家:“如何实现'延迟'查询执行”(对不起,我不是 Java 专家,只是在 .NET 方面有点好)。非常肯定的答案应该存在(否则,Java 输掉了与 .NET 的“圣战”)
  • “类似的东西”您可以实现为调用任何具有所有必需参数(CustomerId 等)的 StoredProcedure (GetCustomersByCountry) 的方法。还有一个额外的字符串参数将被传递到这个过程中。在内部,如果参数不为空,则应将其连接到“WHERE 条件”(如果您需要连接其他表-为此表名再传递一个参数)。您还需要实现一个“工厂”类,它将为您提供这样的“附加参数”集。这种模式由 Fowler 描述:...不记得也找不到模式名称。
【解决方案2】:

查询属于存储库。您可能在任何需要的地方都有 GetCustomersPossiblyInterestedIn(Product p) 的函数签名,但查询本身应该只在存储库类中

【讨论】:

    【解决方案3】:

    我更喜欢使用某种存储库来包装 NHibernate(或其他 ORM)。例如。在 NHibernate 的情况下,存储库将包装 NHibernate 会话。这个存储库可以是每个类(因此 CustomerRepository 和 OrderRepository),或者如果您的域中没有太多类,您也可以从单个存储库开始。

    这是放置 Criteria 查询(LoadAllByName、LoadCustomerWithOrder 等)的理想场所。如果您以后需要切换到不同的 ORM 甚至是不同的持久性机制(我认为非常罕见),您可以换掉包括存储库在内的整个数据层。

    【讨论】:

      猜你喜欢
      • 2011-03-30
      • 2018-11-09
      • 2010-12-14
      • 2011-03-15
      • 2015-03-31
      • 2017-11-14
      • 1970-01-01
      • 2011-05-31
      • 2017-03-08
      相关资源
      最近更新 更多