2013-05-30 10 views
18

Sto scrivendo un'applicazione Go per l'esecuzione sul runtime Go di App Engine.Come posso gestire il contesto di runtime di App Engine Go per evitare il blocco di App Engine?

Ho notato che praticamente qualsiasi operazione che utilizza un servizio di App Engine (come archivio dati, posta, o anche Capabilities) richiede che si passa un'istanza di appengine.Context che deve essere recuperato utilizzando la funzione appengine.NewContext(req *http.Request) Context.

Mentre scrivo questa app per App Engine, voglio essere in grado di spostarla su un'altra piattaforma (probabilmente una che non supporta alcuna API di App Engine) in modo facile e veloce se dovessi scegliere.

Quindi, sto astromettendo l'interazione effettiva con i servizi App Engine e le API scrivendo piccoli involucri attorno a qualsiasi interazione specifica di App-Engine (incluse le funzioni di gestione delle richieste). Con questo approccio, se mai mi piacerebbe passare a una piattaforma diversa, mi limiterò a riscrivere quei moduli specifici che legano la mia applicazione a App Engine. Facile e diretto

L'unico problema è l'oggetto appengine.Context. Non posso passarlo dai miei gestori di richieste attraverso i miei livelli di logica ai moduli che gestiscono queste API senza legare praticamente tutto il mio codice a App Engine. Potrei passare l'oggetto http.Request da cui è possibile ricavare l'oggetto appengine.Context, ma ciò richiederebbe l'accoppiamento di cose che probabilmente non dovrebbero essere accoppiate. (Penso che sia meglio per nessuna delle mie applicazioni sapere che si tratta di un'applicazione web eccetto quelle parti specificamente dedicate alla gestione delle richieste HTTP.)

La prima soluzione che è venuta alla mente è stata creare una variabile persistente in qualche modulo . Qualcosa di simile a questo:

package context 

import (
    "appengine" 
) 

var Context appengine.Context 

Poi, nella mia richiesta gestori, posso impostare questa variabile con context.Context = appengine.NewContext(r) e nei moduli che utilizzano direttamente App Engine servizi, posso prendere il contesto da accesing context.Context. Nessuno dei codici che intervengono avrebbe bisogno di sapere dell'esistenza dell'oggetto appengine.Context. L'unico problema è che "multiple requests may be handled concurrently by a given instance", che può portare a condizioni di gara e comportamento imprevisto con questo piano. (Una richiesta imposta, un altro set di esso, il primo si accede e ottiene l'appengine.Context oggetto sbagliato.)

ho potuto in teoria memorizzare il appengine.Context al datastore, ma poi avrei dovuto passare un po 'identificativo specifico-request giù i livelli logici per i moduli specifici del servizio che identificano quale oggetto appengine.Context in datastore è quello per la richiesta corrente, che accoppierà di nuovo le cose che non penso dovrebbero essere accoppiate. (E, aumenterebbe l'uso datastore della mia applicazione.)

Potrei anche passare l'oggetto appengine.Context verso il basso l'intera catena logica con il tipo interface{} per tutta la strada e hanno tutti i moduli che non ha bisogno l'oggetto appengine.Context ignorarlo. Ciò eviterebbe di legare la maggior parte della mia applicazione a qualsiasi specifico. Ciò sembra anche molto disordinato, comunque.

Quindi, sono un po 'incerto su come garantire in modo pulito che i moduli specifici di App-Engine che necessitano l'oggetto appengine.Context possano ottenerlo. Spero che voi gente possa darmi una soluzione che devo ancora pensare a me stesso.

Grazie in anticipo!

risposta

8

Questo è difficile perché la regola di scoping autoimposto (che è una sensibile) significa non passare un'istanza Context in giro, e non c'è nulla di simile a Java di ThreadLocal per raggiungere gli stessi scopi con mezzi subdoli. In realtà è una buona cosa, davvero.

Context combina il supporto di registrazione (facile) con un Call per i servizi appengine (non facile). Credo che dieci funzioni appengine necessitino di uno Context. Non riesco a vedere nessuna soluzione pulita se non quella di avvolgere tutti questi elementi dietro la tua facciata.

C'è una cosa che può aiutarti - puoi includere un file di configurazione con la tua app che indica se è in GAE o in altro modo, usando un flag di qualche tipo. Il tuo booleano globale deve solo memorizzare questo flag (non un contesto condiviso). Le funzioni della facciata possono quindi consultare questo flag quando si decide di utilizzare NewContext(r) per ottenere il Context per accedere ai servizi GAE o per utilizzare una struttura simile per accedere ai propri servizi sostitutivi.

Modifica: Come ultima osservazione, quando risolvi questo problema, ti invito a condividere come hai fatto, possibilmente anche con un progetto open source? Cheeky a me per chiedere, ma se non chiedi ... ;-)

+0

Grazie per la risposta! Penso di avere un'idea che potrebbe funzionare per la mia particolare applicazione. Non sono sicuro che funzioni in generale, ma potrebbe funzionare per una percentuale significativa di app di App Engine. Se la mia idea finisce per essere una soluzione semi-generale, la rilascerò definitivamente come un progetto open source. : D – AntiMS

+1

La maggior parte della mia applicazione ha accesso a un ID utente o a un ID di sessione. Il mio piano è solo quello di creare una mappa persistente basata su valori derivati ​​direttamente da un id di sessione o da un id utente. I gestori delle richieste possono creare e archiviare gli oggetti di contesto e i moduli che richiedono un contesto possono recuperarli in modo simile. Non sono completamente sicuro che funzionerà, comunque. (Potrebbero esserci alcuni moduli che non hanno accesso a un ID di sessione o id utente. In alcuni di questi casi potrei dover essere creativo.) – AntiMS

7

I (si spera) risolto questo problema avvolgendo i miei gestori delle richieste (in questo esempio quella denominata "realHandler") come questo:

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 
    ds := NewDataStore(r) 
    realHandler(w, r, ds) 
}) 

NewDataStore crea un DataStore, che è un semplice wrapper che astrae il datastore GAE. Ha un campo non esposta dove memorizza il contesto:

type DataStore struct { 
    c appengine.Context 
} 

func NewDataStore(req *http.Request) *DataStore { 
    return &DataStore{appengine.NewContext(req)} 
} 

All'interno del gestore richiesta, posso solo accedere al datastore astratta quando ne ho bisogno, senza preoccuparsi del contesto GAE, che è già stato stabilito:

func realHandler(w http.ResponseWriter, req *http.Request, db *DataStore) { 
    var s SomeStruct{} 
    key, err := db.Add("Structs", &s) 
    ... 
} 
1

In particolare nel caso di Datastore, è possibile riutilizzare lo stesso appengine.Context tra diversi requsts. Non ho cercato di fare io stesso, ma questo è il modo in cui un'API alternativo per datastore chiamato goon opere:

Goon differisce dal pacchetto datastore in vari modi: si ricorda il contesto AppEngine, che ha bisogno di essere specificato solo una volta al momento della creazione

Il fatto che lo spazio di archiviazione debba dipendere dalla richiesta HTTP sembra ridicolo a proposito. Non credo che Datastore dipenda da una particolare richiesta nel senso comune del numero . Molto probabilmente, è necessario identificare una particolare applicazione di Google App Engine che ovviamente rimane la stessa dalla richiesta alla richiesta. Le mie speculazioni si basano su una rapida scansione su source code di google.golang.org/appengine.

È possibile che si facciano osservazioni simili riguardo alle altre API di Google App Engine. Perchè tutti i dettagli potrebbero essere specifici per l'implementazione e avrei eseguito ricerche più approfondite prima di utilizzare effettivamente tali osservazioni in un'applicazione reale.

1

Vale la pena notare che il team di Go ha introdotto un pacchetto golang.org/x/net/context.

Successivamente il contesto è stato reso disponibile nelle macchine virtuali gestite, il repository è here. La documentazione indica:

Questo repository supporta Go runtime su App Engine, inclusi sia il classico App Engine sia le VM gestite. Fornisce API per l'interazione con i servizi App Engine. Il suo percorso di importazione canonico è google.golang.org/appengine.

Che cosa significa è che si potrebbe facilmente scrivere un'altra pacchetti di ambiente dev seconda appengine.

In particolare diventa molto semplice racchiudere pacchetti come appengine/log (involucro di registro triviale example).

Ma ancora più importante questo permette di creare i gestori in una forma:

func CoolHandler(context.Context, http.ResponseWriter, *http.Request)

C'è un articolo su un pacchetto context su Go blog here. Ho scritto sull'uso del contesto here. Se si decide di utilizzare il gestore con il contesto che lo circonda è bene creare il contesto per tutti i requrests in un unico punto. È possibile farlo utilizzando un router requrest non standard come github.com/orian/wctx.

Problemi correlati