Component Model
The Zenoo Hub employs a component model to enable a development model where an onboarding solution is composed of components.
Component model
The Zenoo Hub employs a component model to enable a development model where an onboarding solution is composed of components.
Components are reusable building blocks that are configurable and testable.
Each component provides a cohesive piece of functionality that is well tested, documented and can be reused in different contexts or clients.
This approach reduces complexity in many aspects of development.
Building from smaller, well-tested pieces of functionality becomes significantly simpler and more manageable.
Let's review an example onboarding project that is assembled from several components.
Two of those, otp and document-check, are ready-to-use components.
huddle
component contains the main workflow, business logic and project-specific configuration.huddle.routes
component contains project-specific route definitions.document-check
component provides ID document and liveness check functionality via a set of workflows and functions, the configuration includes the country and API credentials.otp
component provides a workflow to verify an SMS OTP code using a customer mobile, the configuration includes a number of retries, country code, OTP provider, etc.
Hub Component
A Hub Component is a reusable building block that provides a cohesive piece of functionality which is well documented, configurable and testable.
Each component is identified by a unique name and version. It explicitly declares its dependencies of other components that are used.
It defines one or more DSL closures that used for execution by the DSL engine, these include target
, worfklow
, function
, mapper
, route
and exchange
.
A Hub component is defined as follows:
- name a unique name of a component,
- version a component version, if omitted SHA-256 hash is generated,
- definition a set of DSL closure definitions using Component DSL,
- dependencies a set of resolved references to other components and connectors used in the definition.
- configuration a JSON-like configuration data, see more
- configuration schema a JSON schema for configuration data, see more
Component repository
Hub components are stored in a component repository.
The components are then retrieved by the Execution Engine when a new execution is triggered.
The components are stored in components
Kafka topic with indefinite retention policy.
In addition, it keeps track of the latest component versions using components-latest
Kafka topic.
The process of registering a new Hub component consist of several steps:
- validates component definition based on Component DSL,
- checks the availability of component dependencies,
- validates each DSL closure based on Hub DSL,
- resolves component dependencies versions,
- generates the component version if omitted, using SHA-256 hash of the component,
- registers the component.
The component repository provides a REST API for registering, validating and querying Hub components, see Admin API.
Additionally, Hub components can be registered automatically at the application start using ComponentConfigurer.
Component DSL
A Hub component defines one or more DSL closures; and a set of dependencies to other components and connectors using Component DSL.
Each DSL closure defines one of the following
DSL reference
Each DSL closure (except for target) can be referenced by its name
and corresponding component
as name@component
.
E.g. a target references a workflow defined is a component named workflows
dependencies {
component 'workflows'
}
target {
workflow('test@workflows')
}
The component
can be omitted if referencing a DSL closure defined in the same component.
E.g. a target references a workflow in the same component
target {
workflow('test')
}
workflow('test') {
defintion
}
Target
A target acts as an entry point for a given onboarding process and can be executed via the Hub Client API.
A target can reference an existing workflow or define one using using the Hub DSL.
Each Hub component can define at most one target.
target {
workflow('reference')
}
Exposed
Defines a function that can be executed directly through Hub Client API.
Each Hub component can define multiple exposed functions.
An exposed function can reference an existing function or define one using using the Hub DSL.
E.g. referencing existing function
exposed('reference') {
function('existing@component')
}
exposed('inline') {
sharable {
function('existing.')
}
}
In addition to functions, exposed functions can specify HTTP response details, like headers and status code.
exposed('store')
function('create-record@crm')
.onDecision('ACCEPTED') {
payload ->
response {
status HttpStatus.ACCEPTED
header HttpHeaders.LOCATION, payload.url
body payload
}
}
error()
}
Workflow
Defines a workflow with name using the Hub DSL as a series of routes, exchanges, workflows, functions, etc.
In the workflow definition, it is possible to use DSL closures defined within the component and declared dependencies.
workflow('name') {
definition
}
Function
Defines a function with name using the Hub DSL as a series of exchanges, functions, data mappings and transformations.
In the function definition, it is possible to reference DSL closures defined within the component and declared dependencies.
A valid function does not contain any route or workflow.
function('name') {
definition
}
Route
A route corresponds to a user interaction, a web page or a screen, depending on a Hub Client implementation.
A route can be used in a workflow using the route reference, see more details.
route('name') {
uri '/uri'
export data
validate { payload specification }
checkpoint()
terminal()
}
- uri identifies a route for a Hub Client and is mandatory
- export is used to pass data to a Hub Client, like lists and key-value maps
- validate specifies data constrains for route result submitted by a Hub Client
- checkpoint() marks the route as a checkpoint, disables going back
- terminal() marks the route as terminal and checkpoint
Exchange
An exchange is a connector proxy. It makes external (API) calls using an HTTP connector or a custom connector.
It provides the following tools for handling connector failures:
- timeout, an exchange fails with an error if the connector does not respond within the specified timeout
- retry strategy, retries failed connector requests
- fallback, a workflow, function or expression that is executed when an exchange fails
- validate, specifies a valid connector response, an exchange fails with an error if the connector response doesn't pass the validation
An exchange can be used in a function or workflow using the exchange reference, see more details.
exchange('name') {
http {
definition
}
fallback {
definition
}
validate {
payload specification
}
}
Mapper
An attribute mapper transforms an input into a result using expression. It be used for data mappings, transformations, calculations, etc.
mapper("name") {
input ->
expression
}
A mapper can be used in a function or workflow using the mapper reference, see more details.
As an example, the following mapper generates a client full-name using the firstname and lastname.
mapper("client-fullname") {
input -> [ fullname: "$input.client.firstname $input.client.lastname" ]
}
Dependencies
A Hub component explicitly specifies its dependencies to other components and connectors.
The dependencies are declared as part of the component definition using dependencies block.
A component dependency is referenced using a component ID component-name:revision
. The latest component revision is used when the revision is omitted.
A connector dependency is referenced using a fully classified connector name, i.e. connector-name@component-name:revision
.
Optionally, it is possible to configure component dependencies as below. The configuration is then accessible as a component configuration.
For example
dependencies {
connector 'sms@otp:1.2.0'
component 'zenoo.playground'
component 'zenoo.otp:2.4', [countryCode: '+420', tries: 3]
}
Component configuration
Each Hub component can be configured using a JSON-like configuration data.
In a component definition, it is possible to access a component configuration, the same way context attributes are accessed in the DSL.
There are two ways to configure a component:
- set using a component config service
- set when declaring a component dependency in the
dependencies
section
Component config service
A component configuration comes from a component config service when a component is exposed, i.e. defines target or exposed functions.
The config service looks up a component configuration using the following sources:
- Spring environment, looks up a component configuration under
hub.components.config
using the component name - AWS Secret manager
It is possible to set a component configuration when registering a component. The configuration is then stored using the component config service.
e.g. 'playground.test' component configuration set using Spring environment
hub:
components:
config:
stage:
playground.test:
service: dummy
In a component definition, it is possible to reference and retrieve a component configuration stored in the Component config service:
dependencies {
component 'zenoo.playground', config('stage.zenoo.playground')
}
Optionally, you can specify a configuration namespace to retrieve.
dependencies {
component 'zenoo-otp:2.4', config('[email protected]')
}
Component dependency config
You can specify a custom configuration when declaring a component dependency.
The custom configuration is then merged with a component default configuration can used for configuring the component dependency.
The default component configuration comes from the config service using the component id/name;
E.g. a component configuration used for dependencies configuration and in a workflow definition, the component configuration is [code:1234, tries:3]
dependencies {
component 'zenoo.playground'
component 'zenoo.otp:2.4', [countryCode: code]
}
workflow('play') {
loop(tries) {
workflow('do-something')
}
}
If a component defines a target and/or exposed functions, a configuration validation is performed on the component and its dependencies.
It checks is the root component and all its child/dependency components are configured correctly.
It is possible to used JSON-schema to specify a component configuration or require
check in the Component DSL.
If a component is not configured by its parent (using the dependencies
), it is configured using the config provided at the component registration.
Which acts as a component default/fallback configuration.
Attributes
In addition, it is possible to set and update a component configuration directly in a component definition using attributes.
E.g. a component dependency and a workflow definition using an attribute tries
tries << tries ?: 3
dependencies {
component 'zenoo.playground'
component 'zenoo.otp:2.4', [countryCode: '+420', tries: tries]
}
workflow('play') {
loop(tries) {
workflow('do-something')
}
}
It is possible to use require
to check if a component configuration matches a payload specification, see more
E.g.
require {
api {
url
username
password
}
}
exchange('exchange') {
http {
url api.url
basicAuth api.username, api.password
}
}
Component api key
Each Hub component can be secured using an api key. When registering an component through Admin API
it is possible to specify component api key. Another option is to set this auth key using Spring environment.
If api key is specified then when executing Target or Exposed function through Client API, the client must provide the component api key
in the request header otherwise 401 (no api key) or 403 (wrong api key) status code is returned.
e.g. 'playground.test' component configuration set using Spring environment
hub:
components:
security:
"playground.test": "secret-key"
Updated 4 months ago