FuelPHPとAWS RDSでMVCプログラミング

f:id:watass:20141214211533p:plain

年末にかけてインフルエンザの猛威を受けてしまい、数日ダウンしておりました。
体調はすっかりよくなったのですが、まだ体内を菌がうようよしているらしいので、念のため自宅療養中です。それはさておき、既にネタとして用意しておいたRDSを使ったFuelPHPMVCモデルプログラミングについて、せっかくですから忘れないうちに記事にしておこうと思いたち、病床から記事を書いてます。
年末年始はようやくまとまった時間が取れるということもあり、VagrantとかDockerとか色々試すぞーと張り切っていただけに、体調不良で早速出鼻をくじかれるとは・・・なんとも情けない限りですね。



前回のおさらい

以前もFuelPHPの記事を書きましたが、こちらでは簡略化のため、Model要素を省いたViewとControllerによるVCモデルプログラミングについて書いておりました。MVCモデルプログラミングの利点は、それぞれの要素を分離し、管理しやすくなるところにあります。また、意図が明確なので、複数メンバーによる開発でも、共通認識を持ちやすくなります。
実際のアプリケーションではデータ保持のためにデータベース連携は避けて通れない道でありますので、前回までのVCモデルでは不十分です。Model要素を追加したMVCモデルが必要になります。また、AWSにはフルマネージドデータベースとして提供されるRDSがあります。クラウドを活用する上では確実に抑えたいところです。せっかくなので、今回の記事ではRDSのセットアップから、FuelPHPでどうやってRDSにアクセスするか、というところまで記事にしようと思います。

なお、前回の記事はこちらを参照してください。


AWS RDSとは?

Amazon Relational Database Service (RDS)とは、AWSによって提供されるフルマネージドな関係データベースサービスです。フルマネージドってなんやねん、MySQLの入ったEC2と何が違うんや、そもそもEC2にMySQL入れればそんなサービスいらないんちゃうんか、などなど質問は湧き上がってくるかもしれませんが、もちろん意味はあります。
RDSとはEC2にMySQLを入れただけのサーバとは大きく異なります。RDSは従来、RDBMSを入れてデータベースサーバとして管理していたサーバに必要な諸々の作業を自動化し、管理してくれるサービスです。これが「フルマネージド」の由来です。
具体例を挙げるならば、自動バックアップ、自動フェイルオーバー、MultiAZ配置、リードレプリカの作成などなど、設定するならそれなりの手間を必要とする各種作業を、RDSにお任せすればさくっとやってくれます。個人的に特に頼もしいのは、運用開始後からもDBのスケールアップが簡単にできる点だと思います。実サーバでやっている分にはこうはいきません。まぁEC2上でRDBMS動かしている場合にはある程度できるかもしれませんが・・・

誤解を恐れずに言えば、RDSはデータベースに特化したインスタンスサービスと言っていいでしょう。EC2にインストールしたRDBMSでも同様のことはできますが、AWSのベストプラクティスに倣うならば、専用に用意されているものは、それをなるべく活用するべきです。


開発環境について

前回とほぼ同様ですが、RDSの追加もありますのでまとめて書いておきます。
クライアントPCはマネージメントコンソールの操作とEC2インスタンスへのSSH接続しかしないため、基本的には省略します。ほとんどの操作はマネージメントコンソール上、またはSSH接続したEC2インスタンス上で完結します。

OS:Amazon Linux AMI 2014.09
PHP : Version 5.3.29
FuelPHP:Version 1.7.2
Apache:Version 2.2.29
MySQL:Version 5.6.19a


RDSインスタンスを作成する前の準備

RDSの作成は単純にRDSインスタンスを起動すればよいのですが、VPC上で運用して、セキュアなサービスを組むことを考えるならば、単純に起動するわけにはいきません。ちょっとわかりにくいことに、VPC上でRDSを起動する際、サブネットはVPCで作成したサブネットをそのまま使うことはできません。まず、RDSのメニュー上で、VPCサブネットを元にサブネットグループを作成する必要があります。
この先では過去に作ったサブネットやVPCネットワークを元にRDSインスタンスを作成していきますので、以下の記事に事前に目を通していただけるとスムーズかと思います。

Subnet Groupsの作成

まずはRDSインスタンスを配置する先となるサブネットグループを作成します。
RDS DashboardからSubnetGroupsを選び、Create DB Subnet Groupをクリックします。

f:id:watass:20141230161712p:plain

すると、上記のような詳細設定画面に移動します。
Nameはサブネットグループの名前を、Descriptionは簡単な説明です。無入力はエラーになりますので、適当でもいいのでざっと書いてください。後から変更できます。
VPC IDはRDSインスタンスを配置したいVPCのIDを選択することができます。もちろん、以前作成したVPCを選択します。すると、使用できるアベイラビリティゾーンやサブネットIDを選択できます。サブネットグループに含めたいサブネットを指定し、Addをクリック。
なお、今回は以前作成したDB用のプライベートサブネットを選択します。一方のAZのプライベートサブネットだけでもよいのですが、後々のマルチAZ配置を想定して、両方のAZにおけるプライベートサブネットをあわせて、ひとつの大きなプライベートサブネットグループを作成します。

これでVPC上におけるRDSインスタンスの立ち位置がなんとなくイメージできたでしょうか。図にすると以下の感じです。なお、今回は節約のためにマルチAZ配置は実施しません。
f:id:watass:20141230164306p:plain

Parameter Groupsの作成

パラメータグループとは、RDSインスタンス起動時に選択できるオプションで、DBエンジンの設定パラメータをグループとして管理することができるシステムです。
RDSインスタンスはDB用に特化したインスタンスであり、通常のEC2インスタンスのようにSSHで接続して、設定変更のためにmy.confやpostgresql.confを編集することはできません。その問題の解決のためにパラメータグループは用意されており、設定ファイルで設定できるような文字コードなど諸々の設定が可能です。

パラメータグループはデフォルトで作成されるものもあるのですが、残念なことにデフォルトのパラメータグループは自由に編集できません。これは後々の運用に影響するだけでなく、身近な問題として日本語が文字化けします(デフォルトパラメータグループでは文字コードはlatin1になっている)。後々変更して自由に設定できるように、適当に作成しておくことを推奨します。ただし、適当に作成しすぎると、なぜかインスタンス作成時に選択できないことがあるので、ほどほどに設定しておくとよいです。

Option Groupsの作成

こちらもパラメータグループと同様の理由で適当に作成しておくとよいです。
オプショングループは関連付けされたRDSインスタンスに対して、オプション機能を設定できるシステムらしいです。現状はあまり使用用途は見えませんが、memcache pluginなど設定できるらしいですね。


RDSインスタンスの起動

さて、ここまで来たら準備完了です。後はRDSインスタンスを起動するだけです。
RDS DashboardからLaunch DB Instanceをクリックします。

f:id:watass:20141230170330p:plain

こんな感じの画面が表示されて、まず最初にDBエンジンの選択ができます。
なお、設定項目に関しては、以下でざっとまとめておきます。

Select Engine

DBエンジンの選択です。現時点(2014年12月30日)では、MySQLPostgreSQLORACLESQLServerが選択可能です。
ここではMySQLエンジンを選択しておきます。

Production?

商用利用かどうかの質問です。ここでYesにすると、今後の選択肢のデフォルトは商用利用を前提としたものになっており、設定が容易になります。例えば、マルチAZの有効化やインスタンスサイズなどです。
今回は勉強用の環境ですので、Noとしておきましょう。とはいえ、Yesにしても後々の設定をしっかり行えば問題ありません。

Specify DB Detail

DB Engine
既に選択済みになっていると思いますが、選択したDBエンジンが正しく表示されていることを確認してください。今回はMySQLです。
License Model
DBエンジンによってはライセンスが色々あるので、ここで選択できます。MySQLの場合、もともとオープンソースなので、general-public-licenseのみで選択の余地はありません。
DB Engine Version
DBエンジンのバージョン指定です。今回は現時点の安定版である5.6.19aを選びます。
DB Instance Class
RDSインスタンスサイズです。無料枠で済ませるならば、t2.micro一択です。
Multi-AZ Deployment
RDSをマルチAZ配置するかどうかの選択です。Yesにすると冗長性はもちろん高くなるのですが、無料枠の範囲外になりますので、今回はNoとしましょう。
Storage Type
RDSの用いるストレージの種類です。無料枠ならMagneticかGeneral Purpose(SSD)にしましょう。Provisioned IOPS(SSD)はIOPSを指定できるので、IOがネックになる場合には検討してもよいでしょう。
Allocated Storage
ストレージの容量です。無料枠ならば20GBまでにしましょう。
DB Instance Identifier
RDSインスタンスの名前です。データベース名とは異なるので注意です。
Master Username
データベースへ接続する際に必要となるマスターユーザの名前です。
Master Password
データベースへ接続する際に必要となるマスターユーザのパスワードです。
Confirm Password
マスターユーザのパスワードの再入力です。

Configure Advanced Settings

VPC
RDSインスタンスを配置するVPCを選択できます。今回のように既に作成してある場合、それを選択します。
Subnet Group
RDSインスタンスを配置するサブネットグループを選択できます。先ほど作成したサブネットグループです。
Publicly Accessible
RDSインスタンスはパブリックにアクセス可能とするか、という設定です。Noにすると、VPC内からのみRDSインスタンスに接続できるため、よりセキュアになります。個人的にはNoをおすすめです。
VPC Security Group
VPCに適用するセキュリティグループを選択できます。ACLみたいな感じです。セキュリティグループはEC2 Dashboardから作成できますので、事前に作っておくことを推奨します。MySQLはデフォルトなら3306番ポートを用いるので、Inboundトラフィックで3306番のみ開放したSGを作成しておきましょう。Sourceを呼び出し元のEC2インスタンスが所属するSGにすると、さらにセキュアです。

Database Option

Database Name
データベース名です。接続に使う名前なので、選択は慎重に。
Database Port
RDSの使用するポート番号です。MySQLならデフォルトは3306番になっているはずです。下手にイジる必要はないと思います。
DB Parameter Group
パラメータグループを選択できます。先ほど作成したものを選択しましょう。
Option Group
オプショングループを選択できます。先ほど(ry
Backup Rentention Period
RDSは何日分までのバッグアップを保持するか?という設定です。0日にするとバックアップは作成されません。今回は1日にしておきましょう。2日以上にすると有料になるので注意です。
Backup Window
自動バックアップが作成されるタイミングを設定できます。特に指定がなければNo Preferenceでいいでしょう。その場合、適当なタイミングでバックアップが作成されます。
Auto Minor Version Upgrade
RDSはDBエンジンのマイナーバージョンアップグレードに関して自動化することができますので、それを行うかどうかの設定です。Yesでいいと思います。ちなみにメジャーアップグレードは自動で行われませんので、実施する際には手動で対応してください。
Maintenance Window
RDSは定期的にメンテナンスを実施します。その周期やタイミングを設定することができます。特に指定がなければNo Preferenceでいいです。すると、適当なタイミングでメンテナンスが実施されます。


以上で設定は完了です。RDSインスタンスを立ち上げ、Statusが以下のようにavailableになればRDSインスタンスの作成は完了です。

f:id:watass:20141230182604p:plain


EC2からの接続テスト

とはいえ、作成完了と言われても、実際に繋いでみないと、本当にちゃんと動いているのか不安だと思います。動作確認のため、EC2インスタンスSSHで接続し、mysqlコマンドでRDSに接続してみましょう。

MySQLのパッケージをインストール

まず接続先のEC2インスタンスmysqlコマンドを使えなくては話になりませんので、mysqlパッケージをインストールします。加えて、後々PHPからMySQLに接続するために、php-mysqlもインストールしておくことを推奨します。

# yum install mysql
# yum install php-mysql

RDSエンドポイントを利用してEC2から接続

mysqlコマンドが使えるようになれば、後は別ホストのMySQLに接続できるようになりますので、EC2インスタンスからRDSへの接続も可能になります。ホストにはRDSのエンドポイントとして用意されているURLを用います。
具体的には、以下のようなコマンドで接続できます。

$ mysql -h <エンドポイント名> -u <マスターユーザ名> -D <データベース名> -p

マスターユーザ名、データベース名はRDSインスタンスの作成時に設定したものです。
正常に接続できれば、パスワードが求められますので、RDSインスタンス作成時に設定したマスターユーザパスワードを用いてください。

接続できたら、statusコマンドなどで詳細を見てみましょう。

mysql> status
--------------
mysql  Ver 14.14 Distrib 5.5.40, for Linux (x86_64) using readline 5.1

Connection id:		12
Current database:	fuelphpdb
Current user:		watass@172.31.9.111
SSL:			Not in use
Current pager:		stdout
Using outfile:		''
Using delimiter:	;
Server version:		5.6.19-log MySQL Community Server (GPL)
Protocol version:	10
Connection:		fuelphp.cycc0hjkaoen.ap-northeast-1.rds.amazonaws.com via TCP/IP
Server characterset:	latin1
Db     characterset:	latin1
Client characterset:	utf8
Conn.  characterset:	utf8
TCP port:		3306
Uptime:			25 min 15 sec

Threads: 2  Questions: 1862  Slow queries: 0  Opens: 256  Flush tables: 1  Open tables: 88  Queries per second avg: 1.229
--------------

こんな感じで普通にインストールされたMySQLと同様に扱えることがわかると思います。
以上でEC2からの接続テストは完了です。


FuelPHPから接続する

いよいよ本題、FuelPHPから先ほど用意したRDSへ接続し、情報を取得してきます。
前回の記事では、アクセスすると、コントローラで定義された変数を元にViewが生成され、ユーザ名が表示される仕組みになっていましたが、今回はコントローラを修正して、モデルにユーザ名データの問い合わせを依頼するようにします。コントローラから呼び出されるモデルは、先ほど用意したRDSに接続して、ユーザ名を取得してきます。
上記のイメージを図にするとこんな感じです。

f:id:watass:20141230203636p:plain

モデルとViewの相互作用については、まだちょっと直感にないのでうまく表現できませんが、色々相互作用することもあるらしいです。適当ですいません。というか、この図を表すのにパッケージ図でいいのかどうかさえわりと怪しいです。ごめんなさい。
・・・気をとりなおして、取得するデータは先ほどの要領でRDSに接続して事前に作成しておきます。以下のコマンドでテーブル作成とデータ挿入を行います。

mysql> CREATE TABLE fuelphpdb.user_m(id int,name varchar(20));
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO user_m VALUES (1,'dbuser');
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * from user_m;
+------+--------+
| id   | name   |
+------+--------+
|    1 | dbuser |
+------+--------+
1 row in set (0.00 sec)

Modelの作成

上記イメージに基づき、Model要素を作成します。作成する場所は、作成したプロジェクトのfuel/app/classes/model配下です。今回のケースでいえば、~/welcome/fuel/app/classes/model配下になります。
なお、ファイル名は仮にgetname.phpとしておきます。内容は以下みたいな感じです。

<?php

class Model_Getname extends \Model_Crud{
        protected static $_table_name = 'user_m';
        protected static $_primary_key = 'id';
}

FuelPHPの作法として、モデルにおけるクラス名(Model_なんちゃら〜のなんちゃらの部分)はファイル名と同様であるべきです。また、PHPの開始タグに対して、閉じタグは不要です。(これを知らずに過去記事では結構書いてますね・・・、すいません)
ちなみに、このクラスが何をやっているかというと、CRUDクラスを継承し、接続先のテーブル名を"user_m"、プライマリーキーを"id"と定義しているだけです。
このCRUDクラスを継承する、というところが非常に便利で、FuelPHPによって既に用意されているデータベース操作に関する基本機能、CRUD機能を簡単に実現することができます。Model_Crudを継承すると、様々なメソッドを利用することができます。詳細は公式ドキュメントを見て下さい。

Controllerの変更

モデルを作成したので、コントローラは内部で定義した変数を使うだけでなく、モデルのメソッドを呼び出し、取得した情報を元にViewを生成するように変更します。
以前作成した、~/welcome/fuel/app/classes/controller/login.phpを編集します。

<?php

class Controller_Login extends Controller{
        public function action_index(){
                //$user = 'user';
                //$data['name'] = $user;
                $id = 1;
                $user = Model_Getname::find_one_by('id',$id);
                $data['name'] = $user->name;

                return View::forge('login_view',$data);
        }
}

ここでポイントはModel_Getnameのfind_one_byメソッドです。これはCRUDクラスを継承したクラスであれば使用することができます。find_one_byメソッドでは、第一引数にカラム名、第二引数にデータを指定することで、その条件をWHERE句として、取得した一行を手に入れることができます。
この他にも、CRUDクラスを継承したクラスでは、様々な便利メソッドを利用できます。詳細については公式ドキュメントを参照してください。

Viewの変更

なんとなく察していただけているかと思いますが、実はViewは変更しなくてもOKです。
コントローラがViewを生成する際に使用している変数名が変わっていない限り、それぞれのコンポーネントは明確に分離されていますので、内部の仕組みをViewが意識しなくてもいいんですね。これがMVCモデルの大きなメリットだと思います。

接続先DB情報の設定

以上でプログラムの準備は完了ですが、肝心のRDSへの接続情報が記載されていません。
FuelPHPでは、db.phpというファイルにデータベース情報を記述することで、接続先の情報をFuelPHPに記録させることができます。db.phpは開発環境用、本番環境用、共通設定用に3つあるのですが、今回は開発用のため、開発環境用のdb.phpを編集します。ファイルの実体は~/welcome/fuel/app/confing/development/db.phpになります。

<?php
/**
 * The development database settings. These get merged with the global settings.
 */

return array(
        'default' => array(
                'connection'  => array(
                        'dsn'        => 'mysql:host=<エンドポイント名>;dbname=fuelphpdb',
                        'username'   => 'watass',
                        'password'   => '********',
                ),
        ),
);

こんな感じで設定します。hostの値はEC2から接続したときのように、エンドポイント名を使用しましょう。

アクセスしてみる

以上で準備完了です。http://IPアドレス/welcome/login/にアクセスしてみましょう。

f:id:watass:20141230214906p:plain

いいですね。データベースのユーザ名を取得して、しっかり表示できています。疑り深い方はデータベースを更新すれば、表示が変わることが確認できますので、試してみてください。


まとめ

  • RDSはフルマネージドなデータベースで単なるEC2上のRDBMSとは異なる
  • RDSをVPC上で配置するためにはサブネットグループとか準備が必要
  • FuelPHPではCRUDクラスを継承すれば便利なメソッドが使い放題


そもそもなんでlogin.phpなんだって話だよね

最初はログイン機構を作るつもりだったんですが、フレームワーク初心者的には、ちょっとまとめきれない可能性が出てきたので、結局名前そのままで出しちゃったんですよね。読んだ人からすれば意味不明ですよね。どの辺がログインやねんって話ですよね。
ところで、今回の記事、死ぬほど長いですね。もうちょっとかいつまんで説明すればよかったです。読みにくいったらありゃしない。というか、先週の記事で書いたウェブマーケティングの記事書くって話はどこに行ったんだ。もうツッコミどころ満載ですね。

・・・とりあえずその辺の話はおいておいて、今回の記事を振り返ってみると、やっぱりFuelPHPに限らず、フレームワークとの付き合い方は普通のプログラミング言語との付き合い方とは違う、ということを痛感しますね。
今まではどの言語も、なんとなく既にある機能のイメージがついていて、それを実現したいときに、その取っ掛かりをググればよかったわけですが、フレームワークの場合、どこまでサポートされているかわかりませんし、そもそもフレームワーク特有の「作法」がありますので、それを理解することが大事です。CRUDクラスや、find_one_byメソッドなどもググったり、公式ドキュメントに目を通さないとぜったいに辿りつけなかった情報ですから。
ちなみに、それを感じて以来、良著と名高い「はじめてのフレームワークとしてのFuelPHP」を静岡の書店に行った際に購入しちゃいました。ざっとしか読めていませんが、ユニットテストの話とかが充実しているのはよさそうですね。じっくり読んでいきます。おすすめです。

はじめてのフレームワークとしてのFuelPHP 改訂版

はじめてのフレームワークとしてのFuelPHP 改訂版

今後はもうちょっとFuelPHPの作法をわきまえてコーディングしていきたいですね。それと同時に、開発環境をもっと整備したいなーとか思いつつ、あー来年はJAWS DAYにも参加したいしーとドタバタやってます。だから大事な年末に風邪引くんですね。来年はもっと落ち着いて、色々手を出せるといいなーと思います。


参考にした記事

Amazon RDS ~EC2インスタンスからDBインスタンスへの接続~ |ec2 db インスタンス 接続 方法 | ナレコムAWSレシピ
# EC2からRDSへの接続で基本的な部分はすべて参考にさせていただきました。ありがとうございます。
インターネットから直接Amazon RDS in VPCにアクセスできない時の解決方法 | Developers.IO
# いつものクラスメソッドさんの記事。VPC上でのRDSのセキュリティについて参考になりました。
FuelPHPのモデルと3種類のDB利用方法
# FuelPHPからデータベースへの接続方法がまとまっていて非常に参考になります。
FuelPHP でのデータベース接続設定 〜 root のパスワードを root にしないでください - A Day in Serenity @ kenjis
# FuelPHPにおけるデータベース設定の仕方が丁寧に解説してくださっています。ありがとうございます。
Model_Crud - クラス - FuelPHP ドキュメント
# FuelPHPの日本語公式ドキュメントです。Model_Crudメソッド一覧です。