AppSyncのデータソースにHTTPエンドポイントを設定する

## 概要
- AppSyncのデータソースにhttpエンドポイントが設定することで外部APIのレスポンス結果を利用することができるので、利用して見たのでメモ


## 前提
外部APIとして、OpenWeatherが提供する無料プランのAPIを利用する。APIの利用には Sing up してAPIキーを利用する必要があるので、発行しておく。

- OpenWather：https://openweathermap.org

### 利用するAPI
- current weather data：https://openweathermap.org/current

- リクエスト例

```bash
$ curl api.openweathermap.org/data/2.5/weather?q=Tokyo&appid={API key}
```

- レスポンス
    - 以下のような形式でレスポンスが帰ってくるので、こちらをAppSyncを介して取得できるようにする

```json

{
  "coord": { "lon": 139.7531, "lat": 35.6854 },
  "weather": [
    {
      "id": 803,
      "main": "Clouds",
      "description": "broken clouds",
      "icon": "04d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 290.55,
    "feels_like": 289.43,
    "temp_min": 288.71,
    "temp_max": 291.48,
    "pressure": 1019,
    "humidity": 55
  },
  "visibility": 10000,
  "wind": { "speed": 1.03, "deg": 0 },
  "clouds": { "all": 75 },
  "dt": 1616214160,
  "sys": {
    "type": 1,
    "id": 8077,
    "country": "JP",
    "sunrise": 1616186707,
    "sunset": 1616230318
  },
  "timezone": 32400,
  "id": 1861060,
  "name": "Japan",
  "cod": 200
}
```


## AppSync APIの作成
- AWSコンソール上で、一から構築を選択し、APIの作成を行なってみた

## スキーマ定義の作成
- openweather APIのレスポンスを、そのままGraphqlの型として作成した。作成し終わったらスキーマ定義を保存する

```Graphql
type Clouds {
	all: String
}

type Coord {
	lon: String
	lat: String
}

type CurrentWeather {
	coord: Coord
	weather: [Weather]
	base: String
	main: Main
	visibility: String
	wind: Wind
	clouds: Clouds
	dt: String
	sys: Sys
	timezone: String
	id: String
	name: String
	cod: String
}

type Main {
	temp: String
	feels_like: String
	temp_min: String
	temp_max: String
	pressure: String
	humidity: String
}

## クエリの実行
type Query {
	currentWeather(q: String, appid: String): CurrentWeather
}

type Sys {
	type: String
	id: String
	countory: String
	sunrise: String
	sunset: String
}

type Weather {
	id: String
	main: String
	description: String
	icon: String
}

type Wind {
	speed: String
	deg: String
}

# ルートスキーマ
schema {
	query: Query
}
```

## データソースの作成
以下のようにデータソースを作成する

- データソース名：openweathermap_api
- データソースのタイプ：HTTPエンドポイント
- エンドポイント：https://api.openweathermap.org


## リゾルバーの設定
- 作成したスキーマ定義のQuery「currentWeather」のリゾルバー設定を行う。データソースは先ほど作成した、「openweathermap_api」を利用する

### リクエストマッピングテンプレート
- resourcePathをapiのパス（/data/2.5/weather）に変更する。他はデフォルトで問題なし

```
{
  "version": "2018-05-29",
  "method": "GET",
  "resourcePath": "/data/2.5/weather",
  "params":{
      "query":$util.toJson($ctx.args)
  }
}
```

### レスポンスマッピングテンプレート
- デフォルトのままで問題なし

```
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
  $util.error($ctx.error.message, $ctx.error.type)
#end
## If the response is not 200 then return an error. Else return the body **
#if($ctx.result.statusCode == 200)
    $ctx.result.body
#else
    $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
```


## クエリを投げてみる
- 上記でAPIのレスポンス取得のための設定ができたので、AWSコンソール上からクエリを投げてみる。レスポンスに必要なパラメータのみ指定することが可能

```Graphql
query MyQuery {
  currentWeather(appid: "{APIキー}", q: "Tokyo") {
    weather {
      description
    }
    name
    main {
      feels_like
      humidity
      pressure
      temp
      temp_max
      temp_min
    }
  }
}
```

- レスポンス
    - 指定した形式でレスポンス情報の取得ができた

```JSON
{
  "data": {
    "currentWeather": {
      "weather": [
        {
          "description": "broken clouds"
        }
      ],
      "name": "Tokyo",
      "main": {
        "feels_like": "288.95",
        "humidity": "63",
        "pressure": "1018",
        "temp": "290.61",
        "temp_max": "291.15",
        "temp_min": "289.82"
      }
    }
  }
}
```

## クエリの追加
- クエリを新たに追加し、一つのデータソースで複数リゾルバーで利用することで、異なるURLへのリクエストが可能なので設定して試してみる

### 追加するAPI

- https://openweathermap.org/api/geocoding-api
- リクエスト例

```bash
$ curl http://api.openweathermap.org/geo/1.0/direct?q=Tokyo&appid={APIキー}
```

- レスポンス

```json
[
  {
    "name": "Tokyo Prefecture",
    "local_names": {
      "ascii": "Tokyo Prefecture",
      "en": "Tokyo",
      "feature_name": "Tokyo Prefecture",
      "fr": "Préfecture de Tokyo",
      "ja": "東京都"
    },
    "lat": 35.6895,
    "lon": 139.6917,
    "country": "JP"
  }
]
```

## スキーマ定義
- 新たに下記のスキーマ定義を追加する

```Graphql
type Query {
	currentWeather(q: String, appid: String): CurrentWeather
	geoCoding(q: String, appid: String): [GeoCoding] # こちらを追加
}

# 追加
type GeoCoding {
	name: String
	local_names: localNames
	lat: String
	lon: String
	country: String
}

# 追加
type localNames {
	ascii: String
	en: String
	feature_name: String
	fr: String
	ja: String
}

```

## リゾルバーの設定
- 追加したスキーマ定義のQuery「geoCoding」のリゾルバー設定を行う。データソースは最初に作成したものと同じ、「openweathermap_api」を利用する

### リクエストマッピングテンプレート
- resourcePathをapiのパス（/geo/1.0/direct）に変更する。他はデフォルトで問題なし

```
{
  "version": "2018-05-29",
  "method": "GET",
  "resourcePath": "/geo/1.0/direct",
  "params":{
      "query":$util.toJson($ctx.args),
  }
}
```


### レスポンスマッピングレンプレート
- デフォルトで問題なし

```
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
  $util.error($ctx.error.message, $ctx.error.type)
#end
## If the response is not 200 then return an error. Else return the body **
#if($ctx.result.statusCode == 200)
    $ctx.result.body
#else
    $utils.appendError($ctx.result.body, "$ctx.result.statusCode")
#end
```

## 再度クエリを投げてみる
- クエリにgeoCodingが追加されるので、パラメータを設定してクエリを投げてみる

```Graphql
query MyQuery {
  currentWeather(appid: "{APIキー}", q: "Tokyo") {
    weather {
      description
      icon
      id
      main
    }
    name
    main {
      feels_like
      humidity
      pressure
      temp
      temp_max
      temp_min
    }
  }
  geoCoding(appid: "{APIキー}", q: "Tokyo") {
    country
    lat
    local_names {
      ascii
      en
      feature_name
      fr
      ja
    }
    lon
    name
  }
}
```

- レスポンス
    - geoCodingのAPIレスポンスについても取得ができた。Graphqlへの一回リクエストで複数APIのレスポンス情報が取得できた

```JSON
{
  "data": {
    "currentWeather": {
      "weather": [
        {
          "description": "light rain",
          "icon": "10n",
          "id": "500",
          "main": "Rain"
        }
      ],
      "name": "Tokyo",
      "main": {
        "feels_like": "287.63",
        "humidity": "88",
        "pressure": "1017",
        "temp": "288.95",
        "temp_max": "289.82",
        "temp_min": "288.15"
      }
    },
    "geoCoding": [
      {
        "country": "JP",
        "lat": "35.6895",
        "local_names": {
          "ascii": "Tokyo Prefecture",
          "en": "Tokyo",
          "feature_name": "Tokyo Prefecture",
          "fr": "Préfecture de Tokyo",
          "ja": "東京都"
        },
        "lon": "139.6917",
        "name": "Tokyo Prefecture"
      }
    ]
  }
}
```


## 参考
- https://dev.classmethod.jp/articles/appsync-for-aws-service-proxy/