Development and Operations

How to connect to Grainite from an external client and get the value of a grain?

// 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();

How to update the value of a grain from an external client?

// 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"));

How to insert or update the value of an entry in a sorted map of a grain?

// 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”));

Why use a Value class to send and get data out of Grainite?

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.

How to perform read-modify-write operations on a grain?

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.

// 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);
}

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.

// 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));

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.

// 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();

How to lookup data in a grain from an action handler of another grain?

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.

// 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"));

How to update a grain from another grain?

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.

// 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);

How to update business logic in action handlers?

  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.

How to read the content of a grain from the command line?

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"

How to read the content of a topic from the command line?

gx topic dump emp_topic

How do I see the logs of the application? Where are they located?

gx log allows you to print app logs from the command line. Also, see Locating app logs and data (dx directory).

How can I add counters and metrics to my application that can be consumed by monitoring tools?

Please see Using Counters amd Gauges.

How do I get the IP address of my Grainite server?

Single Node:

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

Cluster:

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.

./grainite/scripts/bin/aws-grainite cluster-ip

How do I check the name of my current cluster?

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.

./grainite/scripts/bin/aws-grainite cluster-current

How do I get a list of all Grainite clusters?

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.

./grainite/scripts/bin/aws-grainite cluster-list

How do I switch to another Grainite cluster?

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.

./grainite/scripts/bin/aws-grainite cluster-switch <cluster-name>

How do I import additional python libraries for use in my action handlers?

Include a requirements.txt file to the top level of your application directory. gx load will import these for you.

How to connect to Grainite from a Spring Web or multithreaded application?

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.

// 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();
    }
}

How can I fully reset Grainite in my development or test environment, cleaning up all data and restarting the server?

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:

  1. Delete the entire database, including logs using rm -rf <path to dx directory> where <path to dx directory> is the location of your dx 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 simply grainite by default.

Example:

sudo rm -rf ~/dx/
docker restart grainite

Last updated