Implémenter l'architecture mutualisée avec des espaces de noms

L'API Namespaces permet d'activer facilement l'architecture mutualisée dans votre application en sélectionnant simplement une chaîne d'espace de noms pour chaque locataire de appengine_config.py à l'aide du package namespace_manager.

Définir l'espace de noms actuel

Vous pouvez obtenir, définir et valider des espaces de nommage à l'aide de namespace_manager. Le gestionnaire d'espaces de noms vous permet de définir un espace de noms actuel pour les API compatibles avec les espaces de noms. Utilisez appengine_config.py pour définir un espace de noms actuel à l'avance. Le datastore et memcache utiliseront automatiquement cet espace de noms.

La plupart des développeurs App Engine utilisent leur domaine G Suite comme espace de nommage actuel. G Suite vous permet de déployer votre application sur n'importe quel domaine que vous possédez. Vous pouvez ainsi facilement utiliser ce mécanisme pour configurer différents espaces de nommage pour des domaines distincts. Vous pouvez ensuite utiliser ces espaces de nommage séparés pour isoler les données sur l'ensemble des domaines. Pour en savoir plus sur la configuration de plusieurs domaines dans le tableau de bord G Suite, consultez l'article Déployer votre application sur une URL G Suite.

L'exemple de code suivant vous montre comment définir l'espace de nommage actuel sur le domaine G Suite utilisé pour mapper l'URL. En particulier, cette chaîne sera la même pour toutes les URL mappées via le même domaine G Suite.

Pour définir un espace de nommage en Python, utilisez le système de configuration App Engine appengine_config.py dans le répertoire racine de votre application. L'exemple simple ci-dessous illustre l'utilisation du domaine G Suite en tant qu'espace de nommage actuel :

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()

Si vous ne spécifiez pas de valeur pour l'élément namespace, celui-ci est défini sur une chaîne vide. La chaîne de l'élément namespace est arbitraire, mais ne doit pas contenir plus de 100 caractères alphanumériques, points, traits de soulignement et traits d'union. En d'autres termes, les chaînes d'espace de noms doivent correspondre à l'expression régulière [0-9A-Za-z._-]{0,100}.

Par convention, tous les espaces de nommage commençant par "_" (trait de soulignement) sont destinés à être utilisés par le système. Cette règle concernant les espaces de nommage système n'est pas appliquée de manière stricte, mais vous risquez d'être confronté à des conséquences négatives non définies si vous ne la respectez pas.

Pour plus d'informations générales sur la configuration de appengine_config.py, consultez la section Configuration du module Python.

Conseils pour éviter les fuites de données

Les fuites de données sur l'ensemble des espaces de nommage constituent l'un des risques communément associés aux applications mutualisées. Les fuites de données involontaires peuvent avoir plusieurs causes, telles que :

  • L'utilisation d'espaces de nommage avec des API App Engine qui ne sont pas encore compatibles avec les espaces de nommage. C'est le cas de Blobstore, par exemple. Si vous utilisez les espaces de nommage avec l'API Blobstore, vous devez éviter d'utiliser des requêtes Blobstore pour les requêtes d'utilisateur final ou des clés Blobstore provenant de sources non fiables.
  • L'utilisation d'un support de stockage externe (plutôt que d'un système Memcache ou d'un datastore) via URL Fetch ou tout autre mécanisme, sans fournir de schéma de compartimentation pour les espaces de nommage.
  • La définition d'un espace de nommage basé sur le domaine de messagerie d'un utilisateur. Dans la plupart des cas, il n'est pas souhaitable que toutes les adresses e-mail d'un domaine accèdent à un espace de nommage. L'utilisation du domaine de messagerie permet également d'éviter que votre application utilise un espace de nommage tant que l'utilisateur n'est pas connecté.

Déployer des espaces de nommage

Les sections suivantes décrivent comment déployer des espaces de nommage avec d'autres API et outils App Engine.

Créer des espaces de nommage pour chaque utilisateur

Certaines applications nécessitent la création d'espaces de nommage basés sur l'utilisateur. Si vous souhaitez compartimenter les données au niveau de l'utilisateur pour les utilisateurs connectés, pensez à inclure l'élément User.user_id(), qui renvoie un ID unique et permanent pour l'utilisateur. L'exemple de code suivant indique comment utiliser l'API Users dans ce sens :

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()

De manière générale, les applications qui créent des espaces de nommage par utilisateur proposent également des pages de destination spécifiques aux différents utilisateurs. Dans ces cas, l'application doit fournir un modèle d'URL qui dicte quelle page de destination afficher à un utilisateur.

Utiliser les espaces de nommage avec le datastore

Pour les requêtes de datastore, le datastore utilise par défaut le paramètre de l'espace de noms actuel défini dans le gestionnaire d'espaces de noms. L'API applique cet espace de noms actuel aux objets Key ou Query lors de leur création. Vous devez donc être vigilant si une application stocke des objets Key ou Query sous des formes sérialisées, étant donné que l'espace de noms est préservé dans ces sérialisations.

Si vous utilisez des objets Key ou Query désérialisés, assurez-vous que leur comportement soit conforme à vos attentes. La plupart des applications simples utilisant un datastore (put/query/get) sans autre mécanisme de stockage fonctionneront comme prévu si vous définissez l'espace de nommage actuel avant d'appeler une API de datastore.

Les objets Query et Key adoptent les comportements uniques suivants en ce qui concerne les espaces de nommage :

  • Lors de leur création, les objets Query et Key héritent de l'espace de nommage actuel, à moins que vous ne définissiez un espace de nommage explicite.
  • Lorsqu'une application crée un objet Key à partir d'un ancêtre, ce nouvel objet Key hérite de l'espace de nommage de l'ancêtre.

L'exemple de code suivant indique que le gestionnaire de requêtes incrémente un compteur dans le datastore pour l'espace de nommage global et un espace de nommage nommé arbitrairement.

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)

Utiliser les espaces de noms avec Memcache

Pour les requêtes de cache mémoire, le cache mémoire utilise, par défaut, l'espace de noms actuel à partir du gestionnaire d'espaces de noms. Dans la plupart des cas, il n'est pas nécessaire de définir explicitement un espace de nommage dans le cache mémoire, et des bogues inattendus peuvent survenir si vous le faites.

Il existe cependant certaines instances uniques pour lesquelles il convient de définir explicitement un espace de nommage dans le cache mémoire. Imaginons, par exemple, que votre application possède des données communes partagées par l'ensemble des espaces de nommage (telles qu'un tableau contenant des codes pays).

L'extrait de code suivant indique comment définir explicitement l'espace de nommage dans le cache mémoire :

Avec l'API Python de cache mémoire (Memcache), vous pouvez obtenir l'espace de nommage actuel du gestionnaire d'espaces de nommage ou le définir de façon explicite lors de la création du service Memcache.

L'exemple de code suivant indique que le gestionnaire de requêtes incrémente un compteur dans memcache pour l'espace de nommage global et un espace de nommage nommé arbitrairement.

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)

Dans l'exemple ci-dessous, l'espace de noms est explicitement défini lorsque vous stockez une valeur dans le cache mémoire :

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

Utiliser les espaces de noms avec l'API Task Queue

Par défaut, les files d'attente d'envoi utilisent l'espace de nommage actuel tel qu'il est défini dans le gestionnaire d'espaces de nommage au moment de la création de la tâche. Dans la plupart des cas, il n'est pas nécessaire de définir explicitement un espace de noms dans la file d'attente de tâches, et des bogues inattendus peuvent survenir si vous le faites.

Les noms de tâches sont partagés par l'ensemble des espaces de nommage. Vous ne pouvez pas créer deux tâches dotées du même nom, même si elles utilisent des espaces de nommage différents. Si vous voulez utiliser le même nom de tâche pour plusieurs espaces de nommage, il vous suffit d'ajouter chaque espace de nommage au nom de tâche.

Lorsqu'une nouvelle tâche appelle la méthode add() de file d'attente de tâches, celle-ci copie l'espace de noms actuel et (le cas échéant) le domaine G Suite à partir du gestionnaire d'espaces de noms. Une fois la tâche exécutée, l'espace de nommage actuel et l'espace de nommage G Suite sont restaurés.

Si l'espace de nommage actuel n'est pas défini dans la requête d'origine (en d'autres termes, si l'opération get_namespace() renvoie le résultat ''), vous pouvez utiliser set_namespace() pour définir l'espace de nommage actuel de la tâche.

Il existe certaines instances uniques pour lesquelles il convient de définir explicitement un espace de nommage pour une tâche qui fonctionne sur l'ensemble des espaces de nommage. Imaginons, par exemple, que vous créiez une tâche qui agrège les statistiques d'utilisation sur l'ensemble des espaces de nommage. Vous pourriez alors définir explicitement l'espace de nommage de la tâche. L'exemple de code suivant indique comment définir explicitement des espaces de nommage avec la file d'attente de tâches.

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)

Utiliser les espaces de nommage avec le Blobstore

Le Blobstore n'est pas segmenté par espace de nommage. Pour préserver un espace de nommage dans le Blobstore, vous devez accéder au Blobstore via un support de stockage prenant en charge l'espace de nommage (à l'heure actuelle, le cache mémoire, le datastore et la file d'attente de tâches). Par exemple, si la Key d'un objet blob est stockée dans une entité du datastore, vous pouvez y accéder à l'aide d'une Key ou d'une Query de datastore qui prend en charge l'espace de nommage.

Si l'application accède au Blobstore via des clés stockées dans un emplacement de stockage compatible avec les espaces de nommage, il n'est pas nécessaire que le Blobstore lui-même soit segmenté par espace de nommage. Afin d'éviter les fuites d'objets blob entre les espaces de nommage :

  • Les applications ne doivent pas utiliser l'élément BlobInfo.gql() pour les requêtes d'utilisateur final. Les requêtes BlobInfo peuvent être utilisées pour des requêtes administratives (telles que la génération de rapports relatifs à tous les objets blob des applications), mais il est déconseillé de les utiliser pour des requêtes d'utilisateur final du fait du risque de fuites de données, les enregistrements BlobInfo n'étant pas tous compartimentés par espace de nommage.
  • Les applications ne doivent pas utiliser de clés Blobstore provenant de sources non approuvées.

Définir des espaces de nommage pour les requêtes Datastore

Dans la console Google Cloud Platform, vous pouvez définir l'espace de nommage pour les requêtes Datastore.

Si vous ne souhaitez pas utiliser la valeur par défaut, sélectionnez l'espace de nommage à utiliser dans la liste déroulante.

Utiliser les espaces de nommage avec l'API Bulk Loader

L'API Bulk Loader est compatible avec l'indicateur --namespace=NAMESPACE qui vous permet de spécifier l'espace de nommage à utiliser. Chaque espace de nommage est traité séparément, de sorte que si vous voulez accéder à tous les espaces de nommage, vous devez tous les parcourir.

Lorsque vous créez une nouvelle instance de Index, celle-ci est affectée par défaut à l'espace de nommage actuel :

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

Vous pouvez également attribuer des droits d'administrateur de manière explicite dans le constructeur :

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

Une fois que vous avez créé une spécification d'index, son espace de noms ne peut plus être modifié :

# 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')
Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Environnement standard App Engine pour Python 2