APIs de várias classes

Se uma única API for particularmente complexa, é recomendável implementá-la a partir de várias classes Java. Para tornar diferentes classes parte da mesma API, tem de:

Por exemplo, as duas classes seguintes fazem parte da API tictactoe:

@Api(name = "tictactoe", version = "v1")
class TicTacToeA {  }

@Api(name = "tictactoe", version = "v1")
class TicTacToeB {  }

A configuração da API é especificada através das propriedades de anotação @Api. No entanto, para várias classes na mesma API, os requisitos de @Api vão além de ter simplesmente as mesmas strings name e version na anotação @Api para cada classe. Na verdade, a sua API de back-end não funciona se existirem diferenças nas configurações da API especificadas nas propriedades das classes @Api. Qualquer diferença nas propriedades @Api para classes num resultado da API de várias classes resulta numa configuração da API "ambígua", que não funciona nos frameworks do Cloud Endpoints para o App Engine.

Existem várias formas de criar uma API de várias classes não ambígua:

  • Certifique-se manualmente de que todas as classes numa única API têm exatamente as mesmas propriedades de anotação @Api.
  • Use a herança de anotações através da herança Java. Nesta herança, todas as classes numa única API herdam a mesma configuração da API de uma classe base anotada com @Api.
  • Use a herança de anotações através da anotação @ApiReference em todas as classes numa única API para que façam referência à mesma configuração da API a partir de uma classe comum anotada com @Api.

Usar @ApiClass para propriedades que podem diferir entre classes

Para usar esta funcionalidade, precisa da seguinte importação:

import com.google.api.server.spi.config.ApiClass;

Embora todas as propriedades na anotação @Api tenham de corresponder a todas as classes numa API, também pode usar a anotação @ApiClass para fornecer propriedades que não têm de ser exatamente iguais entre as classes. Por exemplo:

// API methods implemented in this class allow only "clientIdA".
@Api(name = "tictactoe", version = "v1")
@ApiClass(clientIds = { "clientIdA" })
class TicTacToeA {  }

// API methods implemented in this class provide unauthenticated access.
@Api(name = "tictactoe", version = "v1")
class TicTacToeB {  }

em que TicTacToeA limita o acesso através de uma lista de autorizações de IDs de clientes que contém o ID de cliente permitido e TicTacToeB não limita o acesso.

Todas as propriedades fornecidas pela anotação @ApiClass têm uma propriedade equivalente na anotação @Api. Tenha em atenção que a propriedade equivalente @Api funciona como predefinição ao nível da API. Se existir uma predefinição ao nível da API para essa mesma propriedade, especificada em @Api, a propriedade @ApiClass específica da turma substitui a predefinição ao nível da API.

Os exemplos seguintes ilustram a substituição das propriedades @Api pelos equivalentes @ApiClass específicos da classe:

// For this class "boards" overrides "games".
@Api(name = "tictactoe", version = "v1", resource = "games")
@ApiClass(resource = "boards")
class TicTacToeBoards {  }

// For this class "scores" overrides "games".
@Api(name = "tictactoe", version = "v1", resource = "games")
@ApiClass(resource = "scores")
class TicTacToeScores {  }

// For this class, the API-wide default "games" is used as the resource.
@Api(name = "tictactoe", version = "v1", resource = "games")
class TicTacToeGames {  }

Herança de anotações

As propriedades de anotação @Api e @ApiClass podem ser herdadas de outras classes, e as propriedades individuais podem ser substituídas através da herança Java ou da herança @ApiReference

Usar a herança Java

Uma classe que estende outra classe com anotações @Api ou @ApiClass comporta-se como se tivesse anotações com as mesmas propriedades. Por exemplo:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {  }

// TicTacToeA and TicTacToeB both behave as if they have the same @Api annotation as
// TicTacToeBase
class TicTacToeA extends TicTacToeBase {  }
class TicTacToeB extends TicTacToeBase {  }

As anotações só são herdadas através da criação de subclasses Java e não através da implementação de interfaces. Por exemplo:

@Api(name = "tictactoe", version = "v1")
interface TicTacToeBase {  }
// Does *not* behave as if annotated.
class TicTacToeA implements TicTacToeBase {  }

Como tal, não existe suporte para qualquer tipo de herança múltipla de anotações de frameworks.

A herança também funciona para @ApiClass:

@ApiClass(resource = "boards")
class BoardsBase {  }

// TicTacToeBoards behaves as if annotated with the @ApiClass from BoardsBase.
// Thus, the "resource" property will be "boards".
@Api(name = "tictactoe", version = "v1", resource = "scores")
class TicTacToeBoards extends BoardsBase {  }

onde TicTacToeBoards herda o valor da propriedade resource de boards, substituindo assim a definição da propriedade resource (scores) na respetiva anotação @Api.BoardsBase Lembre-se de que, se alguma classe tiver especificado a propriedade do recurso na anotação @Api, todas as classes têm de especificar essa mesma definição na anotação @Api. Esta técnica de herança permite substituir essa propriedade @Api.

Usar a herança de @ApiReference

Para usar esta funcionalidade, precisa da seguinte importação:

import com.google.api.server.spi.config.ApiReference;

A anotação @ApiReference oferece uma forma alternativa de especificar a herança de anotações. Uma classe que usa @ApiReference para especificar outra classe com anotações @Api ou @ApiClass comporta-se como se tivesse anotações com as mesmas propriedades. Por exemplo:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {  }

// TicTacToeA behaves as if it has the same @Api annotation as TicTacToeBase
@ApiReference(TicTacToeBase.class)
class TicTacToeA {  }

Se forem usados a herança Java e o @ApiReference, as anotações são herdadas apenas através da anotação @ApiReference. As anotações @Api e @ApiClass na classe herdada através da herança Java são ignoradas. Por exemplo:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBaseA {  }
@Api(name = "tictactoe", version = "v2")
class TicTacToeBaseB {  }

// TicTacToe will behave as if annotated the same as TicTacToeBaseA, not TicTacToeBaseB.
// The value of the "version" property will be "v1".
@ApiReference(TicTacToeBaseA.class)
class TicTacToe extends TicTacToeBaseB {  }

Substituir a configuração herdada

Quer herde a configuração através da herança Java ou @ApiReference, pode substituir a configuração herdada através de uma nova anotação @Api ou @ApiClass. Apenas as propriedades de configuração especificadas na nova anotação são substituídas. As propriedades não especificadas continuam a ser herdadas. Por exemplo:

@Api(name = "tictactoe", version = "v2")
class TicTacToe {  }

// Checkers will behave as if annotated with name = "checkers" and version = "v2"
@Api(name = "checkers")
class Checkers extends TicTacToe {  }

A substituição da herança também funciona para @ApiClass:

@Api(name = "tictactoe", version = "v1")
@ApiClass(resource = "boards", clientIds = { "c1" })
class Boards {  }

// Scores will behave as if annotated with resource = "scores" and clientIds = { "c1" }
@ApiClass(resource = "scores")
class Scores {  }

A substituição também funciona quando a herança é feita através de @ApiReference:

@Api(name = "tictactoe", version = "v2")
class TicTacToe {  }

// Checkers will behave as if annotated with name = "checkers" and version = "v2"
@ApiReference(TicTacToe.class)
@Api(name = "checkers")
class Checkers {  }

Herdar anotações de @ApiMethod

A anotação @ApiMethod pode ser herdada de métodos substituídos. Por exemplo:

class TicTacToeBase {
  @ApiMethod(httpMethod = "POST")
  public Game setGame(Game game) {  }
}
@Api(name = "tictactoe", version = "v1")
class TicTacToe extends TicTacToeBase {
  // setGame behaves as if annotated with the @ApiMethod from TicTacToeBase.setGame.
  // Thus the "httpMethod" property will be "POST".
  @Override
  public Game setGame(Game game) {  }
}

Tal como na herança de anotações @Api e @ApiClass, se vários métodos que se substituem mutuamente tiverem anotações @ApiMethod, as propriedades individuais podem ser substituídas. Por exemplo:

class TicTacToeBase {
  @ApiMethod(httpMethod = "POST", clientIds = { "c1" })
  public Game setGame(Game game) {  }
}
@Api(name = "tictactoe", version = "v1")
class TicTacToe extends TicTacToeBase {
  // setGame behaves as if annotated with httpMethod = "GET" and clientIds = { "c1"}.
  @ApiMethod(httpMethod = "GET")
  @Override
  public Game setGame(Game game) {  }
}

Não existe uma anotação @ApiReference nem um equivalente para métodos, pelo que @ApiMethod é sempre herdado através da herança Java e não através de @ApiReference.

Regras de herança e precedência

Para resumir a discussão anterior, a tabela seguinte mostra as regras de herança e a ordem de precedência.

Anotação/herança Regra
@Api Tem de ser idêntico para todas as aulas.
@ApiClass Especificado para uma classe para substituir as propriedades @Api.
Herança Java A classe herda @Api e @ApiClass da classe base.
@ApiReference A classe herda @Api e @ApiClass da classe referenciada.
Usar @ApiReference numa classe (Java) que herda de uma classe base A classe herda os atributos @Api e @ApiClass da classe referenciada, não da classe base.

Exemplos de utilização comuns para a herança de anotações

Seguem-se exemplos de utilização típicos da herança:

Para o controlo de versões da API:

@Api(name = "tictactoe", version = "v1")
class TicTacToeV1 {  }
@Api(version = "v2")
class TicTacToeV2 extends TicTacToeV1 {  }

Para APIs de várias classes:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {}
@ApiClass(resource = "boards")
class TicTacToeBoards extends TicTacToeBase {  }
@ApiClass(resource = "scores")
class TicTacToeScores extends TicTacToeBase {  }

Para testar diferentes versões da mesma API:

@Api(name = "tictactoe", version = "v1")
class TicTacToe {
  protected Foo someMethod() {
    // Do something real;
  }

  public Foo getFoo() {  }
}


@Api(version="v1test")
class TicTacToeTest extends TicTacToe {
  protected Foo someMethod() {
    // Stub out real action;
  }
}

em que someMethod pode devolver respostas predeterminadas, evitar chamadas com efeitos secundários, ignorar um pedido de rede ou de armazenamento de dados, entre outros.

Adicionar as turmas a web.xml

Depois de anotar as suas classes, tem de as adicionar ao ficheiro web.xml. O exemplo seguinte mostra uma única classe:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!-- Wrap the backend with Endpoints Frameworks v2. -->
    <servlet>
        <servlet-name>EndpointsServlet</servlet-name>
        <servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>
        <init-param>
            <param-name>services</param-name>
            <param-value>com.example.skeleton.MyApi</param-value>
        </init-param>
    </servlet>
    <!-- Route API method requests to the backend. -->
    <servlet-mapping>
        <servlet-name>EndpointsServlet</servlet-name>
        <url-pattern>/_ah/api/*</url-pattern>
    </servlet-mapping>
</web-app>

Para adicionar várias classes:

  1. Substitua <param-value>com.example.skeleton.MyApi</param-value> pelo nome da classe API.

  2. Adicione cada classe neste mesmo campo <param-value> separada por uma vírgula, por exemplo:

    <param-value>com.example-company.example-api.Hello,com.example-company.example-api.Goodbye</param-value>