Skip to content

Latest commit

 

History

History
122 lines (91 loc) · 4.98 KB

PLUGIN.md

File metadata and controls

122 lines (91 loc) · 4.98 KB

Lowcoder plugin system (WIP)

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)
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 LowcoderEndpoint interface, containing endpoint handler functions marked with @EndpointExtension annotation. These functions must obey following format:
	@EndpointExtension(uri = <endpoint uri>, method = <HTTP method>)
	public Mono<ServerResponse> <handler name>(ServerRequest request) 
	{
	   ... your endpoint logic implementation		
	}

    for example:

	@EndpointExtension(uri = "/hello-world", method = Method.GET)
	public Mono<ServerResponse> helloWorld(ServerRequest request) 
	{		
		return ServerResponse.ok().body(Mono.just(Hello.builder().message("Hello world!").build()), Hello.class);
	}
  • TODO: class(es) impelemting LowcoderDatasource interface

LowcoderPlugin implementations

Methods of interest:

  • pluginId() - unique plugin ID - if a plugin with such ID is already loaded, subsequent plugins whith this ID will be ignored
  • description() - short plugin description
  • load(ApplicationContext parentContext) - is called during plugin startup - this is the place where you should completely initialize your plugin. If initialization fails, return false
  • unload() - is called during lowcoder API server shutdown - this is the place where you should release all resources
  • endpoints() - needs to contain all initialized PluginEndpoints you want to expose, for example:
	@Override
	public List<PluginEndpoint> endpoints() 
	{
		List<PluginEndpoint> endpoints = new ArrayList<>();
		
		endpoints.add(new HelloWorldEndpoint());
		
		return endpoints;
	}
  • pluginInfo() - should return a record object with additional information about your plugin. It is serialized to JSON as part of the /plugins listing (see "info" object in this example):
[
    {
        "id": "example-plugin",
        "description": "Example plugin for lowcoder platform",
        "info": {}
    },
    {
        "id": "enterprise",
        "description": "Lowcoder enterprise plugin",
        "info": {
            "enabledFeatures": [
                "endpointApiUsage"
            ]
        }
    }
]

TODOs

  1. Implement endpoint security - currently all plugin endpoints are public (probably by adding security attribute to @EndpointExtension and enforcing it)

QUESTIONS / CONSIDERATIONS

  1. currently the plugin endpoints are prefixed with /plugin/{pluginId}/ - this is hardcoded, do we want to make it configurable?