Add graphQL sample application

This commit adds a sample application to the repository.
This application should not be published as part of the build.

In this application, we're trying to showcase the various features and
use cases with our Spring graphQL integration. We're mixing here data
fetchers backed by datastores or remote hypermedia APIs.

Closes gh-15
This commit is contained in:
Brian Clozel
2020-11-13 11:52:30 +01:00
parent f711dad1b6
commit c78f05626a
15 changed files with 432 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
plugins {
id 'org.springframework.boot' version '2.4.0-SNAPSHOT'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
description = "GraphQL sample application"
sourceCompatibility = '1.8'
dependencies {
implementation project(':spring-graphql-web')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.sample.graphql;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GraphQLSampleApplication {
public static void main(String[] args) {
SpringApplication.run(GraphQLSampleApplication.class, args);
}
}

View File

@@ -0,0 +1,56 @@
package io.spring.sample.graphql.project;
import java.util.List;
public class Project {
private String slug;
private String name;
private String repositoryUrl;
private ProjectStatus status;
private List<Release> releases;
public String getSlug() {
return this.slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getRepositoryUrl() {
return this.repositoryUrl;
}
public void setRepositoryUrl(String repositoryUrl) {
this.repositoryUrl = repositoryUrl;
}
public ProjectStatus getStatus() {
return this.status;
}
public void setStatus(ProjectStatus status) {
this.status = status;
}
public List<Release> getReleases() {
return this.releases;
}
public void setReleases(List<Release> releases) {
this.releases = releases;
}
}

View File

@@ -0,0 +1,32 @@
package io.spring.sample.graphql.project;
import graphql.schema.idl.RuntimeWiring;
import org.springframework.boot.graphql.RuntimeWiringCustomizer;
import org.springframework.stereotype.Component;
@Component
public class ProjectDataWiring implements RuntimeWiringCustomizer {
private final SpringProjectsClient client;
public ProjectDataWiring(SpringProjectsClient client) {
this.client = client;
}
@Override
public void customize(RuntimeWiring.Builder builder) {
builder
.type("QueryType", typeWiring ->
typeWiring.dataFetcher("project", env -> {
String slug = env.getArgument("slug");
return client.fetchProject(slug);
}))
.type("Project", typeWiring ->
typeWiring.dataFetcher("releases", env -> {
Project project = env.getSource();
return client.fetchProjectReleases(project.getSlug());
})
);
}
}

View File

@@ -0,0 +1,16 @@
package io.spring.sample.graphql.project;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonCreator;
public enum ProjectStatus {
ACTIVE, COMMUNITY, INCUBATING, ATTIC;
@JsonCreator
public static ProjectStatus fromName(String name) {
return Arrays.stream(ProjectStatus.values())
.filter(type -> type.name().equals(name))
.findFirst().orElse(ProjectStatus.ACTIVE);
}
}

View File

@@ -0,0 +1,54 @@
package io.spring.sample.graphql.project;
public class Release {
private String version;
private ReleaseStatus status;
private String referenceDocUrl;
private String apiDocUrl;
private boolean current;
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public ReleaseStatus getStatus() {
return this.status;
}
public void setStatus(ReleaseStatus status) {
this.status = status;
}
public String getReferenceDocUrl() {
return this.referenceDocUrl;
}
public void setReferenceDocUrl(String referenceDocUrl) {
this.referenceDocUrl = referenceDocUrl;
}
public String getApiDocUrl() {
return this.apiDocUrl;
}
public void setApiDocUrl(String apiDocUrl) {
this.apiDocUrl = apiDocUrl;
}
public boolean isCurrent() {
return this.current;
}
public void setCurrent(boolean current) {
this.current = current;
}
}

View File

@@ -0,0 +1,16 @@
package io.spring.sample.graphql.project;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonCreator;
public enum ReleaseStatus {
GENERAL_AVAILABILITY, MILESTONE, SNAPSHOT;
@JsonCreator
public static ReleaseStatus fromName(String name) {
return Arrays.stream(ReleaseStatus.values())
.filter(type -> type.name().equals(name))
.findFirst().orElse(ReleaseStatus.GENERAL_AVAILABILITY);
}
}

View File

@@ -0,0 +1,47 @@
package io.spring.sample.graphql.project;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.client.Hop;
import org.springframework.hateoas.client.Traverson;
import org.springframework.hateoas.server.core.TypeReferences;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component
public class SpringProjectsClient {
private static final TypeReferences.CollectionModelType<Release> releaseCollection =
new TypeReferences.CollectionModelType<Release>() { };
private final Traverson traverson;
public SpringProjectsClient(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.messageConverters(Traverson.getDefaultMessageConverters(MediaTypes.HAL_JSON)).build();
this.traverson = new Traverson(URI.create("https://spring.io/api/"), MediaTypes.HAL_JSON);
this.traverson.setRestOperations(restTemplate);
}
public Project fetchProject(String projectSlug) {
return this.traverson.follow("projects")
.follow(Hop.rel("project").withParameter("id", projectSlug))
.toObject(Project.class);
}
public List<Release> fetchProjectReleases(String projectSlug) {
CollectionModel<Release> releases = this.traverson.follow("projects")
.follow(Hop.rel("project").withParameter("id", projectSlug))
.follow(Hop.rel("releases"))
.toObject(releaseCollection);
return new ArrayList(releases.getContent());
}
}

View File

@@ -0,0 +1,7 @@
package io.spring.sample.graphql.repository;
import org.springframework.data.repository.CrudRepository;
public interface ArtifactRepositories extends CrudRepository<ArtifactRepository, String> {
}

View File

@@ -0,0 +1,28 @@
package io.spring.sample.graphql.repository;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class ArtifactRepositoriesInitializer implements ApplicationRunner {
private final ArtifactRepositories repositories;
public ArtifactRepositoriesInitializer(ArtifactRepositories repositories) {
this.repositories = repositories;
}
@Override
public void run(ApplicationArguments args) throws Exception {
List<ArtifactRepository> repositoryList = Arrays.asList(
new ArtifactRepository("spring-releases", "Spring Releases", "https://repo.spring.io/libs-releases"),
new ArtifactRepository("spring-milestones", "Spring Milestones", "https://repo.spring.io/libs-milestones"),
new ArtifactRepository("spring-snapshots", "Spring Snapshots", "https://repo.spring.io/libs-snapshots")
);
repositories.saveAll(repositoryList);
}
}

View File

@@ -0,0 +1,58 @@
package io.spring.sample.graphql.repository;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class ArtifactRepository {
@Id
private String id;
private String name;
private String url;
private boolean snapshotsEnabled;
public ArtifactRepository(String id, String name, String url) {
this.id = id;
this.name = name;
this.url = url;
}
public ArtifactRepository() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isSnapshotsEnabled() {
return snapshotsEnabled;
}
public void setSnapshotsEnabled(boolean snapshotsEnabled) {
this.snapshotsEnabled = snapshotsEnabled;
}
}

View File

@@ -0,0 +1,23 @@
package io.spring.sample.graphql.repository;
import graphql.schema.idl.RuntimeWiring;
import org.springframework.boot.graphql.RuntimeWiringCustomizer;
import org.springframework.stereotype.Component;
@Component
public class ArtifactRepositoryDataWiring implements RuntimeWiringCustomizer {
private final ArtifactRepositories repositories;
public ArtifactRepositoryDataWiring(ArtifactRepositories repositories) {
this.repositories = repositories;
}
@Override
public void customize(RuntimeWiring.Builder builder) {
builder.type("QueryType", typeWiring -> typeWiring
.dataFetcher("artifactRepositories", env -> this.repositories.findAll())
.dataFetcher("artifactRepository", env -> this.repositories.findById(env.getArgument("id"))));
}
}

View File

@@ -0,0 +1 @@
management.endpoints.web.exposure.include=health,metrics,info

View File

@@ -0,0 +1,43 @@
schema {
query: QueryType
}
type QueryType {
artifactRepositories : [ArtifactRepository]
artifactRepository(id : ID!) : ArtifactRepository
project(slug: ID!): Project
}
type ArtifactRepository {
id: ID!
name: String!
url: String!
snapshotsEnabled: Boolean
}
type Project {
slug: ID!
name: String!
repositoryUrl: String!
status: ProjectStatus!
releases: [Release]
}
type Release {
version: String!
status: ReleaseStatus!
current: Boolean
}
enum ProjectStatus {
ACTIVE
COMMUNITY
INCUBATING
ATTIC
}
enum ReleaseStatus {
GENERAL_AVAILABILITY
MILESTONE
SNAPSHOT
}

View File

@@ -15,3 +15,4 @@ pluginManagement {
rootProject.name = 'spring-graphql'
include 'spring-graphql-web'
include 'graphql-sample'