さくらのVPSを使ってシステム開発に必要な知識を学ぶ 〜第12回〜

はじめに

本連載は、システム開発に必要な知識を得るために、一通りの流れを学ぶことを目指しています。

本連載の第7回からは、実際の業務を想定したメモアプリを例に、開発の流れを解説しています。今回はアプリケーションサーバー(APサーバー)とデータベースサーバ(DBサーバー)の通信など、バックエンド部分の開発を見ていきます。

APサーバーとDBサーバーの通信設定

APサーバーからDBサーバーにアクセスするには、いくつかのステップを踏む必要があります。

MySQLの設定

MySQLサーバーが外部からの接続を受け入れるように設定します。具体的な設定としては、MySQLの設定ファイル(通常は /etc/mysql/my.cnf/etc/my.cnf など)を編集し、bind-addressをコメントアウト、または 0.0.0.0 に設定して外部接続を許可します。

# /etc/mysql/my.cnf
[mysqld]
bind-address = 0.0.0.0

設定したら、MySQLサービスを再起動します。

$ sudo service mysql reload
$ sudo service mysql restart

MySQLユーザーとデータベースの確認

APサーバーからDBサーバーへの接続に使用するMySQLユーザーとデータベースは、本連載の第9回の記事で作成したものです。

過去に作成したユーザーやデータベースを確認します。

mysql> SELECT user, host FROM mysql.user;
+------------------+-----------+
| user             | host      |
+------------------+-----------+
| debian-sys-maint | localhost |
| ユーザー名         | localhost |
| mysql.infoschema | localhost |
| mysql.session    | localhost |
| mysql.sys        | localhost |
| root             | localhost |
+------------------+-----------+
6 rows in set (0.00 sec)

mysql>

過去に作成したユーザーはホストがlocalhostなので、外部から参照できるようにホストを % に変更します。ホストを%にすると、どのホストからも接続できるようになります。(下記のコマンド例における「ユーザー名」「パスワード」は、実際に作成したユーザー名とパスワードに置き換えて実行してください)

mysql> drop user ユーザー名;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE USER 'ユーザー名'@'%' IDENTIFIED BY 'パスワード';
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> grant all privileges on mydatabase.* to 'ユーザー名'@'%';
Query OK, 0 rows affected (0.01 sec)

mysql> select host,user from mysql.user;
+-----------+------------------+
| host      | user             |
+-----------+------------------+
| %         | ユーザー名         |
| localhost | debian-sys-maint |
| localhost | mysql.infoschema |
| localhost | mysql.session    |
| localhost | mysql.sys        |
| localhost | root             |
+-----------+------------------+
6 rows in set (0.00 sec)

mysql>

mysql> SHOW GRANTS FOR 'ユーザー名'@'%';
+------------------------------------------------------------+
| Grants for ユーザー名@%                                      |
+------------------------------------------------------------+
| GRANT USAGE ON *.* TO `ユーザー名`@`%`                       |
| GRANT ALL PRIVILEGES ON `データベース名`.* TO `ユーザー名`@`%` |
+------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql>

ファイアウォールの設定

DBサーバーのファイアウォールがポート3306(MySQLが使用するポート)への接続を許可していることを確認します。

$ sudo ufw allow 3306
[sudo] password for ubuntu:
Skipping adding existing rule
Skipping adding existing rule (v6)
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
3306                       ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)
3306 (v6)                  ALLOW       Anywhere (v6)

$

Rustのアプリケーションの設定

RustとActix-webを使用している場合、APサーバーのコード内でMySQLへの接続情報を設定します。通常、これは接続URLやホスト、ポート、ユーザー名、パスワードなどが含まれます。これらの情報を適切に設定して、APサーバーがDBサーバーに接続できるようにします。

// Actix-webアプリケーションのデータベース接続設定例
let url = "mysql://ユーザー名:パスワード@DBサーバーのIPアドレス:3306/データベース名";

これらの手順を実行すると、APサーバーがMySQLデータベースにアクセスできるようになります。

ただし、セキュリティ上の理由から、外部からのデータベースへの直接アクセスを最小限に抑え、必要な場合はセキュリティ対策を施すことが重要です。

APIについて

このあと、バックエンドの開発について解説していきますが、その前にAPIについて少し説明しておきます。

APIとは

API(Application Programming Interface)は、コンピュータープログラムやアプリケーション同士が情報をやり取りするための手段やルールのセットです。これを簡単に説明すると、APIは異なるソフトウェアやシステムがコミュニケーションをとる方法を提供する「中間者」のようなものです。

例えば、あなたがスマートフォン上で天気アプリを使用して、現在の天気情報を取得する場合を考えてみましょう。このアプリは、天気予報情報を提供する外部のデータソース(通常はウェブ上の天気予報サービス)と通信する必要があります。ここでAPIが登場します。アプリはAPIを使用して、外部データソースと情報を共有し、必要な情報を取得します。

APIは、プログラムやアプリケーションが他のプログラムやサービスと連携する際に必要となる、データの送受信や操作の方法を定義し、コード化します。これにより、異なるソフトウェア間でデータを共有し連携するための標準化された手法が提供され、開発者が異なるサービスやリソースを利用する際に効率的に連携できるようになります。

簡単に言えば、APIはプログラムやアプリケーションがお互いにコミュニケーションをとり、情報を共有するための「取っ手」や「窓口」のようなものであり、これにより異なるシステム同士が連携し、機能を拡張したり、新しいアプリケーションを開発したりすることができます。

REST API解説

REST API(Representational State Transfer API)は、ウェブ上で情報をやり取りするための一般的な方法の一つです。簡単に言えば、REST APIはウェブサービスの一種で、異なるコンピューターシステム間でデータを共有し、通信するためのルールや規則のセットです。

REST APIは、HTTPプロトコルを使用して情報をリクエスト(要求)およびレスポンス(応答)する方法を定義しています。RESTは、リソース(Resource)と呼ばれる情報を一意に識別し、それらのリソースに対して操作を行うための標準化された方法を提供します。

RESTの基本原則には以下のようなものが含まれます。

  1. リソース:APIは、データや機能を表現するリソースを識別します。例えば、ウェブページ、ユーザーアカウント、商品などがリソースの例です。
  2. HTTPメソッド:HTTPの標準メソッド(GET、POST、PUT、DELETEなど)を使用して、リソースに対して特定のアクションを実行します。例えば、GETメソッドはリソースを取得し、POSTメソッドは新しいリソースを作成します。
  3. ステートレス性:REST APIは通信のステートレス性を持ちます。これは、各リクエストが独立しており、前のリクエストからの情報を保持しないという意味です。情報はリクエスト内に含まれます。
  4. URL:リソースは一意のURLを持ち、それを使用してアクセスされます。例えば、https://example.com/users/123といったURLで特定のユーザーリソースにアクセスできます。

REST APIは、多くのウェブサービスやアプリケーションで広く使用されており、異なるプログラムやプラットフォーム間でデータを共有するために非常に便利な方法です。例えば、ウェブアプリケーションはREST APIを使用してサーバーからデータを取得し、ユーザーに表示することができます。

バックエンド開発

それでは、今回開発したバックエンド部分について解説します。

コードの所在

今回作成したコードはGitHubの下記URLに置いてあります。これを見ながら以下の解説をご覧ください。

https://github.com/ic-lifewood/dev-ops-sample/tree/main/backend

ディレクトリ構成

今回作成したコード群のディレクトリ構成は以下のようになっています。

my_actix_app$
  ├── Cargo.lock
  ├── Cargo.toml
  └── src
      └── main.rs

各ファイルの説明

Cargo.toml

CargoはRustのパッケージマネージャおよびビルドシステムです。Cargo.tomlはCargoの中心的な設定ファイルであり、Rustプロジェクトのメタデータと依存関係を管理するためのものです。

Cargo.tomlにおける主なセクションは以下の通りです。

  • [package]:パッケージのメタデータを含むセクションです。
    • name:パッケージの名前を指定します。
    • version:パッケージのバージョンを指定します。
    • authors:パッケージの作者情報を指定します。
    • edition:使用するRustのエディションを指定します(通常は"2015"または"2018")。
  • [dependencies]:パッケージが依存する他のクレート(ライブラリ)を指定します。各依存関係は、クレート名とバージョンのペアで指定されます。
  • [dev-dependencies]:開発時のみに必要な依存関係を指定します。テスト、ドキュメント生成、Lint ツールなど、本番環境では使用されないが開発時には必要なライブラリをここに指定します。

また、Cargo.tomlはコメントもサポートしています。行頭に # を置くことでコメントを追加できます。

Cargo.lock

Cargo.lockは、Rustプロジェクトで使用される依存関係のロックファイルです。CargoはRustのパッケージマネージャーであり、Cargo.lockは依存関係のバージョンを厳密に管理するためのものです。

通常、Cargo.tomlにはプロジェクトの依存関係が記述されています。これらの依存関係は、バージョンの範囲指定などで表されることがあります。しかし、Cargo.tomlには依存関係の解決方法が書かれているだけで、実際の依存関係のバージョンはビルド時に確定されます。その確定された依存関係のバージョン情報がCargo.lockに保存されます。

依存関係を更新する場合は、Cargo.lockを更新する必要があります。 通常、開発者は cargo update コマンドを使用してCargo.tomlの依存関係を更新し、Cargo.lockを再生成します。

Cargo.lockファイルはバージョン管理システムで管理する必要があります。これにより、チーム全体が同じ依存関係のバージョンを共有し、開発環境を一貫させることができます。

main.rs

Rustプログラムのエントリーポイントであり、通常、プログラムの起点となる場所です。ここでは、main.rsファイルの基本的な構造と役割を解説します。

main.rsファイルは、次のような特徴を持ちます。

  1. ライブラリのインポート
    まず、プログラムが使用する外部ライブラリやモジュールをuseキーワードを使ってインポートします。上記の例では、標準ライブラリのstd::envモジュールをインポートしています。これは、コマンドライン引数を扱うために使用されます。
  2. main関数の定義
    fn main()という名前の関数が定義されます(サンプルコードでは115行目)。この関数はプログラムのエントリーポイントであり、プログラムが実行されると最初に呼び出されます。Rustプログラムは常にmain関数から開始されます。
  3. ロジックの記述
    main関数内には、プログラムの主要なロジックが記述されます。これは、ファイルの読み書き、ネットワーク通信、データ処理など、プログラムが行う具体的な作業に関連するコードです。

main.rsファイルは、Rustプログラムのエントリーポイントとして機能し、プログラムの実行フローを開始します。プログラムの構造とロジックはmain関数内に記述されますが、大規模なプロジェクトでは通常、モジュール化されたコードや外部ファイルからの関数呼び出しを含むことが一般的です。

動作確認

REST APIを試す際には、curlコマンドやPostmanなどを使用してHTTPリクエストを送信しテストします。例えば、メモのリストを取得するGETリクエストは以下のようになります。

$ curl http://ホスト名/api/memos

他のCRUD操作(Create(作成)、Read(参照)、Update(更新)、Delete(削除))も同様にテストできます。REST APIの動作が確認できたら、フロントエンドや他のサービスからこのAPIを呼び出すことができます。以下に各種データ操作の例を紹介します。

GET (一覧)

まずcurlコマンドでデータの一覧を取得してみます。結果はJSON形式で返ってきます。

# curl コマンド
$ curl http://ホスト名/api/memos
[{"id":1,"title":"野菜リスト","memo":"じゃがいも、人参、たまねぎ"},{"id":2,"title":"チキンカレーの材料","memo":"鶏肉、じゃがいも、人参、たまねぎ"}]

これをMySQLに対して発行したSELECT文の実行結果と比較し、データが同じであることを確認しましょう。

mysql> SELECT * FROM MEMOS;

+----+-----------------------------+--------------------------------------------------+
| ID | TITLE                       | MEMO                                             |
+----+-----------------------------+--------------------------------------------------+
|  1 | 野菜リスト                    | じゃがいも、人参、たまねぎ                           |
|  2 | チキンカレーの材料             | 鶏肉、じゃがいも、人参、たまねぎ                      |
+----+-----------------------------+--------------------------------------------------+
2 rows in set (0.00 sec)

mysql>

GET (個別)

次に、データを個別に取得してみます。curlコマンドでは以下のようになります。

# curl コマンド
$ curl http://ホスト名/api/memos/1
{"id":1,"title":"野菜リスト","memo":"じゃがいも、人参、たまねぎ"}
$ curl http://ホスト名/api/memos/2
{"id":2,"title":"チキンカレーの材料","memo":"鶏肉、じゃがいも、人参、たまねぎ"}
$

こちらもSELECT文の実行結果を比較して、データが同じであることを確認します。

mysql> SELECT * FROM MEMOS;

+----+-----------------------------+--------------------------------------------------+
| ID | TITLE                       | MEMO                                             |
+----+-----------------------------+--------------------------------------------------+
|  1 | 野菜リスト                    | じゃがいも、人参、たまねぎ                           |
|  2 | チキンカレーの材料             | 鶏肉、じゃがいも、人参、たまねぎ                      |
+----+-----------------------------+--------------------------------------------------+
2 rows in set (0.00 sec)

mysql>

POST (新規作成)

次はPOSTを用いたデータの新規作成です。こちらもまずcurlコマンドで実行してみます。

# curl コマンド
$ curl -X POST -H "Content-Type: application/json" -d '{"id":0,"title":"新しいメモ","memo":"これは新しいメモです。"}' http://ホスト名/api/memos

その後、MySQLにてSELECT文を実行すると、新しいデータが追加されていることがわかります。

mysql> SELECT * FROM MEMOS;

+----+-----------------------------+--------------------------------------------------+
| ID | TITLE                       | MEMO                                             |
+----+-----------------------------+--------------------------------------------------+
|  1 | 野菜リスト                    | じゃがいも、人参、たまねぎ                           |
|  2 | チキンカレーの材料             | 鶏肉、じゃがいも、人参、たまねぎ                      |
|  3 | 新しいメモ                    | これは新しいメモです。                              |
+----+-----------------------------+--------------------------------------------------+
3 rows in set (0.00 sec)

PATCH (更新)

次はデータの更新です。HTTPではPATCHメソッドを使用します。以下のcurlコマンドを実行します。

# curl コマンド
$ curl -X PATCH -H "Content-Type: application/json" -d '{"title":"更新されたタイトル","memo":"更新されたメモ"}' http://ホスト名/api/memos/3

その後、SELECT文を実行すると、3番目のデータの内容が変更されていることがわかります。

mysql> SELECT * FROM MEMOS;

+----+-----------------------------+--------------------------------------------------+
| ID | TITLE                       | MEMO                                             |
+----+-----------------------------+--------------------------------------------------+
|  1 | 野菜リスト                    | じゃがいも、人参、たまねぎ                           |
|  2 | チキンカレーの材料             | 鶏肉、じゃがいも、人参、たまねぎ                      |
|  3 | 更新されたタイトル             | 更新されたメモ                                     |
+----+-----------------------------+--------------------------------------------------+
3 rows in set (0.01 sec)

mysql>

DELETE (削除)

最後にデータの削除です。こちらはDETELEメソッドを使用します。以下のcurlコマンドを実行します。

# curl コマンド
$ curl -X DELETE http://ホスト名/api/memos/3

その後、MySQLに対してSELECT文を実行すると、3番目のデータが削除されているのがわかります。

mysql> SELECT * FROM MEMOS;

+----+-----------------------------+--------------------------------------------------+
| ID | TITLE                       | MEMO                                             |
+----+-----------------------------+--------------------------------------------------+
|  1 | 野菜リスト                    | じゃがいも、人参、たまねぎ                           |
|  2 | チキンカレーの材料             | 鶏肉、じゃがいも、人参、たまねぎ                      |
+----+-----------------------------+--------------------------------------------------+
2 rows in set (0.00 sec)

mysql>

これで、APサーバーを経由して、DBサーバーのデータを操作することができました。

まとめ

今回は以下の項目を解説しました。

  • APサーバーとDBサーバーの通信設定
  • APIについて
  • Rustで開発したバックエンド部分のコード解説と動作確認

次回は以下の項目を解説する予定です。

  • Angularを使って開発したフロントエンド部分のソースコード解説
  • CI/CDを使ったデプロイ方法