简体   繁体   English

如何使用插件更好地构建 java 库的工件

[英]How to better structure the artifacts of a java library with plugins

Let's say I have a library, which provides two independent plugin interfaces, 2 implementations per plugin, and one parent POM file.假设我有一个库,它提供了两个独立的插件接口,每个插件有 2 个实现,以及一个父 POM 文件。
There are also some abstract tests in the "core", which the plugins have to implement and pass to be considered compliant. “核心”中还有一些抽象测试,插件必须实现并通过这些测试才能被认为是合规的。

From code perspective, the plugin interfaces don't depend on core at all.从代码的角度来看,插件接口根本不依赖于核心。
Only core depends on plugins.只有核心依赖于插件。

My assumptions are:我的假设是:

  • the core abstractions go into the "core" artifact and its tests are packaged in a test-jar.核心抽象 go 进入“核心”工件,其测试被打包在一个测试罐中。
  • each implementation of a "plugin" goes into a separate artifact (4 artifacts in this example). “插件”的每个实现都进入一个单独的工件(本示例中为 4 个工件)。
  • the parent POM also goes into a separate artifact.父 POM 也进入一个单独的工件。

I have considered several options about how to structure the dependencies between each artifact, which can be boiled down to these 2:关于如何构建每个工件之间的依赖关系,我考虑了几个选项,可以归结为以下两个:

  1. Leave it at just 6 artifacts.只保留 6 个工件。 Every "plugin" depends on "core".每个“插件”都依赖于“核心”。 Every "plugin" and "core" all depend on parent artifact.每个“插件”和“核心”都依赖于父工件。
    都依赖于核心
    This makes it possible for the library users to only specify 2 artifacts in their pom.xml/build.gradle, because "core" is a transitive dependency.这使得库用户可以在他们的 pom.xml/build.gradle 中只指定 2 个工件,因为“核心”是一个传递依赖。
    BUT, given I have some changes to the "core" which cause a version bump, I would have to update every plugin implementaton to bump the dependency version.但是,鉴于我对“核心”进行了一些更改,导致版本颠簸,我将不得不更新每个插件实现以颠簸依赖版本。 Also, if users didn't specify the core explicitly, they now depend on outdated core.此外,如果用户没有明确指定核心,他们现在依赖于过时的核心。

  2. Extract plugin interfaces into separate artifacts, so that implementations no longer depend on core.将插件接口提取到单独的工件中,以便实现不再依赖于核心。 Which now creates 8 artifacts.现在创建了 8 个工件。
    提取的插件接口
    Now, unlike the previous approach, library users can no longer skip the "core" dependency in their pom.xml/build.gradle - it is now a given that it has to be specified.现在,与以前的方法不同,库用户不能再跳过 pom.xml/build.gradle 中的“核心”依赖项——现在必须指定它。 Which means, they would have to depend on 3 artifacts.这意味着,他们将不得不依赖 3 个工件。
    But, overall, any update of the core no longer forces a cascading update of the plugins.但是,总的来说,核心的任何更新都不再强制插件的级联更新。 The plugin implementations need version bumps only if their respective interface updates.只有当它们各自的接口更新时,插件实现才需要版本颠簸。
    The downside is probably that I now have 2 more artifacts to maintain.缺点可能是我现在还有 2 个工件要维护。

My questions are:我的问题是:

  • Which approach is the more correct one?哪种方法更正确? Does it depend on project size or some other factors?是否取决于项目规模或其他一些因素?
  • Are there other approaches?还有其他方法吗?
  • Is it bad that users have to depend on plugins & "core" explicitly, even if plugins transitively bring "core" in the first approach?即使插件在第一种方法中传递地带来“核心”,用户必须明确地依赖插件和“核心”是不是很糟糕?
  • Anything that is intrinsic to the problem and cannot be solved?有什么是问题的本质而无法解决的吗? (like, is it a given that 8 artifacts are to be maintained, with no way to minimize that?) (例如,是否要维护 8 个工件,而无法将其最小化?)
  • Is it correct to provide abstract tests in the "test-jar", if I want to make sure that all plugin implementations comply with the interface contracts?如果我想确保所有插件实现都符合接口合同,在“test-jar”中提供抽象测试是否正确? Or do I have to copy-paste the tests in each plugin implementation?还是我必须在每个插件实现中复制粘贴测试?

Reply to @vokail回复@vokail

Generally, If you release a new version of the core, you must release a new version of the plugin, right?一般来说,如果你发布一个新版本的核心,你必须发布一个新版本的插件,对吧?

Currently the code is structured in such a way, that plugin have no dependencies on the core.目前代码的结构是这样的,即插件不依赖于核心。 With 1st scheme, if core updates, plugins must update.对于第一种方案,如果核心更新,插件必须更新。 With 2nd scheme - if core updates, plugins don't care.使用第二种方案 - 如果核心更新,插件不在乎。

I think it's possible to have more than two plugins implementations我认为可能有两个以上的插件实现
for plugin developers they need to use only this as dependency directly对于插件开发人员,他们只需要直接将其用作依赖项

True & true真实与真实

plugin-api need only core-api plugin-api 只需要 core-api

Currently, I cannot invert the dependency in such a way.目前,我无法以这种方式反转依赖关系。 Plugins know nothing about the core, except the plugins API.插件对核心一无所知,除了插件 API。
As a note, there are 2 plugin APIs.请注意,有 2 个插件 API。 Their code doesn't depend on core and their code doesn't depend on each other.他们的代码不依赖于核心,他们的代码也不相互依赖。
With 1st scheme, all plugin APIs are inside a single core artifact.使用第一种方案,所有插件 API 都在一个核心工件中。
With 2nd scheme all plugin APIs are in separate artifacts (so it's 1 core artifact, and 2 separate API artifacts = 3 artifacts in total).使用第二种方案,所有插件 API 都位于单独的工件中(因此它是 1 个核心工件和 2 个单独的 API 工件 = 总共 3 个工件)。

core-api can be implemented by more than one core-impl ( in the future ) core-api 可以由多个 core-impl 实现(未来)

Mhm... Don't see it in the future.嗯……以后看不到了。

It's better to depend for my plugin implementation from an interface only, not from a core one我的插件实现最好只依赖于接口,而不是核心接口

To clarify, this is what I meant.澄清一下,这就是我的意思。
From library user perspective, 1st scheme looks like this:从图书馆用户的角度来看,第一种方案如下所示:

// Implementaton of "A" api, variant 1
implementation 'library:plugin-a1-impl:1.0.0'
// Implementaton of "B" api, variant 2
implementation 'library:plugin-b2-impl:1.0.0'
// Both plugins transitively bring in "library:core:1.0.0".
// But if for example core:1.1.0 is released, it has to be included explicitly  

2nd scheme looks like this:第二种方案如下所示:

// Implementaton of "A" api, variant 1
// Transitively brings in "library:plugin-a-api" - a new artifact
implementation 'library:plugin-a1-impl:1.0.0'
// Implementaton of "B" api, variant 2
// Transitively brings in "library:plugin-b-api" - a new artifact
implementation 'library:plugin-b2-impl:1.0.0'
// Core has to be explicitly specified, nobody depends on it, only core depends on plugins
implementation 'library:core:1.0.0'

just do one artifact and let people depend on that only ( as example to minimize that ).只做一个工件,让人们只依赖它(作为最小化它的例子)。

Currently there are separate projects that depend on the library, and they use different plugin implementations.目前有独立的项目依赖于库,它们使用不同的插件实现。 Users pick between different implementation of the same APIs depending on shared dependencies.用户根据共享依赖项在相同 API 的不同实现之间进行选择。
For example, there's A, and there are 2 implementations: A-oranges, A-apples.例如,有 A,有 2 个实现:A-oranges、A-apples。 If the project already uses oranges, it imports A-oranges.如果项目已经使用了橙子,它会导入 A-橙子。 If it already uses apples, it imports A-apples.如果它已经使用苹果,它会导入 A-apples。
In other words, the plugins are more like adapters between the library and external projects.换句话说,插件更像是库和外部项目之间的适配器。


Another depiction of the differences between 2 options:两个选项之间差异的另一种描述:

Squares represent ".jar" artifacts.正方形代表“.jar”工件。 Circles inside a square represent interfaces/classes and their dependencies on each other.正方形内的圆圈表示接口/类及其相互依赖关系。

选项比较

It could be said, that the code is DIP compliant - both core and plugin implementations depend on abstractions.可以说,代码是 DIP 兼容的——核心和插件实现都依赖于抽象。
It's only a question of artifact structuring - is it worth extracting abstractions into separate artifacts as well?这只是工件结构的问题 - 是否也值得将抽象提取到单独的工件中?

I suppose there is an issue on how and how much often do you release a new version of the core and the plugin.我想关于您发布核心和插件的新版本的频率和频率存在问题。 Generally, If you release a new version of the core, you must release a new version of the plugin, right?一般来说,如果你发布一个新版本的核心,你必须发布一个新版本的插件,对吧? If not so, please specify this.如果不是这样,请说明这一点。

I'm for solution 2, but with a little difference, as the following example:我支持解决方案 2,但略有不同,如下例所示:

工件依赖项

As you can see I've introduced a plugin-api artifact, with only interfaces used by plugins, because:正如你所看到的,我引入了一个 plugin-api 工件,只有插件使用的接口,因为:

  • I think it's possible to have more than two plugins implementations我认为可能有两个以上的插件实现
  • for plugin developers they need to use only this as dependency directly对于插件开发人员,他们只需要直接将其用作依赖项
  • plugin-api need only core-api plugin-api 只需要 core-api
  • core-api can be implemented by more than one core-impl ( in the future ) core-api 可以由多个 core-impl 实现(未来)

Following this approach you focus will be to design plugin-api better you can, stabilize it and then let plugin developers do the job.遵循这种方法,您将专注于更好地设计插件 API,使其稳定,然后让插件开发人员完成这项工作。

What if:如果:

  • core-impl change?核心实现改变? For example a bugfix or new release.例如错误修复或新版本。 Ask yourself: do I need to change core-api?问问自己:我需要更改 core-api 吗? For example to provide a new feautre to plugin-api?例如为plugin-api 提供一个新功能? If so, release a new core-api and then release a new plugin-api如果是这样,请发布一个新的 core-api,然后发布一个新的 plugin-api
  • core-api change?核心 API 变化? Like before就像之前一样
  • plugin-api?插件API? if plugin-api change, you need to change only plugin-impls如果 plugin-api 改变,你只需要改变 plugin-impls

To answer on your questions:要回答您的问题:

Which approach is the more correct one?哪种方法更正确? Does it depend on project size or some other factors?是否取决于项目规模或其他一些因素?

There is no "correct one", depends for sure on project size ( just count how many feature/methos/interfaces you have in core-api and plugin-api ), how many developers works on it and how your release process works没有“正确的”,肯定取决于项目大小(只需计算您在 core-api 和 plugin-api 中有多少功能/方法/接口),有多少开发人员在使用它以及您的发布过程如何工作

Are there other approaches?还有其他方法吗?

See one answer before, you can search from some big project like apache or eclipse foundation one to learn their patterns, but depends heavily on the subject and can be an huge task.之前看到一个答案,您可以从 apache 或eclipse 基础项目等一些大项目中搜索以了解他们的模式,但这在很大程度上取决于主题并且可能是一项艰巨的任务。

Is it bad that users have to depend on plugins & "core" explicitly, even if plugins transitively bring "core" in the first approach?即使插件在第一种方法中传递地带来“核心”,用户必须明确地依赖插件和“核心”是不是很糟糕?

For my understanding, yes.据我了解,是的。 It's better to depend for my plugin implementation from an interface only, not from a core one我的插件实现最好只依赖于接口,而不是核心接口

Anything that is intrinsic to the problem and cannot be solved?有什么是问题的本质而无法解决的吗? (like, is it a given that 8 artifacts are to be maintained, with no way to minimize that?) (例如,是否要维护 8 个工件,而无法将其最小化?)

Well, If you are alone, this is an open source project used only by yourself, don't overengineering this, just do one artifact and let people depend on that only ( as example to minimize that ).好吧,如果你一个人,这是一个仅供你自己使用的开源项目,不要过度设计它,只做一个工件,让人们只依赖它(作为示例,以尽量减少它)。

Is it correct to provide abstract tests in the "test-jar", if I want to make sure that all plugin implementations comply with the interface contracts?如果我想确保所有插件实现都符合接口合同,在“test-jar”中提供抽象测试是否正确? Or do I have to copy-paste the tests in each plugin implementation?还是我必须在每个插件实现中复制粘贴测试?

For me it's better to have a plugin-api and let plugin implementation declare only that, it's more clear and concise.对我来说最好有一个 plugin-api 并让插件实现只声明它,它更清晰简洁。 For tests, I'm not sure if you plan to do tests on implementations by yourself, of "ask" to plugin developers to do the test.对于测试,我不确定您是否打算自己对实现进行测试,或者“要求”插件开发人员进行测试。 For sure copy-paste is not the right choice, you can use a command pattern or similar to make these tests, see here确定复制粘贴不是正确的选择,您可以使用命令模式或类似的方式进行这些测试,请参见此处


After updated question, I'm still for solution 2, event if there are two separated plugin-api, is better to have different plugin-api.更新问题后,我仍然支持解决方案 2,如果有两个单独的 plugin-api,最好有不同的 plugin-api。

It's only a question of artifact structuring - is it worth extracting abstractions into separate artifacts as well?这只是工件结构的问题 - 是否也值得将抽象提取到单独的工件中?

I think yes, in the long run.我认为是的,从长远来看。 If you separate in different artifacts, you can change them independently, for example change something in plugin-apiA and this doesn't affect plugin-apiB.如果您在不同的工件中分开,您可以单独更改它们,例如更改 plugin-apiA 中的某些内容,这不会影响 plugin-apiB。 If you change the core, yes of course.如果你改变核心,当然可以。

Note : for my diagram above I think can still be working, can't you make an abstract set of interfaces for plugin-api and have a common artifact for them?注意:对于我上面的图表,我认为仍然可以工作,你不能为 plugin-api 制作一组抽象的接口并为它们提供一个通用的工件吗?

If plugin A and B are two distinct type of plugin, then the option 2 is a better pick however:如果插件 A 和 B 是两种不同类型的插件,那么选项 2 是更好的选择:

  1. If A depends on core@v1如果 A 依赖于 core@v1
  2. If B depends on core@v2如果 B 依赖于 core@v2

Then core@v2 have to be binary compatible with core@v1 , otherwise it will fail when for example someone depends on an implementation of A and an implementation of B: there always have to upgrade the plugin version in any case.然后core@v2必须与core@v1二进制兼容,否则当有人依赖 A 的实现和 B 的实现时它将失败:无论如何总是必须升级插件版本。

You could probably use Java Module do hide the details (eg: only provide an interface that is likely to never changes) which will makes the solution 2 of Vokail useless in some sense: you don't need a core-impl because Java module will ensure you that, apart from your core module, no one access the details (the impl).您可能可以使用 Java 模块隐藏细节(例如:仅提供一个可能永远不会更改的接口)这将使 Vokail 的解决方案 2 在某种意义上无用:您不需要core-impl ,因为 Java 模块将确保除了核心模块之外,没有人可以访问详细信息(impl)。 This also allow you to reuse the same package.这也允许您重复使用相同的 package。

If A and B interface are in core, then the likeness of a binary incompatibility fall down.如果 A 和 B 接口在核心,那么二进制不兼容的相似性就会下降。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM