Add design documentation for extension mechanism - #9
Conversation
Introduces the Technical Design Specification for decoupling domain-specific logic (Energy, Protocols, UI) from the platform Core. Closes openremote/openremote#2327
ebariaux
left a comment
There was a problem hiding this comment.
That's a great start, I think you covered quite a bit of ground and it's good to have things explicitly described and written down.
I made quite a few comments that I think will be subjective. My proposal would be that once there's been feedback from a few reviewers, we organise a session to go over all the remarks and align on a direction to proceed with.
|
For agents, also ENTSOE, openWeather, battery simulator might become extensions |
|
@remyberden and @pierrekil: Good to think about naming conventions and way we explain from a marketing communication perspective to less/non technical users. |
MartinaeyNL
left a comment
There was a problem hiding this comment.
This is a really well written document! Thanks! 👏
Only two small comments from my own pair of eyes. (and field of expertise I guess)
Feel free to respond or explain details.
| energy/ | ||
| ├── src/main/java/org/openremote/extension/energy/ | ||
| │ ├── model/ # Java Asset implementations | ||
| │ │ ├── ElectricityAsset.java | ||
| │ │ └── EnergyModelProvider.java # Implements AssetModelProvider SPI | ||
| │ ├── manager/ # Core logic & Container services | ||
| │ │ └── EnergyOptimisationService.java # Implements ContainerService SPI | ||
| ├── src/main/resources/ | ||
| │ ├── org/openremote/extension/energy/setup/database/ # Flyway scripts | ||
| │ │ └── V20260131_01__RenameEnumValues.sql | ||
| │ └── META-INF/services/ # SPI Registration | ||
| │ ├── org.openremote.model.AssetModelProvider | ||
| │ ├── org.openremote.model.ContainerService | ||
| │ ├── org.openremote.model.ExtensionMetadata | ||
| │ └── org.openremote.model.setup.SetupTasks | ||
| ├── src/test/groovy/org/openremote/extension/energy/ | ||
| │ ├── EnergyOptimisationTest.groovy # Spock Integration Tests | ||
| │ ├── ForecastSolarServiceTest.groovy | ||
| │ └── ManagerTestSetup.groovy # Test-specific environment logic | ||
| └── src/test/resources/ | ||
| └── META-INF/services/ | ||
| └── org.openremote.model.setup.SetupTasks # Test-only setup tasks |
There was a problem hiding this comment.
Out of curiosity, don't extensions have their own build.gradle file?
As in, I assume extensions can have their own third party dependencies outside of OpenRemote.
And the list of "other extensions it depends on" would be in one central place,
so either in build.gradle or inside the org.openremote.model.ExtensionMetadata file, not both.
Or do I misunderstand something?
There was a problem hiding this comment.
Gradle dependencies and extension dependencies serve different purposes.
Gradle defines the dependencies required to build and run the extension code, while ExtensionMetadata defines logical dependencies used by Manager for extension activation and validation. These may overlap, but don't necessarily have to. For example, a composite extension could depend on several other extensions without having compile-time dependencies on them.
For the initial implementation the extension dependencies are explicitly declared in ExtensionMetadata to keep things simple. We could later consider deriving or validating this information from the Gradle configuration if that proves useful.
Also added build.gradle to the extension artifact layout diagram for completeness.
| The `OR_ENABLED_EXTENSIONS` environment variable is introduced. | ||
|
|
||
| * **Behavior**: The Manager filters the discovered `ExtensionMetadata` list. The Manager automatically activates all transitive dependencies declared in the metadata. | ||
| * **Persistence**: The list of active extension IDs is persisted in `manager_config.json`. |
There was a problem hiding this comment.
I'm totally against persisting this info inside the manager_config.json 😂
It's better to, as a first step, introduce a simple HTTP GET endpoint to retrieve the list of installed extensions.
That would prevent multiple sources of truth, and prevents breaking changes when extending this API.
There was a problem hiding this comment.
Just got context from @wborn on what the persistence is meant for.
Apparently it's the other way around, where we would persist OR_ENABLED_EXTENSIONS inside the manager_config.json file. However, this is not the intended use of this config file, as it's the "configuration of the Manager User Interface". So we'd probably store this in a separate extensions.json file instead.
There was a problem hiding this comment.
Yes it's probably better to persist it in another file. I've removed this implementation detail in 599c232.
| ### Dependency Management (Build-time) | ||
|
|
||
| Extensions are developed as independent Gradle projects. | ||
| To include an extension in an OpenRemote deployment, it is added as a standard dependency in the project's `build.gradle`. |
There was a problem hiding this comment.
| To include an extension in an OpenRemote deployment, it is added as a standard dependency in the project's `build.gradle`. | |
| To include an extension in an OpenRemote deployment, it is added as a standard dependency in the project's `build.gradle` file. |
| To include an extension in an OpenRemote deployment, it is added as a standard dependency in the project's `build.gradle`. | ||
|
|
||
| - **Transitive Dependencies**: Gradle handles the resolution of shared libraries between the Core and multiple extensions, ensuring a consistent and conflict-free classpath. | ||
| - **Composition**: Composite Extensions define a collection of dependencies in their `build.gradle`, allowing a developer to include a single "Composite" artifact to pull in a complete suite of functional and data modules. |
There was a problem hiding this comment.
| - **Composition**: Composite Extensions define a collection of dependencies in their `build.gradle`, allowing a developer to include a single "Composite" artifact to pull in a complete suite of functional and data modules. | |
| - **Composition**: Composite Extensions define a collection of dependencies in their `build.gradle` file, allowing a developer to include a single "Composite" artifact to pull in a complete suite of functional and data modules. |
| ### Repository Structure | ||
|
|
||
| The monorepo uses Gradle subprojects to isolate each extension. | ||
| A shared `build.gradle` in the root provides common build logic, ensuring all extensions use the same compiler settings and dependency versions. |
There was a problem hiding this comment.
| A shared `build.gradle` in the root provides common build logic, ensuring all extensions use the same compiler settings and dependency versions. | |
| A shared `build.gradle` file in the root provides common build logic, ensuring all extensions use the same compiler settings and dependency versions. |
| - **Discovery**: The Manager discovers the available extensions through the `ExtensionMetadata` SPI. | ||
| - **Activation**: The Manager determines the active extension set from `OR_ENABLED_EXTENSIONS` and automatically includes transitive extension dependencies declared in the metadata. | ||
| - **Component Filtering**: `AssetModelProvider`, `SetupTasks`, `ContainerService`, and other extension components belonging to inactive extensions are not initialized or registered. The existing SPI discovery mechanisms therefore need to become extension-aware. | ||
| - **Validation**: The Manager validates that required extension dependencies are present and detects invalid or circular dependency configurations before initialization. |
There was a problem hiding this comment.
I assume "invalid" includes conflicting asset types, or other model definition names? And so thinking about the future. If we ever have e.g. "unofficial" extensions conflicting with "official" asset types people using "UI-Driven Configuration" would need be informed about this.
Introduces the Technical Design Specification for decoupling domain-specific logic (Energy, Protocols, UI) from the platform Core.
Closes openremote/openremote#2327