The easiest way to get started with Elide is to use the Spring Boot starter dependency. The starter bundles all of the dependencies you will need to stand up a web service. This tutorial uses the starter, and all of the code is available here.
You can deploy and play with this example on Heroku or locally. The landing page will let you toggle between the swagger UI and Graphiql for the example service.
Don’t like Spring/Spring Boot? - check out the same getting starting guide using Jetty/Jersey and Elide standalone. Don’t like Java? Here is an example project using Elide with Kotlin.
To include elide into your spring project, add the single starter dependency:
<dependency>
<groupId>com.yahoo.elide</groupId>
<artifactId>elide-spring-boot-starter</artifactId>
<version>${elide.version}</version>
</dependency>
Elide models are some of the most important code in any Elide project. Your models are the view of your data that you wish to expose. In this example we will be modeling a software artifact repository since most developers have a high-level familiarity with artifact repositories such as Maven, Artifactory, npm, and the like.
There will two kinds of models:
ArtifactGroup
, ArtifactProduct
, and ArtifactVersion
. For brevity we will omit package names and import statements.Downloads
model.@Include(rootLevel = true, type = "group")
@Entity
public class ArtifactGroup {
@Id
private String name = "";
private String commonName = "";
private String description = "";
@OneToMany(mappedBy = "group")
private List<ArtifactProduct> products = new ArrayList<>();
}
@Include(type = "product")
@Entity
public class ArtifactProduct {
@Id
private String name = "";
private String commonName = "";
private String description = "";
@ManyToOne
private ArtifactGroup group = null;
@OneToMany(mappedBy = "artifact")
private List<ArtifactVersion> versions = new ArrayList<>();
}
@Include(type = "version")
@Entity
public class ArtifactVersion {
@Id
private String name = "";
private Date createdAt = new Date();
@ManyToOne
private ArtifactProduct artifact;
}
{
tables: [
{
name: Downloads
table: downloads
description:
'''
Analytics for artifact downloads.
'''
joins: [
{
name: artifactGroup
to: group
kind: toOne
type: left
definition: '{{group_id}} = {{artifactGroup.name}}'
},
{
name: artifactProduct
to: product
kind: toOne
definition: '{{product_id}} = {{artifactProduct.name}}'
}
]
dimensions: [
{
name: group
type: TEXT
definition: '{{artifactGroup.name}}'
}
{
name: product
type: TEXT
definition: '{{artifactProduct.name}}'
}
{
name: date
type: TIME
definition: '{{date}}'
grains: [{
type: DAY
}]
}
]
measures: [
{
name: downloads
type: INTEGER
definition: 'SUM({{downloads}})'
}
]
}
]
}
So now we have some models, but without an API it is not very useful. Before we add the API component, we need to create the schema in the database that our models will use. Our example uses liquibase to manage the schema. When Heroku releases the application, our example will execute the database migrations to configure the database with some test data automatically. This demo uses Postgres. Feel free to modify the migration script if you are using a different database provider.
You may notice the example liquibase migration script adds an extra table, AsyncQuery
. This is only required if leveraging Elide’s asynchronous API to manage long running analytic queries.
There may be more tables in your database than models in your project or vice versa. Similarly, there may be more columns in a table than in a particular model or vice versa. Not only will our models work just fine, but we expect that models will normally expose only a subset of the fields present in the database. Elide is an ideal tool for building micro-services - each service in your system can expose only the slice of the database that it requires.
Bringing life to our API is trivially easy. We need a single Application class:
/**
* Example app using elide-spring.
*/
@SpringBootApplication
public class App {
public static void main(String[] args) throws Exception {
SpringApplication.run(App.class, args);
}
}
The application is configured with a Spring application yaml file (broken into sections below).
The Elide Spring starter uses a JPA data store (the thing that talks to the database). This can be configured like any other Spring data source and JPA provider. The one below uses an H2 in-memory database:
spring:
jpa:
hibernate:
show_sql: true
naming:
physical-strategy: 'org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl'
dialect: 'org.hibernate.dialect.H2Dialect'
ddl-auto: 'validate'
jdbc:
use_scrollable_resultset: true
datasource:
url: 'jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1'
username: 'sa'
password: ''
driver-class-name: 'org.h2.Driver'
Elide has its own configuration to turn on APIs and setup their URL paths:
elide:
json-api:
path: /api/v1
enabled: true
graphql:
path: /graphql/api/v1
enabled: true
swagger:
path: /doc
enabled: true
version: "1.0"
The following configuration enables elide’s asynchronous API for analytic queries:
async:
enabled: true
threadPoolSize: 7
maxRunTime: 65
cleanupEnabled: true
queryCleanupDays: 7
defaultAsyncQueryDAO: true
To enable analytic queries, we have to turn on the the aggregation data store. This example also enables HJSON configuration for analytic models:
aggregation-store:
enabled: true
default-dialect: h2
dynamic-config:
path: src/main/resources/analytics
enabled: true
With these new classes, you have two options for running your project. You can either run the App
class using your
favorite IDE, or you can run the service from the command line:
java -jar target/elide-spring-boot-1.0.jar
Our example requires the following environment variables to be set to work correctly with Heroku and Postgres.
If running inside a Heroku dyno, Heroku sets these variables for us. If you don’t set them, the example will use the H2 in memory database.
With the App
class and application yaml file, we can now run our API.
You can now run the following curl commands to see some of the sample data that the liquibase migrations added for us. Don’t forget to replace localhost:8080 with your Heroku URL if running from Heroku!
curl http://localhost:8080/api/v1/group
curl -g -X POST -H"Content-Type: application/json" -H"Accept: application/json" \
"http://localhost:8080/graphql/api/v1" \
-d'{
"query" : "{ group { edges { node { name commonName description } } } }"
}'
Here are the respective responses:
{
"data": [
{
"attributes": {
"commonName": "Example Repository",
"description": "The code for this project"
},
"id": "com.example.repository",
"relationships": {
"products": {
"data": [
{
"id": "elide-demo",
"type": "product"
}
]
}
},
"type": "group"
},
{
"attributes": {
"commonName": "Elide",
"description": "The magical library powering this project"
},
"id": "com.yahoo.elide",
"relationships": {
"products": {
"data": [
{
"id": "elide-core",
"type": "product"
},
{
"id": "elide-standalone",
"type": "product"
},
{
"id": "elide-datastore-hibernate5",
"type": "product"
}
]
}
},
"type": "group"
}
]
}
{
"data": {
"group": {
"edges": [
{
"node": {
"commonName": "Example Repository",
"description": "The code for this project",
"name": "com.example.repository"
}
},
{
"node": {
"commonName": "Elide",
"description": "The magical library powering this project",
"name": "com.yahoo.elide"
}
}
]
}
}
}
You can navigate through the entity relationship graph defined in the models and explore relationships:
List groups: group/
Show a group: group/<group id>
List a group's products: group/<group id>/products/
Show a product: group/<group id>/products/<product id>
List a product's versions: group/<group id>/products/<product id>/versions/
Show a version: group/<group id>/products/<product id>/versions/<version id>
So far we have defined our views on the database and exposed those views over HTTP. This is great progress, but so far we have only read data from the database.
Fortunately for us adding data is just as easy as reading data. For now let’s use cURL to put data in the database.
curl -X POST http://localhost:8080/api/v1/group/com.example.repository/products -H"Content-Type: application/vnd.api+json" -H"Accept: application/vnd.api+json" -d '{"data": {"type": "product", "id": "elide-demo"}}'
curl -g -X POST -H"Content-Type: application/json" -H"Accept: application/json" "http://localhost:8080/graphql/api/v1" -d'{ "query" : "mutation { group(ids: [\"com.example.repository\"]) { edges { node { products(op: UPSERT, data: {name: \"elide-demo\"}) { edges { node { name } } } } } } }" }'
When you run that cURL call you should see a bunch of json returned, that is our newly inserted object!
{
"data": [
{
"attributes": {
"commonName": "",
"description": ""
},
"id": "elide-demo",
"relationships": {
"group": {
"data": {
"id": "com.example.repository",
"type": "group"
}
},
"versions": {
"data": []
}
},
"type": "product"
}
]
}
{
"data": {
"group": {
"edges": [
{
"node": {
"products": {
"edges": [
{
"node": {
"name": "elide-demo"
}
}
]
}
}
}
]
}
}
}
Notice that, when we created it, we did not set any of the attributes of our new product record. Updating our data to help our users is just as easy as it is to add new data. Let’s update our model with the following cURL call.
curl -X PATCH http://localhost:8080/api/v1/group/com.example.repository/products/elide-demo \
-H"Content-Type: application/vnd.api+json" -H"Accept: application/vnd.api+json" \
-d '{
"data": {
"type": "product",
"id": "elide-demo",
"attributes": {
"commonName": "demo application",
"description": "An example implementation of an Elide web service that showcases many Elide features"
}
}
}'
curl -g -X POST -H"Content-Type: application/json" -H"Accept: application/json" \
"http://localhost:8080/graphql/api/v1" \
-d'{
"query" : "mutation { group(ids: [\"com.example.repository\"]) { edges { node { products(op: UPDATE, data: { name: \"elide-demo\", commonName: \"demo application\", description: \"An example implementation of an Elide web service that showcases many Elide features\" }) { edges { node { name } } } } } } }"
}'
Analytic queries leverage the same API as reading any other Elide model. Note that Elide will aggregate the measures selected by the dimensions requested. Learn more about analytic queries here.
curl -g "http://localhost:8080/api/v1/downloads?fields[downloads]=downloads,group,product"
curl -g -X POST -H"Content-Type: application/json" -H"Accept: application/json" \
"http://localhost:8080/graphql/api/v1" \
-d'{
"query" : "{ downloads { edges { node { downloads group product } } } }"
}'
Here are the respective responses:
{
"data": [
{
"attributes": {
"downloads": 35,
"group": "com.example.repository",
"product": "elide-core"
},
"id": "0",
"type": "downloads"
}
]
}
{
"data": {
"downloads": {
"edges": [
{
"node": {
"downloads": 35,
"group": "com.example.repository",
"product": "elide-core"
}
}
]
}
}
}