Skip to content

New plugin system for backend api-service #751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ client/node_modules/
client/packages/lowcoder-plugin-demo/.yarn/install-state.gz
client/packages/lowcoder-plugin-demo/yarn.lock
client/packages/lowcoder-plugin-demo/.yarn/cache/@types-node-npm-16.18.68-56f72825c0-094ae9ed80.zip
application-dev.yml
20 changes: 11 additions & 9 deletions deploy/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@
## Build Lowcoder api-service application
##
FROM maven:3.9-eclipse-temurin-17 AS build-api-service

# Build lowcoder-api
COPY ./server/api-service /lowcoder-server
WORKDIR /lowcoder-server
RUN --mount=type=cache,target=/root/.m2 mvn -f pom.xml clean package -DskipTests

# Create required folder structure
RUN mkdir -p /lowcoder/api-service/plugins /lowcoder/api-service/config /lowcoder/api-service/logs

# Define lowcoder main jar and plugin jars
ARG JAR_FILE=/lowcoder-server/lowcoder-server/target/lowcoder-server-*.jar
ARG PLUGIN_JARS=/lowcoder-server/lowcoder-plugins/*/target/*.jar

# Copy lowcoder server application and plugins
RUN cp ${JAR_FILE} /lowcoder/api-service/server.jar \
&& cp ${PLUGIN_JARS} /lowcoder/api-service/plugins/
RUN mkdir -p /lowcoder/api-service/config /lowcoder/api-service/logs /lowcoder/plugins

# Copy lowcoder server configuration
COPY server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml /lowcoder/api-service/config/
Expand Down Expand Up @@ -43,6 +37,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends gosu \
# Copy lowcoder server configuration
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder/api-service /lowcoder/api-service

# Copy lowcoder api service app, dependencies and libs
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/app /lowcoder/api-service/app
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/dependencies /lowcoder/api-service/dependencies
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/libs /lowcoder/api-service/libs
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/plugins /lowcoder/api-service/plugins
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/set-classpath.sh /lowcoder/api-service/set-classpath.sh

EXPOSE 8080
CMD [ "sh" , "/lowcoder/api-service/entrypoint.sh" ]

Expand Down Expand Up @@ -202,6 +203,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-instal

# Add lowcoder api-service
COPY --chown=lowcoder:lowcoder --from=lowcoder-ce-api-service /lowcoder/api-service /lowcoder/api-service
RUN mkdir -p /lowcoder/plugins/ && chown lowcoder:lowcoder /lowcoder/plugins/

# Add lowcoder node-service
COPY --chown=lowcoder:lowcoder --from=lowcoder-ce-node-service /lowcoder/node-service /lowcoder/node-service
Expand Down
6 changes: 5 additions & 1 deletion deploy/docker/api-service/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ ${JAVA_HOME}/bin/java -version
echo

cd /lowcoder/api-service
source set-classpath.sh

exec gosu ${USER_ID}:${GROUP_ID} ${JAVA_HOME}/bin/java \
-Djava.util.prefs.userRoot=/tmp \
-Djava.security.egd=file:/dev/./urandom \
-Dhttps.protocols=TLSv1.1,TLSv1.2 \
-Dlog4j2.formatMsgNoLookups=true \
-Dspring.config.location="file:///lowcoder/api-service/config/application.yml,file:///lowcoder/api-service/config/application-selfhost.yml" \
--add-opens java.base/java.nio=ALL-UNNAMED \
-cp "${LOWCODER_CLASSPATH:=.}" \
${JAVA_OPTS} \
-jar "${APP_JAR}" --spring.webflux.base-path=${CONTEXT_PATH} ${CUSTOM_APP_PROPERTIES}
org.lowcoder.api.ServerApplication --spring.webflux.base-path=${CONTEXT_PATH} ${CUSTOM_APP_PROPERTIES}

6 changes: 3 additions & 3 deletions server/api-service/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ dependency-reduced-pom.xml
.run/**
logs/**
tmp/**
/openblocks-server/logs/

# Ignore plugin.properties which are generated dynamically
**/plugin.properties

# to ignore the node_modeules folder
node_modules
Expand All @@ -34,5 +35,4 @@ package-lock.json
# test coverage
coverage-summary.json
app/client/cypress/locators/Widgets.json
/openblocks-domain/logs/
application-lowcoder.yml
application-lowcoder.yml
63 changes: 63 additions & 0 deletions server/api-service/PLUGIN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Lowcoder backend plugin system

This is an ongoing effort to refactor current plugin system based on pf4j library.

## Reasoning

1. create a cleaner and simpler plugin system with clearly defined purpose(s) (new endpoints, new datasource types, etc..)
2. lowcoder does not need live plugin loading/reloading/unloading/updates, therefore the main feature of pf4j is rendered useless, in fact it adds a lot of complexity due to classloaders used for managing plugins (especially in spring/boot applications)
3. simpler and easier plugin detection - just a jar with a class implementing a common interface (be it a simple pojo project or a complex spring/boot implementation)

## How it works

The main entrypoint for plugin system is in **lowcoder-server** module with class **org.lowcoder.api.framework.configuration.PluginConfiguration**
It creates:
- LowcoderPluginManager bean which is responsible for plugin lifecycle management
- Adds plugin defined endpoints to lowcoder by creating **pluginEndpoints** bean
- TODO: Adds plugin defined datasources to lowcoder by creating **pluginDatasources** bean

### lowcoder-plugin-api library

This library contains APIs for plugin implementations.
It is used by both, lowcoder API server as well as all plugins.

### PluginLoader

The sole purpose of a PluginLoader is to find plugin candidates and load them into VM.
There is currently one implementation that based on paths - **PathBasedPluginLoader**, it:
- looks in folders and subfolders defined in **application.yaml** - entries can point to a folder or specific jar file. If a relative path is supplied, the location of lowcoder API server application jar is used as parent folder (when run in non-packaged state, eg. in IDE, it uses the folder where ServerApplication.class is generated)

```yaml
common:
plugin-dirs:
- plugins
- /some/custom/path/myGreatPlugin.jar
```
- finds all **jar**(s) and inspects them for classes implementing **LowcoderPlugin** interface
- instantiates all LowcoderPlugin implementations

### LowcoderPluginManager

The main job of plugin manager is to:
- register plugins found and instantiated by **PluginLoader**
- start registered plugins by calling **LowcoderPlugin.load()** method
- create and register **RouterFunction**(s) for all loaded plugin endpoints
- TODO: create and register datasources for all loaded plugin datasources

## Plugin project structure

Plugin jar can be structured in any way you like. It can be a plain java project, but also a spring/boot based project or based on any other framework.

It is composed from several parts:
- class(es) implementing **LowcoderPlugin** interface
- class(es) implementing **PluginEndpoint** interface, containing endpoint handler functions marked with **@EndpointExtension** annotation. These functions must obey following format:

```java
@EndpointExtension(uri = <endpoint uri>, method = <HTTP method>)
public EndpointResponse <handler name>(EndpointRequest request)
{
... your endpoint logic implementation
}
```
- TODO: class(es) impelemting **LowcoderDatasource** interface

84 changes: 84 additions & 0 deletions server/api-service/distribution/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-root</artifactId>
<version>${revision}</version>
</parent>

<artifactId>distribution</artifactId>
<packaging>pom</packaging>

<properties>
<assembly.lib.directory>${project.build.directory}/dependencies</assembly.lib.directory>
</properties>


<!-- Dependency added here only to make sure this module is built after
everything alse was built -->
<dependencies>
<dependency>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-sdk</artifactId>
</dependency>
<dependency>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-infra</artifactId>
</dependency>
<dependency>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-domain</artifactId>
</dependency>
<dependency>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-server</artifactId>
</dependency>
</dependencies>

<build>
<finalName>lowcoder-api-service</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${assembly.lib.directory}</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<prependGroupId>true</prependGroupId>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>distro-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<attach>false</attach>
<descriptors>
<descriptor>src/assembly/bin.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
72 changes: 72 additions & 0 deletions server/api-service/distribution/src/assembly/bin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0 http://maven.apache.org/xsd/assembly-2.2.0.xsd">
<id>bin</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>

<files>
<file>
<source>src/assembly/set-classpath.sh</source>
<outputDirectory></outputDirectory>
</file>
</files>
<fileSets>
<fileSet>
<directory>${assembly.lib.directory}</directory>
<outputDirectory>dependencies</outputDirectory>
<excludes>
<exclude>${project.groupId}:*</exclude>
</excludes>
</fileSet>
</fileSets>

<moduleSets>
<!-- Main lowcoder API server application -->
<moduleSet>
<useAllReactorProjects>true</useAllReactorProjects>
<includes>
<include>org.lowcoder:lowcoder-server</include>
</includes>
<binaries>
<outputDirectory>app</outputDirectory>
<includeDependencies>false</includeDependencies>
<unpack>false</unpack>
</binaries>
</moduleSet>

<!-- Lowcoder API server dependencies -->
<moduleSet>
<useAllReactorProjects>true</useAllReactorProjects>
<includes>
<include>org.lowcoder:lowcoder-domain</include>
<include>org.lowcoder:lowcoder-infra</include>
<include>org.lowcoder:lowcoder-sdk</include>
</includes>
<binaries>
<outputDirectory>libs</outputDirectory>
<includeDependencies>false</includeDependencies>
<unpack>false</unpack>
</binaries>
</moduleSet>

<!-- Lowcoder plugins -->
<moduleSet>
<useAllReactorProjects>true</useAllReactorProjects>
<includeSubModules>true</includeSubModules>
<includes>
<include>org.lowcoder:*Plugin</include>
</includes>
<excludes>
<exclude>org.lowcoder:sqlBasedPlugin</exclude>
</excludes>
<binaries>
<outputDirectory>plugins</outputDirectory>
<includeDependencies>false</includeDependencies>
<unpack>false</unpack>
</binaries>
</moduleSet>
</moduleSets>
</assembly>
11 changes: 11 additions & 0 deletions server/api-service/distribution/src/assembly/set-classpath.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

#
# Set lowcoder api service classpath for use in startup script
#
export LOWCODER_CLASSPATH="`find libs/ dependencies/ app/ -type f -name "*.jar" | tr '\n' ':' | sed -e 's/:$//'`"

#
# Example usage:
#
# java -cp "${LOWCODER_CLASSPATH}" org.lowcoder.api.ServerApplication
Loading