Laravel 8.xで天気予報のWeb APIを作成

Laravel 8.83.11で天気予報のWeb APIを作成しました。ソースコードは下記のgithubリポジトリで公開しています。

https://github.com/fukagai-takuya/weather-forecast

* 天気予報データは他のサイトからWeb APIで取得してデータベースに格納するようにしています。Laravelを使ってWeb APIを用意した簡単なプログラムになります。
* この投稿ではプログラムの概要と動作確認方法のみについて記載しました。プログラムを作成した際に実行したコマンドとプログラムの内容は下記の投稿に記載しました。

https://qiita.com/fukagai-takuya/items/3cf47a7ff2c5ba290899

https://qiita.com/fukagai-takuya/items/e60b0aeb3a44a44c95ba


# 1. 作成したプログラムの概要

1.1. Web APIで日時を指定した問い合わせがあったら、その日時のニューヨーク、ロンドン、パリ、ベルリン、東京の5都市の天気データを返します。時刻は日本標準時(JST)ではなく協定世界時(UTC)です。GETメソッドでの問い合わせを想定しています。Ubuntu 20.04のローカルサーバーでテストしました。

下記のようなWeb APIの問い合わせをした際、

```
http://localhost:8000/api/get-weather-forecast?date=2022-05-13 10:25:49
```

指定された日時の天気予報データが存在すれば、下記のようなリスポンスが返ってきます。(3時間間隔の天気予報データのうち、指定された日時の前後1時間半以内の日時の天気予報データを返します。)

```
{
    "Result": "Success",
    "id": "9",
    "dt": "1652432400",
    "dt_txt": "2022-05-13 09:00:00",
    "new_york_main": "Clouds",
    "new_york_description": "overcast clouds",
    "london_main": "Clouds",
    "london_description": "overcast clouds",
    "paris_main": "Clouds",
    "paris_description": "scattered clouds",
    "berlin_main": "Clouds",
    "berlin_description": "broken clouds",
    "tokyo_main": "Rain",
    "tokyo_description": "moderate rain",
    "updated_at": "2022-05-12 07:56:09",
    "created_at": "2022-05-12 06:11:20"
}
```

Web APIのテストはテストツールPostmanとブラウザのChromeを使って行いました。ChromeでのテストはURL入力欄に下記のようなパラメータ付きのアドレスを入力して行いました。

```
http://localhost:8000/api/get-weather-forecast?date=2022-05-13 10:25:49
```

1.2. 天気予報のデータは下記のサイトの5 Day/3 Hour Forecastのページに記載されたWeb APIから6時間間隔で取得するようにしています。5日ほど先までの3時間間隔の天気予報データを取得することができます。取得したデータはSQLiteのデータベースに格納するようにしました。すでに格納した日時の天気予報データは新しいデータで更新します。

```
https://openweathermap.org/api
```

1.3. ユーザーがWeb APIを使った天気予報データの問い合わせをしたら、まず、SQLiteに格納したデータを参照します。該当する日時の天気予報データがなければ、上記のサイトにWeb APIで問い合わせます。それでも天気予報データを取得できなければエラーを返します。下記のリスポンスはエラーリスポンスの例になります。

```
{
    "Result": "Failed",
    "Error": "No weather data was found for the specified date.",
    "Date": "2022-06-12 10:25:40"
}
```

# 2. Ubuntu 20.04でのプログラム実行環境の用意

2.1. リポジトリのソースコードを下記のコマンド等で取得します。

```
$ git clone https://github.com/fukagai-takuya/weather-forecast
```

2.2. weather-forecastディレクトリに移動します。

```
$ cd weather-forecast
```

2.3. Laravelの環境設定ファイル .env ファイルを用意します。

下記のコマンドで .env.weather-forecast をコピーし、必要に応じて修正します。
```
$ cp .env.weather-forecast .env
```

2.4. 下記のコマンドでvendorディレクトリを用意します。(composerはインストール済みと仮定しています。)

```
$ composer install
```

2.5. データベースはSQLiteを使用しました。下記のコマンドでSQLiteとphpのSQLite接続用ドライバーをインストールします。(下記のコマンドは php 7.4 を使用している場合のコマンドの例になります。)

```
$ sudo apt install sqlite3
$ sudo apt-get install php7.4-sqlite3
```

下記のコマンドで空のデータベースファイルを用意します。

```
$ touch database/database.sqlite
```

下記のコマンドでデータベーステーブルを用意します。

```
$ php artisan migrate
```

コンソールに下記のように出力され、データベーステーブルが作成されます。

```
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (20.28ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (24.93ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (17.96ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (25.99ms)
Migrating: 2022_05_05_074622_create_jobs_table
Migrated:  2022_05_05_074622_create_jobs_table (23.33ms)
Migrating: 2022_05_06_013400_weather_data
Migrated:  2022_05_06_013400_weather_data (34.54ms)
```

2.6. crontabの設定をします。下記の設定では1分間隔で起動するようにしていますが、プログラム内部で6時間間隔で天気予報データを取得するようにしています。この投稿に記載した動作確認はcrontabの設定をしなくても可能です。

```
* * * * * cd /path-to-weather-forecast-project && php artisan schedule:run >> /dev/null 2>&1

```

# 3. Ubuntu 20.04での動作確認

3.1. 簡単な動作確認のため、サーバーを起動します。

```
$ php artisan serve
```

3.2. 下記のようにdateパラメータに天気予報データを取得したい日時を指定し、天気予報データを取得します。

```
http://localhost:8000/api/get-weather-forecast?date=2022-05-13 10:25:49
```

1.に記載したようなJSONフォーマットのリスポンスが返ってきます。

日時データは 2022-05-13 10:25:49 のようなフォーマットを想定しているため、フォーマットの異なる 2022-05-14 10:25:4X のようなデータを渡すと、下記のようなリスポンスが返ってきます。

```
{
    "Result": "Failed",
    "Error": "Format Error",
    "Date": "2022-05-14 10:25:4X"
}
```

また、2022-99-14 10:25:42 のような日時を渡すと下記のようなリスポンスが返ってきます。

```
{
    "Result": "Failed",
    "Error": "Incorrect Date",
    "Date": "2022-99-14 10:25:42"
}
```

# 4. PHPUnitを使用したプログラムのテスト

下記のコマンドでPHPUnitを使用した簡単なFeatureテストを実行するようにしました。

```
$ php artisan test
```

全てのテストにパスするとコンソールに下記のようなメッセージが出力されます。

```
PASS  Tests\Unit\ExampleTest
✓ example
 
 PASS  Tests\Feature\ExampleTest
✓ example
 
 PASS  Tests\Feature\WeatherForecastInquiryTest
✓ example
✓ get weather forecast
✓ get weather forecast empty input
✓ get weather forecast format error
✓ get weather forecast incorrect date
✓ get weather forecast data will not be found
✓ get weather forecast data will be found
✓ weather data table columns
✓ artisan shedule run command
✓ artisan migrate fresh command
 
Tests:  12 passed
Time:   2.96s
```

# 5. タスクスケジューラのテスト

5.1. ジョブの起動間隔の変更

2.6. に記載したように下記のコマンドを1分間隔で起動するようcrontabで設定します。
ここでは確認のためターミナルから直接下記のコマンドを実行します。

```
$ php artisan schedule:run
```

動作確認のため app/Console/Kernel.php を下記のように修正します。下記の修正により php artisan schedule:run コマンドを実行するとジョブがすぐにキューに追加されるようになります。

```
protected function schedule(Schedule $schedule)
{
    // $schedule->job(new WeatherForecastInquiryJob)->everySixHours();
    $schedule->job(new WeatherForecastInquiryJob)->everyMinute();
}
```

5.2. 下記のコマンドが問題なく実行されるか確認します。

```
$ php artisan schedule:run
$ php artisan queue:work
```

php artisan schedule:run はcrontabから1分間隔で呼び出されるコマンドです。
php artisan queue:work はキューからジョブを取り出して実行するコマンドです。ジョブが実行されると外部からWeb APIで天気予報データが取得され、データベースに格納されます。

問題なければ php artisan schedule:run を実行すると下記のように出力され、

```	
[2022-05-13T08:13:06+00:00] Running scheduled command: App\Jobs\WeatherForecastInquiryJob
```

php artisan queue:work を実行すると下記のように出力されます。

```
[2022-05-13 08:13:33][10] Processing: App\Jobs\WeatherForecastInquiryJob
[2022-05-13 08:13:34][10] Processed:  App\Jobs\WeatherForecastInquiryJob
```

php artisan queue:work の実行は継続されるため、Ctrl-C等を押して終了させます。

5.3. Supervisorを使用して php artisan queue:work を実行するテスト

Linuxサーバー上で使用する際は[こちらのドキュメント](https://laravel.com/docs/9.x/queues#supervisor-configuration)に記載されているようにSupervisorから php artisan queue:work コマンドを呼び出すように設定します。

私が確認した際のコマンドと設定ファイルの例を以下に記します。

下記のコマンドでSupervisorをインストールし、

```
$ sudo apt-get install supervisor
```

下記のように記載した /etc/supervisor/conf.d/laravel-worker.conf を用意しました。

```
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path-to-weather-forecast-project/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=fukagai
numprocs=8
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-weather-forecast-worker.log
stopwaitsecs=3600
```

下記のコマンドでSupervisorを起動し、

```
$ sudo service supervisor start
```

下記のように php artisan schedule:run コマンドを3回続けて実行したところ、

```
$ php artisan schedule:run
$ php artisan schedule:run
$ php artisan schedule:run
```

/var/log/supervisor/laravel-weather-forecast-worker.log に下記のようなログが出力されていました。

```
[2022-05-13 07:14:16][7] Processing: App\Jobs\WeatherForecastInquiryJob
[2022-05-13 07:14:16][8] Processing: App\Jobs\WeatherForecastInquiryJob
[2022-05-13 07:14:17][8] Processed:  App\Jobs\WeatherForecastInquiryJob
[2022-05-13 07:14:17][9] Processing: App\Jobs\WeatherForecastInquiryJob
[2022-05-13 07:14:17][7] Processed:  App\Jobs\WeatherForecastInquiryJob
[2022-05-13 07:14:17][9] Processed:  App\Jobs\WeatherForecastInquiryJob
```

:::note info
補足：
Supervisorが起動されていれば下記のコマンドを入力すると

```
$ sudo service supervisor status
```

コンソールに下記のように出力されます。

```
supervisord is running
```

Supervisorは下記のコマンドで停止させられます。

```
$ sudo service supervisor stop
```

:::