Как я могу управлять контекстом среды App Engine Go, чтобы избежать блокировки App Engine?
Я пишу приложение Go для запуска во время выполнения App Engine Go.
Я замечаю, что практически любая операция, использующая сервис App Engine (например, Datastore, Mail или даже Capabilities), требует, чтобы вы передали ему экземпляр appengine.Context
, который должен быть получен с помощью функции appengine.NewContext(req *http.Request) Context
.
Пока я пишу это приложение для App Engine, я хочу легко и быстро перенести его на какую-либо другую платформу (возможно, такую, которая не поддерживает какой-либо из API App Engine), если я этого захочу.
Итак, я абстрагирую фактическое взаимодействие с сервисами App Engine и API, написав небольшие обертки вокруг любого взаимодействия с конкретным приложением (включая функции обработки запросов). При таком подходе, если я когда-либо захочу перейти на другую платформу, я просто переписал те конкретные модули, которые привязывают мое приложение к App Engine. Легко и просто.
Единственная проблема заключается в том, что объект appengine.Context
. Я не могу передать это из моих обработчиков запросов через свои слои логики в модули, которые обрабатывают этот API, не привязывая почти весь мой код к App Engine. Я мог бы передать объект http.Request
, из которого может быть выведен объект appengine.Context
, но для этого потребуется связать вещи, которые, вероятно, не должны быть связаны. (Я считаю, что ни одна из моих приложений не может даже знать это веб-приложение, кроме тех частей, которые специально предназначены для обработки HTTP-запросов.)
Первое решение, появившееся на ум, состояло в том, чтобы просто создать постоянную переменную в каком-то модуле. Что-то вроде этого:
package context
import (
"appengine"
)
var Context appengine.Context
Затем, в обработчиках запросов, я могу установить эту переменную с помощью context.Context = appengine.NewContext(r)
и в модулях, которые непосредственно используют службы App Engine, я могу получить контекст, присоединившись к context.Context
. Ни один из промежуточных кодов не должен знать о существовании объекта appengine.Context
. Единственная проблема заключается в том, что "несколько запросов могут обрабатываться одновременно с данным экземпляром" , что может привести к условиям гонки и неожиданному поведению с этим планом. (Один запрос устанавливает его, другой устанавливает его, первый обращается к нему и получает неправильный объект appengine.Context
.)
Я мог бы теоретически хранить appengine.Context
в хранилище данных, но тогда мне пришлось бы передавать определенный идентификатор запроса по логическим слоям в модули, специфичные для службы, определяющие, какой объект appengine.Context
в хранилище данных является тем, который текущий запрос, который снова соединит вещи, которые, как я думаю, не должен сочетаться. (И это увеличит использование хранилища данных приложения.)
Я мог бы также передать объект appengine.Context
по всей логической цепочке с типом interface{}
весь путь и иметь любой модуль, который не нуждается в объекте appengine.Context
, игнорировать его. Это позволит избежать привязки большей части моего приложения к чему-либо конкретному. Однако это также кажется очень беспорядочным.
Итак, я немного пораньше, как для чистого обеспечения модулей App-Engine, которые нуждаются в объекте appengine.Context
, можно получить его. Надеюсь, вы, ребята, можете дать мне решение, о котором я еще не подумал.
Спасибо заранее!
Ответы
Ответ 1
Это сложно, потому что ваше самонастраиваемое правило определения (что является разумным) означает не пропускать экземпляр Context
, и нет ничего похожего на Java ThreadLocal
для достижения тех же целей с помощью скрытых средств. На самом деле это хорошая вещь.
Context
объединяет поддержку ведения журнала (легко) с помощью Call
для сервисов appengine (не просто). Я думаю, что десять функций appengine нуждаются в Context
. Я не вижу никакого чистого решения, кроме как обертывать все это за вашим собственным фасадом.
Есть одна вещь, которая может вам помочь - вы можете включить файл конфигурации с вашим приложением, который указывает, находится ли он в GAE или иначе, используя какой-либо флаг. Ваш глобальный логический объект должен хранить этот флаг (а не общий контекст). Затем ваши функции фасада могут проконсультироваться с этим флагом при принятии решения о том, следует ли использовать NewContext(r)
для получения Context
для доступа к службам GAE или использовать скрытую структуру для доступа к вашим собственным услугам-заменителям.
Изменить: как последнее замечание, когда вы решите это, я могу предложить вам поделиться тем, как вы это сделали, возможно, даже с проектом с открытым исходным кодом? Чувствую меня, чтобы спросить, но если вы не спросите...; -)
Ответ 2
Я (надеюсь) решил эту проблему, обернув мои обработчики запросов (в этом примере один из них называется "realHandler" ) следующим образом:
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ds := NewDataStore(r)
realHandler(w, r, ds)
})
NewDataStore создает DataStore, который является простой оболочкой, которая абстрагирует хранилище данных GAE. Он имеет неэкспонированное поле, где он хранит контекст:
type DataStore struct {
c appengine.Context
}
func NewDataStore(req *http.Request) *DataStore {
return &DataStore{appengine.NewContext(req)}
}
Внутри обработчика запроса я могу просто получить доступ к абстрактному хранилищу данных, когда мне это нужно, не беспокоясь о контексте GAE, который уже установлен:
func realHandler(w http.ResponseWriter, req *http.Request, db *DataStore) {
var s SomeStruct{}
key, err := db.Add("Structs", &s)
...
}
Ответ 3
В частности, в случае Datastore вы должны иметь возможность повторно использовать один и тот же appengine.Context
среди разных запросов. Я не пытался это сделать сам, но способ, которым работает альтернативный API для хранилища данных goon:
Goon отличается от пакета хранилища различными способами: он запоминает контекст appengine, который нужно указывать только один раз во время создания
То, что хранилище должно зависеть от HTTP-запроса, звучит смешно, кстати. Я не думаю, что Datastore зависит от конкретной просьбы в обычном смысле. Скорее всего, ему нужно было определить конкретное приложение Google App Engine, которое, очевидно, остается неизменным с запросом на запрос. Мои предположения основаны на быстром просмотре исходного кода google.golang.org/appengine
.
Вы можете делать подобные наблюдения в отношении других API приложений Google App Engine. Причина в том, что все детали могут быть специфичными для реализации, и я провел более глубокие исследования, прежде чем фактически использовать эти наблюдения в реальном приложении.
Ответ 4
Стоит отметить, что команда Go представила пакет golang.org/x/net/context.
Позже контекст был доступен в управляемых виртуальных машинах, здесь здесь. В документации указано:
Этот репозиторий поддерживает текущую среду Go в App Engine, включая как классические App Engine, так и управляемые виртуальные машины. Он предоставляет API для взаимодействия с службами App Engine. Его путь канонического импорта google.golang.org/appengine
.
Это означает, что вы можете легко написать еще один пакет из среды dev в зависимости от appengine
.
В частности, очень легко обернуть пакеты, такие как appengine/log
(тривиальная обертка журнала пример).
Но еще важнее это позволяет создавать обработчики в форме:
func CoolHandler(context.Context, http.ResponseWriter, *http.Request)
Здесь есть статья о пакете context
в блоге Go здесь. Я написал об использовании контекста здесь. Если вы решите использовать обработчик с проходящим вокруг контекстом, хорошо создать контекст для всех реквистеров в одном месте. Вы можете сделать это, используя нестандартный requrest router, например github.com/orian/wctx.