Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Take Control Of Your APIs With GraphQL

Take Control Of Your APIs With GraphQL

Introducing GraphQL APIs and the benefits of using them, and how to interface with these APIs on Android.

Adam McNeilly

April 21, 2020
Tweet

More Decks by Adam McNeilly

Other Decks in Programming

Transcript

  1. Take Control Of Your APIs With GraphQL Adam McNeilly -

    @AdamMc331 @AdamMc331 #AndroidMakers 1
  2. PokeAPI1 // https://pokeapi.co/api/v2/pokemon/ { "count": 964, "next": "https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20", "previous": null,

    "results": [ { "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }, { "name": "ivysaur", "url": "https://pokeapi.co/api/v2/pokemon/2/" }, { "name": "venusaur", "url": "https://pokeapi.co/api/v2/pokemon/3/" } ] } 1 https://pokeapi.co/ @AdamMc331 #AndroidMakers 5
  3. PokeAPI1 // https://pokeapi.co/api/v2/pokemon/squirtle // Originally 11,000 lines { "abilities": [],

    "base_experience": 63, "forms": [], "game_indices": [], "height": 5, "held_items": [], "id": 7, "is_default": true, "location_area_encounters": "https://pokeapi.co/api/v2/pokemon/7/encounters", "moves": [], "name": "squirtle", "order": 10, "species": {}, "sprites": {}, "stats": [], "types": [], "weight": 90 } 1 https://pokeapi.co/ @AdamMc331 #AndroidMakers 6
  4. What Are The Drawbacks Of REST APIs? • Responses may

    not have enough data @AdamMc331 #AndroidMakers 7
  5. What Are The Drawbacks Of REST APIs? • Responses may

    not have enough data • Responses may have too much data @AdamMc331 #AndroidMakers 7
  6. What Are The Drawbacks Of REST APIs? • Responses may

    not have enough data • Responses may have too much data • Responses are not controlled by the client @AdamMc331 #AndroidMakers 7
  7. What Are The Drawbacks Of REST APIs? • Responses may

    not have enough data • Responses may have too much data • Responses are not controlled by the client • Any changes to responses have to go through another developer @AdamMc331 #AndroidMakers 7
  8. GraphQL Query Syntax { pokemon(name: "bulbasaur") { // Put All

    Fields Here } } @AdamMc331 #AndroidMakers 9
  9. Only Get What You Ask For // Query { pokemon(name:

    "bulbasaur") { name } } // Response { "data": { "pokemon": { "name": "Bulbasaur" } } } @AdamMc331 #AndroidMakers 10
  10. Another Example // Query { pokemon(name: "bulbasaur") { name number

    } } // Response { "data": { "pokemon": { "name": "Bulbasaur", "number": "001" } } } @AdamMc331 #AndroidMakers 11
  11. What Are The Benefits Of GraphQL APIs? • Responses have

    just enough data @AdamMc331 #AndroidMakers 12
  12. What Are The Benefits Of GraphQL APIs? • Responses have

    just enough data • Responses are controlled by the client @AdamMc331 #AndroidMakers 12
  13. What Are The Benefits Of GraphQL APIs? • Responses have

    just enough data • Responses are controlled by the client • Any changes to responses can be done by the client @AdamMc331 #AndroidMakers 12
  14. Introspection Query 4 • A query that asks for meta

    data about a GraphQL Schema 4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries @AdamMc331 #AndroidMakers 22
  15. Introspection Query 4 • A query that asks for meta

    data about a GraphQL Schema • Will return all of the possible queries and mutations 4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries @AdamMc331 #AndroidMakers 22
  16. Introspection Query 4 • A query that asks for meta

    data about a GraphQL Schema • Will return all of the possible queries and mutations • Will return all of the models in a graph 4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries @AdamMc331 #AndroidMakers 22
  17. Introspection Query 4 • A query that asks for meta

    data about a GraphQL Schema • Will return all of the possible queries and mutations • Will return all of the models in a graph • Foot note has a deep dive into these queries 4 https://graphqlmastery.com/blog/graphql-introspection-and-introspection-queries @AdamMc331 #AndroidMakers 22
  18. Create A [something].graphql File // pokemonqueries.graphql // same directory as

    our schema.json file query PokemonQuery($first: Int!) { pokemonList: pokemons(first: $first) { name types image } } @AdamMc331 #AndroidMakers 27
  19. Write A Query From Our Code val query = PokemonQuery.builder()

    .first(DEFAULT_LIMIT) .build() val callback = object : ApolloCall.Callback<PokemonQuery.Data>() { override fun onFailure(e: ApolloException) { Log.e("ApolloService", e.message, e) } override fun onResponse(response: Response<PokemonQuery.Data>) { val pokemonList = response.data() ?.pokemonList() ?.map { apolloPokemon -> val name = apolloPokemon.name() val typeNames = apolloPokemon.types() val image = apolloPokemon.image() } } } apolloClient.query(query).enqueue(callback) @AdamMc331 #AndroidMakers 30
  20. Write A Query From Our Code val query = PokemonQuery.builder()

    .first(DEFAULT_LIMIT) .build() val callback = object : ApolloCall.Callback<PokemonQuery.Data>() { override fun onFailure(e: ApolloException) { Log.e("ApolloService", e.message, e) } override fun onResponse(response: Response<PokemonQuery.Data>) { val pokemonList = response.data() ?.pokemonList() ?.map { apolloPokemon -> val name = apolloPokemon.name() val typeNames = apolloPokemon.types() val image = apolloPokemon.image() } } } apolloClient.query(query).enqueue(callback) @AdamMc331 #AndroidMakers 30
  21. Write A Query From Our Code val query = PokemonQuery.builder()

    .first(DEFAULT_LIMIT) .build() val callback = object : ApolloCall.Callback<PokemonQuery.Data>() { override fun onFailure(e: ApolloException) { Log.e("ApolloService", e.message, e) } override fun onResponse(response: Response<PokemonQuery.Data>) { val pokemonList = response.data() ?.pokemonList() ?.map { apolloPokemon -> val name = apolloPokemon.name() val typeNames = apolloPokemon.types() val image = apolloPokemon.image() } } } apolloClient.query(query).enqueue(callback) @AdamMc331 #AndroidMakers 30
  22. Write A Query From Our Code val query = PokemonQuery.builder()

    .first(DEFAULT_LIMIT) .build() val callback = object : ApolloCall.Callback<PokemonQuery.Data>() { override fun onFailure(e: ApolloException) { Log.e("ApolloService", e.message, e) } override fun onResponse(response: Response<PokemonQuery.Data>) { val pokemonList = response.data() ?.pokemonList() ?.map { apolloPokemon -> val name = apolloPokemon.name() val typeNames = apolloPokemon.types() val image = apolloPokemon.image() } } } apolloClient.query(query).enqueue(callback) @AdamMc331 #AndroidMakers 30
  23. Write A Query From Our Code val query = PokemonQuery.builder()

    .first(DEFAULT_LIMIT) .build() val callback = object : ApolloCall.Callback<PokemonQuery.Data>() { override fun onFailure(e: ApolloException) { Log.e("ApolloService", e.message, e) } override fun onResponse(response: Response<PokemonQuery.Data>) { val pokemonList = response.data() ?.pokemonList() ?.map { apolloPokemon -> val name = apolloPokemon.name() val typeNames = apolloPokemon.types() val image = apolloPokemon.image() } } } apolloClient.query(query).enqueue(callback) @AdamMc331 #AndroidMakers 30
  24. Creating An Apollo Client val okHttpClient = OkHttpClient.Builder() .build() val

    apolloClient = ApolloClient.builder() .serverUrl("https://graphql-pokemon.now.sh/") .okHttpClient(okHttpClient) .build() @AdamMc331 #AndroidMakers 35
  25. Query Code Is More Modern // repository.kt // No builder

    pattern val query = PokemonQuery(first = DEFAULT_LIMIT) // Use fields not methods val name = apolloPokemon.name val typeNames = apolloPokemon.types val image = apolloPokemon.image @AdamMc331 #AndroidMakers 39
  26. Queries With Duplicated Code query PokemonQuery($first: Int!) { pokemonList: pokemons(first:

    $first) { name types image } } query PokemonDetailQuery($pokemonName: String) { pokemon: pokemon(name: $pokemonName) { name types image } } @AdamMc331 #AndroidMakers 41
  27. Share Information With A Fragment fragment ApolloPokemon on Pokemon {

    name types image } query PokemonQuery($first: Int!) { pokemonList: pokemons(first: $first) { ...ApolloPokemon } } query PokemonDetailQuery($pokemonName: String) { pokemon: pokemon(name: $pokemonName) { ...ApolloPokemon } } @AdamMc331 #AndroidMakers 42
  28. A Common Problem /** * This class has a direct

    dependency on a REST API. * It returns a data class we defined. * Not the data class generated by Apollo. * This makes swapping them difficult. */ class PokemonListViewModel( private val restApi: PokemonAPI ) : ViewModel() { @AdamMc331 #AndroidMakers 44
  29. Leverage Repository Interfaces interface PokemonRepository { fun getPokemonList(): PokemonListResponse? fun

    getPokemonDetail(pokemonName: String): Pokemon? } class PokemonListViewModel( private val repository: PokemonRepository ) : ViewModel() { @AdamMc331 #AndroidMakers 45
  30. Allows You To A/B Test Your New API private fun

    getRepository(): PokemonRepository { if (isInGraphQLTestGroup) { return ApolloService() } else { return RetrofitService() } } @AdamMc331 #AndroidMakers 47
  31. Why A/B Test A New API? • You can monitor

    performance of a page using different APIs @AdamMc331 #AndroidMakers 48
  32. Why A/B Test A New API? • You can monitor

    performance of a page using different APIs • This is a big change, you should ship with confidence @AdamMc331 #AndroidMakers 48
  33. Why A/B Test A New API? • You can monitor

    performance of a page using different APIs • This is a big change, you should ship with confidence • You won't need to do this for every page, but is helpful for your first page to use GraphQL @AdamMc331 #AndroidMakers 48
  34. Our Repository Doesn't Return Generated Code interface PokemonRepository { fun

    getPokemonList(): PokemonListResponse? fun getPokemonDetail(pokemonName: String): Pokemon? } @AdamMc331 #AndroidMakers 50
  35. Leverage Kotlin Extension Functions // pokemonqueries.graphql fragment ApolloPokemon on Pokemon

    { name types image } // ApolloService.kt private fun ApolloPokemon.toPokemon(): Pokemon { return Pokemon( name = this.name.orEmpty(), firstType = this.types?.getOrNull(0), secondType = this.types?.getOrNull(1), frontSpriteUrl = this.image ) } @AdamMc331 #AndroidMakers 51
  36. Makes The Parsing Of Requests Cleaner val callback = object

    : ApolloCall.Callback<PokemonQuery.Data>() { override fun onFailure(e: ApolloException) { Log.e("ApolloService", e.message, e) } override fun onResponse(response: Response<PokemonQuery.Data>) { val pokemonList = response.data() ?.pokemonList ?.filterNotNull() ?.map { apolloPokemon -> val pokemon = apolloPokemon.fragments.apolloPokemon.toPokemon() } } } @AdamMc331 #AndroidMakers 52
  37. Callbacks val callback = object : ApolloCall.Callback<PokemonQuery.Data>() { override fun

    onFailure(e: ApolloException) { // ... } override fun onResponse(response: Response<PokemonQuery.Data>) { // ... } } @AdamMc331 #AndroidMakers 54
  38. RxJava25 val query = PokemonQuery(first = DEFAULT_LIMIT) apolloClient.rxQuery(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())

    .subscribe { // ... } 5 https://www.apollographql.com/docs/android/advanced/rxjava2/ @AdamMc331 #AndroidMakers 55
  39. Coroutines6 override suspend fun getPokemon(): PokemonResponse { val query =

    PokemonQuery(first = DEFAULT_LIMIT) val response = apolloClient.query(query).toDeferred().await() } 6 https://www.apollographql.com/docs/android/advanced/coroutines/ @AdamMc331 #AndroidMakers 56