perjantai 2. kesäkuuta 2017

Trying out Graphql with Golang and Vue.js

I've made normal Rest APIs for many years now. For some time now, there has been interest for Facebooks GraphQL especially on the front-end side. I too have been curious about it but haven't had the change to play with it. So, I made a small project to test it and to test Vue.js too which also is new for me. I have used Angular a bit and more React but Vue.js was something totally new.

GraphQL with Go

There actually is a ready project for using GraphQL so it's quite simple to start using it on the backend side.

I couldn't think of any really good topic to make so I wrote some Star Wars characters into a file and used those.

var personType = graphql.NewObject(
  graphql.ObjectConfig{
    Name:   "person",
    Fields: fields,
  },
)

There is only one type and it's definition is simple enough.

var queryType = graphql.NewObject(graphql.ObjectConfig{
  Name: "RootQuery",
  Fields: graphql.Fields{
    "person": &graphql.Field{
      Type: personType,
      Args: args,
      Resolve: func(p graphql.ResolveParams) (interface{}, error) {
        return filterPerson(data, p.Args), nil
      },
    },
    "personList": &graphql.Field{
      Type: graphql.NewList(personType),
      Resolve: func(p graphql.ResolveParams) (interface{}, error) {
        return data, nil
      },
    },
  },
})

Now, the query type includes two fields to make queries. Person field filters the data with a query parameter from the request. PersonList field just returns the whole data or list of Star Wars characters.

schema, _ = graphql.NewSchema(
  graphql.SchemaConfig{
    Query: queryType,
  },
)

Schema defines the capabilities of the server with queryType.

func main() {
  err := importJSONDataFromFile(jsonDataFile)
  if err != nil {
    fmt.Printf("Error: %s\n", err.Error())
    return
  }

  http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
    result := executeQuery(r.URL.Query()["query"][0], schema)
    w.Header().Add("Access-Control-Allow-Origin", "*")
    json.NewEncoder(w).Encode(result)
  })

  fs := http.FileServer(http.Dir("frontend/dist"))
  http.Handle("/", fs)

  fmt.Println("Now server is running on port 8000")
  http.ListenAndServe(":8000", nil)
}

The main function of the server does four things. First, it gets the Star Wars character json and makes the personType "fields" and queryType "data" available. Second, it sets listening endpoint to /graphql for queries. Third, it defines a file server for static content for root "/" endpoint. And for last, it starts the server.

At this point, qraphql queries already work. If your start the server, and query it for example with:

curl http://localhost:8000/graphql?query={person(name:%22Darth%22){id,name,surname}}

You get an answer:

{
  data: {
    person: {
      id: "1",
      name: "Darth",
      surname: "Vader"
    }
  }
}

Vue.js

To really test what the difference is between using ordinary Rest APIs and GraphQL I set up a small front-end to fetch and show the data.


I first set up the vue.js project under front-end directory, in production it would be delivered from there. In development it can be run separetily with hot reloading and the works.

cd frontend
npm install -g vue-cli
vue init webpack graphql-vue-example
mv graphql-vue-example/* .
rmdir graphql-vue-example
npm install
npm run dev

Now, this sets up an initial project, it needs some work to get all things like we want them. Almost everything that needs to be changed, is at "frontend/src/components/Persons.vue". It contains the html template, vue javascript for the page and scoped styles.

<template>
  <div class="personclass">
    <h1>{{ msg }}</h1>
    <input id="personName" type="text" value="Darth" v-model="personName"></input>
    <button class="btn btn-primary" v-on:click="getPerson()">Get Star wars person</button>
    <h2>One fetched person</h2>
    <div>{{ person.person.id }} {{ person.person.name }} {{ person.person.surname }}</div>
    <h2>List of all</h2>
    <div v-for="value in persons.personList">
      {{ value.id }} {{ value.name }} {{ value.surname }}
    </div>
  </div>
</template>

If you write a name of a character to the input field and then click the button, it goes and fetches the character in question. Of course, only if there is a match. There also is the whole list, which is fetched immediately when the page is rendered.

<script>
export default {
  name: 'personclass',
  data () {
    return {
      msg: 'Star Wars persons',
      person: {person: {id: '', name: '', surname: ''}},
      persons: ''
    }
  },
  mounted () {
    this.getPersons()
  },
  methods: {
    getPerson () {
      console.log(this.personName)
      this.$http.get('http://localhost:8000/graphql?query=' + 
          encodeURIComponent('{person(name:"' + this.personName + '"){id,name,surname}}'))
          .then(response => {
        this.person = JSON.parse(response.body).data
      }, response => {
        console.log(response)
      })
    },
    getPersons () {
      this.$http.get('http://localhost:8000/graphql?query=' + 
          encodeURIComponent('{personList{id,name,surname}}')).then(response => {
        this.persons = JSON.parse(response.body).data
      }, response => {
        console.log(response)
      })
    }
  }
}
</script>

The Vue javascript part is simple enough. There are two methods, getPersons gets all the Star Wars characters and it's called when the page is mounted. Method getPerson is called from the html page when the button is clicked and it returns only the character which is being searched.


The whole example project in github: https://github.com/jelinden/graphql-vue-example

References

https://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html
https://github.com/graphql-go/graphql
https://vuejs.org/


lauantai 29. huhtikuuta 2017

Cache on the background with golang, Part 2

Part one can be found here.

Expiring cache


First, we're still using the var cache cmap.ConcurrentMap
Second, we need a new field to store, the expire information. So, let's save a struct as a value for the cache item.
type CacheItem struct {
    Key    string
    Value  []byte
    Expire time.Time
}
Adding an item to cache is almost the same, let's just take the expiring into account and also the set maximum size of the cache.
func AddItemToCache(key string, value []byte, expire time.Duration) {
    if cache.Count() <= cacheSize {
        cache.Set(key, CacheItem{Key: key, Value: value, Expire: time.Now().Add(expire)})
    } else {
        panic("cacheSize reached")
    }
}
Getting an item from the cache is trivial. Again, let's take the expired items into account by removing them.
func GetItemFromCache(key string) *CacheItem {
    if cache.Has(key) {
        if tmp, ok := cache.Get(key); ok {
            item := tmp.(CacheItem)
            if item.Expire.After(time.Now()) {
                return &item
            }
            removeItem(item.Key)
        }
    }
    return nil
}

Usages of an expiring cache

You can fill the cache with a scheduler on the background, or by filling the cache when you get a nil from a get. You can also take this further by fetching the almost expired item on the background and still giving the item from the cache. This way there is not so much cache misses when there is less traffic.
cacheItem := GetItemFromCache(weatherURLs[1])
if cacheItem != nil {
    w.Write(cacheItem.Value)
} else {
    if weather := fetchWeather(weatherURLs[1]); weather != nil {
        j, _ := json.Marshal(&weather.Query.Results.Channel.Item)
        AddItemToCache(weatherURLs[1], j, time.Minute)
        w.Write(j)
    } else {
        json.NewEncoder(w).Encode("not found")
    }
}

Removing stale items

In addition to removing stale items when bumping into them while getting an item from cache, we can remove stale items by running
func removeStaleCacheItems() {
    for _, item := range cache.Items() {
        cItem := item.(CacheItem)
        if time.Now().After(cItem.Expire) {
            removeItem(cItem.Key)
        }
    }
}
once in a minute for example. We loop all items and check if the current time is after expiration time. This is started as a background job in the init function.
func init() {
    go doEvery(time.Minute, removeStaleCacheItems)
}

func doEvery(d time.Duration, f func()) {
    for range time.Tick(d) {
        f()
    }
}

Testing

Testing is otherwise about the same, but we have the new expiring test. Easy enough, just give a little time for an added item and then sleep a bit more than that. When getting the first added item after expiring, you get nil back as supposed to.
func TestExpire(t *testing.T) {
    assert := assert.New(t)
    AddItemToCache("item", []byte("value"), time.Second)
    assert.Equal(string(GetItemFromCache("item").Value), "value", "should be equal")
    time.Sleep(time.Second * 2)
    assert.True(GetItemFromCache("item") == nil, "should be equal")
}

The whole source code can be found from github.