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

L'API Namespaces vous permet d'activer facilement une architecture mutualisée dans votre application en sélectionnant simplement une chaîne d'espace de noms pour chaque locataire dans le fichier web.xml à l'aide du package NamespaceManager.

Définir l'espace de noms actuel

Vous pouvez obtenir, définir et valider des espaces de noms à l'aide de NamespaceManager. 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 web.xml pour définir un espace de noms actuel à l'avance. Le datastore et Memcache utilisent automatiquement cet espace de noms.

La plupart des développeurs App Engine utilisent leur domaine G Suite comme espace de noms 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 noms 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.

Vous pouvez définir des espaces de noms en Java à l'aide de l'interface de filtre de servlet avant d'appeler les méthodes de servlet. L'exemple simple ci-dessous illustre l'utilisation du domaine G Suite en tant qu'espace de noms actuel :

Java 8

// Filter to set the Google Apps domain as the namespace.
public class NamespaceFilter implements Filter {

  @Override
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {
    // Make sure set() is only called if the current namespace is not already set.
    if (NamespaceManager.get() == null) {
      // If your app is hosted on appspot, this will be empty. Otherwise it will be the domain
      // the app is hosted on.
      NamespaceManager.set(NamespaceManager.getGoogleAppsNamespace());
    }
    chain.doFilter(req, res); // Pass request back down the filter chain
  }

Java 7

// Filter to set the Google Apps domain as the namespace.
public class NamespaceFilter implements Filter {
  @Override
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {
    // Make sure set() is only called if the current namespace is not already set.
    if (NamespaceManager.get() == null) {
      // If your app is hosted on appspot, this will be empty. Otherwise it will be the domain
      // the app is hosted on.
      NamespaceManager.set(NamespaceManager.getGoogleAppsNamespace());
    }
    chain.doFilter(req, res); // Pass request back down the filter chain
  }

Le filtre d'espace de noms doit être configuré dans le fichier web.xml. Notez que s'il existe plusieurs entrées de filtre, le premier espace de noms défini est celui qui sera utilisé.

L'exemple de code suivant illustre la configuration du filtre d'espace de noms dans web.xml :

Java 8

<!-- Configure the namespace filter. -->
<filter>
    <filter-name>NamespaceFilter</filter-name>
    <filter-class>com.example.appengine.NamespaceFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>NamespaceFilter</filter-name>
    <url-pattern>/sign</url-pattern>
</filter-mapping>

Java 7

<!-- Configure the namespace filter. -->
<filter>
    <filter-name>NamespaceFilter</filter-name>
    <filter-class>com.example.appengine.NamespaceFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>NamespaceFilter</filter-name>
    <url-pattern>/sign</url-pattern>
</filter-mapping>

Pour obtenir des informations plus générales sur le fichier web.xml et le mappage de chemins d'URL vers les servlets, consultez le déscripteur de déploiement : web.xml.

Vous pouvez également définir un nouvel espace de noms pour une opération temporaire, en restaurant l'espace de noms d'origine une fois l'opération terminée, à l'aide du schéma try/finally présenté ci-dessous :

Java 8

// Set the namepace temporarily to "abc"
String oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
  //      ... perform operation using current namespace ...
} finally {
  NamespaceManager.set(oldNamespace);
}

Java 7

// Set the namepace temporarily to "abc"
String oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
//    ... perform operation using current namespace ...
} finally {
  NamespaceManager.set(oldNamespace);
}

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 noms 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.

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 noms 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.getUserId(), qui renvoie un ID unique et permanent pour l'utilisateur. L'exemple de code suivant indique comment utiliser l'API Users dans ce sens :

Java 8

if (com.google.appengine.api.NamespaceManager.get() == null) {
  // Assuming there is a logged in user.
  namespace = UserServiceFactory.getUserService().getCurrentUser().getUserId();
  NamespaceManager.set(namespace);
}

Java 7

if (com.google.appengine.api.NamespaceManager.get() == null) {
  // Assuming there is a logged in user.
  namespace = UserServiceFactory.getUserService().getCurrentUser().getUserId();
  NamespaceManager.set(namespace);
}

De manière générale, les applications qui créent des espaces de noms pour chaque 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 nom 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 nouvel objet Key à partir d'un ancêtre, ce nouvel objet Key hérite de l'espace de noms de l'ancêtre.
  • Aucune API pour Java ne permet de définir explicitement l'espace de noms d'un objet Key ou Query.
L'exemple de code suivant indique que le gestionnaire de requêtes SomeRequest incrémente la valeur de l'espace de noms actuel ainsi que de l'espace de noms -global- nommé arbitrairement dans une entité de datastore Counter.

Java 8

public class UpdateCountsServlet extends HttpServlet {

  private static final int NUM_RETRIES = 10;

  @Entity
  public class CounterPojo {

    @Id
    public Long id;
    @Index
    public String name;
    public Long count;

    public CounterPojo() {
      this.count = 0L;
    }

    public CounterPojo(String name) {
      this.name = name;
      this.count = 0L;
    }

    public void increment() {
      count++;
    }
  }

  /**
   * Increment the count in a Counter datastore entity.
   **/
  public long updateCount(String countName) {

    CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
    if (cp == null) {
      cp = new CounterPojo(countName);
    }
    cp.increment();
    ofy().save().entity(cp).now();

    return cp.count;
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws java.io.IOException {

    // Update the count for the current namespace.
    updateCount("request");

    // Update the count for the "-global-" namespace.
    String namespace = NamespaceManager.get();
    try {
      // "-global-" is namespace reserved by the application.
      NamespaceManager.set("-global-");
      updateCount("request");
    } finally {
      NamespaceManager.set(namespace);
    }
    resp.setContentType("text/plain");
    resp.getWriter().println("Counts are now updated.");
  }

Java 7

public class UpdateCountsServlet extends HttpServlet {
  private static final int NUM_RETRIES = 10;

  @Entity public class CounterPojo {
    @Id public Long id;
    @Index public String name;
    public Long count;

    public CounterPojo() {
      this.count = 0L;
    }

    public CounterPojo(String name) {
      this.name = name;
      this.count = 0L;
    }

    public void increment() {
      count++;
    }
  }

  // Increment the count in a Counter datastore entity.
  public long updateCount(String countName) {

    CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
    if (cp == null) {
      cp = new CounterPojo(countName);
    }
    cp.increment();
    ofy().save().entity(cp).now();

    return cp.count;
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws java.io.IOException {

    // Update the count for the current namespace.
    updateCount("request");

    // Update the count for the "-global-" namespace.
    String namespace = NamespaceManager.get();
    try {
      // "-global-" is namespace reserved by the application.
      NamespaceManager.set("-global-");
      updateCount("request");
    } finally {
      NamespaceManager.set(namespace);
    }
    resp.setContentType("text/plain");
    resp.getWriter().println("Counts are now updated.");
  }

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 noms dans le memcache :

Par défaut, l’API Memcache pour Java envoie une requête au gestionnaire d’espaces de noms concernant l’espace de noms actuel à partir de MemcacheService. Vous pouvez également indiquer explicitement un espace de noms lorsque vous générez le memcache à l'aide de getMemcacheService(Namespace). Pour la plupart des applications, il est inutile de spécifier explicitement un espace de noms.

L'exemple de code suivant montre comment créer un memcache qui utilise l'espace de noms actuel du gestionnaire d'espaces de noms :

Java 8

// Create a MemcacheService that uses the current namespace by
// calling NamespaceManager.get() for every access.
MemcacheService current = MemcacheServiceFactory.getMemcacheService();

// stores value in namespace "abc"
oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
  current.put("key", value); // stores value in namespace “abc”
} finally {
  NamespaceManager.set(oldNamespace);
}

Java 7

// Create a MemcacheService that uses the current namespace by
// calling NamespaceManager.get() for every access.
MemcacheService current = MemcacheServiceFactory.getMemcacheService();

// stores value in namespace "abc"
oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
  current.put("key", value);  // stores value in namespace “abc”
} finally {
  NamespaceManager.set(oldNamespace);
}

L'exemple de code suivant spécifie explicitement un espace de noms lors de la création d'un service Memcache :

Java 8

// Create a MemcacheService that uses the namespace "abc".
MemcacheService explicit = MemcacheServiceFactory.getMemcacheService("abc");
explicit.put("key", value); // stores value in namespace "abc"

Java 7

// Create a MemcacheService that uses the namespace "abc".
MemcacheService explicit = MemcacheServiceFactory.getMemcacheService("abc");
explicit.put("key", value);  // stores value in 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 noms actuel et l'espace de noms G Suite sont restaurés.

Si l'espace de noms actuel n'est pas défini dans la requête d'origine (en d'autres termes, si l'opération get() renvoie le résultat null), la file d'attente de tâches définit l'espace de noms sur "" dans les tâches exécutées.

Il existe certaines instances uniques pour lesquelles il convient de définir explicitement un espace de noms pour une tâche qui fonctionne sur l'ensemble des espaces de noms. 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 noms avec la file d'attente de tâches.

Tout d'abord, créez un gestionnaire de file d'attente de tâches qui incrémente le nombre dans une entité de datastore Counter :

Java 8

public class UpdateCountsServlet extends HttpServlet {

  private static final int NUM_RETRIES = 10;

  @Entity
  public class CounterPojo {

    @Id
    public Long id;
    @Index
    public String name;
    public Long count;

    public CounterPojo() {
      this.count = 0L;
    }

    public CounterPojo(String name) {
      this.name = name;
      this.count = 0L;
    }

    public void increment() {
      count++;
    }
  }

  /**
   * Increment the count in a Counter datastore entity.
   **/
  public long updateCount(String countName) {

    CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
    if (cp == null) {
      cp = new CounterPojo(countName);
    }
    cp.increment();
    ofy().save().entity(cp).now();

    return cp.count;
  }

Java 7

public class UpdateCountsServlet extends HttpServlet {
  private static final int NUM_RETRIES = 10;

  @Entity public class CounterPojo {
    @Id public Long id;
    @Index public String name;
    public Long count;

    public CounterPojo() {
      this.count = 0L;
    }

    public CounterPojo(String name) {
      this.name = name;
      this.count = 0L;
    }

    public void increment() {
      count++;
    }
  }

  // Increment the count in a Counter datastore entity.
  public long updateCount(String countName) {

    CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
    if (cp == null) {
      cp = new CounterPojo(countName);
    }
    cp.increment();
    ofy().save().entity(cp).now();

    return cp.count;
  }

et

Java 8

// called from Task Queue
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
  String[] countName = req.getParameterValues("countName");
  if (countName.length != 1) {
    resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    return;
  }
  updateCount(countName[0]);
}

Java 7

// called from Task Queue
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
  String[] countName = req.getParameterValues("countName");
  if (countName.length != 1) {
    resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    return;
  }
  updateCount(countName[0]);
}

Créez ensuite des tâches avec un servlet :

Java 8

public class SomeRequestServlet extends HttpServlet {

  // Handler for URL get requests.
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

    // Increment the count for the current namespace asynchronously.
    QueueFactory.getDefaultQueue()
        .add(TaskOptions.Builder.withUrl("/_ah/update_count").param("countName", "SomeRequest"));
    // Increment the global count and set the
    // namespace locally.  The namespace is
    // transferred to the invoked request and
    // executed asynchronously.
    String namespace = NamespaceManager.get();
    try {
      NamespaceManager.set("-global-");
      QueueFactory.getDefaultQueue()
          .add(TaskOptions.Builder.withUrl("/_ah/update_count").param("countName", "SomeRequest"));
    } finally {
      NamespaceManager.set(namespace);
    }
    resp.setContentType("text/plain");
    resp.getWriter().println("Counts are being updated.");
  }
}

Java 7

public class SomeRequestServlet extends HttpServlet {
  // Handler for URL get requests.
  @Override
  protected void doGet(HttpServletRequest req,
                       HttpServletResponse resp)
      throws IOException {

    // Increment the count for the current namespace asynchronously.
    QueueFactory.getDefaultQueue().add(
        TaskOptions.Builder.withUrl("/_ah/update_count")
            .param("countName", "SomeRequest"));
    // Increment the global count and set the
    // namespace locally.  The namespace is
    // transferred to the invoked request and
    // executed asynchronously.
    String namespace = NamespaceManager.get();
    try {
      NamespaceManager.set("-global-");
      QueueFactory.getDefaultQueue().add(
          TaskOptions.Builder.withUrl("/_ah/update_count")
              .param("countName", "SomeRequest"));
    } finally {
      NamespaceManager.set(namespace);
    }
    resp.setContentType("text/plain");
    resp.getWriter().println("Counts are being updated.");
  }
}

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 noms :

  • N'utilisez pas com.google.appengine.api.blobstore.BlobInfoFactory pour les requêtes de l'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 noms.
  • 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.

Une nouvelle instance de la fonction Index hérite de l'espace de noms du SearchService utilisé pour la créer. Une fois que vous avez créé une référence à un index, son espace de noms ne peut plus être modifié. Il existe deux manières de définir l'espace de noms pour un SearchService avant de l'utiliser pour créer un index :

  • Par défaut, un nouveau SearchService utilise l'espace de noms actuel. Vous pouvez définir l'espace de noms actuel avant de créer le service :

Java 8

// Set the current namespace to "aSpace"
NamespaceManager.set("aSpace");
// Create a SearchService with the namespace "aSpace"
SearchService searchService = SearchServiceFactory.getSearchService();
// Create an IndexSpec
IndexSpec indexSpec = IndexSpec.newBuilder().setName("myIndex").build();
// Create an Index with the namespace "aSpace"
Index index = searchService.getIndex(indexSpec);

Java 7

// Set the current namespace to "aSpace"
NamespaceManager.set("aSpace");
// Create a SearchService with the namespace "aSpace"
SearchService searchService = SearchServiceFactory.getSearchService();
// Create an IndexSpec
IndexSpec indexSpec = IndexSpec.newBuilder().setName("myIndex").build();
// Create an Index with the namespace "aSpace"
Index index = searchService.getIndex(indexSpec);

Vous pouvez spécifier un espace de noms dans la SearchServiceConfig lors de la création d'un service :

Java 8

// Create a SearchServiceConfig, specifying the namespace "anotherSpace"
SearchServiceConfig config =
    SearchServiceConfig.newBuilder().setNamespace("anotherSpace").build();
// Create a SearchService with the namespace "anotherSpace"
searchService = SearchServiceFactory.getSearchService(config);
// Create an IndexSpec
indexSpec = IndexSpec.newBuilder().setName("myindex").build();
// Create an Index with the namespace "anotherSpace"
index = searchService.getIndex(indexSpec);

Java 7

// Create a SearchServiceConfig, specifying the namespace "anotherSpace"
SearchServiceConfig config =  SearchServiceConfig.newBuilder()
    .setNamespace("anotherSpace").build();
// Create a SearchService with the namespace "anotherSpace"
searchService = SearchServiceFactory.getSearchService(config);
// Create an IndexSpec
indexSpec = IndexSpec.newBuilder().setName("myindex").build();
// Create an Index with the namespace "anotherSpace"
index = searchService.getIndex(indexSpec);
Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Environnement standard App Engine pour Java 8