Getting Started
The reference implementation will serve queries and mutations based on in-memory data read from newline-delimited JSON files.
First, we will define some types to represent the data in the newline-delimited JSON files. Rows of JSON data will be stored in memory as ordered maps:
type Row = BTreeMap<models::FieldName, serde_json::Value>;
Our application state will consist of collections of various types of rows:
#[derive(Debug, Clone)]
pub struct AppState {
pub articles: BTreeMap<i32, Row>,
pub authors: BTreeMap<i32, Row>,
pub institutions: BTreeMap<i32, Row>,
pub metrics: Metrics,
}
In our main
function, the data connector reads the initial data from the newline-delimited JSON files, and creates the AppState
:
fn init_app_state() -> AppState {
// Read the JSON data files
let articles = read_json_lines(ARTICLES_JSON).unwrap();
let authors = read_json_lines(AUTHORS_JSON).unwrap();
let institutions = read_json_lines(INSTITUTIONS_JSON).unwrap();
let metrics = Metrics::new().unwrap();
AppState {
articles,
authors,
institutions,
metrics,
}
}
Finally, we start a web server with the endpoints which are required by this specification:
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn Error>> {
let app_state = Arc::new(Mutex::new(init_app_state()));
let app = Router::new()
.route("/health", get(get_health))
.route("/metrics", get(get_metrics))
.route("/capabilities", get(get_capabilities))
.route("/schema", get(get_schema))
.route("/query", post(post_query))
.route("/query/explain", post(post_query_explain))
.route("/mutation", post(post_mutation))
.route("/mutation/explain", post(post_mutation_explain))
.layer(axum::middleware::from_fn_with_state(
app_state.clone(),
metrics_middleware,
))
.with_state(app_state);
// Start the server on `localhost:<PORT>`.
// This says it's binding to an IPv6 address, but will actually listen to
// any IPv4 or IPv6 address.
let host = net::IpAddr::V6(net::Ipv6Addr::UNSPECIFIED);
let port = env::var("PORT")
.map(|s| s.parse())
.unwrap_or(Ok(DEFAULT_PORT))?;
let addr = net::SocketAddr::new(host, port);
let server = axum::Server::bind(&addr).serve(app.into_make_service());
println!("Serving on {}", server.local_addr());
server.with_graceful_shutdown(shutdown_handler()).await?;
Ok(())
}
Note: the application state is stored in an Arc<Mutex<_>>
, so that we can perform locking reads and writes in multiple threads.
In the next chapters, we will look at the implementation of each of these endpoints in turn.