组织多个 scala 相互关联的 sbt 和 git 项目 - 最佳实践建议

Posted

技术标签:

【中文标题】组织多个 scala 相互关联的 sbt 和 git 项目 - 最佳实践建议【英文标题】:Organizing multiple scala interrelated sbt & git projects - best practice suggestions 【发布时间】:2014-12-16 15:45:37 【问题描述】:

使用scala,使用sbt 进行构建,使用git 进行版本控制,当您的团队代码不再是单个项目时,有什么好的方法来组织它?在某个时候,您开始考虑将代码分离到单独的库或项目中,并根据需要在它们之间导入。你会如何组织这些事情?还是你会避开诱惑,只管理同一个 sbt 和 git 单一“项目”下的所有包?

兴趣点是:(随意更改)

避免发明过度设计的想象需求的新“头痛”。 仍然可以在给定的开发机器或CI server 上轻松构建所有内容。 为生产打包:能够使用SbtNativePackager 打包您的东西以用于生产,而不会有太多痛苦。 轻松控制您在给定开发机器上使用的每个库的哪个版本,并能够在它们之间无缝切换。 避免 git 操作变得比通常情况下更糟糕。

此外,您会使用某种“本地 sbt/maven 团队存储库”吗?可能需要做些什么来实现它?不过,希望这不是必需的。

谢谢!

【问题讨论】:

它基本上在很大程度上取决于项目的性质以及它在多大程度上需要或您需要模块化。无论如何,一种选择是将它们保留为单独的模块,但在 multi-project 配置中。这样,您可以将它们聚合到父项目中,因此可以将其视为一件好事,尤其是当团队处于开发的早期阶段时。通过这种方式,您可以保留稍后轻松分离它们的选项,但您可以使用单个 sbt 命令构建所有测试并运行测试。 这很好,但我不确定我是否遵循那里定义的类路径依赖项。它们是否意味着一个项目会自动获得它的类路径 classpath dependency,或者这是否也意味着编译一个项目会总是编译另一个项目? 通过类路径依赖,它们表示模块间依赖,这是非常灵活的,从某种意义上说,您可以将test 依赖于testcompile 依赖于testcompile 依赖于@ 987654334@ 甚至是compile 上的test,这非常有用。因此,简单地说,这意味着当项目A 依赖于.dependsOn(B)B 时,默认情况下您可以在项目B 中使用项目B 中的所有类A 但是聚合是另一回事,这意味着当项目A聚合项目BC(独立于是否依赖它们)时,你构建A,@ 987654346@ 和 C 将自动构建。当您想要测试或编译所有这些时,这也非常有用。 是的,他们有。是的,这是管理子项目的好方法。 【参考方案1】:

我在沙子中使用以下几行:

最终进入不同可部署项目的代码进入同一个存储库的不同文件夹中,在一个伞式项目下 - SBT 称之为 multi-project build(我使用 maven 而不是 SBT,但这些概念非常相似的)。它将被构建/部署到不同的 jars。

在进行有意义的划分时,我会尝试考虑最终的可部署项。例如,如果我的系统 foosys 有 foosys-frontendfoosys-backend 可部署项,其中 foosys-frontend 执行 html 模板,foosys-backend 与数据库通信,两者通过 REST API 进行通信,那么我会将它们作为单独的项目,以及用于公共代码的foosys-core 项目。不允许 foosys-core 依赖于 html 模板库(因为 foosys-backend 不想要那个),也不允许依赖于 ORM 库(因为 foosys-frontend 不想要那个)。但我不担心将与 REST 库一起使用的代码与“核心域对象”分开,因为 foosys-frontendfoosys-backend 都使用 REST 代码。

现在假设我添加了一个新的foosys-reports deployable,它访问数据库来做一些报告。然后我可能会根据foosys-core 创建一个foosys-database 项目来保存foosys-backendfoosys-reports 使用的共享代码。而且由于foosys-reports 不使用REST 库,我可能还应该从foosys-core 中分离出foosys-rest。所以我最终得到了一个 foosys-core 库,另外两个依赖它的库项目(foosys-databasefoosys-rest),以及三个可部署项目(foosys-reports 取决于 foosys-databasefoosys-frontend 取决于 @ 987654349@ 和 foosys-backend 取决于两者)。

您会注意到,这意味着对于可能使用该代码的每个组合 可部署项都有一个代码项目。所有三个可部署的代码都在foosys-core 中。仅包含在一个可部署项目中的代码将进入该可部署项目的项目。三个可部署的两个中的代码进入foosys-restfoosys-database。如果我们想要一些代码是 foosys-frontendfoosys-reports 可部署的一部分,但不是 foosys-backend 可部署的,我们必须为该代码创建另一个项目。从理论上讲,这意味着随着我们添加更多可部署项目,项目数量会呈指数级增长。在实践中,我发现这并没有太大问题 - 大多数理论上可能的组合实际上没有意义,所以只要我们只在实际有代码可以放入新项目时创建新项目就可以了。如果我们最终在 foosys-core 中有几个类实际上并没有用于每个可部署的类,那也不是世界末日。

在此视图中最好将测试理解为另一种可部署的。所以我会有一个单独的 foosys-test 项目,其中包含用于测试所有三个可部署项目的通用代码(取决于 foosys-core),也许还有一个 foosys-database-test 项目(取决于 foosys-testfoosys-database)在foosys-backendfoosys-reports 之间通用的测试助手代码(例如数据库集成测试设置代码)。最终,我们可能会得到一个完全并行的 -test 项目层次结构。

只有在项目具有不同的发布生命周期时,才将项目移动到单独的 git 存储库(同时,将整体构建单独)。

不同存储库中的代码必须独立进行版本控制,因此在某种意义上这是一个空洞的定义。但我认为只有在必要时才应该继续使用单独的 git 存储库(类似于this post:只有当数据太大而无法使用更友好的东西时才应该使用 Hadoop)。一旦您的代码位于多个 git 存储库中,您必须手动更新它们之间的依赖关系(在开发机器上,您可以使用 -SNAPSHOT 依赖关系和 IDE 支持来工作,就好像版本仍然同步一样,但您必须手动更新它每次与 master 重新同步时,都会增加开发摩擦)。由于您正在异步发布和更新依赖项,因此您必须采用并强制执行语义版本控制之类的东西,以便人们知道何时更新foocorp-utils 上的依赖项是安全的,何时不安全。您必须发布变更日志,并进行早期预警 CI 构建,以及更彻底的代码审查流程。这一切都是因为反馈周期要长得多;如果你在下游项目中破坏了某些东西,你不会知道这一点,直到他们更新他们对 foocorp-utils 的依赖,几个月甚至几年后(是的,几年 - 我见证了这一点,在一个 80 人的创业公司中,不是大型公司)。因此,您需要流程来防止这种情况发生,而一切都会相应地变得不那么敏捷。

这样做的正当理由包括:

项目的完整构建花费的时间太长,这会减慢您正在处理的代码的集成速度 - 但请先尝试加快速度。 部署所有可部署项目的时间太长 - 不过,请再次尝试自动化并加快速度。让所有内容保持同步有一个真正的优势,你不想放弃它,直到你绝对必须这样做。 需要单独的团队处理代码。如果您彼此之间没有持续的沟通,那么无论如何您都需要进程开销(语义版本控制等),因此您最好获得更快的构建时间。 (要明确一点,我认为每个 git 存储库都应该有一个单独的团队来拥有并负责它,并且当团队拆分时,他们应该拆分存储库。我对发布过程和职责有进一步的想法,但是这个答案已经很长了) .

我会使用团队 maven 存储库,可能是 Nexus。实际上,即使在您进入多项目阶段之前,我也会推荐这个。它非常容易运行(只是一个 Java 应用程序),您可以通过它proxy your external dependencies,这意味着您有一个可靠的依赖 jar 源,即使您的上游依赖项之一消失,您的构建也将是可重现的。

我打算将我的团队合作方式写成一篇博文,但同时我很乐意回答任何进一步的问题。

【讨论】:

谢谢@Imm,虽然我的场景可能有不同的细微差别,但这种深思熟虑非常有帮助!如果写了博客文章的链接,将来也会很好。 Nexus 看起来确实很酷 - 很高兴知道。我喜欢它宣传的代理功能,这似乎消除了对外部资源的脆弱的时间依赖性。然而,我有点想知道,它的免费版本在什么时候不再足够了,你需要做出飞跃...... 我在使用免费版本的 nexus 时从来没有遇到过问题,存储库的数量级为 TB;我认为付费版本增加了额外的功能,而不是大小或类似的问题。 是的,我避免使用 git 子模块或子树。由于我尝试尽可能将所有东西放在一个 git 存储库中,只有在项目在逻辑上独立或由不同团队工作时才拆分,因此很少需要跨多个不同的 git 存储库(跨 maven 模块)进行更改版本化并一起发布很好) - 通常一个团队会做出并测试他们自己的更改并经历一个发布周期,然后另一个团队才会更新他们的依赖关系。请记住,并非每个 Maven 版本都必须对应于完整部署。 也就是说,当您确实需要使用来自不同存储库的项目进行开发时,将它们链接起来非常简单 - 只需将依赖项的版本更改为相关的 -SNAPSHOT,然后在 eclipse 中只需相互依赖,其中一个的变化会立即反映在另一个上。在命令行上,您必须在依赖项目之前构建依赖关系,这很麻烦,但确实足够公平。 maven 发布插件会阻止您使用 -SNAPSHOT 依赖项进行发布,因此它会强制您首先释放依赖项。 我不认为可部署的大小有很大的不同。我在一个约 500kloc 的单体式项目中使用了这种结构,其中大约有 6 个可部署项,我已经在一个约 20kloc 的微服务项目中使用了它,该项目具有数十个可部署项。有些人担心用相对少量的实际代码拥有大量的 maven 模块,但我还没有看到它会导致任何实际问题。【参考方案2】:

我来晚了,但是我的 2 美分。

大多数 scala 项目和/或我在过去工作中从事的任何项目都以非常相似的结构告终。通常与其他团队成员达成共识(这有助于验证决定)。唯一的主要哲学差异是在技术基础设施层或业务模块上分离项目。以下示例:

常见项目

App.Utils:所有其他项目使用的共享实用程序代码(最少到 0 个依赖项) App.Core:共享业务代码(模型、核心助手、接口、类型)

选项 1:模块分离

App.Inventory:带有服务、数据库代码、助手的库存模块 App.Orders:带有服务、数据库、助手的订单管理模块

这可以非常方便且易于按业务领域进行管理,然后您可以根据需要部署单个模块。如果需要,您还可以稍后决定将模块分离为单独的 API(共享代码库仍在 utils 和 core 中)。这里的缺点是该方法会使项目数量膨胀。

选项 2:技术层分离

App.Database:数据库访问函数 App.Services:业务服务的核心实现

在这种方法中,所有领域的所有逻辑/服务都在服务项目中,数据库也是如此。所以说库存的代码在数据库和服务项目中分开。这允许按传统技术层进行分离。对于较小的项目,这可能会更快。

就个人而言,我更喜欢选项 1 中更模块化的分离。它更具可扩展性,并且在更改代码时通常感觉更简单。

-K

【讨论】:

以上是关于组织多个 scala 相互关联的 sbt 和 git 项目 - 最佳实践建议的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 SBT 和 IntelliJ IDEA 管理多个相互依赖的模块?

如何强制 Scala 使用不同的库版本?

scala:如何在SBT设置中定义多个相关任务之间的执行顺序?

如何使用 scala sbt 构建多个 jar 文件

使用 sbt 运行多个应用程序

将多个 sbt 任务合并为一个