はじめに
こちらの記事は「tecotec Advent Calendar 2019」の5日目です。
当方フリーランスエンジニアなのですが、先月書いた記事: "Slackはただのコミュニケーションツールじゃない、企業の技術を映す鏡だ" がにわかにバズったことから、現在ご一緒にお仕事させていただいているtecotecさんに声をかけていただきました。ありがたい限りです。
ということで自分の中で温めていた、少々重苦しいテーマについて語ろうと思います。
この記事について
私は新卒で就職してから、王道のようでいて珍しいようでいて、それでいて激動のキャリアを歩んできました。
SIerでのレガシーシステム保守、データ分析・機械学習ベンチャー部門での便利屋、独立してwebエンジニア……
特にSIerでのレガシーシステム保守経験が、自分のキャリアの基盤であり、同時に今に至るまで引きずっている複雑な気持ちの根源となっています。
こちらの記事でActive Recordというデザインパターン(というよりもはやwebフレームワークの必須教養科目?)については、一切知らない人にも通じるように語りたいと思いますが、それはこの記事でメインとしたいことではありません。
それよりも、私のエンジニアとしての半生を語りながら
- 10年単位で稼働している巨大プロジェクトが抱えやすい問題
- Active Recordは「銀の弾丸」か
そして、もう一つ。
- エンジニア間で生まれる断絶あるいは格差
という少々重苦しいテーマ群について、外堀から徐々に語ることを目的としています。
途中でJavaやPHPのコードに触れますが、特に前提知識は必要ありません。
あらかじめ謝罪しておきますが、この記事は明確な結論を持ちません。
それは私の中に明確な結論がないからです。
その理由だけは……明確に語らせて頂きますので、しばしお付き合いください。
SQLおじさんという言葉
ある時期の自分は、SQLの信奉者でした。
一部の方は、このことを聞いて「SQLおじさん」という言葉を思い浮かべられたかもしれません。
端的に言えば SQLおじさんとは**「DBに関連するロジックは全てプログラムから切り離してDB側で処理すべき」** という強い思想に基づく記事を投稿した結果、議論を呼んでしまったとあるエンジニアを指して生まれた一種のスラングです。
ただ、元ネタとなった「SQLおじさん」関連の記事を調べる限り、この記事で扱うような**「SQLを直接プログラムに書き込むようなコード」** を是としていた可能性は極めて低いものと思われます。
なのでこの記事では**「SQL闇落ち系エンジニア」**という言葉を使いましょう。……言葉のキレが極めて悪いですが許してください。
例えば、SQL闇落ち系エンジニアの世界に「ORマッパー」……データベースに対するアクセスをなるべく抽象化しよう = SQLというド直球なロジックはなるべく隠蔽しよう、という概念はありません。
「SQLを直にプログラムに記載すれば良いじゃないか?SQLは非常に強力なんだから」。
Laravel, Rails, Django。
そういった代表的web系フレームワークは「わざわざ」DBアクセス方法を提供してくれています(これがまさしく先程述べた"Active Record"です)。
「何の意味があるんだ? SQLがあれば自由自在にデータを取得できるのに」。
……少しでもWebフレームワークを勉強したことがある方なら、このような考え方にすぐ顔をしかめることでしょう。
ですが……
私はまさに、このような考え方の持ち主でした。
社会人経験3年に満たない25歳のエンジニアでしたが、あの頃の私はまさに「SQL闇落ち系エンジニア」でした。
例えばRailsのような比較的モダンなwebフレームワークを利用した環境からからエンジニアとしてデビューされた方々に、上記の考え方は理解ができないと思います。
でも、知ってほしい。
「何故そのようなエンジニアが生まれるのか」。
言い換えれば。
「エンジニアはどのようにして闇に落ちるのか」。
そのために、改めまして私のSIer時代から語らせていただきたく。
どうぞよろしくお願いいたします。
本題
エンジニアとしてのデビュー
私は2015年4月、新卒未経験で受託開発を中心とした100人規模の会社に就職いたしました。その規模の会社を "SIer" と呼称することが適当かどうかは別にして、その会社のビジネス形態は確実に "SIer" と呼ばれるようなものでした。
受託開発中心の割に元請け率が多いことを売りにしていたその会社はなかなかの歴史があり、大規模な案件に元請けとして長期期間関わっているということも多々ありました。
その中の一つに私はアサインされます。
製造業保守案件でした。
詳細はあまり言いたくないのですが、製造業とまとめられる中でもおそらく最大規模に属するであろう、超巨大なカテゴリの「製造」を扱っていました。
早速実際の業務のお話に入りたいのですが……
皆様はBOMという言葉を聞いたことがあるでしょうか。
爆弾(BOMB)ではありません。Bills of materials、部品表の略です。
私が携わったのは、BOM……Bills of materialsの管理アプリケーションの保守業務でした。
BOMは日本語でこそ「部品表」ですが、製造業における「部品表」はまさに製造の「心臓」です。
最も重要な器官の一つであり、同時に常に鼓動し、部品という血流を流し続ける、その中心地点です。
設計・生産はすべてこの"BOM"のCRUDをトリガとして流れていると言っても過言ではありません。
まぁ要するに。
とても重要で、同時に……
とても。極めて。
複雑だということです。
Javaの黎明期から開発・保守・改修が続けられてきたそのコードが有するクラスは1000に至り、テーブルの総量は100を軽く超えました。一つの機能が「中心的にアクセスするテーブル」の数だけで数十に至ることもザラにありました。
全貌を把握することは、不可能。
中心の業務知識とシステムの仕様を身につけるだけでも、1年では全くの不足。
……そんなシステムの保守・改修に1年目の私はアサインされたわけです。
そして2年半、会社に辞意を告げるまでそのアサインは続きました。
圧倒的に信頼できる「力」
エンジニアにとって「ググる」力は非常に大事です。ググるための情報源を大量に持っていること、stackoverflowの英語に臆さないこと、面倒くさくてもとりあえず公式ドキュメントを読み込むこと、気分転換に@yametaroさんのQiitaを読むこと……
非常に大事です。
さて、そんなエンジニアライフの1年目。
私の現場にはインターネットが通っていませんでした。
否、厳密に言えば通っていたのですが、数人のメンバで7Gの帯域をpocket wifiで共有していたため、あまりマトモに使えなかったのです。
これは2年目に私が総務にゴネてpocket wifiの量が物理的に増えたことで改善いたしました。
仮にそうでなかったにせよ、Java黎明期から受け継がれた某マンモス級ベンダー独自のフレームワークに支配された非常に難解・複雑なそのソースコードは、ネットに落ちている知識程度でどうこうなるものは非常に少なかったです。
それなのに会社の評価基準に「自分で調べられること」のような項目があったので評価面談時にキレていた記憶があります。今や懐かしい記憶です。良い思い出とは言えませんが。
また、転職活動時や職務経歴書を書くに即して**「フレームワークの利用経験」**を問われるというのがひどいコンプレックスでした。ググれば情報があるフレームワーク利用よりはるかに複雑な(と少なくとも当時の自分は思っていた)自分の経験は、転職市場での価値は低い。とにかく悔しかった。
……かなり話が逸れましたが、最後に語りたいテーマに繋がってくるので今しばらくお付き合い頂けますと幸いです。
話を戻します。
そんな「ググる」があまり意味をなさない環境の中、ただ一つ、ずっと変わらずそこにあって、ずっと変わらず重要な「技術」がありました。
お察しいただいている通り、それが……SQLでした。
それは私にとって圧倒的に信頼できる「力」でした。
複雑化するSQL、肥大する全能感
製造業保守業務の中には、「部品表リストの出力」というものもありました。
数百行のSQLを書き、出力し、加工し、提出する。
現在私は個人事業主としてデータ分析や機械学習案件のコンサルもスポットで進めているのですが、その基盤の、さらに基盤にあたる技術は確実にここで養われました。
SQLは、本当に凄まじいツールです。
SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)
あたりを是非お読みいただきたいのですが、卓越したSQLは芸術の域に達します。マジで。
例えばこちらの記事にあるように、SQLの GROUP BY 句には列名だけでなく式も記述することができる ことを初めて知ったときの衝撃は凄まじいものでした。
そうして私はどんどんSQLの技術を上げていき、数百行に至るSQLに立ち向かうことも多々ありました。
また、同時に……
私にとって。
SQLは。
必然的に。
ソースコードに直接記載されるものでもありました。
そのシステムのソースコードが、ずっと、そうであったように。
実際にどのようなコードだったかと言うと、こちらの記事にあるようにPreparedStatementを利用したソースコードでした。
Connection con = DriverManager(hoge,hoge,hoge);
String sql = "select name from hogeData where id = ?";
Statement st = con.preparedStatement(sql);
st.setInt(1, 1);
ResultSet rs = st.executeQuery();
クエリの結果……レコードの集合体は変数:rsに格納され、複数行取得されることが前提なので適宜ループで取得します。
この方法自体は未だに企業向けのJava研修等で教えられるものであり、それ自体は決して悪いものではありません。
Connectionの取得は共通化して隠蔽した上で、この方法とほぼ同様にResultSetに対する直接的なアプローチでデータをやり取りしているソースコードは、この世界に大量にあることと思います。
繰り返しますが、決してそれ自体が悪いということはありません。
しかし。
この方法にはいろいろな「時限爆弾」が仕掛けられているのです。
特にJavaやOracleを導入するような大規模案件で爆発しがちな、強烈な爆弾です。
そのことを記憶に留めて頂いて、先に2つ目のキーワードについてご説明させてください。
Active Recordとの出会い
さて、少し時系列が飛びます。
その現場を離れた後、会社でも悪目立ちしていた私は辞意表明とほぼ同タイミングでベンチャー部門に勧誘され、結果的に退職・独立を延期することになり、営業支援やらコンサルやらAWS運用やらLaravel炎上案件やらを経験し、その後独立。
Vue.js × Laravelというカリカリの自社開発BtoC、Web系案件を経験します。
ここで Active Record という私の価値観を壊す存在と出会うわけです。
この案件に携わる前からLaravelに二度ほど触れたことはありましたが、片方はAWS APIアクセスをメインとした少し特殊なソースコード、もう片方は……
**もうなんというかMVCではなくVCになっており、CにSQLがベタ書きされていたような案件でした。**アレはこの世の地獄でした。
そのため、Active Recordという考え方に出会うまで、少しタイムラグがあったわけです。
話を戻します。
Active Record……それは端的に言えば、「DBのレコード1行を1オブジェクトとして扱おう」という発想に基づく技術です。
端的に言って、
- レコードを完全にプログラム上の「オブジェクト」として扱える
- DBと密結合しやすいロジックの強力な**「隠蔽」**
という2つのメリットがあります。
具体例を踏まえて説明しましょう。
LaravelのModel関連は非常に機能が多いのですが、分量の都合上かなり端折ってお伝えすることになってしまうことをご容赦ください。
例えば、Webアプリに登録されたユーザーを管理する「users」テーブルがあるとします。
「あるとします」……とか言いましたが、Laravelではこのusersテーブルはコマンド一つでログイン処理も含めて自動生成されたりします。
詳細に関してはLaravel auth
あたりのキーワードで検索してみてください。
さて、そんなこんなでユーザー情報を管理するusers
テーブルがこの世に生まれ落ちます。
Userのデータがたくさん入っているので、複数形でusers
テーブルです。
そして、その中の1行が1クラスによって表現されるのがLaravelにおけるModel1の特徴です。
つまりクラス名は単数形となり、User.php
となります。
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'firstname', 'lastname', 'email', 'password',
];
// Mutator
public function setPasswordAttribute($value)
{
$this->attributes['password'] = Hash::make($value);
}
// Accessor
public function getFullNameAttribute($value)
{
return $this->firstname . ' ' . $this->lastname
}
// Relation
public function skills()
{
return $this->hasMany('App\Models\UserSkill');
}
// Other methods
/**
* ユーザーに対するメール送信を実行する
*
* @param $request
* @return void
*/
public function sendEmail()
{
// 何らかの処理
}
......
}
何のことやら意味がわからないかもしれませんが、説明いたしますのでご安心ください。
例えば、usersテーブルに登録された全ユーザーを取得するためには以下のようなコードを書きます。
$users = User::all();
こうすると変数:$users
にはUserインスタンスの集合体(LaravelのCollectionインスタンス2) が格納されます。
id:1番のユーザーだけ見つけたいなら、こうです。
$id = 1;
$user = User::find($id);
引数には主キーを渡すことができます。
この場合、$user
には当然、Userインスタンスが格納されます。
さて、とにかくこれでid:1番のレコードを表す$user
が取得できました。
このオブジェクトを操作することは、すなわちこのレコードを操作することになります。
例えばこのユーザーのパスワードを強制的に変更してみましょう。
$user->password = 'newpassword';
$user->save();
はい、これで完璧にパスワードがupdateされます。
「平文じゃん!インシデントじゃん!」と思われましたか?
でも大丈夫なんです。
User.php
を確認してみましょう。
// Mutator
public function setPasswordAttribute($value)
{
$this->attributes['password'] = Hash::make($value);
}
どこからpasswordにアクセスしても、セットする際には必ず暗号化するようになっています。
こちらはパスワードの暗号化を**「一切意識する必要がありません」**。
SQLの関数で暗号化するか、プログラム側で毎回暗号化するか。
そんな面倒なことはModelへの定義一発で全て解決します。
これはオブジェクト指向で言うところの「セッタ(Setter)」の機能です。
LaravelのModelではMutatorと呼ばれています。
では逆に「ゲッタ(Getter)」はあるのでしょうか。
もちろんあります。Accessorと呼ばれています。
しかも滅茶苦茶強力です。
テーブル側にカラム:firstnameとlastnameがあるとします。
冗長なのでその場合、テーブルでfullnameは確実に持たないですよね?
でも、このように……
// Accessor
public function getFullNameAttribute($value)
{
return $this->firstname . ' ' . $this->lastname
}
$user->firstname;
$user->lastname;
$user->fullname;
DBのカラムであるか、自前で作ったAccessorを経由して取得した属性か。
これら全て一切意識する必要がなくアクセスできるようになります。
さて、ここまでSELECTやUPDATEがありましたが、一切SQLが登場していません。
では、joinのような複雑な構文はどうでしょう?
これもまた非常に優れています。
例えば、Webサイトが転職サイトだったとして、ユーザーのスキルを記録する別テーブルがあるとしましょう。
user_skills
3というテーブルです。
user_skills
はカラム:user_idを持ち、 usersの主キー:idと結合し、ユーザー1:スキルNの関係であるとします。
// Relation
public function skills()
{
return $this->hasMany('App\Models\UserSkill');
}
このように定義すれば(厳密にはUserSkill側のモデルも定義する必要があるのですが)
$skills = $user->skills;
これでjoin等を一切書くことなく、UserSkillsインスタンスの集合体(Collection)が取得できました。
さて。
ここまで頑なにSQLを登場させないことのメリットは何なのでしょうか?
先述したとおり、ひとつ目は
- レコードを完全にプログラム上の「オブジェクト」として扱える
です。
オブジェクト指向の
「抽象化されたオブジェクトに対して、命令して、振る舞わせる」
という考え方を知っていれば、その考え方に極めて忠実にコーディングが進められます。
例えば、このユーザーに対してなんらかのメールを送りたい、という処理も非常に簡単です。
// Other methods
/**
* ユーザーに対するメール送信を実行する
*
* @return void
*/
public function sendEmail()
{
// 何らかの処理
}
......
}
$user->sendEmail();
これならControllerに書くことになってもスッキリです4。
そしてもう一つの大きなメリット、それが……
- DBと密結合しやすいロジックの強力な**「隠蔽」**
です。
「DBと密結合しやすいロジック」の代表例がまさにSQLです。
これに関しては保留していた「SQLベタ書きコードに仕組まれた時限爆弾」について語る方がわかりやすいでしょう。
「SQLベタ書きコード」に仕組まれた時限爆弾
さて、もうとっくにお忘れと思いますが、こちらのコード。
Connection con = DriverManager(hoge,hoge,hoge);
String sql = "select name from hogeData where id = ?";
Statement st = con.preparedStatement(sql);
st.setInt(1, 1);
ResultSet rs = st.executeQuery();
こちらのコードが、大規模なアプリケーションに組み込まれることを考えてみましょう。
例えば。例えばです。ホントに例えば。
**「超複雑なデータのリストを表示して欲しい」**という要望があったとします。
超複雑なデータは20種類のテーブルを極めて複雑な条件で結合することによって得られます。
(そんな要件あらんやろと思ったあなたは幸せです)
String sql = "
select
TABLEA.A,
TABLEB.B,
TABLEC.C,
......
from
・・・・・・・
以下500行以上
";
Statement st = con.preparedStatement(sql);
ResultSet rs = st.executeQuery();
はい。
さて。このコード、どこに実装します?
Utility的な共通処理を作って、そこにメソッドを定義する。一つの手段ですね。
(Java書くことから2年以上離れているので、もしコード部分に誤りありましたら遠慮なくご指摘ください)
public class Utility {
public ReultSet executeSuperQuery(Connection con){
String sql = "
select
TABLEA.A,
TABLEB.B,
TABLEC.C,
......
from
......
以下500行以上
";
Statement st = con.preparedStatement(sql);
return st.executeQuery();
}
}
ただ、もしこの方法を用いた場合、Utilityクラスは
executeSuperQuery2,
executeSuperQuery3,
executeSuperQuery4,
executeSuperQuery5,
……
のように容赦なくメソッドが定義されていき、最終的な記述量は人間の限界を超えるでしょう。
ということで、適当なクラスに分離しましょうという発想になります。
public class SuperSQLExecuter {
public ReultSet executeSuperQuery(Connection con){
String sql = "
select
TABLEA.A,
TABLEB.B,
TABLEC.C,
......
from
......
以下500行以上
";
Statement st = con.preparedStatement(sql);
return st.executeQuery();
}
}
さて。
この "SuperSQLExecuter" とは、何者なのでしょうか?
オブジェクト指向自体議論が分かれる概念ではありますが、「あらゆる概念・役割・機能を抽象化して、クラス単位で表現しよう」という発想にそれほど大きな差異はないはずです。
しかし、この「SuperSQLExecuter」 のインスタンス……つまり「実態」は、根本的に「何」を表現しているのでしょう?
Active Record、あるいはModel、あるいはCollectionが「何」を表しているか。
それは非常に明瞭で、業務上の1オブジェクトを具現化したもの、すなわちレコード or レコードの集合体です。
一方、"「SuperSQLExecuter」 のインスタンス"はどうも判然としません。
「SQLを実行したい」という手続き的発想があって、それを強引にクラスとしてまとめたような……
そう、結局 "Utility" 的な発想から逃れられないわけです。
それ自体は悪いことではありません。
世の中に数多く存在するライブラリはほぼそのような発想で成り立っています。
問題は、SuperSQLExecuter
はゴリッゴリにビジネスロジックのメインを担っているということです。
ゴリッゴリです。
例えば「見る人に応じてSQLの結合条件を変更したい」という要件があったとしましょう。
SuperSQLExecuter
に定義されたSQLはif文での分岐が加わり、この時点で難読化します。
別SQLに分離することもできますが、それはそれでクラスの分量が純粋に極めて大きくなり、また人間の限界を超えていきます。
あるいはSQLだけを返すようなメソッドに分離するか。
……それはそれで正しいのですが、また新たな問題を生みます。
それは、このメソッドの戻り値が結局「どのような情報を持っているか」を知りたいとき、どうすればよいか。
結論として**「中に埋め込まれているSQLを読み込むしかない」**という頭を抱えたくなる状況が生まれます。
でも中でprivateメソッドが何度も呼ばれた結果発生したSQLなど、どのように辿れば良いと言うのでしょうか。
追々、外部ドキュメントに頼らざるを得ないような状況が生まれがちです。
LaravelのModelであれば、テーブル定義とAccessorをざっと見れば確実にわかります。
この混沌とした状況は、結局のところ**「SQLというフリースタイルな構文を、フリースタイルなメソッドがラッピングしている」**ということに起因します
SQLが言葉通り「隠蔽されない = 常に更新されるファイルに定義され、自由自在に改修されてしまう状態に置かれている = そもそもprivate変数云々の議論をする以前にその都度書いて使うことが前提とされている」場合、ここで示してきた通り収拾がつかなくなります。悪い意味で何でもできちゃいますから。
SQLの実行結果をオブジェクト単位でもし扱えるなら、そしてオブジェクト内のデータに対するアクセス方法が限定されているなら、ここまで混沌とした状況は生まれにくいはずです。
Active RecordはSQLを高度に「隠蔽」し、個別のメソッドに細分化することで、「収拾がつかなくなること」をなるべく防いでくれています。
ただ、現実として……一度難解なSQLをプログラム側に定義してしまった現場では、無情にもSQLそのものに対する改修は積み重ねられていく。
さて。
そんなこんなで、SuperSQLExecuter
が極めて巨大になったり、複雑になった頃合いに、顧客から一言、こう言われるわけです。
このリスト……更新できるようにしてくれない?
……繰り返しますが、実話じゃないですからね。念のため。
(現実はもっと複雑ですから……)
隠蔽と疎結合
結局、このような悲劇がなぜ起こるのかと言えば**「密結合だから」**という一言に尽きるでしょう。
プログラム側に、DB側の機能である「SQL」が接着剤で張り付いているかのように張り付いてしまっている。
そして分離できなくなってしまう。
Active Recordにおいては「SQL」は高度に隠蔽されています。
それによって実現されるのは**「疎結合」**であると言えます。
説明するまでもないかもしれませんが、「疎結合」はイメージとしてはマジックテープでくっついている状態、「密結合」は瞬間強力接着剤でくっついている状態です。
「疎結合」なら、テストの際などに切り分けて、別々に検証することが出来ます。
その観点から言えば、Active Recordは
- アプリケーションからみて外部の存在であるDBに関わる情報がすべてオブジェクトの内部にラップされ
- オブジェクトのメソッド、あるいはインスタンス変数へのアクセスという形で行えるようになり
- また, 十分うまく設計できているならばテスト時にはモックオブジェクトに差し替えることでDBから
完全に分離した形で単体テストもできる
という「疎結合」を導きます。
参考:Active Record Sucks! (あるいはSQLおじさんの憂鬱)
……ただ、実のところ「疎結合」「密結合」という概念は簡単なようでいて、気軽に語るには極めて難しく、実際上記の参考記事も「Active RecordはDBと密結合だ」と批判的な結論を導いています。
これは「何と何が結合しているのか」「何と何を結合させるべきなのか」という観点が様々だからです。
ただ、少なくとも言えることは……
コードへのSQL直記載は**「ド密結合」**ということです。
それでも、仮にそのことを理解していたとしても、「闇落ちエンジニア」だった頃の私は……
SQLを直に書くことをやめなかったでしょう。
むしろ、Laravelに初めて触れたとき**「SQLを……書かせろ……」**と荒ぶりそうになったことを覚えています。
長々とお付き合い頂き、ありがとうございました。
ようやくこの記事は核心に入ります。
それでも自分はやり方を変えなかった
多分、「滅茶苦茶頑張れば」、最初に配属されたその現場にORMを導入することもできたのだと思います。
でも、そもそもそんな発想に至るわけがありません。
ソースコードはコメントによる履歴の追記まみれ、「なるべく革新のない」コードを求められました。
deployはかなり特殊なツールを使って、1時間以上かけて行っていました。
ファイルの差分はExcelで管理していました。
10年以上の単位でそうやって動いている現場で、ORMやら、Gitホスティングによるコードレビューやら、そんな技術を導入するのは工数的ハードル以上に、心理的ハードルから不可能と言っていいでしょう。
むしろ自分は**「それこそが普通だ」**と心から信じていましたから。
それでも自分の中にあった閉塞感と、違和感、何より漠然とした強烈な嫉妬心は無視しきれなかったわけです。
何に対する強烈な嫉妬心か?
Qiitaに投稿するようなエンジニアに対する、嫉妬心。
あるいは開発を愛せるエンジニアに対する、嫉妬心。
だから私はその現場を中から変えることではなく、独立を選びました。(結果的にベンチャー部門に移動し、1年経験を積んでから独立という流れになりましたが……)
「自分のようにどんどん若手エンジニアは独立しよう」。
それこそがこの記事の結論……
だったら、どれだけ楽だったか。
冒頭に述べた「明確な結論が自分の中にない」。
その意味について語らせてください。
Active Recordは銀の弾丸か
今、自分のスキル・知識はあの現場にいた頃と比較になりません。
では今、自分があの現場に戻ったとして、何もかも自由にできたとして。
「Active Recordを何らかの方法で導入できるか?できたとして、するか?」
無理です。
絶対に無理です。
絶対に、しません。
数十のテーブルを極めて複雑な条件で結合する。
そんな要件に対して、Active Recordは弱いです。
そもそも、OracleとJavaで構築されているようなシステムは、業務要件が極めて複雑で、それに応じた実装をするだけで必死ということも多々あります。
そんな必死で作られたシステムが、10年の時を経て、様々な「新人エンジニア」に揉まれながら、人の手の及ばない規模のシステムになっていく。
日本にはそんなシステムが数え切れないほど存在しています。
そんなシステムには、マイクロサービス的思想を含むモダンな開発技法をいくら持ち込んだところで、手も足も出ません。
ゼロベースで作り直せるなら別ですが、そんな余力がある大企業はまずないでしょう。
結局、「頑なに自説を曲げない」エンジニアライフを選ばざるを得なかったあの頃の自分は、あの頃の環境の中で、「正しかった」わけです。
便宜上「SQLを直書きするようなコードよりActive Recordの方が正しい」というような流れで、この記事は書きました。
でも、本当は違うのです。
「そもそもActive Recordは様々な欠点を抱えている」。
それも散々議論されていることですが、そちらもここで語りたかったことではない。
この記事で最も言いたかったこと。それは。
**「Active Recordの利用を議論できるような現場が、すでに圧倒的に恵まれている」**なのです。
ならどうするのか。
「そのような現場に行くために転職する」ことが正解なのか。
じゃあ、世に数多あるレガシーシステムたちはどうなる……?
もちろん自分が関わらなくても、なんだかんだ、いろんな人が携わって守っていきます。
社会は、技術会社はそうやって回ります。
そして何より、あの現場は自分を育ててくれた親のようなものです。それを否定することは、……「そんな現場はさっさと辞めるべきだ」ということは。
自分自身の否定に他ならない。
それは自分にはできない。
でも、最終的に自分は独立し、今はAWS・Lambdaを利用したサーバレスAPI構築や、S3へのSPAのホスティング等、比較的新しい開発に携わっています。
自分は今、それなりに楽しい。
これは私の、私の人生にとっての「正解」だったというだけの話です。
一方。
時折思い出すのです。
社会人2年目、Qiitaを読みながら「なんでこの人たちはそんな開発について楽しそうに語れるんだ……?」と嫌悪感にも似た感情を抱いていたことを。
社会人4年目、別案件に移動してから、Web系経験が深く、大規模案件の経験は比較的浅い上司が「なんだかんだシステムは1ヶ月もあれば仕様は把握できる」と言ったときに、「いや、いくらなんでも舐め過ぎです!」と怒ったことを。
どんどん新しい技術を導入する先輩に、激しい抵抗を抱いていたことを。
そして2年程度でエンジニアを辞めていった人たち。
「自分には技術がない」と、そう自嘲するエンジニアたち。
「現場に文句を言うべきではなく、現場を変えていくべきだ」という、「正論」と、その正論が想定する範囲が極めて狭いことに対する、激しい怒り。やるせなさ。
「あの世界」を見たエンジニアと、見ていないエンジニアの断絶。
「自分は開発を愛している」と言えるエンジニアと、そうでないエンジニア。
それら、全部を抱えた上で。
「Active Recordを、ORMを導入すれば、どんな現場でも幸せになれるよ」。
そんなことが言えたら、どれだけ楽だったか。
でも、口が裂けてもそんなことは言えない。
自分はあの世界に対して、何もできなかった。
それは仕方がないことだった。
その中で確実に言えることは……
エンジニア間の断絶、格差。
それらは、想像を遥かに絶する残酷さを帯びています。
それを解決する手段を、思想を……自分はまだ、持ち合わせていません。
それが冒頭に「明確な結論が自分の中にない」と述べた理由です。
終わりに
今、tecotecさんと楽しくお仕事させていただいてます!
という極めてAdvent Calender向きの内容を書かせて頂きました!
いやー、tecotecさん!ありがとうございます!!5
では、またどこかでお会いしましょう。
-
DBのテーブルに対応するActive Recordの"Model"と MVCの"Model"は微妙に意味合いが違うという議論もあるのですが、ここでは省略いたします。 ↩
-
https://readouble.com/laravel/5.8/ja/collections.html 参照。厳密にはこちらのクラスを継承した
Illuminate\Database\Eloquent\Collection
になります。 ↩ -
Laravelではテーブル名や関連テーブル名、主キー・外部キーの命名にも厳密なルールがありますが、詳細は省略させていただきます。ご容赦を。 ↩
-
RepositoryPattern適用してさらにアクセスを抽象化すべきでは?みたいな話も省略させてください。あと、もっと細かい話をするとメソッドは主語→動詞のネーミングにしたいので、目的語であるuserが先に来るのは少し違和感がありますが、そこもご容赦を…… ↩
-
……色々すみません……これからもよろしくお願いいたします…… ↩