n层业务/服务层设计

6

我正在重新评估我们的n层架构,并希望根据您的经验获得一些建议。这是我们典型的.NET n层(有时是n层)设计。

Project.UI
Project.Services
Project.Business
Project.Model
Project.DataAccess

DataAccess通常由Entity Framework 4Repository类组成。我尝试遵循聚合根的概念,以避免为每个表创建一个存储库,但在我的经验中,这比说起来容易得多。我倾向于在存储库和表之间有约70%的匹配。

模型通常由我的Entity Framework 4实体组成,我一直在成功地使用Self-Tracking EF实体。

业务是我最困难的部分。我通常为每个存储库拥有一个Manager类。该类将包含像.Add()这样的方法,在转发到repository.Add()之前执行业务验证。

服务,通常仅在我确实希望创建基于Web服务的解决方案时才实现。该层将负责在DTO和实体之间进行请求/响应处理。最重要的是提供更粗粒度的接口。例如TradingService.SubmitTrade(),它实际上是用于业务交易的facade,其中可能包括AccountManager.ValidateCash(),OrderManager.SubmitOrder()等。

关注点
我的业务层非常以实体为中心,实际上只是连接实体和存储库的粘合剂,并在两者之间进行验证。我看到过许多设计,其中Service层是持有对存储库的引用(实质上跳过了“业务层”)。它执行验证,但其责任(和命名)是更高级别的、更粗粒度的业务交易。使用上面的例子,TradingService.submitTrade()将不会委托给任何业务管理器类,它本身将查询必要的存储库,执行所有验证等。

我喜欢我的设计,因为我可以在多个服务调用中重用业务层方法,但我讨厌每个存储库都有一个匹配的业务层管理器,这会创建大量额外的工作。也许解决方案是在业务层级别上采用不同类型的分组?例如,将像PhoneManager和EmailManager(注意我有Phone实体和Email实体)这样的单独的Manager类合并到一个逻辑Manager类中,例如ContactsManager(注意我没有“Contact”实体类型)。具有诸如ContactManager.GetPhones()和ContactManager.GetEmail()等方法。

我想更多地了解其他人如何组织和委派职责,无论他们是否拥有Service层、Business层、两者都有等。什么持有ORM上下文引用等。

1个回答

2
我倾向于按照你在最后所述的方式进行,将业务层中的事物分组为更符合业务角度的管理器。
以联系人为例,我肯定不会有一个电话管理器或电子邮件管理器。 "ContactsManager" 对我来说是更有用的分组,实现了基本相同的功能,只需要处理更少的管理器。从业务角度来看,电话号码和电子邮件只是联系人的小部分。它们有自己的表和实体,并不意味着它们需要自己的管理器。这并不意味着你不能重用它们。如果您需要在多个地方使用电子邮件地址,ContactsManager 可以具备相关方法。
我们通常在环境中拥有一个数据库/实体层,然后是一个位于服务器上的业务层,处理业务规则和业务逻辑。该业务层通过 WCF 作为服务暴露给客户端(或至少与客户端相关的内容)。
听起来你已经知道你想要做什么,所以我建议你对其进行一些原型工作,看看它对你是否有效。 :)

你是否遇到过跨越多个业务管理器类的服务调用?换句话说,你的服务接口甚至可以比业务管理器更加粗粒度。例如ContactService.CreateUser(),将委托(充当门面)给UserManager.CreateUser()和ContactManager.AddEmail(),ContactManager.AddPhone()等。此外,即使我们将电子邮件和电话相关逻辑分组到ContactsManager中,也没有什么问题拥有PhoneRepository和EmailRepository。在我的看来,Repositories不能像Managers一样“逻辑化”。 - e36M3
很少见,但确实发生过。我更喜欢尽可能多地将逻辑保留在业务层中,因为这样如果以后有人来找我要求一个能做一些与服务相同功能的网页(是的,这种情况也有发生),我可以直接使用业务层逻辑而不是调用服务(在我的案例中,它们位于同一台服务器上)。所以在这种情况下,我会创建一个业务管理器方法,它本身可以使用其他业务管理器。虽然在简化方面真正获得了收益时,我会尽量避免使用这种方式。 - Tridus
我知道你的意思,我使用 DI 来创建我的 Managers,并通过构造函数为它们提供所需的 repositories。一个 Manager 创建另一个 Manager 将需要 Managers 意识到 DI 容器。然而,如果没有这个,有时我会通过复制和粘贴来重复逻辑。例如 DataEntryManager.EnterPrice() 和 FileManager.UploadPriceFile()。你可以想象,在解析所述的“价格文件”之后,我们将不得不插入价格。我可以复制一些那个逻辑,或者让 FileManager 重用 DataEntryManager。 - e36M3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接