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.