Skip to content

Commit

Permalink
Migrate legacy getting started to elide standalone readme (#1106)
Browse files Browse the repository at this point in the history
* Moved getting starting docs from Elide main page into README since they are specific to Elide Standalone

* Spelling fix

* Added heroku button and example code repo link

* Inspection rework
  • Loading branch information
aklish authored Dec 11, 2019
1 parent aa9ca26 commit b86539e
Showing 1 changed file with 297 additions and 15 deletions.
312 changes: 297 additions & 15 deletions elide-standalone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ Table of Contents
* [Overview](#overview)
* [Who is this for?](#whofor)
* [Getting Started](#gettingstarted)
* [Usage](#usage)
* [Settings Class](#settings-class)
* [Filters](#filters)
* [Additional Configuration](#additional-config)
* [More Detailed Examples](#moredetail)

## <a name="overview"></a>Overview
Expand All @@ -20,13 +16,18 @@ The Elide standalone application takes an opinionated stance on its technology s
1. Start your web service:
* `$ java -jar YOUR_APP.jar`


## <a name="whofor"></a>Who is this for?

The Elide standalone application is for all new and existing users of Elide. This is the **fastest way to setup an Elide web service** and we have provided several avenues of customization for Elide standalone. However, if you need even more flexibility in your application than what is provided, then you should consider using the Elide __middleware__ directly.
The Elide standalone application is an alternative to Spring Boot for getting started quickly with Elide. However, if you need more flexibility in your application than what is provided, then you should consider using the Elide __middleware__ directly.

## <a name="gettingstarted"></a>Getting Started

This tutorial will use elide-standalone, and all of the code is [available here](https://github.com/aklish/elide-heroku-example). You can deploy and play with this example on Heroku or locally. The landing page will let you toggle between the [swagger UI](https://swagger.io/tools/swagger-ui) and [Graphiql](https://github.com/graphql/graphiql) for the example service.

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/aklish/elide-heroku-example)

### Add Elide as a Dependency

To include `elide-standalone` into your project, add the single dependency:
```xml
<dependency>
Expand All @@ -36,23 +37,304 @@ To include `elide-standalone` into your project, add the single dependency:
</dependency>
```

A complete example of using Elide standalone to setup a simple service can be found [here](https://elide.io/pages/guide/01-start.html).
### Create Models

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.

The first models we’ll need are `ArtifactGroup`, `ArtifactProduct`, and `ArtifactVersion`. For brevity we will omit package names and import statements.

#### ArtifactGroup.java

```java
@Include(rootLevel = true, type = "group")
@Entity
public class ArtifactGroup {
@Id
public String name = "";

public String commonName = "";

public String description = "";

@OneToMany(mappedBy = "group")
public List<ArtifactProduct> products = new ArrayList<>();
}
```

#### ArtifactProduct.java

```java
@Include(type = "product")
@Entity
public class ArtifactProduct {
@Id
public String name = "";

public String commonName = "";

## <a name="usage"></a>Usage
public String description = "";

Using Elide standalone out of box is intended to require minimal effort. For persistence, you will minimally need a JPA compatible database (i.e. MySQL), a `Settings` class, and your JPA-annotated data models.
@ManyToOne
public ArtifactGroup group = null;

### <a name="settings-class"></a>Settings Class
@OneToMany(mappedBy = "artifact")
public List<ArtifactVersion> versions = new ArrayList<>();
}
```

ElideStandalone is configured by implementing the ElideStandaloneSettings interface. Please see the ElideStandaloneSettings class for documentation about fields.
#### ArtifactVersion.java

Similarly, if you need other metadata across your application, it is important to note that the injector is bound with the following:
```java
@Include(type = "version")
@Entity
public class ArtifactVersion {
@Id
public String name = "";

```java
@Inject @Named("elideAllModels") Set<Class> entities;
public Date createdAt = new Date();

@ManyToOne
public ArtifactProduct artifact;
}
```

### Spin up the API

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][https://github.com/aklish/elide-heroku-example/blob/master/src/main/resources/db/changelog/changelog.xml] 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.

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.

### App & Settings

Bringing life to our API is trivially easy. We need two new classes: Main and Settings.

#### Main.java

```java
public class Main {
public static void main(String[] args) throws Exception {
ElideStandalone app = new ElideStandalone(new Settings());
app.start();
}
}
```

#### Settings.java

```java
public class Settings implements ElideStandaloneSettings {
/**
* Tells elide where our models live.
*/
@Override
public String getModelPackageName() {
return ArtifactGroup.class.getPackage().getName();
}

/**
* Configuration properties for how to talk to the database.
*/
@Override
public Properties getDatabaseProperties() {
Properties options = new Properties();

//Here we use H2 in memory instead of Postgres
options.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
options.put("javax.persistence.jdbc.driver", "org.h2.Driver");
options.put("javax.persistence.jdbc.url", "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1");
options.put("javax.persistence.jdbc.user", "sa");
options.put("javax.persistence.jdbc.password", "");

return options;
}
}
```

### Supporting Files

Elide standalone uses a JPA data store (the thing that talks to the database) that is [configured programmatically](https://github.com/aklish/elide-heroku-example/blob/master/src/main/java/example/Settings.java#L95-L111) (no persistence.xml required).

If you want to see the logs from your shiny new API, you will also want a [log4j config](https://github.com/aklish/elide-heroku-example/blob/master/src/main/resources/log4j2.xml).
Your log4j config should go in `src/main/resources` so log4j can find it.

### Running

With these new classes, you have two options for running your project. You can either run the `Main` class using your
favorite IDE, or you can run the service from the command line:

```mvn exec:java -Dexec.mainClass="example.Main"```

Our example requires the following environment variables to be set to work correctly with Heroku and Postgres.

1. JDBC_DATABASE_URL
2. JDBC_DATABASE_USERNAME
3. JDBC_DATABASE_PASSWORD

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 `Main` and `Settings` classes 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!

#### JSON-API

```curl
curl http://localhost:8080/api/v1/group
```

#### GraphQL
```curl
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:
#### JSON-API
```json
{
"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"
}
]
}
```
#### GraphQL
```json
{
"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"
}
}
]
}
}
}
```
### Looking at more data
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>
```
Likewise, you can inject the hk2 `ServiceLocator` if you wish to use injection throughout your application.
### Writing Data
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.
#### Inserting Data
Fortunately for us adding data is just as easy as reading data. For now let’s use cURL to put data in the database.
#### JSON-API
```
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"}}'
```
#### GraphQL
```
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 } } } } } } }" }'
```
### Modifying Data
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.
#### JSON-API
```curl
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"
}
}
}'
```
#### GraphQL
```curl
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 } } } } } } }"
}'
```
It’s just that easy to create and update data using Elide.
### <a name="filters"></a>Filters
Expand Down

0 comments on commit b86539e

Please sign in to comment.