AshJsonApi 사용해 보기

Ash 는 리소스를 JSON 형식으로 API 로 노출하는 익스텐션도 있다. 튜토리얼을 따라가며 살펴 보자.

Ash 기본 튜토리얼의 코드 부터 시작한다. 이 때 mix 프로젝트는 Phoenix 가 설치된 프로젝트여야 한다. 다음 명령어를 실행시켜 AshJsonApi 를 설치한다.

% mix igniter.install ash_json_api

위 명령어를 실행하면 단계적으로 사용자의 확인을 받아가며 AshJsonApi 가 설치 된다. 모두 Y 를 선택해 설치를 완료한 다. 이후 오류 없이 phoenix 서버를 실행 된다면 준비가 완료된 것이다. Swagger UI 가 제공되므로 바로 확인할 수 있다. API 의 기본 경로는 /api/json 이고 Swagger UI 의 경로는 /api/json/swaggerui 이다.

% mix phx.server

# 이후 브라우저에서 http://localhost:4000/api/json/swaggerui 호출
설치 즉시 제공되는 Swagger UI 화면

이제 도메인과 리소스에 익스텐션 설정을 추가하자. 다음 명령어를 실행하면 Ticket 리소스에 익스텐션 설정을 추가할 수 있다. 다른 리소스에 추가하려면 리소스 이름을 변경한다.

% mix ash.patch.extend Helpdesk.Support.Ticket json_api
# 도메인에 다음과 같이 익스텐션 설정이 추가된다
defmodule Helpdesk.Support do
  use Ash.Domain, extensions: [AshJsonApi.Domain]
  ...

# 지정한 리소스에는 다음과 같이 설정이 추가된다.
defmodule Helpdesk.Support.Ticket do
  use Ash.Resource, extensions: [AshJsonApi.Resource]
  # ...
  json_api do
    type "ticket"
  end
end

다음은 해당 리소스에 접근하기 위한 라우트 설정이 필요하다. API 주소를 설정하는 것으로 이해하면 되겠다. 라우트 설정은 도메인 또는 리소스에 선언할 수 있는데 도메인에 설정되는 것이 권장되는 것 같다. 도메인 코드에 라우트 선언을 추가하자.

defmodule Helpdesk.Support do
  use Ash.Domain, extensions: [AshJsonApi.Domain]

  # 추가하는 부분
  json_api do
    routes do
      base_route "/tickets", Helpdesk.Support.Ticket do
        get :read
        index :read
        post :open
      end
    end
  end
  
  ...
end

base_route 는 일종의 스코프처럼 동작하며 해당 리소스의 기본 주소로 사용된다. get 은 단일 리소스 획득, index 는 리소스 목록 획득, post 는 리소스를 만드는 역할을 한다. 뒤에 따라오는 atom 은 리소스의 action 이름이다. action type 과 route type 이 맞지 않으면 에러가 발생한다(예: get :open, post :read 등). routes 에서 사용할 수 있는 route type 의 예시는 다음과 같다.

routes do
  base "/posts"

  get :read
  get :me, route: "/me"
  index :read
  post :confirm_name, route: "/confirm_name"
  patch :update
  related :comments, :read
  relationship :comments, :read
  post_to_relationship :comments
  patch_relationship :comments
  delete_from_relationship :comments
end

도메인에 API 라우트 선언을 추가하고 나서 서버를 재 실행한 뒤 /api/json/swaggerui 를 다시 열어보면 선언한 API 가 문서로 만들어 진 것을 확인할 수 있다.

Swagger UI 에 추가된 API 문서

Swagger UI 에서 직접 테스트를 해 볼 수도 있고 브라우저 및 curl 로도 테스트 해 볼 수 있다.

% curl -X POST 'localhost:4000/api/json/tickets' \
--header 'Accept: application/vnd.api+json' \
--header 'Content-Type: application/vnd.api+json' \
--data-raw '{
  "data": {
    "type": "ticket",
    "attributes": {
      "subject": "This ticket was created through the JSON API"
    }
  }
}'  

% curl 'localhost:4000/api/json/tickets'

% curl 'localhost:4000/api/json/tickets/<uuid>' // uuid 는 조회할 ticket 의 uuid

목록 또는 단일 레코드를 조회 해 보면 데이터 수나 id 값은 표시되는데 레코드의 값은 표시되지 않을 수 있다.

{
  "data": [
    {
      "attributes": {},
      "id": "1731818f-e40f-4376-9356-292e17243d05",
      "links": {},
      "meta": {},
      "type": "ticket",
      "relationships": {}
    },
    {
      "attributes": {},
      "id": "310143d0-3c3a-4507-9ea4-6f84d2ba5122",
      "links": {},
      "meta": {},
      "type": "ticket",
      "relationships": {}
    },
    ...
  ]
}

이 때는 리소스의 attribute 별 public? 설정을 true 로 바꿔주면 해당 attribute 의 값이 표시된다.

defmodule Helpdesk.Support.Ticket do
  ...

  attributes do
    uuid_primary_key :id

    attribute :subject, :string do
      allow_nil? false
      public? true  # 추가
    end

    attribute :status, :atom do
      constraints one_of: [:open, :closed]
      public? true
      default :open
      allow_nil? false  # 추가
    end
  end

  ...
end
{
  "data": [
    {
      "attributes": {
        "status": "open",
        "subject": "Issue 3"
      },
      "id": "1731818f-e40f-4376-9356-292e17243d05",
      "links": {},
      "meta": {},
      "type": "ticket",
      "relationships": {}
    },
    {
      "attributes": {
        "status": "closed",
        "subject": "Issue 4"
      },
      "id": "310143d0-3c3a-4507-9ea4-6f84d2ba5122",
      "links": {},
      "meta": {},
      "type": "ticket",
      "relationships": {}
    },
  ...
}

AshJsonApi 익스텐션을 통해 비교적 손쉽게 리소스에 REST API 를 붙일 수 있었다. 내부 리소스를 외부에 직접 노출하는 것에 대한 이견은 있을 수 있지만 만약 여러가지 이유로 인해 내부 리소스를 바로 외부에 노출하기로 결정했다면 매우 유용한 도구가 될 것 같다. Swagger 형태의 API 문서가 같이 제공되는 점도 큰 장점이라 할 수 있겠다.

댓글 남기기