다중 클래스 API

단일 API가 특히 복잡한 경우, 여러 자바 클래스에서 이를 구현해야 할 수 있습니다. 동일 API를 여러 클래스 부분으로 만들려면 다음을 수행해야 합니다.

예를 들어 다음 두 개의 클래스는 모두 tictactoe API에 포함됩니다.

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

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

API 구성은 @Api 주석 속성을 통해 지정됩니다. 하지만 동일 API에 여러 클래스가 있는 경우에는 단순히 각 클래스의 @Api 주석에서 동일한 nameversion 문자열을 사용하는 것 외에도 @Api에 대한 요구사항이 더 있습니다. 실제로 클래스의 @Api 속성에 지정된 API 구성이 조금만 달라도 백엔드 API가 작동하지 않습니다. 멀티클래스 API에서는 클래스의 @Api 속성이 조금만 달라도 API 구성이 '모호해지기' 때문에 App Engine용 Cloud Endpoints Frameworks에서 API가 작동하지 않게 됩니다.

모호하지 않은 멀티클래스 API를 만들기 위한 방법에는 몇 가지가 있습니다.

  • 단일 API의 모든 클래스가 정확히 동일한 @Api 주석 속성을 갖는지 직접 확인합니다.
  • 자바 상속성을 통해 주석 상속성을 사용합니다. 이 상속성을 사용할 경우 단일 API의 모든 클래스가 공통의 @Api 주석이 지정된 기본 클래스로부터 동일한 API 구성을 상속합니다.
  • 단일 API의 모든 클래스에서 @ApiReference 주석을 통한 주석 상속성을 사용하여 각 클래스가 공통의 @Api 주석이 지정된 클래스에서 동일한 API 구성을 참조하도록 합니다.

클래스 간 달라질 수 있는 속성에 @ApiClass 사용

이 기능을 사용하려면 다음과 같은 가져오기가 필요합니다.

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

@Api 주석의 모든 속성은 API의 모든 클래스에서 일치해야 하지만 @ApiClass 주석을 추가로 사용하면 클래스 간에 정확히 동일할 필요가 없는 속성을 제공할 수 있습니다. 예를 들면 다음과 같습니다.

// 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 { … }

여기에서 TicTacToeA는 허용되는 클라이언트 ID가 포함된 클라이언트 ID 허용 목록을 사용해 액세스를 제한하는 반면 TicTacToeB는 액세스를 제한하지 않습니다.

@ApiClass 주석에서 제공되는 모든 속성은 @Api 주석에서 상응하는 속성을 갖습니다. @Api의 상응 속성은 API 전체의 기본값으로 사용됩니다. 동일한 속성에 대해 @Api에 지정된 API 전체 기본값이 있는 경우에는 클래스별 @ApiClass 속성이 API 전체 기본값을 재정의합니다.

다음 예시에서는 @Api 속성이 클래스별 @ApiClass의 상응 속성에 의해 재정의되는 경우를 보여줍니다.

// 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 { … }

주석 상속성

@Api@ApiClass 주석 속성은 다른 클래스로부터 상속될 수 있으며, 자바 상속성 또는 @ApiReference 상속성을 통해 개별 속성이 재정의될 수 있습니다.

자바 상속성 사용

@Api 또는 @ApiClass 주석이 지정된 다른 클래스를 확장하는 클래스는 마치 동일한 속성의 주석이 지정된 것처럼 동작합니다. 예를 들면 다음과 같습니다.

@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 { … }

주석은 인터페이스 구현이 아니라 자바 하위 클래스를 통해서만 상속됩니다. 예를 들면 다음과 같습니다.

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

따라서 프레임워크 주석에 대해서는 어떠한 종류의 다중 상속성도 지원되지 않습니다.

상속성은 @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 { … }

여기서 TicTacToeBoardsBoardsBase로부터 resource 속성 값 boards를 상속하므로 해당 클래스의 @Api 주석에 포함된 resource 속성 설정(scores)이 재정의됩니다. 어떤 클래스의 @Api 주석에 resource 속성이 지정되었으면 모든 클래스가 @Api 주석에서 동일한 설정을 지정해야 합니다. 이러한 상속성 기술로 @Api 속성을 재정의할 수 있습니다.

@ApiReference 상속성 사용

이 기능을 사용하려면 다음과 같은 가져오기가 필요합니다.

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

@ApiReference 주석은 주석 상속성을 지정하기 위한 또 다른 방법을 제공합니다. @ApiReference를 사용해 @Api 또는 @ApiClass 주석이 있는 다른 클래스를 지정하는 클래스는 마치 동일한 속성의 주석이 지정된 것처럼 동작합니다. 예를 들면 다음과 같습니다.

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

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

자바 상속성과 @ApiReference를 모두 사용하는 경우 주석 상속은 @ApiReference 주석을 통해서만 이루어집니다. 자바 상속성을 통해 상속되는 클래스의 @Api@ApiClass 주석은 무시됩니다. 예를 들면 다음과 같습니다.

@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 { … }

상속된 구성 재정의

자바 상속성을 사용해 구성을 상속하든, 혹은 @ApiReference를 사용해 구성을 상속하든 상관없이 새로운 @Api 또는 @ApiClass 주석을 사용해 상속된 구성을 재정의할 수 있습니다. 새로운 주석에 지정된 구성 속성만 재정의됩니다. 지정되지 않은 속성은 계속 상속됩니다. 예를 들면 다음과 같습니다.

@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 { … }

상속성 재정의는 @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 { … }

또한 @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 { … }

@ApiMethod 주석 상속

@ApiMethod 주석은 재정의된 메서드로부터 상속될 수 있습니다. 예를 들면 다음과 같습니다.

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) { … }
}

@Api@ApiClass 주석 상속성과 마찬가지로, 서로를 재정의하는 여러 메서드에 @ApiMethod 주석이 포함된 경우 개별 속성을 재정의할 수 있습니다. 예를 들면 다음과 같습니다.

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) { … }
}

메서드의 경우 @ApiReference 주석 또는 그에 상응하는 주석이 없으므로 @ApiMethod는 항상 @ApiReference가 아니라 자바 상속성을 통해 상속됩니다.

상속성 및 우선 순위 규칙

앞의 내용을 요약해서, 다음 테이블은 상속성 규칙과 우선 순위를 보여줍니다.

주석/상속성 규칙
@Api 모든 클래스에 대해 동일해야 합니다.
@ApiClass 특정 클래스가 @Api 속성을 재정의하도록 지정합니다.
자바 상속성 클래스가 기본 클래스의 @Api@ApiClass를 상속합니다.
@ApiReference 클래스가 참조 클래스의 @Api@ApiClass를 상속합니다.
기본 클래스로부터 상속하는 클래스(자바)에서 @ApiReference 사용 클래스가 기본 클래스로부터 상속하지 않고 참조 클래스의 @Api@ApiClass를 상속합니다.

주석 상속성의 일반적인 사용 사례

다음은 상속성의 일반적인 사용 사례입니다.

API 버전 관리:

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

다중 클래스 API:

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

동일 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;
  }
}

여기서 someMethod는 미리 결정된 응답을 반환하거나, 부작용이 있는 호출을 방지하거나, 네트워크 또는 데이터 저장소 요청을 건너뛰는 등의 작업을 수행할 수 있습니다.

web.xml에 클래스 추가

클래스에 주석을 지정한 후에는 이를 web.xml 파일에 추가해야 합니다. 다음 예에서는 단일 클래스를 보여줍니다.

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

여러 클래스를 추가하려면 다음 안내를 따르세요.

  1. <param-value>com.example.skeleton.MyApi</param-value>를 해당하는 API 클래스 이름으로 바꿉니다.

  2. 동일한 <param-value> 필드 내에 각 클래스를 쉼표로 구분해서 추가합니다. 예를 들면 다음과 같습니다.

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