Como implementar a multilocação usando namespaces

Com a API Namespaces, é possível ativar facilmente a multilocação no aplicativo. Basta selecionar uma string de namespace para cada locatário em appengine_config.py usando o pacote namespace_manager.

Como definir o namespace atual

Acesse, defina e valide os namespaces usando namespace_manager. Com o gerenciador de namespaces, é possível configurar um namespace atual para APIs ativadas para namespace. Para fazer isso antecipadamente, use appengine_config.py. Esse namespace será usado automaticamente pelo armazenamento de dados e pelo Memcache.

A maioria dos desenvolvedores do App Engine usa o domínio do G Suite como namespace atual. Com o G Suite, é possível implantar o aplicativo em qualquer domínio próprio. Portanto, esse mecanismo pode ser usado para configurar facilmente namespaces diferentes para domínios distintos. Em seguida, use esses namespaces separados para segregar dados entre os domínios. Para mais informações sobre como configurar vários domínios no painel do G Suite, acesse Como implantar seu aplicativo no URL do G Suite.

A amostra de código a seguir mostra como definir o namespace atual no domínio do G Suite que foi usado para mapear o URL. Particularmente, essa string será a mesma para todos os URLs mapeados por meio do mesmo domínio do G Suite.

Para definir um namespace no Python, use o sistema de configuração do App Engine appengine_config.py no diretório raiz do aplicativo. No simples exemplo a seguir, demonstramos como usar o domínio do G Suite como namespace atual:

from google.appengine.api import namespace_manager

# Called only if the current namespace is not set.
def namespace_manager_default_namespace_for_request():
    # The returned string will be used as the Google Apps domain.
    return namespace_manager.google_apps_namespace()

Se você não especificar um valor para namespace, o namespace será definido como uma string vazia. A string namespace é arbitrária, mas também limitada a um máximo de 100 caracteres alfanuméricos, pontos, sublinhados e hifens. Mais explicitamente, as strings de namespace precisam corresponder à expressão regular [0-9A-Za-z._-]{0,100}.

Por convenção, todos os namespaces que começam com "_" (sublinhado) são reservados para uso do sistema. Essa regra de namespaces do sistema não é obrigatória, mas será fácil encontrar consequências negativas indefinidas se você não a seguir.

Para mais informações gerais sobre como configurar appengine_config.py, consulte Configuração do módulo Python.

Como evitar vazamento de dados

Um dos riscos comumente associados a aplicativos multilocatários é o perigo de que dados vazem entre namespaces. Os vazamentos de dados inesperados podem surgir de muitas fontes, como estas:

  • Usar namespaces com APIs do App Engine que ainda não são compatíveis com namespaces. Por exemplo, o Blobstore não é compatível com namespaces. Se você usar Namespaces com o Blobstore, precisará evitar o uso de consultas Blobstore para solicitações de usuários finais ou chaves Blobstore de fontes não confiáveis.
  • Usar um meio de armazenamento externo (em vez de Memcache e armazenamento de dados), via URL Fetch ou algum outro mecanismo, sem fornecer um esquema de compartimentalização para namespaces.
  • Definir um namespace com base no domínio do e-mail de um usuário. Na maioria dos casos, não é desejável que todos os endereços de e-mail de um domínio tenham acesso a um namespace. Usar o domínio do e-mail também impede que o aplicativo use um namespace antes que o usuário faça login.

Como implantar namespaces

Veja nas seções a seguir como implantar namespaces com outras APIs e ferramentas do App Engine.

Como criar namespaces por usuário

Alguns aplicativos precisam criar namespaces por usuário. Se você quiser compartimentar dados no nível do usuário para usuários conectados, use User.user_id(), que retorna um código permanente e exclusivo. A amostra de código a seguir demonstra como usar a API de usuários para esse fim:

from google.appengine.api import users

def namespace_manager_default_namespace_for_request():
    # assumes the user is logged in.
    return users.get_current_user().user_id()

Geralmente, os aplicativos que criam namespaces por usuário também fornecem páginas de destino específicas para diferentes usuários. Nesses casos, o aplicativo deve fornecer um esquema de URL que dite qual página de destino exibir para um usuário.

Como usar namespaces com o Datastore

Por padrão, solicitações de armazenamento de dados no Datastore são feitas usando a configuração do namespace atual no gerenciador de namespaces. A API aplica esse namespace atual aos objetos Key (em inglês) ou Query quando eles são criados. Portanto, tome cuidado se um aplicativo armazenar objetos Key ou Query em formulários serializados, porque o namespace é preservado nessas serializações.

Se você estiver usando objetos Key e Query sem serialização, verifique se eles se comportam conforme o esperado. A maioria dos aplicativos simples que usa o armazenamento de dados (put/query/get) sem outros mecanismos de armazenamento funcionará conforme o esperado, configurando o namespace atual antes de chamar qualquer API de armazenamento de dados.

Nos objetos Query e Key, é possível ver os comportamentos únicos a seguir, em relação aos namespaces:

  • Objetos Query e Key herdam o namespace atual quando construídos, a menos que você defina um namespace explícito.
  • Quando um aplicativo cria uma nova Key de um ancestral, a nova Key herda o namespace dele.

O exemplo de código a seguir mostra um gerenciador de solicitações de amostra que incrementa um contador no armazenamento de dados para o namespace global e um namespace especificado arbitrariamente.

from google.appengine.api import namespace_manager
from google.appengine.ext import ndb
import webapp2

class Counter(ndb.Model):
    count = ndb.IntegerProperty()

@ndb.transactional
def update_counter(name):
    """Increment the named counter by 1."""
    counter = Counter.get_by_id(name)
    if counter is None:
        counter = Counter(id=name, count=0)

    counter.count += 1
    counter.put()

    return counter.count

class DatastoreCounterHandler(webapp2.RequestHandler):
    """Increments counters in the global namespace as well as in whichever
    namespace is specified by the request, which is arbitrarily named 'default'
    if not specified."""

    def get(self, namespace='default'):
        global_count = update_counter('counter')

        # Save the current namespace.
        previous_namespace = namespace_manager.get_namespace()
        try:
            namespace_manager.set_namespace(namespace)
            namespace_count = update_counter('counter')
        finally:
            # Restore the saved namespace.
            namespace_manager.set_namespace(previous_namespace)

        self.response.write('Global: {}, Namespace {}: {}'.format(
            global_count, namespace, namespace_count))

app = webapp2.WSGIApplication([
    (r'/datastore', DatastoreCounterHandler),
    (r'/datastore/(.*)', DatastoreCounterHandler)
], debug=True)

Como usar namespaces com o Memcache

Por padrão, o memcache usa o namespace atual do gerenciador de namespaces para solicitações do memcache. Na maioria dos casos, você não precisa definir um namespace explicitamente no memcache. Fazer isso pode causar bugs inesperados.

Entretanto, há algumas ocasiões específicas em que é adequado definir um namespace explicitamente no memcache. Por exemplo, o aplicativo pode ter dados em comum compartilhados entre todos os namespaces (como uma tabela contendo códigos de países).

Veja no snippet de código abaixo como definir o namespace explicitamente no memcache:

Usando a API do Python para memcache, é possível acessar o namespace atual do gerenciador de namespaces ou defini-lo explicitamente ao criar o serviço do memcache.

O exemplo de código a seguir mostra um gerenciador de solicitações de amostra que incrementa um contador no memcache para o namespace global e um namespace especificado arbitrariamente.

from google.appengine.api import memcache
from google.appengine.api import namespace_manager
import webapp2

class MemcacheCounterHandler(webapp2.RequestHandler):
    """Increments counters in the global namespace as well as in whichever
    namespace is specified by the request, which is arbitrarily named 'default'
    if not specified."""

    def get(self, namespace='default'):
        global_count = memcache.incr('counter', initial_value=0)

        # Save the current namespace.
        previous_namespace = namespace_manager.get_namespace()
        try:
            namespace_manager.set_namespace(namespace)
            namespace_count = memcache.incr('counter', initial_value=0)
        finally:
            # Restore the saved namespace.
            namespace_manager.set_namespace(previous_namespace)

        self.response.write('Global: {}, Namespace {}: {}'.format(
            global_count, namespace, namespace_count))

app = webapp2.WSGIApplication([
    (r'/memcache', MemcacheCounterHandler),
    (r'/memcache/(.*)', MemcacheCounterHandler)
], debug=True)

O exemplo abaixo define o namespace explicitamente ao armazenar um valor no memcache:

  // Store an entry to the memcache explicitly
memcache.add("key", data, namespace='abc')

Como usar namespaces com a fila de tarefas

Por padrão, as filas push usam o namespace atual configurado no administrador de namespace no momento em que a tarefa for criada. Na maioria dos casos, você não precisa definir um namespace explicitamente na fila de tarefas, e fazer isso pode gerar bugs inesperados.

Os nomes de tarefas são compartilhados com todos os namespaces. Não é possível criar duas tarefas com o mesmo nome, mesmo se usarem namespaces diferentes. Se quiser usar o mesmo nome de tarefa para vários namespaces, é possível simplesmente anexar cada namespace ao nome da tarefa.

Quando o método add() da fila de tarefas é chamado por uma nova tarefa, são copiados o namespace atual e, se aplicável, o domínio do G Suite do gerenciador de namespaces. Quando a tarefa é executada, o namespace atual e o namespace do G Suite são restaurados.

Se o namespace atual não estiver definido na solicitação de origem (em outras palavras, se get_namespace() retornar ''), use set_namespace() para definir o namespace atual para a tarefa.

Há algumas ocasiões específicas em que é adequado definir um namespace explicitamente para uma tarefa que funciona em vários namespaces. Por exemplo, é possível criar uma tarefa que agrega estatísticas de uso para todos os namespaces. Depois, você define o namespace da tarefa explicitamente. A amostra de código a seguir demonstra como definir namespaces explicitamente com a fila de tarefas.

from google.appengine.api import namespace_manager
from google.appengine.api import taskqueue
from google.appengine.ext import ndb
import webapp2

class Counter(ndb.Model):
    count = ndb.IntegerProperty()

@ndb.transactional
def update_counter(name):
    """Increment the named counter by 1."""
    counter = Counter.get_by_id(name)
    if counter is None:
        counter = Counter(id=name, count=0)

    counter.count += 1
    counter.put()

    return counter.count

def get_count(name):
    counter = Counter.get_by_id(name)
    if not counter:
        return 0
    return counter.count

class DeferredCounterHandler(webapp2.RequestHandler):
    def post(self):
        name = self.request.get('counter_name')
        update_counter(name)

class TaskQueueCounterHandler(webapp2.RequestHandler):
    """Queues two tasks to increment a counter in global namespace as well as
    the namespace is specified by the request, which is arbitrarily named
    'default' if not specified."""
    def get(self, namespace='default'):
        # Queue task to update global counter.
        current_global_count = get_count('counter')
        taskqueue.add(
            url='/tasks/counter',
            params={'counter_name': 'counter'})

        # Queue task to update counter in specified namespace.
        previous_namespace = namespace_manager.get_namespace()
        try:
            namespace_manager.set_namespace(namespace)
            current_namespace_count = get_count('counter')
            taskqueue.add(
                url='/tasks/counter',
                params={'counter_name': 'counter'})
        finally:
            namespace_manager.set_namespace(previous_namespace)

        self.response.write(
            'Counters will be updated asyncronously.'
            'Current values: Global: {}, Namespace {}: {}'.format(
                current_global_count, namespace, current_namespace_count))

app = webapp2.WSGIApplication([
    (r'/tasks/counter', DeferredCounterHandler),
    (r'/taskqueue', TaskQueueCounterHandler),
    (r'/taskqueue/(.*)', TaskQueueCounterHandler)
], debug=True)

Como usar namespaces com o Blobstore

O Blobstore não é segmentado por namespace. Para preservar um namespace no Blobstore, é necessário acessar o Blobstore por um meio de armazenamento que considere namespaces (atualmente, apenas o memcache, o armazenamento de dados e a fila de tarefas). Por exemplo, se a Key de um blob for armazenada em uma entidade do armazenamento de dados, é possível acessá-la com uma Key ou Query do armazenamento de dados que considere o namespace.

Se o aplicativo está acessando o Blobstore por meio de chaves armazenadas em armazenamento que considera namespaces, o próprio Blobstore não precisa ser segmentado por namespace. Os aplicativos precisam evitar vazamentos de blobs entre namespaces com as seguintes ações:

  • Não usar BlobInfo.gql() para solicitações de usuário final. Use as consultas BlobInfo para solicitações administrativas (como gerar relatórios sobre todos os blobs de aplicativos), mas usá-las para solicitações de usuários finais pode resultar em vazamentos de dados, porque os registros de BlobInfo não são compartimentados por namespace.
  • Não usar chaves do Blobstore de fontes não confiáveis.

Como definir namespaces para consultas ao Datastore

No Console do Google Cloud Platform, é possível definir o namespace para consultas ao Datastore.

Se você não quiser usar o padrão, selecione o namespace desejado na lista suspensa.

Como usar namespaces com o carregador em massa

O carregador em massa é compatível com uma sinalização --namespace=NAMESPACE que permite especificar o namespace a ser usado. Cada namespace é manipulado separadamente. Se quiser acessar todos os namespaces, itere-os.

Quando você cria uma nova instância de Index, ela é atribuída ao namespace atual por padrão:

# set the current namespace
namespace_manager.set_namespace("aSpace")
index = search.Index(name="myIndex")
# index namespace is now fixed to "aSpace"

Você também pode atribuir um namespace explicitamente no construtor:

index = search.Index(name="myIndex", namespace="aSpace")

Depois de criar uma especificação de índice, o namespace dela não poderá ser alterado:

# change the current namespace
namespace_manager.set_namespace("anotherSpace")
# the namespaceof 'index' is still "aSpace" because it was bound at create time
index.search('hello')
Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Ambiente padrão do App Engine para Python 2