App Configuration Options
YAML file configuration options
The tables and topics for Grainite applications are defined in a YAML configuration file:
App Name
Name of the app to load onto the server. With this, all topics and tables within this app will have their names prepended with the app name. This allows for multiple apps to have the same table and topic names, as well as integrate with each other.
Package ID (Java/Kotlin only)
Project-related package ID. This field is used by gx gen all
to generate classes and files. For example, for the YAML above, gx gen all -M
would generate the files with the following structure:
Python Directory (Python only)
This is where you specify the location of the Python source code files, if you application uses any Python code. This is used as the path prefix for the script_path
of your Python action handlers (see the Python example in Action Handlers below). For example, you would put src
here if your Python client and action handler scripts were stored like the following:
Then the script_path
of your action handlers defined in ActionHandler.py
would be handlers/ActionHandler.py
.
Environment Name
The name of the environment for this application. This can be overridden for various gx
commands by using --env_name
.
Applications are scoped by environments, allowing for instances of the same application to run on a single server in complete isolation.
Environment names must not start with a double leading underscore (__)
and must not contain a colon (:
).
JARs (Java only)
The path, relative to the config file, of the JAR files that contain the action handlers. Generally, all action handlers are packaged within a single JAR, but if that is not the case, it is possible to provide multiple JAR files:
The JARs provided to Grainite need to be fat/uber JARs, which means that they need to contain all their dependencies. In order to build the fat JAR, you will need to provide additional arguments or options, depending on your build system.
Head over to the Maven/Gradle Setup page for information on how to package your application.
Topics
A topic has a topic_name
, key_name
,key_type
and a value_type
.
The topic_name
uniquely identifies a topic within the app, the key_name
provides a way for grains to subscribe to topics, while key_type
and value_type
define the type of the key and value.
Exception Handlers (Java only)
There are two kinds of exception handlers:
1. Standard Exception Handler
An exception handler must have a method_name
and a list of exceptions
. Optionally, a class_name
can be specified as well. If the class_name
is not specified, it defaults to the action handler's parent class.
The exception handler name (DefaultExceptionHandler
in the example above) serves as an identifier to allow re-using exception handlers with multiple actions.
When an action handler throws a specified exception, the specified "exception handler" is invoked and its result is used.
2. Status Code Exception Handler
A status code exception handler must have a status_code
and a list of exceptions
. Optionally, a reason
can be specified as well.
When an action handler throws a specified exception, the result for the action is automatically converted into an ActionResult.failure
object with the provided status_code
and reason
.
To attach an exception handler to an action, please refer to the Actions section.
Please refer to the GrainContext API page for instructions on writing an exception handler.
Tables
A table has a table_name
, key_type
, value_type
maps
and action_handlers
.
The table_name
uniquely identifies a table, the key_type
and value_type
define the type of key and value for each grain in the table, and maps
define the structure for an entry in a map. maps
can be identified by either id
and/or name
. action_handlers
contain definitions for action handlers in each grain in the table.
Automatic Secondary Indexes
This feature was added in 2315.
auto_index_json
can be used to have Grainite automatically create secondary indices for all top level properties for a json stored in a Grain’s value.
For example, consider this:
If auto_index_json: true
is set for this Table, Grainite will automatically create indices for name == "John"
and location == "Los Angeles"
for this Grain.
These indices can then be queried using the table.find()
API:
Action Handlers
An Action Handler has a name
, type
, actions
and some additional properties based on the provided type
.
The name
serves as the identifier for the action handler. This field is required if there are more than one action handlers for a table.
The type
defines the language that the action handler is written in. Currently, there are three types that are supported:
java
python
action_handlers
used to be called action_classes
in previous versions. Functionally, both are the same and either one of them can be used.
However, it is highly recommended to use action_handlers
instead of action_classes
as it more accurately defines their roles.
Java action handlers require a class_name
which is the full class name of the defined Java class. These classes should be part of the project package (defined by package_id
).
timeout
was added in 2229 and enables users to configure timeouts (in seconds) for all actions in their action handlers. If an action takes longer than the provided timeout, it is automatically retried. By default, this timeout is set to 60 seconds.
Actions
actions
define the types of actions that are expected to be handled in the provided action handler. Note that method_name
and handles_multiple_actions
are both optional.
action_name
defines the name of the action, while method_name
defines the name of the method (in the action handler), that is expected to handle the action. With this, it is possible for a single method to handle multiple actions.
If method_name
is not provided for an action, the method_name
is the same as the action_name
.
error_sink_topic
defines a topic in which all failed events for this action are sent.
exception_handlers
define the types of exception handlers associated with this action. When an action throws an exception, the exception handler corrospending to the exception is called and its result is used instead.
At the moment,error_sink_topic
and exception_handlers
are not available in Python. Only action handlers implemented in Java can make use of these features.
There are two types of methods that can be defined within action handlers:
Single-Action Variant
This method takes a single Action
and returns a single ActionResult
for a per-action action handler.
Multi-Action Variant
This method takes a list of Action
and returns a list of ActionResult
for a batch-action action handler.
handles_multiple_actions
can be set to true
to use the Multi Action Variant, or false
to use the Single Action Variant.
By default, handles_multiple_actions
is set to false
.
Subscriptions
A method can have multiple subscriptions to topics. A subscription
is defined by a subscription_name
, topic_name
and topic_key
. When a new event is appended to a topic, all subscriptions for that topic are triggered and the method corresponding to the subscription is invoked.
The subscription_name
can be used to control the subscription (defer, pause, etc.), while the topic_name
and topic_key
identify the topic and key (for that topic) for the subscription.
To subscribe to a topic in another Application, simply specify the app and topic name, separated by a colon, in the YAML - <app_name>:<topic_name>
.
Configuration
config
allows users to pass data from the configuration file to action handlers or tasks and access it via GrainContext
:
Secrets
A secret can be passed to the configuration using the prefix $secret
. So for a secret named my_secret
which is stored in Grainite, it can be passed in through the configuration file as below:
This can be used by calling the resolveProperty
GrainiteContext API:
Starting in release version 2327, string interpolation for secrets is now supported.
Assuming the value for my_secret
is 123
, the above will resolve to 123-abc
when context.resolveProperty("some_token")
is called in the Action Handler.
🧪 Imports (Early Access)
imports
provide a convenient way to incorporate complex configurations into your application, enabling Java programs to generate configurations programmatically, based on user-supplied configuration parameters. imports
can even be used to share complex application configuration and logic with others through simple configurations.
An import
is a way for applications to programmatically provide their required resources (tables, topics, etc.) to gx
.
Imports are resolved at the time an app is loaded (via gx load
). To view the resolved app config without running gx load
, you can use gx beta app-resolve
.
Each import
config must contain a name
which will be used by the gx
CLI to search for a properties file - <name>GrainiteImport.properties
, in the provided JAR file(s). Consider the example from above; since the name of the import is DatabaseCDC
, gx
will look for a DatabaseCDCGrainiteImport.properties
file in the root of the provided JAR file.
The GrainiteImport.properties
must contain a config_provider_class
property to point to the class which will generate an application configuration. For instance, the DatabaseCDC properties file could look like this:
By default, gx
will invoke the generateGrainiteConfig
method but this can be changed by providing a config_provider_method
property.
The method header for the "config generator" method should be the following:
The method takes in a map representing the config
options defined by the user in their app.yaml
. For the DatabaseCDC example, the map will consist of:
The method must return a Map<String, Object>
, representing the resources required by this import. Currently, the following resources can be generated by an import:
Here is an example of what my.pkg.database_cdc.ConfigProvider
might generate:
The configuration returned will be merged with the existing app.yaml configuration, and will be utilized by gx load
.
In the event of conflicting imports, the one declared first in the app.yaml file will take precedence. If an import's configuration clashes with the user-defined configuration specified in the app.yaml
, the user-defined configuration will take precedence.
Last updated