dillonkearns / elm-graphql

Autogenerate type-safe GraphQL queries in Elm.
https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest
BSD 3-Clause "New" or "Revised" License
778 stars 107 forks source link

Feedback from my first spike #15

Closed martimatix closed 6 years ago

martimatix commented 6 years ago

Hi @dillonkearns

I did a quick spike with graphqelm and I'm pleased at how productive I was in a short space of time. 😃

I love examples and it's great that there's something that I can refer to with Ellie.

module Main exposing (main)

import Graphqelm exposing (RootQuery)
import Graphqelm.Document as Document
import Graphqelm.Http
import Graphqelm.SelectionSet exposing (SelectionSet, with)
import Html exposing (Html, a, div, h1, h2, p, pre, text)
import Html.Attributes exposing (href, target)
import RemoteData exposing (RemoteData)
import Github.Object
import Github.Object.User as User
import Github.Query as Query

type alias Response =
    { user : Maybe User
    }

query : SelectionSet Response RootQuery
query =
    -- define the top-level query
    -- this syntax is based on the json decode pipeline pattern
    Query.selection Response
        |> with (Query.user { login = "justasitsounds" } user)

type alias User =
    -- as with JSON decoding, it's common to use type alias constructors
    { name : Maybe String
    , avatarUrl : String
    }

user : SelectionSet User Github.Object.User
user =
    User.selection User
        |> with User.name
        |> with (User.avatarUrl identity)

makeRequest : Cmd Msg
makeRequest =
    query
        |> Graphqelm.Http.buildQueryRequest
            "https://api.github.com/graphql"
        |> Graphqelm.Http.withHeader "authorization" "Bearer [secret!]"
        |> Graphqelm.Http.send (RemoteData.fromResult >> GotResponse)

type Msg
    = GotResponse Model

type alias Model =
    RemoteData Graphqelm.Http.Error Response

init : ( Model, Cmd Msg )
init =
    ( RemoteData.Loading
    , makeRequest
    )

view : Model -> Html.Html Msg
view model =
    div []
        [ div []
            [ h1 [] [ text "Generated Query" ]
            , pre [] [ text (Document.serializeQuery query) ]
            ]
        , div []
            [ h1 [] [ text "Response" ]
            , Html.text (toString model)
            ]
        ]

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GotResponse response ->
            ( response, Cmd.none )

main : Program Never Model Msg
main =
    Html.program
        { init = init
        , update = update
        , subscriptions = \_ -> Sub.none
        , view = view
        }

The only thing that wasn't obvious to me was what to do if you didn't want to enter any optional arguments. It might be worth mentioning in the docs that in this case, you can use identity. That might be obvious to seasoned Elm developers but it's always nice when documentation caters for programmers of all levels.

Good work on the http docs - I was able to figure out how to enter http headers easily. 👍

dillonkearns commented 6 years ago

That's awesome feedback, thanks Mario! I'm trying to figure out the best place for learning resources right now. Do you think that it would be sufficient to make it clear that the ellie example is the tutorial, and add some comments explaining the concepts inline there? Or would it be better to also have details in the README?

Also, I'm curious, did you do your spike on Ellie or with an editor? How was the experience of discovering the functions and interfaces, was it pretty easy to figure out what to pass in where?

martimatix commented 6 years ago

Different folks like to consume documentation and instructional material in different ways. Personally, I prefer to read something from a readme with a link to a working example.

I did my spike with my editor - I already had something set up when I was playing around with Elm-format. My spike followed the patterns from the Ellie example so there wasn't a lot for me to figure out. Later, I want to extend my Github spike by trying other things such as pagination and ordering records.

Something that was a little confusing to me in the Ellie example was discriminating:

Not saying it's bad or that it should be written differently - just something that took me a couple of minutes to understand it all. It's been several months since I've immersed myself in an Elm so part of me figuring things out was refamilaring myself with Elm conventions.

I'll close this issue for now. Just wanted to give feedback from my initial usage.

dillonkearns commented 6 years ago

That's really helpful to hear your account of playing around with it, Mario.

It is definitely a double-edged sword to have a Swapi.Object module with a Human type in addition to a Swapi.Object.Human module. The benefit is that the names are predictable and easier to find (in face, Elmjutsu will autocomplete with Swapi.Object.Human and then you can build off of that to select fields off of it). But of course it's confusing because it's a bit clever, which can be a bad thing when learning a tool...

Originally I actually defined the Human type in the Swapi.Object.Human module. This worked great, except that it introduced circular dependencies so it wasn't viable 😢. I wish there were some nice solution like that.

To your point about pagination, that would be awesome to get your feedback on that experience. I've been thinking about some wild ideas for making that a nicer experience. The so-called Relay Connections Cursor Specification (it's not specific to Relay, so it's kind of a misleading name) is a standard way for paginating that Github follows. I've been thinking about generating a special function that returns a special pagination data type for connections that follow that spec. And from there, you could pass that to a function and it just tell it to follow the pagination data and make a request for the next page.

I found this article really helpful in understanding the concepts behind the relay protocol: https://dev-blog.apollodata.com/explaining-graphql-connections-c48b7c3d6976

dillonkearns commented 6 years ago

I think it's going to take a lot of brainstorming and experimentation to come up with a solid approach for pagination. Of course it should hopefully be pretty good without any special mechanism as is, but I think there's some potential here!