Creating an Add-On
SeedStack is an extensible solution that can be enriched with add-ons to provide new functionality or features. Writing an add-on is not a difficult task as it is very similar to writing an application but some rules and conventions must be obeyed. This guide will describe these rules and conventions.
Project structure #
An add-on almost always consists in an API/SPI with one or more implementations. Depending on the complexity of the add-on and the re-usability requirements, several project structures can be used.
Single-module add-on #
The simplest form of add-on is a single module project. In this case, the API/SPI and the implementation will be contained in a single JAR artifact. The typical single-module add-on structure is:
single-addon |- src/main/java | |- org.myorg.feature <-- API goes in the add-on base package | |- internal <-- Implementation | |- spi <-- SPI if any |- src/test/java ...
Example single module add-on: https://github.com/seedstack/jpa-addon
Add-on with a separated API/SPI #
When you need to provide the API/SPI to clients separately from the implementation you need to create a multi-module add-on:
- The
specs
module will contain the API (and the SPI if any), - The
core
module will contain the implementation.
The typical structure for such add-on is:
multi-addon |- core | |- src/main/java | | |- org.myorg.feature | | |- internal <-- Implementation | |- src/test/java | ... |- specs | |- src/main/java | | |- org.myorg.feature <-- API goes in the add-on base package | | |- spi <-- SPI if any | |- src/test/java ...
Java packages are still the same that found in the single module add-on but segregated in two maven modules.
Multiple implementations add-on #
When you have multiple implementations or implementation extensions for an add-on you can add a new sub-module per implementation/extension. This type of add-on is the same a the previous one with additional sub-modules:
- The
specs
module will contain the API (and the SPI if any), - The
core
module will contain the main/common implementation, - The
impl1
module will contain theimpl1
implementation, - The
impl2
module will contain theimpl2
implementation, - …
multi-addon |- core | |- src/main/java | | |- org.myorg.feature | | |- internal <-- Main/common implementation | |- src/test/java | ... |- impl1 | |- src/main/java | | |- org.myorg.feature.impl1 | | |- internal <-- Implementation/extension 1 | |- src/test/java | ... |- impl2 | |- src/main/java | | |- org.myorg.feature.impl2 | | |- internal <-- Implementation/extension 2 | |- src/test/java | ... |- specs | |- src/main/java | | |- org.myorg.feature <-- API goes in the add-on base package | | |- spi <-- SPI if any | |- src/test/java ...
If you don’t have any API, like when it is provided by a third party, you can omit the specs module completely.
Example multi-module add-on: https://github.com/seedstack/i18n-addon
Dependencies #
As the add-on is a reusable component which will be used in various contexts, the rules on dependencies are tighter than on applications.
As a general rule, try to minimize the number of dependencies in your add-on to help avoid unintended side-effects and limit its impact on client projects.
- Any dependency that will be provided by the client project or its runtime environment must
be specified with a
provided
scope. The meaning here is that the dependency is required for proper operation but must be provided downstream. - If your add-on contains optional features that have dependencies you have two options:
- Either package those features in their own implementation sub-module with their own dependencies,
- Or package those features in the core implementation, mark their dependencies as
optional
and use conditional code to initialize those features only when their dependency requirements are met.
API/SPI #
All API classes go in the base package of the add-on. All SPI classes go in a spi
subpackage of the base package. Try
to keep the API classes as flat as possible, ideally without subpackage.
Implementation #
All implementation classes go in a *.internal.*
which is completely excluded from backwards compatibility requirements
between versions.