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
specsmodule will contain the API (and the SPI if any), - The
coremodule 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
specsmodule will contain the API (and the SPI if any), - The
coremodule will contain the main/common implementation, - The
impl1module will contain theimpl1implementation, - The
impl2module will contain theimpl2implementation, - …
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
providedscope. 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
optionaland 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.