GraphQL es un lenguaje de consulta moderno para recuperar datos de un servidor. Existe una gran cantidad de documentación sobre la creación de API para un montón de plataformas, pero desafortunadamente la documentación oficial de Java contiene solo un ejemplo, que implica la creación de una aplicación basada en Spring Framework . El ejemplo oculta algunos detalles de implementación, lo que obliga al usuario a leer la fuente. En este artículo, arreglaremos esto y crearemos un análogo en la combinación de Google Guice y Spark . Antes de continuar, le recomiendo que se familiarice con el tutorial original , porque No profundizaré en la arquitectura de la biblioteca y la descripción de las entidades Java GraphQL
1. Creando una aplicación Guice
Gradle. , gradle init
:
Select type of project to generate: application
Select implementation language: Java
Select build script DSL: Kotlin
Select test framework: JUnit Jupiter
Project name: guice-spark-graphql
Source package: guice.spark.graphql
Java src/main/java/guice/spark/graphql/App.java
build.gradle.kts
dependencies {
// This dependency is used by the application.
implementation("com.google.guava:guava:29.0-jre")
//Guice
implementation("com.google.inject:guice:5.0.1") //NEW
//Spark
implementation("com.sparkjava:spark-core:2.9.3") //NEW
implementation("com.sparkjava:spark-template-velocity:2.7.1") ////NEW spark template engine
implementation("org.slf4j:slf4j-simple:1.7.21") //NEW fix Spark SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
implementation("com.fasterxml.jackson.core:jackson-databind:2.12.3") //NEW
//graphql
implementation("com.graphql-java:graphql-java:16.2") //NEW
// Use JUnit Jupiter API for testing.
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2")
// Use JUnit Jupiter Engine for testing.
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.2")
}
Guice, GraphQL Spark Slf4j, Jackson Velocity Template. Guava , -
2. GraphQL
GraphQL , API. schema.graphqls
src/main/resources
type Query { bookById(id: ID): Book } type Book { id: ID name: String pageCount: Int author: Author } type Author { id: ID firstName: String lastName: String }
3. Guice
Guice DI src/main/java/guice/spark/graphql/App.java
public class App {
@Inject
private GraphQLService service;
public static void main(String[] args) {
App app = new App();
Injector injector = Guice.createInjector(new GraphQLModule());
injector.injectMembers(app);
app.service.initialize();
}
}
Guice Injector, GraphQLModule . injector.injectMembers(app)
service GraphQLService
, ,
4. GraphQLModule
Guice . src/main/java/guice/spark/graphql/GraphQLModule.java
public class GraphQLModule extends AbstractModule {
protected void configure() {
bind(GraphQLService.class).asEagerSingleton();
bind(GraphQL.class).toProvider(GraphQlProvider.class).asEagerSingleton();
bind(ObjectMapper.class).asEagerSingleton();
}
}
configure
:
GraphQLService
Spark c Spark, GraphQL
GraphQL
ObjectMapper
JSON
5. GraphQlProvider
, GraphQL
, Provider
Guice, . GraphQlProvider
public class GraphQlProvider implements Provider<GraphQL> {
private GraphQLDataFetchers graphQLDataFetchers;
@Inject
public GraphQlProvider(GraphQLDataFetchers graphQLDataFetchers) {
this.graphQLDataFetchers = graphQLDataFetchers;
}
@Override
public GraphQL get() {
URL url = Resources.getResource("schema.graphqls");
String sdl = null;
try {
sdl = Resources.toString(url, Charsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
GraphQLSchema graphQLSchema = buildSchema(sdl);
return GraphQL.newGraphQL(graphQLSchema).build();
}
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
.build();
}
}
get() , GraphQL . ,
6. GraphQLDataFetchers
DataFetcher
GraphQL Java. DataFetcher - .
src/main/java/guice/spark/graphql/GraphQLDataFetchers.java
@Singleton
public class GraphQLDataFetchers {
private static List<Map<String, String>> books = Arrays.asList(
ImmutableMap.of("id", "book-1",
"name", "Harry Potter and the Philosopher's Stone",
"pageCount", "223",
"authorId", "author-1"),
ImmutableMap.of("id", "book-2",
"name", "Moby Dick",
"pageCount", "635",
"authorId", "author-2"),
ImmutableMap.of("id", "book-3",
"name", "Interview with the vampire",
"pageCount", "371",
"authorId", "author-3")
);
private static List<Map<String, String>> authors = Arrays.asList(
ImmutableMap.of("id", "author-1",
"firstName", "Joanne",
"lastName", "Rowling"),
ImmutableMap.of("id", "author-2",
"firstName", "Herman",
"lastName", "Melville"),
ImmutableMap.of("id", "author-3",
"firstName", "Anne",
"lastName", "Rice")
);
public DataFetcher getBookByIdDataFetcher() {
return dataFetchingEnvironment -> {
String bookId = dataFetchingEnvironment.getArgument("id");
return books
.stream()
.filter(book -> book.get("id").equals(bookId))
.findFirst()
.orElse(null);
};
}
public DataFetcher getAuthorDataFetcher() {
return dataFetchingEnvironment -> {
Map<String, String> book = dataFetchingEnvironment.getSource();
String authorId = book.get("authorId");
return authors
.stream()
.filter(author -> author.get("id").equals(authorId))
.findFirst()
.orElse(null);
};
}
}
@Singleton
Guice - . configure
GraphQLModule
, -
7. GraphQLService
API web . GraphQLService
src/main/java/guice/spark/graphql/GraphQLService.java
public class GraphQLService {
private final GraphQL graphQL;
private final ObjectMapper mapper;
@Inject
public GraphQLService(GraphQL graphQL, ObjectMapper mapper) {
this.graphQL = graphQL;
this.mapper = mapper;
}
public void initialize() {
post("/graphql", (request, response) -> {
GraphQLRequestBody body = mapper.readValue(request.body(), GraphQLRequestBody.class);
String query = body.getQuery();
if (query == null) {
query = "";
}
ExecutionResult executionResult = graphQL.execute(
ExecutionInput.newExecutionInput()
.query(query)
.operationName(body.getOperationName())
.variables(body.getVariables())
.build()
);
response.type("application/json");
return mapper.writeValueAsString(executionResult.toSpecification());
});
get("/playground", (req, res) -> new VelocityTemplateEngine().render(
new ModelAndView(Collections.emptyMap(), "playground.html"))
);
}
initialize
Jetty web server 4567. :
POST http://localhost:4567/graphql ObjectMapper
GraphQLRequestBody
GraphQL . JSON .
GraphQLRequestBody:
public class GraphQLRequestBody {
private String query;
private String operationName;
private Map<String, Object> variables;
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public String getOperationName() {
return operationName;
}
public void setOperationName(String operationName) {
this.operationName = operationName;
}
public Map<String, Object> getVariables() {
return variables;
}
public void setVariables(Map<String, Object> variables) {
this.variables = variables;
}
}
GET http://localhost:4567/playground playground.html , src/main/resources
. API .
. http://localhost:4567/playground
API
. :
query {
bookById(id: "book-1") {
name,
author {
firstName
}
}
}
API:
{
"data": {
"bookById": {
"name": "Harry Potter and the Philosopher's Stone",
"author": {
"firstName": "Joanne"
}
}
}
}