GMOペパボに入って3年、やってきたことを振り返った
2018年7月1日でGMOペパボに入社してから3年がたった。自分自身1つの会社で3年を超えて働いたことがなかったので、もう3年も経つのかという気持ちが大きい。3年間あっという間のような長かったような不思議な感覚がある。いい機会なので何をやってきたか振り返ってみた。
ちなみに入社前まではWEBの会社では働いたことはなく、PHPもRubyも仕事では書いたこともない、ましてや技術イベントで登壇などしたこともない、ほぼゼロからのスタートのようなものだった。なぜ入社できたのかはわからないが、とにかくがむしゃらに自分で書いたコードをGitHubにおいたり、ある程度人に見せれるものを作ってHerokuで動かしたりてアピールしていたように思う。
サービス運用
入社してしばらくはがむしゃらに日々の運用案件をこなしていた。サービスの全体像を掴んだり、PHPとRubyで書かれたアプリケーションのソースを読みながら、バグの原因を掴んで治したりして、まずはチームに不可欠な人になろうと必死にやっていた。
先にも書いたように、PHPもRubyも入社まで書いたことがなかったので、入社後勉強の時間を設けてくれてパーフェクトPHPやパーフェクトRubyを読みつつ勉強させてくれてすごくありがたかった。
2週間PHPの勉強期間をもらえました。それからは業務+Railsの勉強期間。ありがたすぎます・・
— Hiromi Kimura (@kimromi) July 2, 2015
コードのリーディング力(というか勘所を掴む能力的なもの)は低い方ではないので、「すべての答えはソースコードにある」という気持ちで読みまくっていた。RubyGemsやComposerのようなエコシステムにも触れ、自分でライブラリを書いてみたりした。
コードレビューや先輩のコードを見たり、OSSのソースコードを読んだりして設計を学んだ。仕事では、運用を省力化してそれ以外の新規開発に充てる時間を増やすために自動化をしたりBotをガリガリ書いたりした。運用案件のissueがopenされてからcloseされるまでの時間を自動で計測してくれるBotを作ってその時間をいかに短くしていくか取り組んだりもした。
開発環境についてはVagrantで立ち上げたVMにChefをあてて作るスタイルがすでにできていた(現在も利用している)。当時は技術的にもよくわかっていなかったので、vagrant upで開発環境出来て便利だな思っていた。
社内の読書会をきっかけにGolangも少しだけ書いてみたがいまいち身が入らず挫折した。
インフラ
そのうち、アプリケーションのサーバーが物理サーバーから自社のプライベートクラウド(OpenStackで組まれている)に移設されていくようになり、自分も進んで取り組み始めた。インフラの構築や監視、ネットワーク周り、ChefやTerraformでコード管理していく技術を学んだ。Dockerも本を読むなどして勉強し、世の中にはこんな便利なものがあるのかと感じた。
一般的なWEBアプリケーションの構成も知り、keepalivedをつかったロードバランサや、Nginxでリバースプロキシを作って各アプリケーションサーバーにリクエストを振り分けたりできるようにもなった。ドメインのサービスに携わっているのもあって、DNSの知識も得ることができた(DNSサーバーの運用はしてないけど)。一通りアプリケーションをサーバーから立ち上げてドメインの設定して、SSL証明書とってNginxに設定してhttpsでサービス公開して監視、運用、移設もできるようになった。
この頃、それまで既存のPHPのレガシーアプリにツラミを感じていたのだが、10年以上問題なく動いててサービスを支えてくれていることに感謝の気持ちを持てるようになってきた。
あともうひとつ、この頃にシニアエンジニアの昇格試験を受けた。udzuraさんにメンターになっていただき、それまでの自分のやってきたことを振り返りつつアドバイスいただいた。結果は見事に撃沈だったが本当に勉強になったし、自分には何が足りないのかがわかった。
チームリーダー
2017年9月、長年チームに在籍してサービスをまるっと知り尽くしている先輩が退社されたのと同時に、ムームードメインチームのリーダーになった。先輩が抜けた穴はかなり大きく大変だったが、コード化や自動化できていなかったところを皆で協力して少しずつコードにしていくことで省力化することができてきている。
リーダーとしては俺について来いタイプでは無いので、どちらかというと見守りつつ下から支えるタイプであると思う(サーバント型?)。自分の意見を押し通すではなく、自分の意見は持ちつつも皆できちんと議論した上で噛み砕きつつ、スッと説得力のあるアイデアを出してまとめるみたいなのを心掛けている。今までリーダーの経験はなくて、最初はどうやったらチームがうまく纏まって機能するか、また雰囲気のいいチームになってメンバーたちが出社が苦にならないかを考えていた。うまくいったかどうかはわからないが、問題はない(と思っている)。リーダーになってから3ヶ月くらい、技術的なアプローチが全然できてなかったので、2018年からは少し肩肘の力を抜いて、本来のエンジニアとしての技術を磨いてアウトプットする作業もやっている。
学生のインターンシップのメンターも初めて経験した。また中途採用の面接官もするようになった。今現在高いスキルはは持ち合わせていなくても継続して学び続けることのできる人かどうか、ペパボの風土に合うかどうかなど、人を見る目が少しついたように思う。
フロントエンド
最初にフロントエンドの技術に触れたのは社内サービスの顧客管理システムの1機能の中にVue.jsを採用したところから。それまではPHP、Ruby on Rails内で愚直にjQuery、CoffeeScriptで書いていた。その後ショッピングカートを実装したときにユーザーが目に触れるところでVue.jsを採用してリリースした(2017年7月)。
そのあたりは本番環境にVue.jsを使った話とかVue.js on RailsとかムームードメインのJavaScript環境を整えた話で外部、社内イベントで登壇した。最近のプロジェクトではVue.jsに加えてNuxt.js、TypeScriptを採用してサービスのリニューアルの実装をしている(リニューアルについてはインフォメーションで少しだけ触れている)。
- 本番環境にVue.jsを使った話 / Use Vue.js at production - Speaker Deck
- Vue.js on Rails / vue-js-on-rails - Speaker Deck
- ムームードメインのJavaScript環境を整えた話 / prepare muumuu-domain's javascript - Speaker Deck
最近触れるようになってきたフロントエンドの技術だが、なぜか面白みと楽しさを感じる。ただうまく言語化できていないので考えていきたい。
ペパボに入ってサーバーサイド、インフラ、フロントエンドとやっているけど、フロントエンドが一番おもしろいし楽しいと感じる。なんでだろう。シンプルに自分が見えるものが動くというのが好きなのかな。
— Hiromi Kimura (@kimromi) May 24, 2018
見えてきた自分の長所と短所
3年間とにかくがむしゃらに走ってきたように思う。その中でもとにかく手を動かすというのを心がけている。最初はあまり大きくない問題に対して大きなコスト(時間)をかけていた。シニアエンジニアの立候補をきっかけに、問題の大きさをきちんと捉え大きな問題をなるべく小さなコストかつ未来につながる方法で対応していくということをできるようになってきたように感じる。技術的に何かが突出しているエンジニアではない(自分もそれが向いているとあまり思っていない)ので、バランスをとりつつ引き出しをたくさん持った中から手数を多く出していくようにこれからもやっていく。
あとエンジニアにはあまり関係ないが、私は周りの人のことをものすごく観察していて、ちょっと見ただけでその人が何を考えてどんな気持ちなのかなんとなく分かるようなスキルが人よりもありそうということがひょんなことからわかった。自分ではそれが普通だと思っていたのだけど、意外に人はそこまで敏感ではないということに気づいた。チームのリーダーもそのあたりを見込まれて選ばれたのかもしれない。その点も強みとしていけたらよいと思う。
短所として、中長期的な観点で考えて道筋を立てていくことが得意ではない。個人的なことでも、仕事においてもそうである。入社する前まではとにかくWEBの会社に入るというのを目標にしてきてそれに向かっていたが、それが実現してからの3年間特に目標もなく進んでいたように思う。立場的にも中長期的な観点でかつ広い視点で見なければならないようになりつつあるので大きな課題である。
所感
3年間やってきて、本当にやっと最近になって自分のやっていることが売上に直結したりチームにものすごく役に立つものであったりすることが増えてきたように思う。エンジニアは様々な問題を解決する職種であるというところから、問題の重要度&緊急度に対してのコストのかけ方を肌感で掴んできたからだと考えている。まだまだやることは膨大にあるし一つ一つクリアしていきながら自分自身をレベルアップさせていきたい。またこの先エンジニアとしてどうなりたいのか、そもそもなぜエンジニアをやっているのか、エンジニアとしてこれからもやっていきたいのかというところからもう一度見直しながら残り半年過ごしていきたい。
【Rails】request.remote_ipの値がプライベートネットワークの通信で120.0.0.1になる
https://qiita.com/yasu/items/da7ebdb01cb3209583df こちら参考にした。
環境
Rails 4.2.10
結論
config/application.rb
に以下を追加すればOK
require 'ipaddr' module Kimromi class Application < Rails::Application # これを追加 config.action_dispatch.trusted_proxies = %w(127.0.0.1 ::1).map { |proxy| IPAddr.new(proxy) } end end
原因
request.remote_ipにはTRUSTED_PROXIESというプライベートネットワークのIPアドレス帯を除外する機能がある。
TRUSTED_PROXIES = [ "127.0.0.1", # localhost IPv4 "::1", # localhost IPv6 "fc00::/7", # private IPv6 range fc00::/7 "10.0.0.0/8", # private IPv4 range 10.x.x.x "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255 "192.168.0.0/16", # private IPv4 range 192.168.x.x ].map { |proxy| IPAddr.new(proxy) }
今回の自分の環境の通信は10.*.*.*
だったので、この"10.0.0.0/8"
に掛かり除外され、request.remote_ipとして127.0.0.1
だけが残る形となっていた。
なお、TRUSTED_PROXIESの設定はconfig.action_dispatch.trusted_proxies
で上書きできる。プライベートネットワーク帯の除外は自環境では特に必要なかったので、ローカル通信の127.0.0.1
と::1
だけ残した形で以下のように設定してOKだった。
config.action_dispatch.trusted_proxies = %w(127.0.0.1 ::1).map { |proxy| IPAddr.new(proxy) }
しかしRailsのログが127.0.0.1のまま
Started GET "/" for 127.0.0.1 at 2018-04-23 20:01:42 +0900
こんな感じでRailsのログには127.0.0.1のまま出力されていた。ただこれは既知の問題だったようで、本家のRailsの方で修正されていた。
ソースコードを追ってみるとrails v5.0.7には当たっていなく、v5.1.0には当たっていたのでv5.1.0以上にあげる治っていそう。 https://github.com/rails/rails/blob/v5.1.0/railties/lib/rails/application/default_middleware_stack.rb
早くRails5にしたい。
php-activerecordのすすめ
GMOペパボ Advent Calendar 2017の初日のエントリーです!
私が所属しているムームードメインではPHP環境とRuby on Railsが並行で運用されています。その中でPHP環境はが統一されたWebフレームワークを使用しておらず、データベース接続のクエリビルダーも自作されていて、書き方が冗長だったり、気をつけないとSQLインジェクションの危険がでるようなものであったので、何かいいクエリビルダーはないかと探していました。他のチームではpixieを導入していたりしていましたが、なにかもっと書いてて楽しくなるようなものはないか、RailsもやっているしActiveRecordライクに書けるものがないか探りました。
そこで見つけたのがphp-activerecordです。使っていてよくできてるなと感じましたので今回は使い方を紹介しようと思います。
インストール
$ composer require php-activerecord/php-activerecord
PHP 5.3以上で、PDOでクエリ実行はPrepared Statementを使用しています。
接続設定
<?php ActiveRecord\Config::initialize(function($cfg) { $cfg->set_model_directory('/path/to/your/model_directory'); $cfg->set_connections( [ 'development' => 'mysql://username:password@localhost/development_database_name', 'test' => 'mysql://username:password@localhost/test_database_name', 'production' => 'mysql://username:password@localhost/production_database_name' ] ); $cfg->set_default_connection('production'); });
set_model_directory()
でmodelのファイル群をおいているディレクトリを指定すると自動でrequire_onceしてくれます(source)set_connections()
で環境ごとの接続設定をURLのようなフォーマットで指定します。MySQL、SQLite、PostgreSQL、Oracleデータベースに対応しています。(source)set_default_connection()
で環境を指定します。指定しない場合はdevelopmentです。(source)
Modelの作成
下のようなpeopleテーブルがあったとします。
create table people( id integer not null primary key autoincrement, name text, created_at datetime, updated_at datetime );
こんなmodelをかけば準備完了です。model名は単数形(Person)で、テーブル名は複数形(people)になっているところはRailsのActiveRecordと同じで、そのルールに乗れない場合はstatic $table_name = 't_people';
のようにテーブル名をmodelの中でかけばOKです。
<?php class Person extends ActiveRecord\Model { }
ちなみにphp-activerecordで複数形<->単数形の変換はどうやってるのか調べたところ、ActiveSupportのinflectionsのアルゴリズムを参考に、ライブラリ内で実装を書いていました。(source)
CRUD
Create
<?php Person::create(['name' => 'kimromi1']); $p = new Person(); $p->name = 'kimromi2'; $p->save();
created_at
、update_at
カラムがあれば自動で日時を挿入します。(source)
Read
<?php Person::all(); Person::all(['conditions' => ['name like ?', 'kimromi%']]); // where Person::count(); Person::first(); Person::last(); Person::find(1); // find by id Person::find_by_name('kimromi2'); // find_by_[カラム名]のメソッドで検索できる Person::find_by_sql('select * from people'); // raw SQL Person::all(['limit' => 10, 'offset' => 5)]) Person::all(['order' => 'name desc']) Person::all(['group' => 'name', 'having' => 'length(name) > 7'])
Update
<?php $p = Person::first(); $p->name = 'kimromi3'; $p->save(); $p->update_attributes([ 'name' => 'kimromi4' ]);
update_at
カラムも更新します。
Delete
<?php $p = Person::first(); $p->delete(); // 1件 Person::delete_all(); // 全件 Person::delete_all(['conditions' => ['name = ?', 'kimromi4']]); // 条件指定
見覚えのある感じですね。
Query Log
log()
という名前のメソッドを持っているクラスをセットしておくとCRUDのたびにクエリログを吐いてくれます。
<?php class Logger { public function log($a) { var_dump($a); } }
<?php ActiveRecord\Config::initialize(function($cfg) { $cfg->set_model_directory('/path/to/your/model_directory'); $cfg->set_connections(['development' => 'mysql://username:password@localhost/development_database_name']); $cfg->set_logging(true); $cfg->set_logger(new Logger()); });
Associations
Railsと同じhas_many
、has_one
、belongs_to
の関連をもつ機能があります。
<?php class Person extends ActiveRecord\Model { static $has_many = [ ['domains'], ]; } class Domain extends ActiveRecord\Model { static $belongs_to = [ ['person'], ]; }
<?php $p = Person::first(); $p->domains; // DomainインスタンスのArrayで返る $d = $p->create_domain(['sld' => 'kimromi', 'tld' => 'com']); $d->person;
Validations
modelのバリデーションもあります。
<?php class Person extends ActiveRecord\Model { static $validates_presence_of = [ ['name'] ]; }
validates_presence_of
: null or 空文字以外validates_size_of / validates_length_of
: 長さvalidates_inclusion_of / validates_exclusion_of
: 指定した配列に含まれるか/含まれないかvalidates_format_of
: 指定した正規表現に当てはまるか(preg_matchで検証)validates_numericality_of
: 数値かどうかvalidates_uniqueness_of
: 一意であるかどうかvalidate()
: カスタムメソッドで検証
Callbacks
before_
、after_
のcallbackも実装されています。
before_save
: called before a model is savedbefore_create
: called before a NEW model is to be inserted into the databasebefore_update
: called before an existing model has been savedbefore_validation
: called before running validatorsbefore_validation_on_create
: called before validation on a NEW model being insertedbefore_validation_on_update
: same as above except for an existing model being savedbefore_destroy
: called before a model has been deletedafter_save
: called after a model is savedafter_create
: called after a NEW model has been inserted into the databaseafter_update
: called after an existing model has been savedafter_validation
: called after running validatorsafter_validation_on_create
: called after validation on a NEW model being insertedafter_validation_on_update
: same as above except for an existing model being savedafter_destroy
: called after a model has been deleted
その他
Cache
クエリ実行結果を一定時間(デフォルトは30sec)メモリにキャッシュすることができ、高速化を図れます。キャッシュのキーは[テーブル名]-[主キーの値]
になっており、レコードごとにキャッシュしているようでした。(source)
<?php ActiveRecord\Config::initialize(function($cfg) { $cfg->set_model_directory('/path/to/your/model_directory'); $cfg->set_connections(['development' => 'mysql://username:password@localhost/development_database_name']); // Memcacheモジュールのみ対応... $cfg->set_cache("memcache://localhost:11211", ["expire" => 60]); }); class Person extends ActiveRecord\Model { // キャッシュするmodel内でフラグをON static $cache = true; }
Transaction
Model::transaction()
に無名関数を渡せば、関数内の処理が始まるときにtransactionを張り、処理が正常に終了するとcommitします。処理の途中でExceptionがスローされるか、戻り値にfalseが返るとrollbackされます。(source)
<?php Person::transaction(function() { $p = Person::create(['name' => 'kimromi']); $p->create_domain(['sld' => 'kimromi', 'tld' => 'com']); // throw new Exception("rollback!"); # rollback // return false; # rollback });
Aliased attributes
カラム名の別名をつけるalias_attributeも用意されています。(これが地味に一番助かる...)
<?php class Person extends ActiveRecord\Model { // nameカラムをnick_nameで扱えるようになる static $alias_attribute = [ 'nick_name' => 'name', ] }
Serialization
<?php $p = Person::create(['name' => 'kimromi']); $p->to_json(); # => {"id":"1","name":"kimromi","created_at":"2017-11-30T17:22:01+0000","updated_at":"2017-11-30T17:22:01+0000"} $p->to_array(); # => # { # ["id"]=> # string(1) "1" # ["name"]=> # string(8) "kimromi" # ["created_at"]=> # string(24) "2017-11-30T17:23:08+0000" # ["updated_at"]=> # string(24) "2017-11-30T17:23:08+0000" # } $p->to_csv(); # => 1,kimromi,2017-11-30T17:24:11+0000,2017-11-30T17:24:11+0000 $p->to_xml(); # => # <?xml version="1.0" encoding="UTF-8"?> # <person> # <id>1</id> # <name>kimromi</name> # <created_at>2017-11-30T17:24:37+0000</created_at> # <updated_at>2017-11-30T17:24:37+0000</updated_at> # </person>
ActiveRecordに比べてちょっと不便なところ
RailsのActiveRecordに比べて当然機能は落ちますが、その中でもちょっと不便に感じているところです。
has_many
で指定したテーブルを取得したときの値がArray型なため、2階層以上の関連テーブルを検索するのに苦労する(relationを使わずjoinでとらないといけない)- enumがないため、連番のカラムが管理しにくい
- CacheがMemcacheモジュールしか対応してなく、Memcachedモジュールに対応していない
- 開発があまり活発でなく枯れ気味
最近は目立った新規機能もあまりないため、Forkして独自で機能あげていこうかなと考えています。
まとめ
今回はphp-activerecordの使い方を紹介しました。最近はPHPのWebフレームワークも増えてきたし使う機会はあまり多くなさそうですが、日本語のドキュメントが少なかったのでまとめてみました。ライブラリとして思ったより機能も多く実装されていたので内部実装を追うのも勉強になりそうです。PHP Advent Calendar 2017にも参加しているので次回第2弾としてパフォーマンスの調査や内部実装の話などしたいと思っています。
(チームの人がこのエントリを見ながら便利!とPHPをガンガン書いてくれますように😌 )
明日は@kuroyamさんです! -> GMOペパボ Advent Calendar 2017