Comment on page
Development and Operations
Java
Python
// Connect to grainite with hostname, data port and admin port.
Grainite client = GrainiteClient.getClient(GRAINITE_SERVER, 5056);
// Get handle to the table that contains the grain
Table employeeTable = client.getTable(APP_NAME, EMPLOYEE_TABLE);
//Get handle to the grain
Grain employeeGrain = employeeTable.getGrain(Key.of(EMPLOYEE_ID));
// Extract the value from the grain
String empData = employeeGrain.getValue().asString();
# Connect to grainite with hostname, data port and admin port.
client = grainite_client.get_client(GRAINITE_SERVER, 5056)
# Get handle to the table that contains the grain
employee_table = client.get_table(APP_NAME, EMPLOYEE_TABLE)
# Get handle to the grain
employee_grain = employee_table.get_grain(Key(EMPLOYEE_ID))
# Extract the value from the grain
emp_data = employee_grain.get_value().as_string()
Java
Python
// Connect to grainite with hostname, data port and admin port.
Grainite client = GrainiteClient.getClient(GRAINITE_SERVER, 5056);
// Get handle to the table that contains the grain
Table employeeTable = client.getTable(APP_NAME, EMPLOYEE_TABLE);
//Get handle to the grain
Grain employeeGrain = employeeTable.getGrain(Key.of(EMPLOYEE_ID));
//Update the value of the grain
employeeGrain.setValue(Value.of("John Smith"));
# Connect to grainite with hostname, data port and admin port.
client = grainite_client.get_client(GRAINITE_SERVER, 5056)
# Get handle to the table that contains the grain
employee_table = client.get_table(APP_NAME, EMPLOYEE_TABLE)
# Get handle to the grain
employee_grain = employee_table.get_grain(Key(EMPLOYEE_ID))
# Update the value of the grain
employee_grain.set_value(Value("John Smith"))
Java
Python
// Set the firstName key of the sorted map 0 to John from external client
employeeGrain.mapPut(0, Key.of(“firstName”), Value.of(“John”));
//Set the firstName key of the sorted map 0 to John inside an action handler
context.mapPut(0, Key.of(“firstName”), Value.of(“John”));
# Set the firstName key of the sorted map 0 to John from external client
employee_grain.map_put(0, Key("firstName"), Value("John"))
# Set the firstName key of the sorted map 0 to John inside an action handler
context.map_put(0, Key("firstName"), Value("John"))
Grainite stores data in byte arrays and does not interpret the data type. Value is a helper class that converts the data to the byte array. Value parameters to
Value.of
are String
, long
, Double
and Java's Serializable
.While you can do this purely from an external client as shown in examples, it is best to do this in an action handler that runs within the Grainite context. The reason is that when executing within action handlers, the read and update to the grain happen atomically. This means that there are no lost updates or stale reads. All access to the state of a grain is serialized within an action handler.
Java
Python
// ActionHandler
public ActionResult handleEmployeeEvents(Action action, GrainContext context) {
// Get the value for the key which is in the context. ActionHandlers execute within the context of a single grain. In this example, the grain represents a single employee.
Employee emp = context.getValue().asType(Employee.class);
String employeeID = context.getKey().asString();
// Get the event from the topic if it was sent to a topic.
TopicEvent topicRequest = (TopicEvent) action;
HashMap<String, Object> event =
JsonIterator.deserialize(topicRequest.getPayload().asString(), HashMap.class);
// Extract the changes from the event, for example the new address
emp.setAddress(event.get(NEW_ADDRESS));
// Save the updated value of the employee in the grain value
context.setValue(Value.of(emp));
// Return success
return ActionResult.success(action);
}
# ActionHandler
def handle_employee_events(action: Action, context: GrainContext) -> ActionResult:
# Get the value for the key which is in the context. ActionHandlers execute within the context of a single grain. In this example, the grain represents a single employee.
emp = context.get_value().as_dict()
employee_id = context.get_key().as_string()
# Get the event from the topic if it was sent to a topic.
assert isinstance(action, TopicEvent)
event = json.loads(action.payload.as_string())
# Extract the changes from the event, for example the new address
emp["address"] = event[NEW_ADDRESS]
# Save the updated value of the employee in the grain value
context.set_value(Value(emp))
# Return success
return ActionResult.Success(action)
The Python API has no equivalent function for the
Value.asType()
method in the Java API, since asType()
expects an object that implements Serializable
, whereas interfaces are not a part of normal Python code. Value
in Python can instead handle any of types specified in the Python documentation.You can invoke this action handler from an external client in a couple of ways. First, you can directly invoke the action handler from the client.
Java
Python
// Client
Grainite client = GrainiteClient.getClient(GRAINITE_SERVER, 5056);
Table employeeTable = client.getTable(Constants.APP_NAME, Constants.EMPLOYEE_TABLE);
Grain employeeGrain = employeeTable.getGrain(Key.of(EMPLOYEE_ID));
// Directly invoke the action handler from the client. Grainite will run the action handler within the context of the employee grain
employeeGrain.invoke("handleEmployeeEvents", Value.of(HashMap of employee data in json));
# Client
client = grainite_client.get_client(GRAINITE_SERVER, 5056)
employee_table = client.get_table(Constants.APP_NAME, Constants.EMPLOYEE_TABLE)
employee_grain = employee_table.get_grain(Key(EMPLOYEE_ID))
# Directly invoke the action handler from the client. Grainite will run the action handler within the context of the employee grain
employee_grain.invoke("handleEmployeeEvents", Value())
employeeGrain.invoke("handleEmployeeEvents", Value(dict of employee data in json))
You can also send an event to a topic that handleEmployeeEvents is subscribed to. When the event lands in the topic, Grainite will automatically route it to handleEmployeeEvents. This subscription information of an action handler of a Table to a Topic is specified in
app.yaml
.Java
Python
// Client
Grainite client = GrainiteClient.getClient(GRAINITE_SERVER, 5056);
// Get topic employee_events_topic.
Topic topic = client.getTopic(Constants.APP_NAME,
Constants.EMPLOYEE_EVENTS_TOPIC);
// Create event to spend to topic with key: `Key Foo` and Payload: `Hello, world!`.
Event topicEvent = new Event(Key.of(NEW_ADDRESS), Value.of("2901 Tasman Drive, Santa Clara, CA"));
// Append event to topic.
topic.append(topicEvent);
client.close();
# Client
client = grainite_client.get_client(GRAINITE_SERVER, 5056)
# Get topic employee_events_topic.
topic = client.get_topic(Constants.APP_NAME, Constants.EMPLOYEE_EVENTS_TOPIC)
# Create event to spend to topic with key: `Key Foo` and Payload: `Hello, world!`.
topic_event = Event(Key(NEW_ADDRESS), Value("2901 Tasman Drive, Santa Clara, CA"))
# Append event to topic.
topic.append(topic_event)
client.close()
It is common to require data belonging to a grain when processing an action handler of another grain. For example, an employee might need to look up details of the departments that the employee has worked in during the processing of an action handler. Therefore, a subset of the Grainite client API is available within the context of a grain handler that enables querying data from other grains. For example, this code is from an action handler.
Java
Python
// Get handle to the rulesGrain action handler
GrainView rulesGrain = context.lookupGrain(Constants.APP_NAME, Constants.RULES, Value.of(Constants.ALL_RULES));
// Get data from the key importantRule of map # 1 o" that rules grain
Value rulesNamesValue = rulesGrain.mapGet(1, Key.of("importantRule"));
# Get handle to the rules_grain action handler
rules_grain = context.lookup_grain(Constants.RULES, Value(Constants.ALL_RULES), Constants.APP_NAME)
# Get data from the key important_rule of map # 1 of that rules grain
rules_names_value = rules_grain.map_get(1, Key("important_rule"))
While lookups of a grain state from another grain are permitted, updates should still follow the best pattern of sending events asynchronously. There are 2 ways to send events from one grain to another. First, send an event directly to the receiving grain to model a 1-1 communication pattern. Second, send an event to a topic that the receiving grain subscribes to. This might be preferred if we want to track the events that are sent between grains and keep them for history. Note that the context object is only available when executing within an action handler.
Java
Python
// Send an event to a topic subscribed by department.
context.sendToTopic(Constants.DEPARTMENT_UPDATES_TOPIC, Key.of("Accounting"), Value.of(updatesObj), null);
// Send an event directly to an action handler belonging to a grain
context.sendToGrain(Constants.DEPARTMENT_TABLE, Key.of("Accounting’", new GrainContext.GrainOp.Invoke(Constants.HANDLE_DEPARTMENT_UPDATES_ACTION, Value.of(updatesObj)), null);
# Send an event to a topic subscribed by department.
context.send_to_topic(Constants.DEPARTMENT_UPDATES_TOPIC, Key("Accounting"), Value(updates_obj), None)
# Send an event directly to an action handler belonging to a grain
context.send_to_grain(Constants.DEPARTMENT_TABLE, Key("Accounting"), message.Invoke(Constants.HANDLE_DEPARTMENT_UPDATES_ACTION, Value(updates_obj)), None))
- 1.Edit the code in the action handlers
- 2.If using Java, compile it (e.g.
mvn compile package
) - 3.Run
gx load
. This will load the new action handler into Grainite.
gx table dump
gives all the grains from a table. To print the grain as JSON, provide the --json
option.gx table dump employee "john"
gx topic dump emp_topic
gx log
allows you to print app logs from the command line. Also, see Locating app logs and data (dx
directory).gx config
will print out metadata, including the host IP (e.g. localhost), relevant to a single node server instance of Grainite running as a Docker container. To view only the IP address and none of the other information, run gx config host
To get the IP address of the cluster you are managing, run the command below from the Grainite cluster scripts corresponding to the cloud provider your cluster is hosted in. If you do not have access to the cluster via the Grainite cluster scripts, please contact your administrator to get the IP address of the cluster.
AWS
Azure
GCP
./grainite/scripts/bin/aws-grainite cluster-ip
./grainite/scripts/bin/azure-grainite cluster-ip
./grainite/scripts/bin/gcp-grainite cluster-ip
When you have multiple Grainite clusters, it's helpful to check the name of the current cluster your machine is accessing via Grainite cluster scripts and kubectl. To get the name, run the Grainite cluster scripts command below corresponding to the cloud provider your cluster is hosted in. If you do not have access to the cluster via the Grainite cluster scripts, please contact your administrator to get the name of the cluster.
AWS
Azure
GCP
./grainite/scripts/bin/aws-grainite cluster-current
./grainite/scripts/bin/azure-grainite cluster-current
./grainite/scripts/bin/gcp-grainite cluster-current
When you have multiple Grainite clusters to manage, you can list all the clusters that exist in your VPC using the Grainite cluster scripts command corresponding to the cloud provider your cluster is hosted in.
AWS
Azure
GCP
./grainite/scripts/bin/aws-grainite cluster-list
./grainite/scripts/bin/azure-grainite cluster-list
./grainite/scripts/bin/gcp-grainite cluster-list
When you have multiple Grainite clusters to manage, you can switch the current cluster being accessed via Grainite cluster scripts and kubectl using the below command corresponding to the cloud provider your cluster is hosted in. Replace
<cluster-name>
with the name of the cluster you would like to switch to.AWS
Azure
GCP
./grainite/scripts/bin/aws-grainite cluster-switch <cluster-name>
./grainite/scripts/bin/azure-grainite cluster-switch <cluster-name>
./grainite/scripts/bin/gcp-grainite cluster-switch <cluster-name>
Include a
requirements.txt
file to the top level of your application directory. gx load
will import these for you.In a Spring boot or multi-threaded application, each request is handled by a different thread. Grainite connections are thread-local. Attempting to share them between threads leads to exceptions. The simplest way to connect to Grainite from Spring boot is to create the connection, use it and close it in the same thread. There is no need to cache a connection in application code, the underlying Grainite library takes care of that.
Java
Python
// Example method in a spring boot app that connects to grainite
public String getRules() {
Grainite client = null;
try {
client = GrainiteClient.getClient("localhost", 5056);
Table rulesTable = client.getTable(Constants.APP_NAME, Constants.RULES_TABLE);
System.out.println("After getting table");
Grain rulesGrain = rulesTable.getGrain(Key.of(Constants.ALL_RULES));
ResultOrStatus<Value> result = rulesGrain.invoke(Constants.GET_RULES_EVENT_ACTION, Value.of("hello"));
return result.getResult().asString();
} catch (Exception e) {
e.printStackTrace();
return "Error";
} finally {
client.close();
}
}
# Example method in a spring boot app that connects to grainite
def get_rules():
client = None
try:
client = grainite_client.get_client("localhost", 5056)
rules_table = client.get_table(Constants.APP_NAME, Constants.RULES_TABLE)
print("After getting table")
rules_grain = rules_table.get_grain(Key(Constants.ALL_RULES))
result_or_status = rules_grain.invoke(Constants.GET_RULES_EVENT_ACTION, Value("hello"))
return result_or_status.result.as_string()
except Exception as e:
print(traceback.format_exc())
return "Error"
finally:
client.close()
Please select the instructions below based on whether the Grainite server is running in a single-node Docker container or as a multi-node Kubernetes cluster:
Single-Node
Cluster
- 1.Delete the entire database, including logs using
rm -rf <path to dx directory>
where<path to dx directory>
is the location of yourdx
directory, which is~/dx/
by default. - 2.Restart the container using
docker restart <grainite>
where<grainite>
is the name of the Docker container for Grainite, which is simplygrainite
by default.
Example:
sudo rm -rf ~/dx/
docker restart grainite
The Grainite deployment scripts downloaded during cluster creation include a
cluster-reset
command for each type of cluster environment. Below is an example for GCP where my_grainite_cluster
is the user-specified name of the cluster:grainite/scripts/bin/gcp-grainite cluster-reset my_grainite_cluster
Since the command wipes all data from the Grainite database instance and is irreversible, a prompt will appear, requiring the name of the cluster to be typed out as confirmation.
Last modified 3mo ago