I'm confused on how to approach this.
It seems to be that GAE wants every client library to use a context.Context scoped to a http.Request.
I previously have experience doing something like this:
main.go
type server struct {
db *firestore.Client
}
func main() {
// Setup server
s := &server{db: NewFirestoreClient()}
// Setup Router
http.HandleFunc("/people", s.peopleHandler())
// Starts the server to receive requests
appengine.Main()
}
func (s *server) peopleHandler() http.HandlerFunc {
// pass context in this closure from main?
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // appengine.NewContext(r) but should it inherit from background somehow?
s.person(ctx, 1)
// ...
}
}
func (s *server) person(ctx context.Context, id int) {
// what context should this be?
_, err := s.db.Client.Collection("people").Doc(uid).Set(ctx, p)
// handle client results
}
firebase.go
// Firestore returns a warapper for client
type Firestore struct {
Client *firestore.Client
}
// NewFirestoreClient returns a firestore struct client to use for firestore db access
func NewFirestoreClient() *Firestore {
ctx := context.Background()
client, err := firestore.NewClient(ctx, os.Getenv("GOOGLE_PROJECT_ID"))
if err != nil {
log.Fatal(err)
}
return &Firestore{
Client: client,
}
}
This has big implications on how to scope a project wide client. E.g hanging off of a server{db: client}
and attaching handlers on that struct or having to pass it off via dependency injection within the request.
I do notice that the calls out using the client require another context. So maybe it should be like:
main.go
create a ctx := context.Background()
main.go
pass that into new clienthandler ctx := appengine.NewContext(r)
Basically the initial setup doesn't matter off of context.Background()
because new requests have a different context from App Engine?
I could pass in ctx into the handler from main and then NewContext off of that + the request?
What's the idiomatic approach to this?
note: I had firestore methods off of the Firestore
struct as well in previous iterations...
You should reuse the firestore.Client
instance for multiple invocations. However, this was not possible in the old Go runtime in GAE standard. So in that case you must create a new firestore.Client
per request.
But if you use the new Golang 1.11 runtime for GAE standard, then you're free to use any context you like. In that case you can initialize firestore.Client
in the main()
function or in an init()
function using the background context. Then you can make API calls in the request handlers using the request context.
package main
var client *firestore.Client
func init() {
var err error
client, err = firestore.NewClient(context.Background())
// handle errors as needed
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
doc := client.Collection("cities").Doc("Mountain View")
doc.Set(r.Context(), someData)
// rest of the handler logic
}
Here's an example GAE app that I've implemented using Go 1.11 and Firestore that demonstrates the above pattern: https://github.com/hiranya911/firecloud/blob/master/crypto-fire-alert/cryptocron/web/main.go
More on the Go 1.11 support in GAE: https://cloud.google.com/appengine/docs/standard/go111/go-differences