blob: 862253cb03c74e4531012f7663b8e49c60db1175 [file] [log] [blame] [view]
<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-->
# SPI extensions in OpenWhisk
Alternate implementations of various components follow an SPI (Service Provider Interface) pattern:
* The pluggable component is defined as an Spi trait:
```scala
import whisk.spi.Spi
trait ThisIsPluggable extends Spi { ... }
```
* Implementations implement the Spi trait
```scala
class TheImpl extends ThisIsPluggable { ... }
class TheOtherImpl extends ThisIsPluggable { ... }
```
Runtime resolution of an Spi trait to a specific implementation is provided by:
* `SpiLoader` - a utility for loading the implementation of a specific Spi, using a resolver to determine the implentations factory classname, and reflection to load the factory object.
* `application.conf` - each `Spi` is resolved to a classname based on the config key provided to `SpiLoader`.
Only a single implementation per Spi is usable at runtime, since the key will have a single string value.
# Example
The process to create and use an SPI is as follows:
## Define the Spi and implementations
* Create your Spi trait `YourSpi` as a trait that is an extension of `whisk.spi.Spi`.
* Create your factory object which extends `YourSpi` and provides the relevant functionality.
* Create your functionality classes with whatever name you like. The factory object is supposed to build and return those instances.
## Invoke SpiLoader.get to acquire an instance of the SPI
SpiLoader uses a TypesafeConfig key to use for resolving which implementation should be loaded.
The config key used to find the implementations classname is `whisk.spi.<SpiInterface>`.
For example, the SPI interface `whisk.core.database.ArtifactStoreProvider` would load a specific implementation indicated by the `whisk.spi.ArtifactStoreProvider` config key.
(so you cannot use multiple SPI interfaces with the same class name in different packages)
Invoke the loader using `SpiLoader.get[<the SPI interface>](<implicit resolver>)`
```scala
val messagingProvider = SpiLoader.get[MessagingProvider]
```
## Defaults
Default implementation resolution is dependent on the config values in order of priority from:
1. `application.conf`
2. `reference.conf`
So use `reference.conf` to specify defaults.
# Runtime
Since SPI implementations are loaded from the classpath, and a specific implementation is used only if explicitly configured it is possible to optimize the classpath based on your preference of:
* Include only default implementations, and only use default implementations.
* Include all implementations, and only use the specified implementations.
* Include some combination of defaults and alternative implementations, and use the specified implementations for the alternatives, and default implementations for the rest.
## Including the implementation
Base openwhisk docker images provide 2 extension points in the classpath for including the implementation.
### Application Jars
The application jars can be added to `$APP_HOME/ext-lib` for e.g. in `openwhisk/controller` image the implementation jars can be added to `/controller/ext-lib` and for `openwhisk/invoker` they can be added to `/invoker/ext-lib`.
### Application Configuration
The configuration files can be added to `$APP_HOME/config`.