This document summarizes a Go library for interacting with Firebase. It describes Firebase's capabilities and limitations, and motivates the need for a new Go binding. The presented library mimics Firebase's JavaScript API, implements streaming via channels, and uses interfaces to enable testable, dependency-free unit tests. It addresses Go's lack of generics by accepting an unmarshalling callback to parse stream events into custom types.
2. Firebase Overview
● Real-time Backend-as-a-Service
● NoSQL style JSON data store
● Security rules language
○ Multi-tenancy
● User management
● REST API
○ https://mydb.firebaseio.com/path/to/data.json?
orderBy=”index”&equalTo=”tommy”
3. Firebase Shortcomings
● Very limited query capabilities
○ Intended for real-time applications
○ Generally only O(N) queries on one index at a time
● REST API typically lags behind JS API in
features.
● Data must be de-normalized (un-nested)
○ You’re on your own joining it all back together
4. Firebase Language Bindings
Except for JS, all Firebase language bindings
use the REST API
● GET / PUT / POST / PATCH
○ Read / write data
● Real-time updates via Server-Sent Events
○ Long poll protocol spec, part of HTML5
○ http://en.wikipedia.org/wiki/Server-sent_events
5. Firebase Go Binding
Mimics the official JS API:
type Client interface {
// path
Child(path string) Client
// read / write
Value(destination interface{}) error
Push(value interface{}, params map[string]string)
(Client, error)
Set(path string, value interface{}, params map[string]
string) (Client, error)
Update(path string, value interface{}, params map
[string]string) error
Remove(path string, params map[string]string) error
}
Example:
var w Widget
err := client.Child(“my/path”).Value(&w)
w.Name = “fred”
c, err := client.Child(“my/path”).Set(w)
c, err := client.Child(“newOne”).Push(w)
// c.Key() has name of new resource
6. Why Write a New Go Binding?
Options were limited:
● github.com/cosn/firebase
○ Nice approach to designing testable library
○ Incomplete, unmaintained
● github.com/JustinTulloss/firebase
○ Cleans up some of cosn’s implementation
No streaming support, tests were deficient
7. Firebase Go Binding
https://godoc.org/github.com/ereyes01/firebase
● Idiomatic Go implementations of:
○ Streaming (via channels)
○ Firebase server-side timestamps
■ Custom JSON encoder
○ Your Firebase data as structs (or maps if you prefer)
● Well unit-tested, testable by lib consumers
8. Testable Go Library
Ideal unit test:
● Tests only YOUR code being changed
● No external dependencies, no talking to the
network
○ How to mock in Go?
Interfaces!
func NewClient(root, auth string, api Api) Client
9. Testable Go Library
The “Api” interface handles HTTP
communication with Firebase.
● A nil api -> you talk to Firebase
(production)
● Non-nil api- test mode
○ Intercept the communication with your stub / test
implementation
10. Testable Go Library
type Api interface {
Call(method, path, auth string, body interface{},
params map[string]string, dest interface{}) error
Stream(path, auth string, body interface{}, params map
[string]string, stop <-chan bool) (<-chan RawEvent, error)
}
11. Firebase Streaming
How it works in HTTP:
● GET request, path to resource + query
● Accept: text/event-stream
● Must follow redirects
● Data:
event: patch
data: {“path”:”right/here”,“data”:{“my”: “data”}}
event: keep-alive
data: null
event: keep-alive
data: null
12. Firebase Streaming in Go
In Go:
● Model stream as a channel
● Channel of what type?
○ Everything unmarshals to map[string]interface{} as:
■ string / float64 / bool / map / []interface{}
○ But my “data” is my own struct type!
○ I want events via chan <MyType>
■ Type-generic channel, type is a parameter
13. No Generics? No Problem! (Sort of)
You can do reflection gymnastics:
○ func ParametricType(myType reflect.Type)
○ Call like this:
■ ParametricType(reflect.TypeOf(Widget{}))
■ Meanwhile, in ParametricType():
ptr := reflect.New(myType).Interface()
● Allocates a new Widget object, returns pointer
● Type-generic instantiation
14. No Generics? No Problem! (Sort of)
That’s a working solution, but…
Exposes reflection in an external API.
Alternative:
● interface stream + unmarshaller callback
● Longer, but perhaps cleaner interface
15. No Generics? No Problem! (Sort of)
Watch(unmarshaller EventUnmarshaller, stop <-chan bool)
(<-chan StreamEvent, error)
type EventUnmarshaller func(jsonText []byte) (interface{},
error)
type StreamEvent struct {
Event string
Path string
Resource interface{}
RawData string
Error error
}
16. Now You Can Stream Widgets!
func widgetParser(text []byte) (interface{} error) {
var w Widget
err := json.Unmarshal(text, &w)
return w, err
}
…
events, err := client.Watch(widgetParser, stopChan)
for event := range events {
widget := event.Resource.(Widget)
}
17. Fork Me on Github!
https://github.com/ereyes01/firebase