рдХреЛрдЯрд▓рд┐рди рдФрд░ рдорд╛рдЗрдХреНрд░реЛрдПрдиреЗрдЯ рдкрд░ рдЧреНрд░рд╛рдлрдХреНрдпреВрдПрд▓ рдХреИрд╕реЗ рдХрд░реЗрдВ рдФрд░ рдХрдИ рдорд╛рдЗрдХреНрд░реЛрд╕рд░реНрд╡рд┐рд╕ рдХреЗ рдПрдкреАрдЖрдИ рдХреЗ рд▓рд┐рдП рдПрдХ рдПрдХрд▓ рдПрдХреНрд╕реЗрд╕ рдкреНрд╡рд╛рдЗрдВрдЯ рдмрдирд╛рдПрдВ

рдЧреНрд░рд╛рдлрдХреЙрд▓ рдлреЗрд╕рдмреБрдХ рджреНрд╡рд╛рд░рд╛ рд╡рд┐рдХрд╕рд┐рдд рдПрдХ рдПрдкреАрдЖрдИ рдХреНрд╡реЗрд░реА рд▓реИрдВрдЧреНрд╡реЗрдЬ рд╣реИред рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдЬреЗрд╡реАрдПрдо рдкрд░ рдЧреНрд░рд╛рдлрдХреНрд▓рд┐рди рдПрдкреАрдЖрдИ рдХреЗ рдЙрджрд╛рд╣рд░рдг рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдкрд░ рдЪрд░реНрдЪрд╛ рдХреА рдЬрд╛рдПрдЧреА, рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ, рдХреЛрдЯрд▓рд┐рди рднрд╛рд╖рд╛ рдФрд░ рдорд╛рдЗрдХреНрд░реЛрдиреЙрдЯ рдлреНрд░реЗрдорд╡рд░реНрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ ; рдЕрдзрд┐рдХрд╛рдВрд╢ рдЙрджрд╛рд╣рд░рдг рдЕрдиреНрдп рдЬрд╛рд╡рд╛ / рдХреЛрдЯрд▓рд┐рди рдлреНрд░реЗрдорд╡рд░реНрдХ рдкрд░ рдкреБрди: рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛ рд╕рдХрддреЗ рд╣реИрдВред рдлрд┐рд░ рдпрд╣ рджрд┐рдЦрд╛рдпрд╛ рдЬрд╛рдПрдЧрд╛ рдХрд┐ рд╕рднреА рдбреЗрдЯрд╛ рд╕реНрд░реЛрддреЛрдВ рддрдХ рдкрд╣реБрдВрдЪ рдХреЗ рд▓рд┐рдП рдПрдХ рд╕рд╛рдорд╛рдиреНрдп рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдкреНрд░рджрд╛рди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╣реА рдбреЗрдЯрд╛ рдЧреНрд░рд╛рдлрд╝ рдореЗрдВ рдХрдИ рдЧреНрд░рд╛рдлрд╝рдХреЙрд▓ рд╕реЗрд╡рд╛рдУрдВ рдХреЛ рдХреИрд╕реЗ рд╕рдВрдпреЛрдЬрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПред рдпрд╣ рдЕрдкреЛрд▓реЛ рд╕рд░реНрд╡рд░ рдФрд░ рдЕрдкреЛрд▓реЛ рдлреЗрдбрд░реЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ ред рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк, рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╡рд╛рд╕реНрддреБрдХрд▓рд╛ рдкреНрд░рд╛рдкреНрдд рдХреА рдЬрд╛рдПрдЧреА:


рдЖрд░реНрдХрд┐рдЯреЗрдХреНрдЪрд░


рдЖрд░реНрдХрд┐рдЯреЗрдХреНрдЪрд░ рдХрд╛ рдкреНрд░рддреНрдпреЗрдХ рдШрдЯрдХ рдХрдИ рдореБрджреНрджреЛрдВ рдкрд░ рдкреНрд░рдХрд╛рд╢ рдбрд╛рд▓рддрд╛ рд╣реИ рдЬреЛ рдХрд┐ рдЧреНрд░рд╛рдлрдХреЙрд▓ рдПрдкреАрдЖрдИ рдХреЗ рд╡рд┐рдХрд╛рд╕ рдХреЗ рджреМрд░рд╛рди рдЙрддреНрдкрдиреНрди рд╣реЛ рд╕рдХрддреЗ рд╣реИрдВред рдбреЛрдореЗрди рдореЙрдбрд▓ рдореЗрдВ рд╕реМрд░ рдордВрдбрд▓ рдХреЗ рдЧреНрд░рд╣реЛрдВ рдФрд░ рдЙрдирдХреЗ рдЙрдкрдЧреНрд░рд╣реЛрдВ рдХрд╛ рдбреЗрдЯрд╛ рд╢рд╛рдорд┐рд▓ рд╣реИред


:



Planet service


, GraphQL, :


implementation("io.micronaut.graphql:micronaut-graphql:$micronautGraphQLVersion")
implementation("io.gqljf:graphql-java-federation:$graphqlJavaFederationVersion")

( )


GraphQL Java Micronaut, , , , GraphQL . Spring and Micronaut; GET POST /graphql. тАФ , GraphQL Java Apollo Federation.


GraphQL Schema Definition Language (SDL) :


type Query {
    planets: [Planet!]!
    planet(id: ID!): Planet
    planetByName(name: String!): Planet
}

type Mutation {
    createPlanet(name: String!, type: Type!, details: DetailsInput!): Planet!
}

type Subscription {
    latestPlanet: Planet!
}

type Planet @key(fields: "id") {
    id: ID!
    name: String!
    # from an astronomical point of view
    type: Type!
    isRotatingAroundSun: Boolean! @deprecated(reason: "Now it is not in doubt. Do not use this field")
    details: Details!
}

interface Details {
    meanRadius: Float!
    mass: BigDecimal!
}

type InhabitedPlanetDetails implements Details {
    meanRadius: Float!
    mass: BigDecimal!
    # in billions
    population: Float!
}

type UninhabitedPlanetDetails implements Details {
    meanRadius: Float!
    mass: BigDecimal!
}

enum Type {
    TERRESTRIAL_PLANET
    GAS_GIANT
    ICE_GIANT
    DWARF_PLANET
}

input DetailsInput {
    meanRadius: Float!
    mass: MassInput!
    population: Float
}

input MassInput {
    number: Float!
    tenPower: Int!
}

scalar BigDecimal

Planet service ( )


Planet.id ID, 5- . GraphQL Java . , null, ( Kotlin GraphQL nullable ). @directiveтАЩ . . IntelliJ IDEA, JS GraphQL plugin .


GraphQL API:


  • schema-first


    (, , API),


  • code-first




; . schema-first . .


Micronaut , GraphQL IDE тАФ GraphiQL тАФ GraphQL :


graphql:
  graphiql:
    enabled: true

GraphiQL ( )


Main :


object PlanetServiceApplication {

    @JvmStatic
    fun main(args: Array<String>) {
        Micronaut.build()
            .packages("io.graphqlfederation.planetservice")
            .mainClass(PlanetServiceApplication.javaClass)
            .start()
    }
}

Main ( )


GraphQL :


@Bean
@Singleton
fun graphQL(resourceResolver: ResourceResolver): GraphQL {
    val schemaInputStream = resourceResolver.getResourceAsStream("classpath:schema.graphqls").get()
    val transformedGraphQLSchema = FederatedSchemaBuilder()
        .schemaInputStream(schemaInputStream)
        .runtimeWiring(createRuntimeWiring())
        .excludeSubscriptionsFromApolloSdl(true)
        .build()

    return GraphQL.newGraphQL(transformedGraphQLSchema)
        .instrumentation(
            ChainedInstrumentation(
                listOf(
                    FederatedTracingInstrumentation()
                    // uncomment if you need to enable the instrumentations. but this may affect showing documentation in a GraphQL client
                    // MaxQueryComplexityInstrumentation(50),
                    // MaxQueryDepthInstrumentation(5)
                )
            )
        )
        .build()
}

GraphQL ( )


FederatedSchemaBuilder Apollo Federation. GraphQL Java , (. ).


RuntimeWiring тАФ data fetcherтАЩ, type resolverтАЩ , GraphQLSchema; :


private fun createRuntimeWiring(): RuntimeWiring {
    val detailsTypeResolver = TypeResolver { env ->
        when (val details = env.getObject() as DetailsDto) {
            is InhabitedPlanetDetailsDto -> env.schema.getObjectType("InhabitedPlanetDetails")
            is UninhabitedPlanetDetailsDto -> env.schema.getObjectType("UninhabitedPlanetDetails")
            else -> throw RuntimeException("Unexpected details type: ${details.javaClass.name}")
        }
    }

    return RuntimeWiring.newRuntimeWiring()
        .type("Query") { builder ->
            builder
                .dataFetcher("planets", planetsDataFetcher)
                .dataFetcher("planet", planetDataFetcher)
                .dataFetcher("planetByName", planetByNameDataFetcher)
        }
        .type("Mutation") { builder ->
            builder.dataFetcher("createPlanet", createPlanetDataFetcher)
        }
        .type("Subscription") { builder ->
            builder.dataFetcher("latestPlanet", latestPlanetDataFetcher)
        }
        .type("Planet") { builder ->
            builder.dataFetcher("details", detailsDataFetcher)
        }
        .type("Details") { builder ->
            builder.typeResolver(detailsTypeResolver)
        }
        .type("Type") { builder ->
            builder.enumValues(NaturalEnumValuesProvider(Planet.Type::class.java))
        }
        .build()
}

RuntimeWiring ( )


root- Query ( root- Mutation Subscription), , planets, , DataFetcher:


@Singleton
class PlanetsDataFetcher(
    private val planetService: PlanetService,
    private val planetConverter: PlanetConverter
) : DataFetcher<List<PlanetDto>> {
    override fun get(env: DataFetchingEnvironment): List<PlanetDto> = planetService.getAll()
        .map { planetConverter.toDto(it) }
}

PlanetsDataFetcher ( )


env . DTO, :


@Singleton
class PlanetConverter : GenericConverter<Planet, PlanetDto> {
    override fun toDto(entity: Planet): PlanetDto {
        val details = DetailsDto(id = entity.detailsId)

        return PlanetDto(
            id = entity.id,
            name = entity.name,
            type = entity.type,
            details = details
        )
    }
}

PlanetConverter ( )


GenericConverter тАФ Entity тЖТ DTO. , details тАФ , , API. , details id. , RuntimeWiring details Planet DataFetcher, ( details.id, ):


@Singleton
class DetailsDataFetcher : DataFetcher<CompletableFuture<DetailsDto>> {

    private val log = LoggerFactory.getLogger(this.javaClass)

    override fun get(env: DataFetchingEnvironment): CompletableFuture<DetailsDto> {
        val planetDto = env.getSource<PlanetDto>()
        log.info("Resolve `details` field for planet: ${planetDto.name}")

        val dataLoader: DataLoader<Long, DetailsDto> = env.getDataLoader("details")

        return dataLoader.load(planetDto.details.id)
    }
}

DetailsDataFetcher ( )


, CompletableFuture . Details DetailsService, , N+1: , :


{
  planets {
    name
    details {
      meanRadius
    }
  }
}

GraphQL


details SQL . java-dataloader; BatchLoader DataLoaderRegistry:


// bean's scope is `Singleton`, because `BatchLoader` is stateless
@Bean
@Singleton
fun detailsBatchLoader(): BatchLoader<Long, DetailsDto> = BatchLoader { keys ->
    CompletableFuture.supplyAsync {
        detailsService.getByIds(keys)
            .map { detailsConverter.toDto(it) }
    }
}

// bean's (default) scope is `Prototype`, because `DataLoader` is stateful
@Bean
fun dataLoaderRegistry() = DataLoaderRegistry().apply {
    val detailsDataLoader = DataLoader.newDataLoader(detailsBatchLoader())
    register("details", detailsDataLoader)
}

BatchLoader DataLoaderRegistry ( )


BatchLoader Details . , SQL N+1. , GraphQL , SQL . BatchLoader stateless , . DataLoader BatchLoader; stateful, , DataLoaderRegistry. - GraphQL , . GraphQL Java.


Details GraphQL , RuntimeWiring TypeResolver, GraphQL DTO :


val detailsTypeResolver = TypeResolver { env ->
    when (val details = env.getObject() as DetailsDto) {
        is InhabitedPlanetDetailsDto -> env.schema.getObjectType("InhabitedPlanetDetails")
        is UninhabitedPlanetDetailsDto -> env.schema.getObjectType("UninhabitedPlanetDetails")
        else -> throw RuntimeException("Unexpected details type: ${details.javaClass.name}")
    }
}

TypeResolver ( )


enumтАЩ :


.type("Type") { builder ->
    builder.enumValues(NaturalEnumValuesProvider(Planet.Type::class.java))
}

enum ( )


http://localhost:8082/graphiql GraphiQL IDE, , ; IDE 3 : (query/mutation/subscription), :


graphiql


GraphQL IDE, , GraphQL Playground Altair ( desktop , web-). :


рдЕрд▓реНрдЯреЗрдпрд░


query, , : _service _entities. , GraphQL Java Apollo Federation; .


Planet, :


рд╡реЗрджреА рдбреЙрдХреНрд╕


type, @deprecated isRotatingAroundSun .


:


type Mutation {
    createPlanet(name: String!, type: Type!, details: DetailsInput!): Planet!
}

( )


query, . , , input type:


input DetailsInput {
    meanRadius: Float!
    mass: MassInput!
    population: Float
}

input MassInput {
    number: Float!
    tenPower: Int!
}

Input


Query, DataFetcher:


@Singleton
class CreatePlanetDataFetcher(
    private val objectMapper: ObjectMapper,
    private val planetService: PlanetService,
    private val planetConverter: PlanetConverter
) : DataFetcher<PlanetDto> {

    private val log = LoggerFactory.getLogger(this.javaClass)

    override fun get(env: DataFetchingEnvironment): PlanetDto {
        log.info("Trying to create planet")

        val name = env.getArgument<String>("name")
        val type = env.getArgument<Planet.Type>("type")
        val detailsInputDto = objectMapper.convertValue(env.getArgument("details"), DetailsInputDto::class.java)

        val newPlanet = planetService.create(
            name,
            type,
            detailsInputDto.meanRadius,
            detailsInputDto.mass.number,
            detailsInputDto.mass.tenPower,
            detailsInputDto.population
        )

        return planetConverter.toDto(newPlanet)
    }
}

DataFetcher ( )


, - . subscription:


type Subscription {
    latestPlanet: Planet!
}

Subscription ( )


DataFetcher subscription Publisher:


@Singleton
class LatestPlanetDataFetcher(
    private val planetService: PlanetService,
    private val planetConverter: PlanetConverter
) : DataFetcher<Publisher<PlanetDto>> {

    override fun get(environment: DataFetchingEnvironment) = planetService.getLatestPlanet().map { planetConverter.toDto(it) }
}

DataFetcher subscription ( )


mutation subscription GraphQL IDE IDE; ( IDE subscription URL ws://localhost:8082/graphql-ws):


subscription {
  latestPlanet {
    name
    type
  }
}

subscription


:


mutation {
  createPlanet(
    name: "Pluto"
    type: DWARF_PLANET
    details: { meanRadius: 50.0, mass: { number: 0.0146, tenPower: 24 } }
  ) {
    id
  }
}

mutation


:


рдореНрдпреВрдЯреЗрд╢рди рд╕рджрд╕реНрдпрддрд╛


SubscriptionтАЩ Micronaut :


graphql:
  graphql-ws:
    enabled: true

GraphQL WebSocket ( )


subscriptionтАЩ Micronaut тАФ chat application. GraphQL Java.


query mutation :


@Test
fun testPlanets() {
    val query = """
        {
            planets {
                id
                name
                type
                details {
                    meanRadius
                    mass
                    ... on InhabitedPlanetDetails {
                        population
                    }
                }
            }
        }
    """.trimIndent()

    val response = graphQLClient.sendRequest(query, object : TypeReference<List<PlanetDto>>() {})

    assertThat(response, hasSize(8))
    assertThat(
        response, contains(
            hasProperty("name", `is`("Mercury")),
            hasProperty("name", `is`("Venus")),
            hasProperty("name", `is`("Earth")),
            hasProperty("name", `is`("Mars")),
            hasProperty("name", `is`("Jupiter")),
            hasProperty("name", `is`("Saturn")),
            hasProperty("name", `is`("Uranus")),
            hasProperty("name", `is`("Neptune"))
        )
    )
}

query ( )


query query, :


private val planetFragment = """
    fragment planetFragment on Planet {
        id
        name
        type
        details {
            meanRadius
            mass
            ... on InhabitedPlanetDetails {
                population
            }
        }
    }
""".trimIndent()

@Test
fun testPlanetById() {
    val earthId = 3
    val query = """
        {
            planet(id: $earthId) {
                ... planetFragment
            }
        }

        $planetFragment
    """.trimIndent()

    val response = graphQLClient.sendRequest(query, object : TypeReference<PlanetDto>() {})

    // assertions
}

Query ( )


variables, :


@Test
fun testPlanetByName() {
    val variables = mapOf("name" to "Earth")
    val query = """
        query testPlanetByName(${'$'}name: String!){
            planetByName(name: ${'$'}name) {
                ... planetFragment
            }
        }

        $planetFragment
    """.trimIndent()

    val response = graphQLClient.sendRequest(query, variables, null, object : TypeReference<PlanetDto>() {})

    // assertions
}

Query ( )


, . . Kotlin raw strings, string templates, , $ ( GraphQL) ${'$'}.


GraphQLClient тАФ (framework-agnostic OkHttp). Java GraphQL , , Apollo GraphQL Client for Android and the JVM, .


in-memory H2 ORM Hibernate, micronaut-data-hibernate-jpa. .


Auth service


GraphQL . JWT. Auth service JWT query mutation:


type Query {
    validateToken(token: String!): Boolean!
}

type Mutation {
    signIn(data: SignInData!): SignInResponse!
}

input SignInData {
    username: String!
    password: String!
}

type SignInResponse {
    username: String!
    token: String!
}

Auth service ( )


JWT, GraphQL IDE (Auth service URL http://localhost:8081/graphql):


mutation {
  signIn(data: {username: "john_doe", password: "password"}) {
    token
  }
}

JWT


Authorization ( Altair GraphQL Playground IDE) ; . Bearer $JWT.


JWT micronaut-security-jwt.


Satellite service


:


type Query {
    satellites: [Satellite!]!
    satellite(id: ID!): Satellite
    satelliteByName(name: String!): Satellite
}

type Satellite {
    id: ID!
    name: String!
    lifeExists: LifeExists!
    firstSpacecraftLandingDate: Date
}

type Planet @key(fields: "id") @extends {
    id: ID! @external
    satellites: [Satellite!]!
}

enum LifeExists {
    YES,
    OPEN_QUESTION,
    NO_DATA
}

scalar Date

Satellite service ( )


, Satellite lifeExists . , , GraphQL query/mutation/subscription , . . GraphQL /graphql. , тАФ GraphQL-specific , ( ):


micronaut:
  security:
    enabled: true
    intercept-url-map:
      - pattern: /graphql
        httpMethod: POST
        access:
          - isAnonymous()
      - pattern: /graphiql
        httpMethod: GET
        access:
          - isAnonymous()

Security ( )


DataFetcher, :


@Singleton
class LifeExistsDataFetcher(
    private val satelliteService: SatelliteService
) : DataFetcher<Satellite.LifeExists> {
    override fun get(env: DataFetchingEnvironment): Satellite.LifeExists {
        val id = env.getSource<SatelliteDto>().id
        return satelliteService.getLifeExists(id)
    }
}

LifeExistsDataFetcher ( )


:


@Singleton
class SatelliteService(
    private val repository: SatelliteRepository,
    private val securityService: SecurityService
) {

    // other stuff

    fun getLifeExists(id: Long): Satellite.LifeExists {
        val userIsAuthenticated = securityService.isAuthenticated
        if (userIsAuthenticated) {
            return repository.findById(id)
                .orElseThrow { RuntimeException("Can't find satellite by id=$id") }
                .lifeExists
        } else {
            throw RuntimeException("`lifeExists` property can only be accessed by authenticated users")
        }
    }
}

SatelliteService ( )


Authorization JWT (. ):


{
  satellite(id: "1") {
    name
    lifeExists
  }
}


. ( Base64 ):


micronaut:
  security:
    token:
      jwt:
        enabled: true
        signatures:
          secret:
            validation:
              base64: true
              # In real life, the secret should NOT be under source control (instead of it, for example, in environment variable).
              # It is here just for simplicity.
              secret: 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdA=='
              jws-algorithm: HS256

JWT ( )


. JWT ( validateToken, ).


Date, DateTime GraphQL Java graphql-java-extended-scalars (com.graphql-java:graphql-java-extended-scalars:$graphqlJavaExtendedScalarsVersion -). (scalar Date) :


private fun createRuntimeWiring(): RuntimeWiring = RuntimeWiring.newRuntimeWiring()
    // other stuff
    .scalar(ExtendedScalars.Date)
    .build()

( )


:


{
  satelliteByName(name: "Moon") {
    firstSpacecraftLandingDate
  }
}

Request


{
  "data": {
    "satelliteByName": {
      "firstSpacecraftLandingDate": "1959-09-13"
    }
  }
}

Response


GraphQL API (. ). , :


{
  planet(id: "1") {
    star {
      planets {
        star {
          planets {
            star {
              ... # more deep nesting!
            }
          }
        }
      }
    }
  }
}

тАЬтАЭ query


, MaxQueryDepthInstrumentation. query MaxQueryComplexityInstrumentation; FieldComplexityCalculator, . ( FieldComplexityCalculator , , тАФ , 1):


return GraphQL.newGraphQL(transformedGraphQLSchema)
    // other stuff
    .instrumentation(
        ChainedInstrumentation(
            listOf(
                FederatedTracingInstrumentation(),
                MaxQueryComplexityInstrumentation(50, FieldComplexityCalculator { env, child ->
                    1 + child
                }),
                MaxQueryDepthInstrumentation(5)
            )
        )
    )
    .build()

( )


, MaxQueryDepthInstrumentation / MaxQueryComplexityInstrumentation, IDE, . . IDE IntrospectionQuery, ( GitHub). FederatedTracingInstrumentation , Apollo Gateway ( Apollo Graph Manager; , ). GraphQL Java.


GraphQL . -, , Micronaut:


@Singleton
// mark it as primary to override the default one
@Primary
class HeaderValueProviderGraphQLExecutionInputCustomizer : DefaultGraphQLExecutionInputCustomizer() {

    override fun customize(executionInput: ExecutionInput, httpRequest: HttpRequest<*>): Publisher<ExecutionInput> {
        val context = HTTPRequestHeaders { headerName ->
            httpRequest.headers[headerName]
        }

        return Publishers.just(executionInput.transform {
            it.context(context)
        })
    }
}

GraphQLExecutionInputCustomizer ( )


FederatedTracingInstrumentation , , , Apollo Server , .


:


@Singleton
class CustomDataFetcherExceptionHandler : SimpleDataFetcherExceptionHandler() {

    private val log = LoggerFactory.getLogger(this.javaClass)

    override fun onException(handlerParameters: DataFetcherExceptionHandlerParameters): DataFetcherExceptionHandlerResult {
        val exception = handlerParameters.exception
        log.error("Exception while GraphQL data fetching", exception)

        val error = object : GraphQLError {
            override fun getMessage(): String = "There was an error: ${exception.message}"

            override fun getErrorType(): ErrorType? = null

            override fun getLocations(): MutableList<SourceLocation>? = null
        }

        return DataFetcherExceptionHandlerResult.newResult().error(error).build()
    }
}

( )


тАФ GraphQL (Planet) ( ) Apollo Server. Planet Planet service :


type Planet @key(fields: "id") {
    id: ID!
    name: String!
    # from an astronomical point of view
    type: Type!
    isRotatingAroundSun: Boolean! @deprecated(reason: "Now it is not in doubt. Do not use this field")
    details: Details!
}

Planet Planet service ( )


Satellite service Planet satellites (, , non-nullable non-nullable ):


type Satellite {
    id: ID!
    name: String!
    lifeExists: LifeExists!
    firstSpacecraftLandingDate: Date
}

type Planet @key(fields: "id") @extends {
    id: ID! @external
    satellites: [Satellite!]!
}

Planet Satellite service ( )


Apollo Federation Planet тАФ entity тАФ , ( Satellite service, stub Planet). @key , , . @extends , Planet тАФ , ( Planet service). Apollo Federation Apollo.


Apollo Federation; GraphQL Java, :



API; GitHub.


, Planet FederatedEntityResolver ( , , Planet.satellites); FederatedSchemaBuilder:


@Bean
@Singleton
fun graphQL(resourceResolver: ResourceResolver): GraphQL {

    // other stuff

    val planetEntityResolver = object : FederatedEntityResolver<Long, PlanetDto>("Planet", { id ->
        log.info("`Planet` entity with id=$id was requested")
        val satellites = satelliteService.getByPlanetId(id)
        PlanetDto(id = id, satellites = satellites.map { satelliteConverter.toDto(it) })
    }) {}

    val transformedGraphQLSchema = FederatedSchemaBuilder()
        .schemaInputStream(schemaInputStream)
        .runtimeWiring(createRuntimeWiring())
        .federatedEntitiesResolvers(listOf(planetEntityResolver))
        .build()

    // other stuff
}

GraphQL Satellite service ( )


query (_service and _entities), Apollo Server. query , Apollo ServerтАЩ. Apollo Federation - standalone-. API .


Apollo Server


Apollo Server Apollo Federation :


  • GraphQL




, , frontend- , .


тАФ schema stitching тАФ Apollo deprecated. , , : Nadel. GraphQL Java Apollo Federation; .


:


{
  "name": "api-gateway",
  "main": "gateway.js",
  "scripts": {
    "start-gateway": "nodemon gateway.js"
  },
  "devDependencies": {
    "concurrently": "5.1.0",
    "nodemon": "2.0.2"
  },
  "dependencies": {
    "@apollo/gateway": "0.12.0",
    "apollo-server": "2.10.0",
    "graphql": "14.6.0"
  }
}

-, ( )


const {ApolloServer} = require("apollo-server");
const {ApolloGateway, RemoteGraphQLDataSource} = require("@apollo/gateway");

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
    willSendRequest({request, context}) {
        request.http.headers.set('Authorization', context.authHeaderValue);
    }
}

const gateway = new ApolloGateway({
    serviceList: [
        {name: "auth-service", url: "http://localhost:8081/graphql"},
        {name: "planet-service", url: "http://localhost:8082/graphql"},
        {name: "satellite-service", url: "http://localhost:8083/graphql"}
    ],
    buildService({name, url}) {
        return new AuthenticatedDataSource({url});
    },
});

const server = new ApolloServer({
    gateway, subscriptions: false, context: ({req}) => ({
        authHeaderValue: req.headers.authorization
    })
});

server.listen().then(({url}) => {
    console.log(` Server ready at ${url}`);
});

Apollo Server ( )


, ( ); , .


, ( Authorization ). security, , JWT apollo-server.


, 3 GraphQL Java , , cd apollo-server, :


npm install
npm run start-gateway

:


[nodemon] 2.0.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node gateway.js`
  Server ready at http://localhost:4000/
[INFO] Sat Feb 15 2020 13:22:37 GMT+0300 (Moscow Standard Time) apollo-gateway: Gateway successfully loaded schema.
        * Mode: unmanaged

Apollo Server


GraphQL :


рд╡реЗрджреА рдЕрдкреЛрд▓реЛ рд╕рд░реНрд╡рд░


http://localhost:4000/playground Playground IDE.


, , query MaxQueryComplexityInstrumentation / MaxQueryDepthInstrumentation , GraphQL IDE . , Apollo Server query { _service { sdl } } IntrospectionQuery.


, :


  • subscriptionтАЩ Apollo GatewayтАЩ ( - standalone GraphQL Java )


    Planet service .excludeSubscriptionsFromApolloSdl(true).


  • , GraphQL ,



, /, downstream Apollo ServerтАЩ, Federation; , Apollo.



GraphQL JVM. API GraphQL Java GraphQL API; . Apollo Server, Apollo Federation graphql-java-federation. GitHub. !




All Articles