2013-05-30 2 views
18

Я пишу приложение Go, чтобы запускаться во время выполнения App Engine.Как я могу управлять контекстом среды App Engine Go, чтобы избежать блокировки App Engine?

Я заметил, что практически любая операция, использующая сервис App Engine (например, Datastore, Mail или даже Capabilities), требует, чтобы вы передали ему экземпляр appengine.Context, который должен быть получен с помощью функции appengine.NewContext(req *http.Request) Context.

Хотя я пишу это приложение для App Engine, я хочу легко и быстро перенести его на другую платформу (возможно, такую, которая не поддерживает какой-либо API App Engine), если я так хочу.

Итак, я абстрагирую фактическое взаимодействие с сервисами App Engine и API, написав небольшие обертки вокруг любого взаимодействия с App Engine, включая функции обработки запросов. При таком подходе, если я когда-либо захочу перейти на другую платформу, я просто переписал те конкретные модули, которые привязывают мое приложение к 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. Единственная проблема заключается в том, что "multiple requests may be handled concurrently by a given instance", что может привести к условиям гонки и неожиданному поведению с этим планом. (Один запрос устанавливает его, другой устанавливает это, первый обращается к нему и получает неправильный appengine.Context объект.)

я мог бы теоретически хранить appengine.Context к хранилищу данных, но тогда я должен был бы пройти некоторый запрос конкретного идентификатора вниз по логическим уровням к модулям, специфичным для службы, определяющим, какой объект appengine.Context в хранилище данных является единственным для текущего запроса, который снова соединит вещи, которые, как я думаю, не следует связывать. (И, это приведет к увеличению использования хранилища данных моего приложения.)

Я мог бы также передать appengine.Context объект вниз по всей логической цепочки с типом interface{} весь путь и иметь какой-либо модуль, который не нуждается в appengine.Context объект игнорировать его. Это позволит избежать привязки большей части моего приложения к чему-либо специфическим. Однако это также кажется очень беспорядочным.

Итак, я немного потрудился, как обеспечить чистоту модулей App-Engine, которым нужен объект appengine.Context, и получить его. Надеюсь, вы, ребята, можете дать мне решение, о котором я еще не подумал.

Заранее благодарен!

ответ

8

Это сложно, потому что ваше самонастраиваемое правило видимости (что является разумным) означает не пропускать экземпляр Context, и нет ничего похожего на Java ThreadLocal для достижения тех же целей с помощью скрытых средств. На самом деле это действительно хорошая штука.

Context сочетает в себе систему ведения журнала (легко) с помощью Call для обслуживания приложений (не просто). Я думаю, что десять функций appengine нуждаются в Context. Я не вижу никакого чистого решения, кроме как обертывать все это за вашим собственным фасадом.

Есть одна вещь, которая может вам помочь - вы можете включить конфигурационный файл с вашим приложением, который указывает, находится ли он в GAE или иначе, используя какой-либо флаг. Ваш глобальный логический объект должен хранить этот флаг (не общий контекст). Затем ваши функции фасада могут проконсультироваться с этим флагом при принятии решения о том, использовать ли NewContext(r) для получения Context для доступа к службам GAE или использовать обратную структуру для доступа к вашим собственным услугам-заменителям.

Редактировать: Как последнее замечание, когда вы решите это, я могу предложить вам поделиться тем, как вы это сделали, возможно, даже с проектом с открытым исходным кодом? Чика меня спросить, но если вы не просите ... ;-)

+0

Спасибо за ваш ответ! Я думаю, у меня есть идея, которая может работать для моего конкретного приложения. Я не уверен, что это будет работать в целом, но может работать для некоторых значительных процентов приложений App Engine. Если моя идея станет полу-общим решением, я обязательно выпущу ее как проект с открытым исходным кодом. : D – AntiMS

+1

Большая часть моего приложения имеет доступ к идентификатору пользователя или идентификатору сеанса. Мой план состоит в том, чтобы создать постоянную карту, привязанную к значениям, полученным непосредственно из идентификатора сеанса или идентификатора пользователя. Обработчики запросов могут создавать и хранить объекты контекста, а модули, которые нуждаются в контексте, могут получить их аналогичным образом. Хотя я не совсем уверен, что это сработает. (Могут быть некоторые модули, которые не имеют доступа к идентификатору сеанса или идентификатору пользователя. Возможно, в некоторых случаях мне придется проявить творческий подход). – AntiMS

7

я (надеюсь) решить эту проблему, обернув свои обработчики запросов (в данном примере один называется «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) 
    ... 
} 
1

В частности, в случае хранилища данных вы должны иметь возможность повторно использовать один и тот же appengine.Context среди разных требований. Я не пытался сделать это сам, но это так, как альтернативный API для Datastore называется goon работы:

Goon отличается от пакета датастора различными способами: он запоминает Appengine контекст, который нужно только быть указан один раз во время создания

То, что хранилище должно зависеть от HTTP-запроса, звучит смешно, кстати. Я не думаю, что Datastore зависит от конкретного запроса в обычном смысле. Скорее всего, необходимо определить конкретное приложение Google App Engine, которое, очевидно, остается неизменным с запросом на запрос. Мои предположения основаны на быстром просмотре source codegoogle.golang.org/appengine.

Вы можете делать подобные наблюдения в отношении других API приложений Google App Engine. Из-за всех подробностей может быть конкретной реализации, и я сделал более глубокие исследования, прежде чем фактически использовать эти наблюдения в реальном приложении.

1

Следует отметить, что команда Go представила пакет golang.org/x/net/context.

Позже контекст был предоставлен в управляемых виртуальных машинах, репо - here. В документации указано:

Этот репозиторий поддерживает текущую среду Go в App Engine, включая как классический App Engine, так и управляемые виртуальные машины. Он предоставляет API для взаимодействия с службами App Engine. Его канонический путь импорта - google.golang.org/appengine.

Это означает, что вы можете легко написать еще один пакет из среды dev в зависимости от appengine.

В частности, очень легко обернуть пакеты, такие как appengine/log (тривиальная обертка журнала example).

Но еще более важно, это позволяет создавать обработчик в виде:

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

Там в статье о context пакете на Go блог here. Я писал об использовании контекста here. Если вы решите использовать обработчик с проходящим контекстом, полезно создать контекст для всех запросов в одном месте. Вы можете сделать это, используя нестандартный requrest маршрутизатор, такой как github.com/orian/wctx.