1. 개요

RAML 예제 기사에서 RESTful API 모델링 언어 를 소개하고 Foo 라는 단일 엔터티를 기반으로 간단한 API 정의를 만들었습니다 . 이제 모두 동일하거나 유사한 GET, POST, PUT 및 DELETE 작업을 포함하는 여러 엔터티 유형 리소스가 있는 실제 API를 상상해 보십시오. API 문서가 얼마나 빨리 지루하고 반복적인지 알 수 있습니다.

이 기사에서는 RAML에서 자원 유형특성 기능을 사용하여 공통 섹션을 추출하고 매개변수화 하여 자원 및 메소드 정의의 중복 을 제거하여 복사 및 붙여넣기 오류를 제거하고 API 정의를 더 간결하게 만드는 방법을 보여줍니다.

2. 우리의 API

리소스 유형특성 의 이점을 보여주기 위해 Bar 라는 두 번째 엔터티 유형에 대한 리소스를 추가하여 원래 API를 확장할 것입니다 . 수정된 API를 구성할 리소스는 다음과 같습니다.

  • GET /api/v1/foos
  • POST /api/v1/foos
  • GET /api/v1/foos/{fooId}
  • PUT /api/v1/foos/{fooId}
  • 삭제 /api/v1/foos/{fooId}
  • GET /api/v1/foos/name/{이름}
  • GET /api/v1/foos?name={이름}&ownerName={ownerName}
  • GET /api/v1/bars
  • POST /api/v1/bars
  • GET /api/v1/bars/{barId}
  • PUT /api/v1/bars/{barId}
  • 삭제 /api/v1/bars/{barId}
  • GET /api/v1/bars/fooId/{fooId}

3. 패턴 인식

API의 리소스 List을 읽으면 몇 가지 패턴이 나타나기 시작합니다. 예를 들어 단일 엔터티를 생성, 읽기, 업데이트 및 삭제하는 데 사용되는 URI 및 메서드에 대한 패턴이 있고 엔터티 컬렉션을 검색하는 데 사용되는 URI 및 메서드에 대한 패턴이 있습니다. 컬렉션 및 컬렉션 항목 패턴은 RAML 정의에서 리소스 유형 을 추출하는 데 사용되는 보다 일반적인 패턴 중 하나입니다 .

API의 몇 가지 섹션을 살펴보겠습니다.

[참고: 아래 코드 스니펫에서 세 개의 점(…)만 포함된 줄은 간결성을 위해 일부 줄이 생략되었음을 나타냅니다.]

/foos:
  get:
    description: |
      List all foos matching query criteria, if provided;
      otherwise list all foos
    queryParameters:
      name?: string
      ownerName?: string
    responses:
      200:
        body:
          application/json:
            type: Foo[]
  post:
    description: Create a new foo
    body:
      application/json:
        type: Foo
    responses:
      201:
        body:
          application/json:
            type: Foo
...
/bars:
  get:
    description: |
      List all bars matching query criteria, if provided;
      otherwise list all bars
    queryParameters:
      name?: string
      ownerName?: string
    responses:
      200:
        body:
          application/json:
            type: Bar[]
  post:
    description: Create a new bar
    body:
      application/json:
        type: Bar
    responses:
      201:
        body:
          application/json:
            type: Bar

사용된 HTTP 메서드를 포함하여 /foos/bars 리소스 의 RAML 정의를 비교할 때 각각의 다양한 속성 간에 여러 중복성을 볼 수 있으며 패턴이 나타나기 시작하는 것을 다시 볼 수 있습니다.

리소스 또는 메서드 정의에 패턴이 있는 곳마다 RAML 리소스 유형 또는 특성 을 사용할 기회가 있습니다 .

4. 리소스 유형

API에 있는 패턴을 구현하기 위해 자원 유형 은 이중 꺾쇠 괄호(<< 및 >>)로 묶인 예약 및 사용자 정의 매개변수를 사용합니다.

4.1. 예약된 매개변수

두 개의 예약된 매개변수는 자원 유형 정의에서 사용될 수 있습니다.

  • <<resourcePath>> 는 전체 URI( baseURI 다음 )를 나타내며,
  • <<resourcePathName>> 은 중괄호 { }를 무시하고 맨 오른쪽 슬래시(/) 다음의 URI 부분을 나타냅니다.

리소스 정의 내에서 처리될 때 해당 값은 정의 중인 리소스를 기반으로 계산됩니다.

예를 들어 자원 /foos 가 주어지면 <<resourcePath>> 는 "/foos"로 평가되고 <<resourcePathName>> 은 "foos"로 평가됩니다.

/foos/{fooId} 리소스가 주어지면 <<resourcePath>> 는 "/foos/{fooId}"로 평가되고 << resourcePathName>> 은 "foos"로 평가됩니다.

4.2. 사용자 정의 매개변수

자원 유형 정의 에는 사용자 정의 매개변수도 포함될 수 있습니다. 값이 정의되는 리소스에 따라 동적으로 결정되는 예약된 매개변수와 달리 사용자 정의 매개변수는 이를 포함하는 리소스 유형 이 사용될 때마다 값을 할당해야 하며 해당 값은 변경되지 않습니다.

사용자 정의 매개변수는 리소스 유형 정의 의 시작 부분에서 선언할 수 있지만 그렇게 하는 것이 필수는 아니며 일반적인 관행도 아닙니다. 일반적으로 독자는 해당 이름과 사용되는 컨텍스트를 통해 의도된 용도를 파악할 수 있기 때문입니다.

4.3. 매개변수 기능

자원 정의에서 매개변수가 처리될 때 매개변수의 확장된 값을 변환하기 위해 매개변수가 사용될 때마다 소수의 유용한 텍스트 함수를 사용할 수 있습니다.

매개변수 변환에 사용할 수 있는 기능은 다음과 같습니다.

  • ! 단일화하다
  • ! 복수화하다
  • ! 대문자
  • ! 소문자
  • ! 대문자
  • ! 소문자
  • ! 대문자
  • ! 소문자
  • ! 대문자
  • ! 소문자

함수는 다음 구성을 사용하여 매개변수에 적용됩니다.

<< 매개변수 이름 | ! 함수 이름 >>

원하는 변환을 달성하기 위해 둘 이상의 함수를 사용해야 하는 경우 각 함수 이름을 파이프 기호("|")로 구분하고 사용되는 각 함수 앞에 느낌표(!)를 붙입니다.

예를 들어 자원 /foos 가 주어지면 << resourcePathName >>은 "foos"로 평가됩니다.

  • << 리소스 경로 이름 | ! 단수화 >> ==> "foo"
  • << 리소스 경로 이름 | ! 대문자 >> ==> "FOOS"
  • << 리소스 경로 이름 | ! 단일화하다 | ! 대문자 >> ==> "FOO"

리소스 /bars/{barId} 가 주어지면 << resourcePathName >>은 "bars"로 평가됩니다.

  • << 리소스 경로 이름 | ! 대문자 >> ==> "BARS"
  • << 리소스 경로 이름 | ! 대문자 >> ==> "바"

5. 컬렉션에 대한 리소스 유형 추출

리소스 유형 을 사용하여 공통 속성을 캡처 하여 위에 표시된 /foos/bars 리소스 정의 를 리팩터링해 보겠습니다 . 예약된 매개변수 <<resourcePathName>> 과 사용자 정의 매개변수 <<typeName>> 을 사용하여 사용된 데이터 유형을 나타냅니다.

5.1. 정의

다음은 항목 모음을 나타내는 리소스 유형 정의입니다.

resourceTypes:
  collection:
    usage: Use this resourceType to represent any collection of items
    description: A collection of <<resourcePathName>>
    get:
      description: Get all <<resourcePathName>>, optionally filtered 
      responses:
        200:
          body:
            application/json:
              type: <<typeName>>[]
    post:
      description: Create a new <<resourcePathName|!singularize>> 
      responses:
        201:
          body:
            application/json:
              type: <<typeName>>

API에서 데이터 유형은 기본 리소스 이름의 단일 버전인 대문자이기 때문에 사용자 정의 << typeName >> 매개변수 를 도입하는 대신 << resourcePathName >> 매개변수에 함수를 적용할 수 있습니다. , API의 이 부분에 대해 동일한 결과를 얻으려면:

resourceTypes:
  collection:
  ...
    get:
      ...
            type: <<resourcePathName|!singularize|!uppercamelcase>>[]
    post:
      ...
            type: <<resourcePathName|!singularize|!uppercamelcase>>

5.2. 애플리케이션

<< typeName >> 매개변수 를 통합하는 위의 정의를 사용하여 리소스 /foos 및 / bar 에 "컬렉션" 리소스 유형 을 적용하는 방법은 다음과 같습니다.

/foos:
  type: { collection: { "typeName": "Foo" } }
  get:
    queryParameters:
      name?: string
      ownerName?: string
...
/bars:
  type: { collection: { "typeName": "Bar" } }

리소스 유형 정의가 제공 해야 하는 모든 기능을 계속 활용하면서 두 리소스(이 경우에는 queryParameters 섹션 ) 간의 차이점을 통합할 수 있습니다.

6. 컬렉션의 단일 항목에 대한 리소스 유형 추출

이제 컬렉션의 단일 항목인 /foos/{fooId}/bars/{barId} 리소스를 처리하는 API 부분에 초점을 맞추겠습니다. 다음은 /foos/{fooId} 에 대한 코드입니다 .

/foos:
...
  /{fooId}:
    get:
      description: Get a Foo
      responses:
        200:
          body:
            application/json:
              type: Foo
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
    put:
      description: Update a Foo
      body:
        application/json:
          type: Foo
      responses:
        200:
          body:
            application/json:
              type: Foo
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
    delete:
      description: Delete a Foo
      responses:
        204:
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json

/ bars/ { barId} 리소스 정의에는 GET, PUT 및 DELETE 메서드도 있으며 문자열 "foo" 및 "bar"(및 각각의 복수형 및/또는 대문자 형식).

6.1. 정의

방금 식별한 패턴을 추출하여 컬렉션의 단일 항목에 대한 리소스 유형 을 정의하는 방법은 다음과 같습니다.

resourceTypes:
...
  item:
    usage: Use this resourceType to represent any single item
    description: A single <<typeName>>
    get:
      description: Get a <<typeName>>
      responses:
        200:
          body:
            application/json:
              type: <<typeName>>
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
    put:
      description: Update a <<typeName>>
      body:
        application/json:
          type: <<typeName>>
      responses:
        200:
          body:
            application/json:
              type: <<typeName>>
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json
    delete:
      description: Delete a <<typeName>>
      responses:
        204:
        404:
          body:
            application/json:
              type: Error
              example: !include examples/Error.json

6.2. 애플리케이션

다음은 "항목" 리소스 유형 을 적용하는 방법입니다 .

/foos:
...
  /{fooId}:
    type: { item: { "typeName": "Foo" } }
... 
/bars: 
... 
  /{barId}: 
    type: { item: { "typeName": "Bar" } }

7. 특성

리소스 유형 이 리소스 정의에서 패턴을 추출하는 데 사용되는 반면 특성 은 리소스 전체에서 공통적인 메서드 정의에서 패턴을 추출하는 데 사용됩니다.

7.1. 매개변수

<< resourcePath >> 및 << resourcePathName >> 과 함께 하나의 추가 예약 매개변수를 특성 정의에 사용할 수 있습니다. << methodName >>은 HTTP 메소드(GET, POST, PUT, DELETE 등)로 평가됩니다. 특성 이 정의됩니다. 사용자 정의 매개변수는 특성 정의 내에 나타날 수도 있으며 적용되는 경우 적용되는 리소스의 값을 가져옵니다.

7.2. 정의

"항목" 리소스 유형 은 여전히 ​​중복으로 가득 차 있습니다. 특성이 이러한 특성 을 제거하는 데 어떻게 도움이 되는지 살펴보겠습니다 . 요청 본문을 포함하는 모든 메서드에 대한 특성 을 추출하는 것으로 시작하겠습니다 .

traits:
  hasRequestItem:
    body:
      application/json:
        type: <<typeName>>

이제 정상적인 응답에 본문이 포함된 메서드의 특성 을 추출해 보겠습니다 .

  hasResponseItem:
    responses:
      200:
        body:
          application/json:
            type: <<typeName>>
  hasResponseCollection:
    responses:
      200:
        body:
          application/json:
            type: <<typeName>>[]

마지막으로 404 오류 응답을 반환할 수 있는 모든 메서드 의 특성 은 다음과 같습니다.

  hasNotFound:
    responses:
      404:
        body:
          application/json:
            type: Error
            example: !include examples/Error.json

7.3. 애플리케이션

그런 다음 이 특성리소스 유형 에 적용합니다 .

resourceTypes:
  collection:
    usage: Use this resourceType to represent any collection of items
    description: A collection of <<resourcePathName|!uppercamelcase>>
    get:
      description: |
        Get all <<resourcePathName|!uppercamelcase>>,
        optionally filtered
      is: [ hasResponseCollection: { typeName: <<typeName>> } ]
    post:
      description: Create a new <<resourcePathName|!singularize>>
      is: [ hasRequestItem: { typeName: <<typeName>> } ]
  item:
    usage: Use this resourceType to represent any single item
    description: A single <<typeName>>
    get:
      description: Get a <<typeName>>
      is: [ hasResponseItem: { typeName: <<typeName>> }, hasNotFound ]
    put:
      description: Update a <<typeName>>
      is: | [ hasRequestItem: { typeName: <<typeName>> }, hasResponseItem: { typeName: <<typeName>> }, hasNotFound ]
    delete:
      description: Delete a <<typeName>>
      is: [ hasNotFound ]
      responses:
        204:

리소스 내에 정의된 메서드에 특성 을 적용할 수도 있습니다 . 이는 자원-방법 조합이 하나 이상의 특성 과 일치하지만 정의된 자원 유형 과 일치하지 않는 "일회성" 시나리오에 특히 유용합니다 .

/foos:
...
  /name/{name}:
    get:
      description: List all foos with a certain name
      is: [ hasResponseCollection: { typeName: Foo } ]

8. 결론

이 사용방법(예제)에서는 RAML API 정의에서 중복을 크게 줄이거나 경우에 따라 제거하는 방법을 보여 주었습니다.

먼저 리소스의 중복 섹션을 식별하고 해당 패턴을 인식하고 리소스 유형 을 추출 했습니다. 그런 다음 특성 을 추출하기 위해 리소스 전체에서 공통적으로 사용되는 방법에 대해 동일한 작업을 수행했습니다 . 그런 다음 리소스 유형 과 정의된 리소스 유형 중 하나와 정확히 일치하지 않는 "일회성" 리소스-방법 조합에 특성적용하여 추가 중복을 제거할 수 있었습니다 .

그 결과 단 두 개의 엔터티에 대한 리소스가 포함된 간단한 API가 177개에서 100줄이 조금 넘는 코드로 줄었습니다. RAML 리소스 유형특성 에 대해 자세히 알아 보려면 RAML.org 1.0 사양 을 방문하십시오 .

이 예제의 전체 구현은 github 프로젝트 에서 찾을 수 있습니다 .

다음은 전체 최종 RAML API입니다.

#%RAML 1.0
title: Baeldung Foo REST Services API
version: v1
protocols: [ HTTPS ]
baseUri: http://rest-api.baeldung.com/api/{version}
mediaType: application/json
securedBy: basicAuth
securitySchemes:
  basicAuth:
    description: |
      Each request must contain the headers necessary for
      basic authentication
    type: Basic Authentication
    describedBy:
      headers:
        Authorization:
          description: |
            Used to send the Base64 encoded "username:password"
            credentials
            type: string
      responses:
        401:
          description: |
            Unauthorized. Either the provided username and password
            combination is invalid, or the user is not allowed to
            access the content provided by the requested URL.
types:
  Foo:   !include types/Foo.raml
  Bar:   !include types/Bar.raml
  Error: !include types/Error.raml
resourceTypes:
  collection:
    usage: Use this resourceType to represent a collection of items
    description: A collection of <<resourcePathName|!uppercamelcase>>
    get:
      description: |
        Get all <<resourcePathName|!uppercamelcase>>,
        optionally filtered
      is: [ hasResponseCollection: { typeName: <<typeName>> } ]
    post:
      description: |
        Create a new <<resourcePathName|!uppercamelcase|!singularize>>
      is: [ hasRequestItem: { typeName: <<typeName>> } ]
  item:
    usage: Use this resourceType to represent any single item
    description: A single <<typeName>>
    get:
      description: Get a <<typeName>>
      is: [ hasResponseItem: { typeName: <<typeName>> }, hasNotFound ]
    put:
      description: Update a <<typeName>>
      is: [ hasRequestItem: { typeName: <<typeName>> }, hasResponseItem: { typeName: <<typeName>> }, hasNotFound ]
    delete:
      description: Delete a <<typeName>>
      is: [ hasNotFound ]
      responses:
        204:
traits:
  hasRequestItem:
    body:
      application/json:
        type: <<typeName>>
  hasResponseItem:
    responses:
      200:
        body:
          application/json:
            type: <<typeName>>
  hasResponseCollection:
    responses:
      200:
        body:
          application/json:
            type: <<typeName>>[]
  hasNotFound:
    responses:
      404:
        body:
          application/json:
            type: Error
            example: !include examples/Error.json
/foos:
  type: { collection: { typeName: Foo } }
  get:
    queryParameters:
      name?: string
      ownerName?: string
  /{fooId}:
    type: { item: { typeName: Foo } }
  /name/{name}:
    get:
      description: List all foos with a certain name
      is: [ hasResponseCollection: { typeName: Foo } ]
/bars:
  type: { collection: { typeName: Bar } }
  /{barId}:
    type: { item: { typeName: Bar } }
  /fooId/{fooId}:
    get:
      description: Get all bars for the matching fooId
      is: [ hasResponseCollection: { typeName: Bar } ]
다음 »
포함, 라이브러리, 오버레이 및 확장을 사용하는 모듈식 RAML
« 이전
RAML 소개 – RESTful API 모델링 언어
REST footer banner