具有 Repository、DAL、BAL 的解决方案结构
Solution structure with Repository, DAL, BAL
我们想创建一个架构干净的新项目。所以我们的团队决定:
- 存储库模式
- 数据访问层
- 业务访问层
- 公共层(IPersonRepository、IPersonService、ICSVExport 等抽象)
- 一些核心服务,例如创建 CSV 文件。
- 单元测试
现在我们拥有的是:
PersonsApp.Solution
--PersonsApp.WebUI
-- Controllers (PersonController)
--PersonApp.Persistence
--Core folder
-IGenericRepository.cs (Abstraction)
-IUnitOfWork.cs (Abstraction)
--Infrastructure folder
-DbDactory.cs (Implementation)
-Disposable.cs (Implementation)
-IDbFactory.cs (Abstraction)
-RepositoryBase.cs (Abstraction)
--Models folder
- Here we DbContext, EF models (Implementation)
--Repositories
- PersonRepository.cs (Implementation)
--PersonApp.Service
--Core folder
-IPersonService.cs (Abstraction)
-ICSVService.cs (Abstraction)
--Business
-PersonService.cs (Abstraction)
--System
-CSVService.cs (Abstraction)
--PersonApp.Test
在我看来,我们的结构有点乱。
第一个问题是:
PersonApp.Service
有抽象(接口)和实现
在一个 class 库中。
第二个问题是:
PersonApp.Persistence
有抽象(RepositoryBase
)和
在一个 class 库中实现。但是如果我移动 RepositoryBase
,
IGenericRepository
、IUnitOfWork
在名为
的 class 库中
PersonApp.Abstractions
,那我就循环引用错误了
在 PersonApp.Abstractions
和 PersonApp.Persistence
之间
组织解决方案的最佳方式是什么?
这可能不太好S.O。问题是它问的是基于意见的东西。在规划项目结构时,我的目标是让事情变得简单。如果抽象是为了多态性,我会考虑将接口移动到一个单独的 "common" 程序集中。例如,如果我想提供一个事物的几种可能实现,我将有一个声明接口的公共程序集,然后为特定实现单独的程序集。在大多数情况下,我使用接口作为契约,这样我就可以用模拟来代替真实的。在这些情况下,我将接口嵌套在具体实现之下。我使用一个名为 NestIn 的 VS 插件来提供嵌套支持。这使项目结构保持美观和紧凑。但是,请注意,如果您使用的是 .Net Standard 库,则似乎不支持文件嵌套。 (希望这会改变/已经改变)
所以对于 SomeService,我的文件夹项目结构如下所示:
- 服务[文件夹]
- SomeService.cs[具体]
- SomeService.dependencies.cs [部分] [嵌套]
- ISomeService [嵌套]
.dependencies.cs 文件是部分文件 class,我在其中放置了所有依赖项和构造函数。这让他们在我进行实施工作时被隐藏起来。我过去常常依赖#regions,但坦率地说,我现在受不了了。部分 classes 在我看来要好得多。
我的存储库与域程序集中的实体并存。
- 实体[文件夹]
- 配置[文件夹]
- OrderConfiguration.cs
- Order.cs
- 存储库[文件夹]
- OrderManagementRepository.cs
- OrderManagementRepository.dependencies.cs
- 我OrderManagementRepository.cs
- MySystemDbContext.cs
我不使用通用存储库,而是存储库旨在与它们所服务的控制器或服务配对。我可能有一些为多个消费者提供服务的通用存储库。 (诸如查找之类的东西)这种模式是为了满足 SRP 而为我演变而来的。通用存储库之类的最大问题是它们需要为多个主控服务。虽然 OrderRepository 可能承担单一责任,只关心订单,但我看到的问题是许多不同的地方都需要访问订单信息。这意味着不同的标准,并且需要不同数量的数据。因此,相反,如果我有一个 OrderManagementService 来处理订单、订单行等,并在下订单过程中涉及产品和其他细节,我将使用 OrderManagementRepository 来提供服务所需的几乎所有数据,并且管理用于管理订单的支持操作的包装。这意味着我的服务通常只需要 1 个存储库依赖项(而不是 OrderRepository、ProductRepository 等),而我的 OrderManagemmentRepository 只有 1 个更改原因。 (但这已经离题了。:)
我不久前开始依赖 Nesting,当时你需要 ReSharper 或类似的东西来访问 "Go to Implementation" 的接口。 Go to Definition 会将您带到接口,当在单独的命名空间或程序集中时,这会使在依赖项中导航变得很痛苦。通过将接口嵌套在它们的具体实现下,可以从接口快速点击到它的具体实现并返回。我利用跟踪解决方案管理器中的当前代码文件,以便在我的项目视图 highlights/expands 中浏览代码到当前查看的文件。
最终,您的项目结构应该反映您喜欢如何浏览代码,以使其直观且易于找到您需要的部分。这对不同的人来说会有所不同,所以部分 classes 和嵌套对我来说非常有效,因为我是一个非常喜欢视觉的人,经常使用项目视图。对于热键导航向导的人来说,它可能没有任何好处。最终,尽管我会说保持简单和适应性强。试图在早期阶段过多地计划它就像过早的优化。随着项目的发展,不要害怕四处移动。一个仅仅通过添加代码而增长的项目将不可避免地变成一个不稳定、混乱的混乱局面,无论您尝试提前计划得多么好。好的代码来自不断的重构,重构就是移动、删除和添加东西。当您的风格具有适应性并且您正在以一种不断完善的方式构建并且代码通过自然选择变得更好时,结构就会自由发展。
希望这能给您一些启发。祝绿野好运!
编辑:关于多态接口与契约接口。对于多态接口,我希望有多个可替代的具体实现,在这种情况下,接口(以及任何适用的基 class)将驻留在单独的程序集中。嵌套解决方案适用于唯一替换是为了模拟目的的情况。 (单元测试)最近的一个多态实例示例是当我需要替换内置 SMS 服务包装器以支持新的 SMS 提供程序时。这导致将原始代码中的硬编码具体 class 重构为包含 ISMSProvider 接口和一些通用通用定义的 SMSCore 程序集,然后是用于实现的两个程序集:SMSByMessageMedia 和 SMSBySoprano。
出现的其他情况可能与自定义有关。例如,我有许多个人库等用于通用代码,当为客户实现它们时,可能会有一些我想制作的特定于客户的 "isms"。这些情况通常通过覆盖通用实现(开闭原则)来解决,或者为通用代码可以使用的自定义依赖项实现提供的接口。在这两种情况下,客户端项目无论如何都会引用具体的实现,因此具有可扩展的 classes 和 assembly/namespace 中的依赖接口不会造成任何问题。这样就无需添加多个不同的命名空间和程序集引用。
我们想创建一个架构干净的新项目。所以我们的团队决定:
- 存储库模式
- 数据访问层
- 业务访问层
- 公共层(IPersonRepository、IPersonService、ICSVExport 等抽象)
- 一些核心服务,例如创建 CSV 文件。
- 单元测试
现在我们拥有的是:
PersonsApp.Solution
--PersonsApp.WebUI
-- Controllers (PersonController)
--PersonApp.Persistence
--Core folder
-IGenericRepository.cs (Abstraction)
-IUnitOfWork.cs (Abstraction)
--Infrastructure folder
-DbDactory.cs (Implementation)
-Disposable.cs (Implementation)
-IDbFactory.cs (Abstraction)
-RepositoryBase.cs (Abstraction)
--Models folder
- Here we DbContext, EF models (Implementation)
--Repositories
- PersonRepository.cs (Implementation)
--PersonApp.Service
--Core folder
-IPersonService.cs (Abstraction)
-ICSVService.cs (Abstraction)
--Business
-PersonService.cs (Abstraction)
--System
-CSVService.cs (Abstraction)
--PersonApp.Test
在我看来,我们的结构有点乱。
第一个问题是:
PersonApp.Service
有抽象(接口)和实现 在一个 class 库中。
第二个问题是:
PersonApp.Persistence
有抽象(RepositoryBase
)和 在一个 class 库中实现。但是如果我移动RepositoryBase
,IGenericRepository
、IUnitOfWork
在名为
的 class 库中PersonApp.Abstractions
,那我就循环引用错误了
在PersonApp.Abstractions
和PersonApp.Persistence
之间
组织解决方案的最佳方式是什么?
这可能不太好S.O。问题是它问的是基于意见的东西。在规划项目结构时,我的目标是让事情变得简单。如果抽象是为了多态性,我会考虑将接口移动到一个单独的 "common" 程序集中。例如,如果我想提供一个事物的几种可能实现,我将有一个声明接口的公共程序集,然后为特定实现单独的程序集。在大多数情况下,我使用接口作为契约,这样我就可以用模拟来代替真实的。在这些情况下,我将接口嵌套在具体实现之下。我使用一个名为 NestIn 的 VS 插件来提供嵌套支持。这使项目结构保持美观和紧凑。但是,请注意,如果您使用的是 .Net Standard 库,则似乎不支持文件嵌套。 (希望这会改变/已经改变)
所以对于 SomeService,我的文件夹项目结构如下所示:
- 服务[文件夹]
- SomeService.cs[具体]
- SomeService.dependencies.cs [部分] [嵌套]
- ISomeService [嵌套]
- SomeService.cs[具体]
.dependencies.cs 文件是部分文件 class,我在其中放置了所有依赖项和构造函数。这让他们在我进行实施工作时被隐藏起来。我过去常常依赖#regions,但坦率地说,我现在受不了了。部分 classes 在我看来要好得多。
我的存储库与域程序集中的实体并存。
- 实体[文件夹]
- 配置[文件夹]
- OrderConfiguration.cs
- Order.cs
- 配置[文件夹]
- 存储库[文件夹]
- OrderManagementRepository.cs
- OrderManagementRepository.dependencies.cs
- 我OrderManagementRepository.cs
- OrderManagementRepository.cs
- MySystemDbContext.cs
我不使用通用存储库,而是存储库旨在与它们所服务的控制器或服务配对。我可能有一些为多个消费者提供服务的通用存储库。 (诸如查找之类的东西)这种模式是为了满足 SRP 而为我演变而来的。通用存储库之类的最大问题是它们需要为多个主控服务。虽然 OrderRepository 可能承担单一责任,只关心订单,但我看到的问题是许多不同的地方都需要访问订单信息。这意味着不同的标准,并且需要不同数量的数据。因此,相反,如果我有一个 OrderManagementService 来处理订单、订单行等,并在下订单过程中涉及产品和其他细节,我将使用 OrderManagementRepository 来提供服务所需的几乎所有数据,并且管理用于管理订单的支持操作的包装。这意味着我的服务通常只需要 1 个存储库依赖项(而不是 OrderRepository、ProductRepository 等),而我的 OrderManagemmentRepository 只有 1 个更改原因。 (但这已经离题了。:)
我不久前开始依赖 Nesting,当时你需要 ReSharper 或类似的东西来访问 "Go to Implementation" 的接口。 Go to Definition 会将您带到接口,当在单独的命名空间或程序集中时,这会使在依赖项中导航变得很痛苦。通过将接口嵌套在它们的具体实现下,可以从接口快速点击到它的具体实现并返回。我利用跟踪解决方案管理器中的当前代码文件,以便在我的项目视图 highlights/expands 中浏览代码到当前查看的文件。
最终,您的项目结构应该反映您喜欢如何浏览代码,以使其直观且易于找到您需要的部分。这对不同的人来说会有所不同,所以部分 classes 和嵌套对我来说非常有效,因为我是一个非常喜欢视觉的人,经常使用项目视图。对于热键导航向导的人来说,它可能没有任何好处。最终,尽管我会说保持简单和适应性强。试图在早期阶段过多地计划它就像过早的优化。随着项目的发展,不要害怕四处移动。一个仅仅通过添加代码而增长的项目将不可避免地变成一个不稳定、混乱的混乱局面,无论您尝试提前计划得多么好。好的代码来自不断的重构,重构就是移动、删除和添加东西。当您的风格具有适应性并且您正在以一种不断完善的方式构建并且代码通过自然选择变得更好时,结构就会自由发展。
希望这能给您一些启发。祝绿野好运!
编辑:关于多态接口与契约接口。对于多态接口,我希望有多个可替代的具体实现,在这种情况下,接口(以及任何适用的基 class)将驻留在单独的程序集中。嵌套解决方案适用于唯一替换是为了模拟目的的情况。 (单元测试)最近的一个多态实例示例是当我需要替换内置 SMS 服务包装器以支持新的 SMS 提供程序时。这导致将原始代码中的硬编码具体 class 重构为包含 ISMSProvider 接口和一些通用通用定义的 SMSCore 程序集,然后是用于实现的两个程序集:SMSByMessageMedia 和 SMSBySoprano。
出现的其他情况可能与自定义有关。例如,我有许多个人库等用于通用代码,当为客户实现它们时,可能会有一些我想制作的特定于客户的 "isms"。这些情况通常通过覆盖通用实现(开闭原则)来解决,或者为通用代码可以使用的自定义依赖项实现提供的接口。在这两种情况下,客户端项目无论如何都会引用具体的实现,因此具有可扩展的 classes 和 assembly/namespace 中的依赖接口不会造成任何问题。这样就无需添加多个不同的命名空间和程序集引用。