【问题标题】:Management and structure of a growing .NET project不断发展的 .NET 项目的管理和结构
【发布时间】:2011-09-19 17:33:44
【问题描述】:

我们正在构建一个 .NET 软件平台,用于我们公司内部使用的测试自动化。

应用程序由 GUI (WinForms) 组件和动态加载到其中以执行的各种“操作”组成。

已经有大约 100 个 Action 项目在进行中,而且这个数字还在增加。 其中一些项目与其他项目相互依赖,依此类推。

所有加载的动作都必须引用我们的“SDK”dll 用于各种活动(主应用程序的结果、日志记录等)。

通过这种相对简单的设计,我们面临着一些我们希望以最佳方式解决的管理决策:

  1. 操作(“插件”)是否应该引用我们的 SDK 项目输出,或一些已知的稳定版本? 例如,在开发大型应用程序(仅以 MS Office 为例)时,并非所有团队都自然而然地使用所有组件的源代码。

这种情况的最佳解决方案是什么?为什么?

  1. 如何正确验证所有需要的依赖项(例如第三方库)确实来自正确的位置?

在管理多个相互关联的项目的情况下,常见做法是什么?有什么建议吗?

【问题讨论】:

  • 我会为您的 SDK 提供稳定的接口。当你需要更改一个接口时,开发一个新的并弃用旧的,但要实施一段时间。我会让每个插件项目都针对最新的 SDK 构建。我会研究 MEF 或更好的 DI 工具,例如 Autofac。
  • 我可能更喜欢“使用给定的稳定版本”方法 - 使用 NuGet,您可以轻松地将 SDK 和其他支持文件打包到 NuGet 包中,这些包可以轻松安装到 Visual工作室,而且还整齐地更新了!
  • 我可以在公司内部实施 NuGet 包吗?意思是,允许其他用户在不公开我们的代码的情况下获取最新更新?

标签: c# structure


【解决方案1】:

这是一个没有明确答案的问题,但是……

你可以走两条路。强耦合系统或松耦合系统。

对于强耦合系统,我可以为二进制文件建议两个目录:第 3 方目录和包含贵公司构建的 DLL 的目录,供其他开发人员参考。第 3 方 DLL(在您的公司之外)应该位于源代码控制中,以便所有开发人员从同一位置引用相同版本的第 3 方 DLL,这可以避免开发人员机器不一致以及在每台机器上安装第 3 方软件的问题。内部 DLL 不应在源代码控制中引用,并且应通过自动构建批处理文件或类似文件在每个开发人员机器上构建。在构建发布步骤中,您可以将它们全部复制到同一个目录,只要开发人员获得最新的源代码控制和构建,每个人都可以在您的公司内拥有相同的 DLL。

例如,获取最新版本、构建(使用批处理文件来构建所有需要的项目),然后作为构建后步骤将输出复制到 common。现在您所有的其他项目都可以从同一位置引用通用的公司 DLL 和第三方 DLL,并且每个人都是一致的。

问题在于引用是强耦合的,因此如果没有正确沟通,更改有时会出现问题。

松散耦合系统使用诸如 MEF(托管可扩展性框架)之类的框架,并且您的组件引用“合同 DLL”,它为您的组件定义了接口。项目引用接口或契约 DLL,并不真正关心实现,然后 MEF 为您管理插件。

在这种情况下,您引用的是接口 DLL,而不是实际实现的 DLL。

例如,假设我有一个名为 ILog 的接口和一个名为 LogMessage 的方法。

private ILog _logger;
_logger.LogMessage();

所以,在强耦合的情况下:Action.DLL 直接引用 Logger.DLL。

在松散耦合的情况下,Action.DLL 引用 ILog.DLL(只是接口)。 Logger.DLL 实现 ILog.DLL。但 Action.DLL 并没有直接引用 Logger.DLL。

现在我可以拥有任意数量的实现 ILog 接口的 DLL,但 Action.DLL 不直接引用它们。这非常酷,并且是 MEF 和松散耦合的更令人兴奋的特性之一,即没有依赖关系的能力。

您如何选择,无论哪种方式都是可以接受的,我认为松散耦合的想法最适合您的场景,因为团队只需要了解合同与实际实施。

我不会有一个庞大的合同 DLL,我会尝试将接口分解为逻辑分组。例如,日志记录似乎是一种实用程序类型的接口,因此我将创建一个带有 ILog 接口的实用程序合同 DLL。如何拆分取决于您要执行的操作。或者每个接口都可以是一个契约 DLL,但这可能有点极端。

【讨论】:

    【解决方案2】:

    这是一个有点复杂的话题,尤其是在 .NET 领域。我不知道“最佳”解决方案,但我会解释我们如何管理它;也许你会对自己有用。

    这允许您构建包含大量链接项目的大型系统,但会带来很多复杂性问题。我认为,任何此类解决方案都可以。

    第一:物理结构(我们使用SVN)。

    • 每个项目都有一个源代码管理根
    • 每个项目都有自己的主干、分支和标签
    • trunk 文件夹有一个版本化的 \src 和 \build 文件夹,以及一个未版本化的 \lib 文件夹 \lib 文件夹包含要引用的二进制文件。 这些二进制文件可能是您需要链接到的第 3 方库或其他项目(例如,您的 SDK)。 \lib 下的所有二进制文件都来自企业 ivy 存储库(请参阅http://ant.apache.org/ivy/)。这些天在 .NET 领域有很多关于 NuGet 的动向,所以你也可以去看看。

    您的版本化 \build 文件夹包含构建脚本,例如从 ivy 获取二进制文件、将项目发布到 ivy 或编译每个项目。当您想在每个项目中指向一个持续集成服务器时,它们也会派上用场。

    第二:定义依赖的来源

    答案:它们来自您的 ivy 存储库(它可以像网络共享文件系统一样简单)。 您已经创建了存储库,因此您可以控制其内容。 小心安装在 GAC 中的第 3 方二进制文件。处理这个问题,Visual Studio 很痛苦。

    具体来说:

    如何正确验证所有需要 依赖项(第三方库 例如)确实取自 位置正确吗?

    Ivy 为您提供了极大的依赖灵活性,它还解决了传递依赖;例如,您可以依赖 SDK rev="1.+" status="latest.release",这意味着“SDK 的最新稳定 1.x 版本,或者依赖于 SDK rev="2.+" status="latest.集成”,这意味着 2.x SDK 的最新可用二进制文件(可能是从持续集成构建中生成的)。

    因此,您将始终依赖已编译的二进制文件,而不是项目输出。您可以控制要获取哪个版本的二进制文件。第三方依赖项可能会作为可传递的方式引入您的 SDK。

    这也意味着项目中的代码量将保持在尽可能少的情况下,以便拥有可行的 Visual Studio 解决方案。这也意味着 ReSharper 等重构工具的用处将大大降低。您的构建脚本和分支策略也会有一定程度的复杂性。这在很大程度上取决于组件的逻辑。

    这是一个简短的概述,如果您认为这是您想要的,我可以扩展答案。祝你好运; .NET 生态系统,尤其是 Visual Studio,并没有真正被认为是这样工作的。

    【讨论】:

      猜你喜欢
      • 2014-07-04
      • 1970-01-01
      • 2011-06-26
      • 1970-01-01
      • 2012-10-27
      • 2012-06-30
      • 1970-01-01
      • 2012-08-01
      • 2011-03-15
      相关资源
      最近更新 更多