Defining a Model
Overview
Graph API provides a flexible way to define your graph data model using Rust's enum types and derive macros. This approach gives you the benefits of Rust's type system while maintaining the flexibility of property graphs.
Basic Concepts
A graph consists of two primary elements:
- Vertices (nodes): The entities in your graph
- Edges: The relationships connecting vertices
For each of these, you'll define a Rust enum that represents all possible types.
Model Definition
The equivalent definition using Graph API is:
// Define vertex types for a social media application
#[derive(Debug, Clone, VertexExt)]
pub enum Vertex {
// Person vertex with various properties
Person {
name: String, // Not indexed
#[index(hash)] // Hash index for exact lookups
username: String,
#[index(full_text)] // Full-text index for text search
biography: String,
#[index(range)] // Range index for range queries
age: u8,
},
// Project vertex with minimal properties
Project {
name: String,
},
// Comment vertex
Comment {
text: String,
date: String,
},
}
// Define edge types that connect vertices
#[derive(Debug, Clone, EdgeExt)]
pub enum Edge {
// Simple edges without properties
Created,
Follows,
// Edges with properties
Liked { timestamp: String },
Commented { timestamp: String },
}
This model defines vertex types for people, projects, and comments, along with edge types for the relationships between them.
Creating Instances
Once you've defined your model, you can create instances of vertices and edges:
let mut graph = SimpleGraph::new();
// Create vertices
let bryn = graph.add_vertex(Vertex::Person {
name: "Bryn".to_string(),
username: "bryn123".to_string(),
biography: "Graph enthusiast".to_string(),
age: 28,
});
let julia = graph.add_vertex(Vertex::Person {
name: "Julia".to_string(),
username: "julia456".to_string(),
biography: "Software developer".to_string(),
age: 34,
});
let eve = graph.add_vertex(Vertex::Person {
name: "Eve".to_string(),
username: "eve789".to_string(),
biography: "Network specialist".to_string(),
age: 31,
});
let graph_api = graph.add_vertex(Vertex::Project {
name: "GraphApi".to_string(),
});
let alpaca = graph.add_vertex(Vertex::Project {
name: "Alpaca".to_string(),
});
// Create edges
graph.add_edge(bryn, graph_api, Edge::Created);
graph.add_edge(julia, alpaca, Edge::Created);
graph.add_edge(julia, bryn, Edge::Follows);
graph.add_edge(eve, julia, Edge::Follows);
graph.add_edge(bryn, eve, Edge::Follows);
graph.add_edge(
bryn,
alpaca,
Edge::Liked {
timestamp: "2023-01-01".to_string(),
},
);
graph.add_edge(
bryn,
alpaca,
Edge::Commented {
timestamp: "2023-01-02".to_string(),
},
);
Using Derive Macros
The VertexExt
and EdgeExt
derive macros generate implementations for your model types that enable them to work with
Graph API's traversal and query features.
VertexExt
This macro provides:
- Integration with the indexing system
- Projection types
- Type-safe accessors for properties
- Type-safe filters for traversals
EdgeExt
This macro provides:
- Integration with label-based indexing
- Projection types
- Type-safe accessors for properties
- Type-safe filters for traversals
Indexing
You can define indexes for efficient lookups using attributes:
#[index(hash)]
: Creates a hash index for exact match lookups#[index(range)]
: Creates a range index for range queries#[index(full_text)]
: Creates a full-text index for text search
For more details on indexes and examples, see the Property Graphs section.
Best Practices
When defining your graph model:
- Use descriptive names - Choose clear names for vertex and edge types
- Index strategically - Only index fields used in frequent queries
- Use appropriate index types - Match index types to query patterns