Quantcast
Channel: メドピア開発者ブログ
Viewing all 210 articles
Browse latest View live

エンジニアブログ、始めました。

$
0
0

はじめまして、医師専用サイト「MedPeer」を運営しているメドピアのエンジニアブログ編集担当です。

このブログでは、個性派揃いのメドピアエンジニア・デザイナーが技術情報や職場について等、自由に書いていきます。

 

メドピアとは

第1回の今日は、メドピアとはどんな会社か、そしてその中で我々エンジニア・デザイナーが担う役割とは何なのか、お伝えしたいと思います。

まずメドピアとは、「Supporting Doctors, Helping Patients.」のmissionの下、「集合知によって医療分野の変革を行う」というvision実現を目指しています。具体的には、医師・医学生限定の会員制コミュニティサイトを運営し、その中で会員同士が医療情報のシェアやディスカッション等が出来るサービスを提供しています。

現在の会員数は約7.4万人(2014年12月末時点)日本の医師のおよそ4人に1人がご利用下さっています。

メドピアでのエンジニア・デザイナーとは

その中での私達の役割は、vision実現のための「エンジン」です。実際にサービスを提供するサイトを構築し、改善、運用を続けていくことで、ユーザーへより良いサービスを届け、会社のさらなる成長を押し進めます。

 

次回からは、エンジニアの日常や社内の様子、ホットな技術トピックなどをお届けしたいと思います。

よろしくお願いいたします。

 

なおメドピアでは、missionやvisionに共感して一緒にサービスを開発してくれる仲間を募集しています。

ご興味を持たれた方は是非こちらまで!


UI/UX勉強会、開催しました!

$
0
0

皆様、こんにちは。

ご無沙汰してしまいました、GWはいかがお過ごしでしたか? 連休明けに日焼けで黒くなって来た同僚が居て驚いてしまった、メドピアエンジニアの西澤です。

今日は、先月30日に弊社オフィスで行いました、UI/UX勉強会について報告しようと思います。

これからのデザインに求められるものとは?

何故この勉強会を行う事になったかと言うと、発端は弊社デザイナーの松Mでした。 IoTの本格化もあり、デザイン手法やデザイナーに求められる役割が大きく変わっていく現状に勉強意欲を刺激された松Mはあっという間に社内調整とプレゼンターへの依頼を進め、開催へと漕ぎ着けました。

これから考えるべき事は?

ここからは当日のプレゼンテーションを簡単に紹介させていただこうと思います。 プライバシー・バイ・デザインとこれからのデザイナーの役割
博士(医薬学)笹原 英司さま
特定非営利活動法人ヘルスケアクラウド研究会
一般社団法人日本クラウドセキュリティアライアンス
在日米国商工会議所 ヘルスケアIT小委員会


概要は以下の通りです。
これまで画面の中で完結していた「インターネット」という世界が、IoTによりあらゆるものの中へと広がりを見せることで、プライバシーやアクセシビリティに関してこれまで意識されることのなかった問題が生じてくる。プライバシーについては、問題についての考察、対策等が不可欠になってくると考えられ、またアクセシビリティについては、五感を介して得たインスピレーションをもとに、より直感的にわかりやすく表現、デザインし、誰にでも利用しやすい形にすることが重要になってくる。また、ユーザとのインタラクティブな関係が重要性を増し、デザインの評価にもユーザー自身に参加してもらうことが大事になってくる。 当日の資料がSlideShareで共有されているので紹介させていただきます。
「プライバシー・バイ・デザイン」とこれからのデザイナーの役割

SCQRMについて
常盤 拓司さま
合同会社アライアンス・ポート研究開発担当ディレクター
慶應義塾大学大学院政策・メディア研究科特任講師


PMBOKでは解決できなかった問題を解決する手法として提唱されたSCQRMという考え方についてお話しいただきました。

  • SCQRMとは・・・
    スクラムと読みます。システム開発手法のスクラム(Scrum)とは別の物です。
    Structure-Constructive Qualitative Research Method の頭文字を取った物で、日本語では構造構成的質的研究法と訳せます。

事例数(データ量)を前提にするのではなく、概念化・抽象化できるかに主眼を置き事例として扱うという手法はとても興味深い物です。こちらも共有いただいた資料がありますので、紹介させていただきます。
SCQRMについて

Chlorixについて
Nana Sakisakaさま

Chlorixとは、Nana Sakisakaさんご自身が開発されている、医薬品・化合物・症候・傷病の総合検索エンジンです。
Chlorix
この会のテーマの一つである「誰のためのデザインか?」に対して、「研究者のためのデザインである」という明確な答えをお持ちの上で活動されているところに、ユーザを惹きつけるUXのあり方を見たように思います。
また、官公庁のオープンデータ等をベースに作られ、医師、薬剤師が重視する薬物動態データを掲載するなど、実際の利用シーンを念頭に置いた構成となっているようです。


まとめ

プライバシーやウェブサイト、検索エンジンとデザイン等、それぞれのお立場からデザインについてお話しいただきました。
個人的な感想ですが、UI/UXと一括りで表されることの多いこの言葉、全く別物なのかな、と感じました。 UXを真剣に考え出すと、社会的・文化的背景や利用者個々人のバックグラウンド等、全てを含めて考えた上でベストな解を探すことになってしまうような。

と、少し真面目になってしまいましたが実際は和気藹々と楽しく、刺激的な勉強会でした。勉強会の風景を下に載せておきます。
f:id:MedPeer:20150522100056j:plain今後も誰かの関心事を発端にこのような勉強会も次々開催出来ればと思っております。

だんだん暑くなってきましたね、体調にはお気を付けてお過ごし下さい!

メドピア株式会社 エンジニア 西澤

Browsersyncを利用してお手軽ブラウザ確認環境をつくろう

$
0
0

皆様はじめまして。メドピアエンジニアの中村です。
好きなブラウザは Vivaldiです。

本記事ではWeb開発効率化の為のひとつの手段として、Browsersyncの利用方法と幾つかの機能をご紹介します。

Browsersyncとは

Browsersyncはファイル変更を監視し、自動でブラウザリロードを行ってくれるツールです。
Browsersync - Time-saving synchronised browser testing

同種のツールとしてLiveReloadが有名ですが、

  • ブラウザ側でExtentionや拡張が不要
  • 複数ブラウザで操作の同期もできる
  • その他の機能も豊富

などのメリットから、最近はBrowsersyncの方が人気のようです。

さっそくBrowsersyncを試してみよう

まずはBrowsersyncをインストールしてみましょう。
nodeのパッケージマネージャであるnpmコマンドからインストール可能です。

インストールコマンド

$ npm install browser-sync --save-dev# local環境にインストール
$ npm install browser-sync -g# global環境にインストール

これでインストール完了です。

ではBrowsersyncを実行してみましょう。
なお、サンプルとしてまずは以下のようなシンプルなサイト構成で解説します。

.
├── css
│   └── main.css
└── index.html

index.htmlはmain.cssを参照しているものとします。

ではコマンド実行してみます。

$ brower-sync start--server--files"**/*"[BS] Access URLs:
 -------------------------------------
       Local: http://localhost:3000
    External: http://192.168.0.57:3000-------------------------------------
          UI: http://localhost:3001
 UI External: http://192.168.0.57:3001-------------------------------------[BS] Serving files from: ./
[BS] Watching files...

コマンド実行後、上記のようなログメッセージがconsoleに流れつつ、デフォルトブラウザの新しいタブに http://localhost:3000にすごいサイトが表示されるハズです。

f:id:yuzurunakamura:20150604165654p:plain
※ ここではサンプルとして Bootstrapのexampleを利用しています。

この状態のまま、試しにcssを編集してみましょう。

body{background-color: #ccc;
}

すると自動で更新内容が反映されます。すごいですね。

f:id:yuzurunakamura:20150604165820p:plain

--files オプションにglobバターンで指定したファイル群をwatchし、ファイルに変更があれば自動でリロードしてくれます。
ここはLiveReloadと一緒ですね。

Browsersyncでできるコト

他にも様々な機能があるので幾つかご紹介します。

プロキシ実行

--server オプションの代わりに --proxy オプションを利用するとプロキシとして実行することが可能です。

$ browser-sync start--proxy"sugoi-service.jp"--files"**/*"

ローカル共有

前述の通りBrowsersync実行時に、

$ brower-sync start--server--files"**/*"[BS] Access URLs:
 -------------------------------------
       Local: http://localhost:3000
    External: http://192.168.0.57:3000-------------------------------------
          UI: http://localhost:3001
 UI External: http://192.168.0.57:3001-------------------------------------[BS] Serving files from: ./
[BS] Watching files...

のようなログメッセージが流れます。
ローカルネットワーク上ではこの http://192.168.0.57:3000へアクセスしても閲覧可能です。
キャプチャ画像をSlackでやりとりせずともちゃっちゃっと開発画面共有ができますね。

bs-configファイル(設定ファイル)による実行

毎回細かいオプションを付与して実行するのは面倒です。
bs-config.jsという設定ファイルを用意し、

$ browser-sync start--config bs-config.js

のように--configオプションでbs-config.jsファイルを指定実行すれば毎回同じ設定で起動可能です。
なお、bs-config.jsファイルは、

$ browser-sync init

で生成可能なので、こちらでデフォルト設定のbs-config.jsを生成し、お好みで設定すると便利です。

リロードのウェイト、watch対象のファイル群指定/除外、ログ出力設定など、30個以上のオプションがあります。
全オプションは Browsersync optionsを参考にしてください。

Browesersync管理画面

Browersyncには管理画面もついています。
デフォルトでは http://localhost:3001が管理画面で、ここからBrowsersyncを起動したまま幾つかのオプションを切り替え可能です。

f:id:yuzurunakamura:20150604165838p:plain

ブラウザ同期

個人的にBrowesersyncの目玉だと思っている機能です。
複数ブラウザでクリック, スクロール, フォーム操作を同期することができます。

f:id:yuzurunakamura:20150604165900p:plain

なにが嬉しいのかというと、
f:id:yuzurunakamura:20150604170029p:plain
のようにPC/スマホそれぞれのウィンドウ幅にした状態で開発すると、レスポンシブデザイン環境でもスムーズに確認可能です。

また、各種イベントが同期されるのでローカルでのクロスブラウザ確認を容易に行うことができますね。
modernIEやiOSシミュレータから確認するのにも有用です。

リモートデバッグ機能

各要素のアウトライン表示や、グリッド表示を行うことが可能です。

f:id:yuzurunakamura:20150604165919p:plainf:id:yuzurunakamura:20150604165955p:plain

ちなみにCSSアウトライン表示機能その他のフィルタリング機能は Vivaldiにはブラウザに最初から組み込まれています。今すぐインストールしましょう。

ネットワーク制限

f:id:yuzurunakamura:20150604165940p:plain
Chromeにも同様の機能がありますが、特定ブラウザでのネットワーク制限下でのテストを試したい場合には有用かもしれません。

GulpにBrowesersyncを組み込む

LESS/SassやTypeScript/CoffeeScriptコンパイルやminifyの為に既にGulp/Gruntを利用しているケースもあるかと思います。
その場合、以下のようなgulpfile.jsを記述すれば gulpコマンドで実行可能です。

var gulp        = require('gulp');
var browserSync = require('browser-sync').create();

// 静的ファイルのみ場合
gulp.task('browser-sync', function() {
    browserSync.init({
        server: {
            baseDir: "./"}});
});

// プロキシ実行の場合
gulp.task('browser-sync', function() {
    browserSync.init({
        proxy: "sugoi-service.jp"});
});

gulp.task('default', ['browser-sync']);

同様にGruntにも組み込み可能です。Browsersync + Grunt.js
また、GulpやGruntで利用できるAPIのドキュメントも公開されています。Browsersync API
gulpfile.jsに組み込んでしまえば、複数サイトを同時に立ちあげることもできますので、gulpfile.jsにすべて寄せてしまった方が便利でしょう。

まとめ

Browsersyncを利用すると、リロードの手間を省きつつブラウザ確認が容易になります。
既存のGulp/Grunt環境への組み込みもカンタンですのでぜひ試してみてください。

Laravel5.0:FormRequestを使ったValidation

$
0
0

はじめまして。 メドピアエンジニアの酒井です。

まだ使い始めてそんなに経っていませんが、Laravelの小ネタを紹介したいと思います。

Laravel5.0で導入されたFormRequestを使用して Controllerをスッキリさせる方法です。

Laravelを触ったのは5.0が始めてですが。

実際に見てみる

まず、最初に通常のValidationの書き方はこんな感じです。

<?phpuse App\Http\Requests\Request;

Class SampleController extends Controller
{publicfunction getIndex(Request $request){$validator= Validator::make($request->all(), ['name'=>'required',
            'email'=>'required|email']);
        if($validator->fails()){}}}

見ても分かるようにContollerに記述していくとForm項目が増えるにつれ、 Controllerに大量のValidation設定を記述する事になります。

次にFormRequestを使用したControllerです。

<?phpuse App\Http\Requests\SampleRequest;

Class SampleController extends Controller
{publicfunction getIndex(SampleRequest $request){}}

すっきりしましたね!

FormRequestって何?

Laravel5.0から導入されたValidation処理を含んだRequestクラスとなります。

詳しくはこちら

リクエストをdispatchした際、Controllerメソッドの依存関係を解決した後に validate()が実行されるようになっています。

つまり、FormRequestを使用するとControllerメソッドの実行前に Validationの実行が完了している事になります。

FormRequestの使い方

<?phpuse App\Http\Requests\Request;

Class SampleRequest extends Request
{//認証ユーザーが権限あるかチェックするpublicfunction authorize(){returntrue;
    }//Validationの設定publicfunction rules(){return['name'=>'required',
            'email'=>'required|email']}//エラー文言の設定publicfunction messages(){return['name.required'=>'The :attribute field is required.',
            'email.required'=>'The :attribute field is required.',
            'email.email'=>'The :attribute must be a valid email address.',
        ];
    }}

public function authorize()

権限チェックする必要ない場合もこれを指定しないとエラーとなりますので 必ず記述するようにします。

あとはviewに以下のように記述すれば、Form画面にエラーを出力できるようになります。

@if (count($errors) > 0)
    @foreach ($errors->all() as $error)
        {{ $error }}
    @endforeach
@endif

エラー時のリダイレクト先は特に指定がなければ、前画面(Form画面)になります。

頻度の高いValidationルール

rule内容
alpha英字チェック
alpha_num英数字チェック
between:min,max最小値と最大値チェック
emailメールアドレスチェック
numeric数字チェック
regex正規表現によるチェック ※1
required入力値の存在チェック
same:field指定されたフィールドとの同値チェック

詳しくはこちら

※1 regexで「|」を使用する場合はrulesの指定を配列にしないと動作しません。

<?phppublicfunction rules(){return['sort'=>['regex:/^(desc)|(asc)$/']]}}

これでしばらくハマりました。。。

まとめ

Illuminate\Foundation\Http\FormRequestを見ると リダイレクト先やエラー時の挙動を変更できるようになっているので、 Validation関連のだいたいの事はFormRequestで行えると思います。

ControllerがValidation設定ばかりになってきたら、 FormRequestの利用を検討してみるのもいいかもしれませんね。

社内LT紹介!

$
0
0

こんにちは。メドピアエンジニアの西澤です。梅雨らしい雨の日が続いていますが皆様いかがお過ごしですか?

今日はメドピアで不定期(ほぼ月1回)で実施しているLightning Talk大会を紹介します。Lightning Talkについてはこちらをご参照下さい。

ライトニングトークとは - はてなキーワード

実際は5分に制限する事もなく、ドラもドラ娘も置かずに実施しているわけですが(^^;)

弊社はオフィス内にオープンなミーティングスペース(セントラルパーク)があり、LTをやるにも適しています。(私のお気に入りのスペースの1つです。) f:id:ikuheinishizawa:20150701181504j:plain

何枚かLT中の写真も。 f:id:ikuheinishizawa:20150701181506j:plainピザをつまみながら。 f:id:ikuheinishizawa:20150701181509j:plain

これまで発表されたテーマは以下のようなものがあります。(本当はもっとあります!)機会があればこのブログでも紹介出来ればと思っています。

  • Go into Golang(Go言語入門)
  • SwiftでUIKit Dynamics
  • Research Kitを触ってみた
  • (元・某ベンチャーCTOによる)シード期の開発現場について
  • (入社直後のメンバーによる)自身のエンジニアキャリア披露

等等・・・

元々このLT大会、個々のエンジニアが持っているスキルや興味を気楽に皆で共有し、研鑽する事を目的に始まりました。 実際やってみると、興味の幅が広がったり、自分が手を付けられていなかった分野を学べたり、業務で役に立ったり、なかなか良い手応えがあります。また、オープンスペースのおかげもあり、エンジニア以外の社員も少し覗いて行ったり出来るので、交流の場にもなっている、と良いな。

今はエンジニアが技術中心の話題で発表していますがいずれ、ディレクターだったり社外の方も交えて開催出来たらと思っています。

それでは、雨にも負けず楽しく夏を迎えましょう!

メドピア株式会社 エンジニア 西澤

LaravelとLumenのパフォーマンスを比較してみた

$
0
0

こんにちは。エンジニアの尾澤です。

先日、酒井がLaravel5.0:FormRequestを使ったValidationというエントリでLaravelのTipsをご紹介しましたが、今回は、そのLaravelと同じコミュニティで開発している別の軽量フレームワークであるLumenとパフォーマンスの比較をしてみたいと思います。バージョンは5.1を使用します。

Lumenのベンチマークとしては、公式サイトで他の軽量フレームワークであるSilexとSlim 3との比較が紹介されていますが、相互にコードの移植性が高いLaravelとの比較がなかったので、調べてみました。

LaravelとLumenの機能面での比較はこちらのエントリーによくまとめられていますので、参考にしてください。

検証環境

検証には、同じくLaravelコミュニティが提供しているVagrant環境であるHomesteadを使用しました。なお、HomesteadはHHVMをサポートしていますが、今回は使用していません。

検証マシンのスペックは以下の通りです。ちなみにメドピアでは、エンジニア・デザイナの全員にMacbook Proを支給していますので、開発作業は快適です(ง ˙ω˙)ว☆

MacBook Pro (Retina, 14-inch, Mid 2014)
Processor 2.8GHz Intel Core i5
Memory 16GB 1600MHz DDR3
Storage 500GB Flash Storage

検証環境には、それぞれのフレームワークで同じ仕様のアプリケーションを実装しました。アプリケーションはシンプルに、GETリクエストに対して常に同じ内容のテキストをレスポンスとして返します。テキストはJSONフォーマットで、Homestead内のMySQLから取得したデータを使用します。LaravelとLumenでは多少書き方は変わるところもありますが、クラス構成やロジックは同じです。フレームワークの以下の機能を利用しました。

  • Routing
  • Controllers
  • Service Providers
  • Eloquent ORM

速度の比較

速度の比較にはApache Benchを使用しました。接続数100で1000リクエストを発行して、1リクエストあたりのレスポンスタイムを比較してみます。

まずはLaravelから。

user:homestead mymac$ ab -n 1000 -c 100 http://laravel.app/sample

〜中略〜

Concurrency Level:      100
Time taken for tests:   113.675 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      12632114 bytes
HTML transferred:       11679000 bytes
Requests per second:    8.80 [#/sec] (mean)
Time per request:       11367.500 [ms] (mean)
Time per request:       113.675 [ms] (mean, across all concurrent requests)
Transfer rate:          108.52 [Kbytes/sec] received

〜後略〜

Time per requestを確認すると、1リクエストあたり113.675 msかかってることが分かります。

Laravelの検証にあたってはphp artisan optimizeコマンドによるコード最適化を行っていません。最適化による速度の改善については別の機会に書きたいと思います。

次にLumen。

user:homestead mymac$ ab -n 1000 -c 100 http://lumen.app/sample

〜中略〜

Concurrency Level:      100
Time taken for tests:   30.072 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      11840000 bytes
HTML transferred:       11679000 bytes
Requests per second:    33.25 [#/sec] (mean)
Time per request:       3007.196 [ms] (mean)
Time per request:       30.072 [ms] (mean, across all concurrent requests)
Transfer rate:          384.49 [Kbytes/sec] received

〜後略〜

Lumenでは、1リクエストあたり30.072 msでした。Laravelの113.675 msと比較すると3.78倍も速いという結果になりました!こんなに差が出ると思わなかったのでビックリ。

負荷の比較

それでは、サーバの負荷についてはどうでしょう?いくら速くてもリソース消費が著しければ意味がないですからね。Apache Benchを実行している状態で、vagrantインスタンスの負荷をvmstatコマンドで記録し、gnuplotでグラフ化してみました。

Laravelの負荷f:id:o205:20150708185806p:plain

Lumenの負荷f:id:o205:20150708185818p:plain

CPU負荷のグラフを比較すると、Lumenの負荷の方が軽いことが分かります。vmstatの実測値からアイドリング率を平均すると、Laravelが9.0%、Lumenが31.4%でしたので、LumenのCPU負荷はLaravelの75.4%ということになります。だいぶCPUに優しいですね!

メモリ負荷については若干Lumenの方が少ないように見えますが、空きメモリ量の実測値を平均すると、Laravelが879MB、Lumenが897MBで、ほとんど変わらないですね。誤差の範囲といってもよさそうです。フレームワーク自体の消費メモリに優劣ありませんでした。

サイズの比較

Laravelのサイズ

vagrant@homestead:~$ du -sk laravel/vendor/laravel/framework/
3416    laravel/vendor/laravel/framework/

Lumenのサイズ

vagrant@homestead:~$ du -sk lumen/vendor/lumen/vendor/illuminate/ lumen/vendor/laravel/lumen-framework/
2588    illuminate
292     laravel/lumen-framework/

フレームワークそのもののサイズはどうでしょう?composerで取得したコアライブラリの容量を比較してみたところ、Laravelが3416 KB、Lumenが292 KB2880KBで、LumenのサイズはLaravelの8.5%84.3%という結果になりました。かなり減量されていますね!自分も、せめて80%くらいにまでダイエットしたいところです…。軽量というには減量具合が中途半端ですね。自分の場合、それくらいダイエットできればBMI値が人並みになるんですが…。

Lumenのサイズに依存パッケージの分が含まれていないというご指摘をいただきました。illuminateパッケージのサイズを加味して計算して修正しました。

まとめ

以上の検証結果をまとめると、以下のような結論になりました。

f:id:o205:20150715113430p:plain

  • LumenはLaravelの約4倍速い
  • LumenはLaravelの約75%CPUに優しい
  • LumenはLaravelと同じくらいメモリを消費する
  • LumenはLaravelの約10%約85%の大きさ

Lumenは拡張性や機能に制限がありますが、その範囲で要求を満たせるものであれば、フルスタックのLaravelではなくLumenを使ってみるという選択肢もありかもしれませんね。

今回使用したソースコードは、https://github.com/medpeer-inc/benchmark-laravel-lumenに置いてありますので、ぜひ参考にしてみてください。

Zenhack(禅ハック)で最優秀賞いただきました&ハッカソンのススメ

$
0
0

こんにちは、メドピアデザイナーの松村です。
最近行き詰まることが多かったので座禅を組んで己に向き合いたい…と思ってZenhackに参加したら最優秀賞いただきましたやったー٩( ᐛ )و

Zenhackとは?

”禅 と IT で世界の課題に挑む”という壮大なコンセプトのもと由緒ある臨済宗建長寺派大本山建長寺に泊まり込みで行われるハッカソンです。すごい。 www.zenhack.jp

当日の様子と成果物イメージは以下からどうぞ。実況まとめありがたい! togetter.comhacklog.jp

私はハッカソンに何度か参加していますが、表彰されると承認欲求の満たされ方がすごいのでフラストレーションの溜まってるエンジニアさんやデザイナーさんにおすすめです。この快感を多くの方に味わって頂きたいので、僭越ながらまだハッカソンに参加されたことのない方に向けて、ハッカソンのススメ的なものを綴ってみます。

ハッカソンのススメ

ハッカソンて何ぞい?とよく聞かれますが、基本的には技術でお題に応える大喜利だと捉えています。ネタに走るもよし、ガチガチのビジネスプランを携えて挑むもよし。楽しみ方は人それぞれです。自分が一番楽しい!と思うことで勝負しましょう。(えらそう)

楽しむ

イデア出しの段階では俺はこれを作りたいんだーー!というのを思いつくまま紙にぶつけます。

こんなかんじ f:id:umeccco:20150708122557j:plain

お題に沿うのも大事ですが、内容を理解しないまま要素だけ貼り付けると薄っぺらくなるので基本的には自分の持ち味を活かすのが大事かなと思います。

尚このときに出すアイデアはチームビルディングのときに淘汰されることが多いので、一人でもやるよという気概のない方はあとでフラストレーションの溜まらないようにこの段階で持てるネタを出し切りましょう。

協力を求める

イデアを出し切ったら次は仲間探しです。

ボーっとしてるとどんどん人が売り切れいくので恥ずかしがらずにナンパしましょう。スーパーのタイムセールの状況が近いです。

この時大事なのがお互いのアイデアに共感できるか、それを一緒に作りたいか、というビジョンの共有です。これがないと実際手を動かしはじめたときにえらいことになります(過去にチームビルディングが上手く行かず途中でリタイアした悲しい思い出)。今回は本当に良いチームに恵まれました。ありがたい。

キョロキョロする

ハッカソンには普段は会えない人がひょっこり参加されてたりします。今回だと電波少年の土屋Pとかカヤックの岩渕さんとかぐるなびのフロントエンドエンジニアさんとかホームズの中の人とか錚々たる面々です。そんな方々と肩を並べて一夜を共に過ごせる…たまらないですね!

トラブルはつきもの

ハッカソンあるあるとしてWi-fi環境の不具合があります。状況を運営に相談したら慌てず騒がずSOYJOYでも食べて休憩しましょう。スケジュールどおり進まない?あるあるです。(今回はスケジュールどおり進んでてさすがカマコンバレーだなーと思いました)

また、出会ったばかりの人と2日間一緒に過ごすのですから、チームメンバーと険悪になる瞬間もあります。でも仕方ないじゃない。人間だもの。とりあえず目の前の自分の作業を終わらせて、それからお茶でも淹れて話し合いましょう。

プレゼンが上手くいかなかった?これは事前準備あるのみです。プレゼン環境を確認した上で3回は通しで練習すべきだなと痛感しました。(プレゼンほんとにやりなおしたい…orz)

讃える

人のアイデアや技術力に嫉妬することも多々ありますが素直にすごい!あんなの思いつかなかった!くやしい!と声に出して讃えることでより楽しくなります。あと人の能力を認めることで人間的に成長できる気がします。

というわけで少しはハッカソンの楽しさが伝わったでしょうか。 ハッカソンに参加する人はものを作りたくてウズウズしてる人たちばかりなので、今ここにない出会い的なものを求めている人は是非参加してみていただきたいです。

ハッカソン情報の収集は以下のサイトが便利です。

www.doorkeeper.jpconnpass.com

後日談

頂いた賞金はチームで山分けしたのち会社へのお土産に鳩サブレーの一番大きいサイズを買って帰りました。めちゃくちゃ重かったです。f:id:umeccco:20150708124608j:plain中に入っていた小分けの袋にも禅の思想を感じて、鎌倉はすごいところだな〜と思いました。 f:id:umeccco:20150708124723j:plain

SOYJOYもたくさん届いて社員一同ハッピーです!大塚製薬さんありがとうございます! f:id:umeccco:20150713151158j:plainf:id:umeccco:20150713151203j:plain

お知らせ

メドピアでは2015年11月4-5日にHealth2.0というイベントを開催します。 ハッカソンの開催はありませんが、医療に関わる様々な情報が集まる場ですので 興味のある方は是非お立ち寄りください!(事前申込制ですが、申込開始は今しばらくお待ちください!) health2.medpeer.co.jp

ついにHealth 2.0が日本上陸 - 新しいHealthTechの発展の場

$
0
0

こんにちは、@fukumuraです。

メドピアでCTOをやってます。よろしくお願いします。

はじめに

ここは技術ブログですが、 本日は技術の話はせずに「Health 2.0」の話を中心に 来週11/4-5に日本で初開催する「Health 2.0 ASIA-JAPAN」の話もしたいと思います。

Health 2.0とは

https://health2.medpeer.co.jp/img/common/hd_logo_pc.png

皆様、Health 2.0という言葉を聞いたことがありますでしょうか? (2.0は少し古い感じはしますよね、、、でもヘルスケア業界はまだ2.0の段階です)

Health 2.0とは、米国サンフランシスコを拠点に2007年に生まれたHealthTechのカンファレンスです。 新しいHealthTechの発展のために、大企業からHealthTech系のスタートアップやベンチャー企業、VCなどさまざまなプレイヤーが集まり、出会い、 化学反応を起こす場となるべく、世界各国で開催されています。

Health 2.0 Annualカンファレンス

米国で開催されるAnnualカンファレンスは、 今や参加者2000名を超えるイベントとなっています。

f:id:akinorifukumura:20151027113941p:plain

今年2015/10/5-7の3日間、米国サンノゼで開催されたカンファレンスには、 弊社メンバーも参加してきました。 先日その報告会が社内で行われ、いろいろなヘルステックの最新サービスが発表されました。 その中でも私が個人的に面白いなと思ったサービスが有りましたので3つだけご紹介させていただきます。

FirstDerm

www.firstderm.com

サービスの特徴はスマホで撮影した画像で医師からセカンドオピニオンが得られるというサービスです。 患者側アプリと医師側アプリを開発し、患者と医師をつないでいます。 主に皮膚科系を中心に展開していて、患者側はレスポンス速度を買い、 医師側はレスポンス速度で報酬を得るというビジネスモデルです。 技術的なハードルは高くなさそうですが、 日本では、まだまだ遠隔診療の領域なのでビジネス化という点でハードルが高いです。 しかしながら、患者と医師がWin-Winになっている非常に面白いサービスで、 今後もウォッチしていきたいです。

Tyto Care

http://tytocare.com/tytocare.com

遠隔に必要な情報が取得できるデバイスを作り、遠隔診療ができるようにしたサービスです。 実際にデバイスをつくってしまうところがすごいですが、 さらにそこで得たデータはクラウドに蓄積し、病院との連携プラットフォームになっているようです。 また、スマートフォンとデバイスを連携させて確定診断までできるとのことで、 こちらも日本では遠隔診療の壁がまだ高いのですが将来的にはここまでできると日本医療も変わってきそうです。 Web出身の私としてはデバイスまで作りきってしまうTyto careのエンジニアに憧れと嫉妬を感じてしまいます。

athena health

www.athenahealth.com

1997年創業時からクラウド型EHRシステムを開発・提供していて、売上は700億円を超えている会社です。 課金は会員医師に入る医療費の数%としていて、まさに実医療に踏み込んでいる素晴らしいサービスとなっています。

Health 2.0 ASIA-JAPAN のセッション

さらに、来週から開催されるHealth 2.0 ASIA-JAPANでデモやピッチ等で参加されるサービスを3つほどご紹介したいと思います。

Steth IO

www.stethio.com

こちらは、スマホを胸にあてるだけで心拍の様子がわかるというもので 医療機器などが整っていない国でも容易に利用が可能になります。 医師の聴診器を使った音の判断+グラフ描画による視覚の判断、 これらのデータを蓄積して分析することでの予防や発見につなげることができるなど 技術的にもいろいろ夢が広がりそうです。 AppleのWatchOS2より心拍数が取れるようになったので、今後いろいろ出てきそうですね。

BaseHealth

http://www.basehealth.com/www.basehealth.com

遺伝子データや臨床データを収集し、Fitbitなどのデバイスからも情報を収集することで 患者の予防にまで踏み込んだサービスを提供しています。 予防ができるようになると医療費も削減されるので 今後の日本もこのような予防医療が発展していくようにしていきたいです。

BirdsView

http://www.birdsview.jp/

救急車と病院のマッチングをし、搬送を最適化するシステムを提供しているサービスです。 各地で地域全体をひとつの医療システムとしていく地域医療連携の代表例です。

最後に

いかがでしたでしょうか?

海外では活発に医療関連のサービスが開発されていますが、 日本も遠隔医療の環境がすこしずつ変化しており、同じ様な世界がくると感じています。

医療情報の開発をする上で、ガイドライン*1は欠かせませんが、 実はすごいボリュームがあり、 現状では気軽に開発できるような環境ではないかもしれません。

しかし、本カンファレンスはHealthTech業界の情報のハブとなり イノベーションとコラボレーションを加速させる場に少しでもなればと願っております。

f:id:akinorifukumura:20151027114116p:plain

そんな雰囲気を味わいたい方がいたら、 来月日本で初開催する「Health 2.0 ASIA-JAPAN」に是非ご参加ください。

共同創業者のMattew Holt氏とIndu Subaiya氏も来日して、世界のヘルステックの最新トレンドを語ってくれますし、 米国ではとても有名な患者SNS、PatientsLikeMeのRishi Bhalerao氏もスピーカーで参加されます。

皆様奮ってご参加下さい!

health2.medpeer.co.jp


Golang(Go言語)を採用して、たった二人で基盤となるAPIゲートウェイを開発した話

$
0
0

はじめに

初めまして、気がつけば先月の25日で入社1年目を迎えた、
技術部 & Sake部部長@shinofara(篠原)です。
1月頃からGo言語(Golangばかり触りすぎて、PHPをたまに触ると;を忘れて怒られます。
困ったものです....

今回は、僕も含めた2名で進めてきた、弊社初の Go言語(Golangプロダクトについてのお話をしたいと思います。
少し長いですが、お付き合いいただければとてもうれしいです!

※関係無いですが、gopher君可愛いです。

f:id:shinofara:20151209165705p:plain

Go言語のロゴ、マスコットは2009年にRenée French(http://reneefrench.blogspot.jp/)さんによって作成・公表されました。 これらはCreative Commons Attribution 3.0 Unported License(http://creativecommons.org/licenses/by/3.0/)で保護されています。 ライセンスの全文はhttps://golang.org/doc/gopher/READMEをご覧ください。

さて、本題に入りたいと思います。

先月末、2015年11月25日に JSON RPC APIゲートウェイのリリースを行いました。
(と言っても社内限定利用ですが)
開発には Go言語(Golangを採用しています。
アプリ開発を除くと、PHP以外で開発した弊社初のプロダクトになります。

当記事では、MedPeer× Go言語(Golangでの開発に関して
下記の三点にフォーカスしてお話していきたいと思います。

  1. Go言語(Golangでのゲートウェイ開発が始まった経緯
  2. どのような体制・環境でゲートウェイの開発を行ったか
  3. 今後どうして行きたいか

なぜ Go言語(Golangでのプロダクト開発が始まったのか

ゲートウェイ作成の背景

弊社のサービスは、良くも悪くもモノリシックとして、今まで提供してきました。 モノリシックでもスケールさせ続ける事は可能ですが、以下の様な問題が発生しやすくなります。

  1. 小さい機能ひとつのデプロイだとしても、アプリケーション全体に影響を与える事が考えられます。
  2. ソースコードの肥大化に伴い、複雑性も高まってしまう為、修正の影響範囲が特定し辛くなると考えられます。

などなど、モノリシックから マイクロサービスにしていく事に関しては、様々なTech Blogなどで公開されておりますので、今回は省略させていただきます。

マイクロサービスについては、最近 株式会社FiNCがslideshareに公開した、以下のスライドがわかりやすいかと思います。

引用元:株式会社FiNC, slideshare)

そして、弊社でマイクロサービス化を実現する為に、クライアントの認証と、APIへのアクセス制御を考慮した結果ゲートウェイの開発を行う事となりました。

実際に作成したゲートウェイの簡単な図を載せておきます。

f:id:shinofara:20151210155610p:plain

PHPカンパニーが、Go言語(Golang)導入をどの様に決定したのか

今回開発する事になったゲートウェイの開発初期段階に、以下の事を考えた結果、
Go言語(Golangでやってみようという事になりました。

  • 求められている事は何なのか?
  • 求められている事に一番最適な言語は何なのか?

最後の方は勢いだったかもしれません。

全てをお話する事はできませんが、掻い摘みますと 以下の内容になります。

なぜGo言語(Golang)なのか?

  1. 静的型付け言語である事
    コンパイル時に、型の不整合に起因するバグを捕捉する事ができます。
    それ故に開発サイクルの後でバグ修正する多くのコスト、またはその場所で起こる多くのバグのコストを取り除く事ができるからです。 メリットは分かるのですが、やはり動的型付け言語経験者からすると、煩わしかったりもします。
    ですが、 Go言語(Golangには、型推論があるおかげで、静的型付けで型の恩恵を受けながら違和感無く開発できますので、とても親しみやすいです。

  2. 大抵の必要な物は、標準ライブラリに含まれている
    Go言語(Golangは十分な標準ライブラリが潤沢ですので、言語自体をインストールするだけで大抵の事が可能になります。

  3. 本番環境でGo言語を動かす為に必要な物は何一つ準備しなくてよいコンパイルする手間は発生してしまいますが、コンパイル後のバイナリを実行するだけでアプリケーションを立ち上げる事ができます。
    本番構築に関しては後述しますが、環境構築をとてもシンプルに保てると思いました。

Golangにする事で発生するデメリットは何なのか

  • 不慣れな言語に対する学習コスト
    Golangで実戦経験0の状態で着手した事も有りますが、どんな言語でも学習コストは発生してしまいます。
  • 依存管理が課題
    公式では、ベンダー管理が未サポート(1.5からvendorを使ったコンパイルをサポートはしているが、管理に関してはまだです)
    今後この辺りが公式サポートされるのかは要観察です。

ゲートウェイのインフラ周辺、利用したライブラリや、技術について

今回は深くは説明することが出来ませんので、使っている環境、技術などをさっと紹介したいと思います。
詳細に関しては、またお話させていただければと思います。

言語

もちろん Go言語,Golang 1.5
開発初期は 1.4でしたが、vendorサポートなど恩恵を受けたい機能追加がありましたので、早い段階から1.5を使い始めました。

開発環境

goの開発自体はmacでも問題無くできるのですが、mysqlや関係するサービス環境を誰が作っても再現できるようにする為に、docker-composeを使って確認を行っています。

docker実行環境は docker-machineを使って立ち上げています。

開発支援

開発時には以下のコマンドを使ってコーディングスタイルなどの統一化を行う様にしています。

依存管理

良くなかった事でも書きましたが、まだ公式では依存するパッケージのバージョン管理方法に関しては、未サポートなので、実行タイミングや、実行者、環境によってパッケージのバージョンが変わってしまう問題が発生してしまいます。

その為、弊社ではGoogleの公式ドキュメントで推奨と書かれている GO15VENDOREXPERIMENTの使い方を参考にして、バージョンの固定とvendorを使ったコンパイルを実現する事で、誰がコンパイルしても同じ結果を得られる状態にしています。

  • バージョン管理は、PHPcomposerrubybundlerに似た、glideを使ってvendorのバージョン管理しています。
  • vendorを使ったコンパイルは、バージョン 1.5サポートされた設定を使って、GOPATHよりvendor/...を優先して参照させるようにしています。

現在の所、特に問題なく使えてるため、最近はオススメっす!といろいろな場所で話をしています。

マイグレーション

Go言語で作成された、gooseを使ってスキーマ管理とマイグレーションを行っています。
使って見た感想としては、とてもシンプルで使い勝手がよく現時点では満足しています。

バリデーション

バリデーションには、gojsonschemaを採用しています。 ゲートウェイ開発では、Request/Responseのスキーマ定義をJSON-Schemaを採用していますので、gojsonschemaを使う事で、ドキュメントとバリデーションの設定が同時に変更できるというメリットを得ることができています。

ユニットテスト

Go言語(Golangで標準に用意されているtestパッケージのtestingを使っています。
assertとか無く不便かな?と思ってましたが、特に不便なく使えています。

CI(継続的インテグレーションツール

CIツールは、Github Enterpriseと連携可能な CircleCI Enterpriseを利用しています。

クラウドCircleCIでは、golang 1.5のサポートが開始しているのですが、
エンタープライズ版では 1.4の為、cricle.ymlを以下の様に記述しています。

※サポートを今か今かと楽しみに待っています!!(切実

machine:
  environment:
    GO15VENDOREXPERIMENT: 1
    GOROOT: "/home/ubuntu/.go"
    GOPATH: "/home/ubuntu/go"
    PATH: "/home/ubuntu/.go/bin:$PATH"

dependencies:
  cache_directories:
    - "/home/ubuntu/.go"
  pre:
    - if [[ ! -e /home/ubuntu/.go/bin/go ]]; then cd /home/ubuntu; curl https://storage.googleapis.com/golang/go1.5.2.linux-amd64.tar.gz | tar -xz; mv go .go; fi
  override:
    - mkdir -p $HOME/go/src/path/to/organization
    - ln -s $HOME/$CIRCLE_PROJECT_REPONAME $HOME/go/src/path/to/organization

test:
  override:
    - |
      cd $HOME/go/src/go/src/path/to/organization/$CIRCLE_PROJECT_REPONAME;
      go vet $(go list ./...|grep -v vendor);
    - |
      cd $HOME/go/src/go/src/path/to/organization/$CIRCLE_PROJECT_REPONAME;
      go test $(go list ./...|grep -v vendor);

一覧がGreenだと嬉しいですね! f:id:shinofara:20151204172353p:plain

テスト結果はこんな感じです。

f:id:shinofara:20151204172358p:plain

フレームワーク

使用しておりません。 この辺りに関しては、次回お話できればと思います。

本番環境構築

ゲートウェイの本番環境は、AWS上に構築しています。
構築に利用した技術は、以下の2点です。

Goの場合は本番環境にGo言語自体の実行環境は不要ですが、Nginxなどいくつか必要なパッケージがありますので、それらの構成管理として利用しています。
また、terraformはvpc全体のインフラ構成を管理する為に利用しています。
どちらもなれるまでは大変でしたが、なれてみると一昔前の構築にものすごく時間がかかっていた時代は何だったのか!と思いました。

開発からデプロイの簡単な流れ

今回は各工程の深掘りは出来ないですが、よくある図で表してみました。

f:id:shinofara:20151209022708j:plain

今後どうして行きたいか

弊社での Go言語(Golangを使ったプロジェクトはリリースで終わりではありません、ここからが本当の意味でのスタートとなります。

今後は、更に踏み込んだ技術のお話などをブログで公開していく予定です。 例えば開発現場についてとか、フレームワークを使わずに、どの様に開発をしたかなど

もし、弊社のプロダクトや、Go言語(Golangに関して少しでも興味がありましたら、ぜひ一緒に働きましょう! 気軽にお声がけくだされば、ランチとかでも大丈夫です!!

5年モノのサービスに1ヶ月で Sass(SCSS) を導入したお話

$
0
0

皆さんこんにちは、メディカルサービス部エンジニアの中村です。
好きなブキはスプラチャージャーワカメです。

先日、5年程稼働しているサービスへ Sass(SCSS)を導入したのでその技術的知見を共有させていただきます。

f:id:yuzurunakamura:20160128143430p:plain

なお、Sass は現在 SCSS Syntax が主流の為、CSSプリプロセッサについては Sass, ファイルタイプについては SCSS と表記します。

状況と経緯

メドピア株式会社では、医師限定サービス MedPeerを運営しています。
MedPeer サービス内ではさらに、薬剤評価掲示版、症例検討会、症例相談、ディスカッションなど、機能の異なる約10種のサービスを提供しております。

2011年頃、MedPeer サービスは現在稼働している Web アプリケーションフレームワークに移行しました。
長らく運用されてきたサービスのフロントエンドは、外注や様々な経緯を経て以下のような問題を抱えておりました。

  • Bootstrap v2.0.2 のまま。しかし bootstrap.cssが大量に書き換えられておりバージョン追従不可
  • DOM が Grid system に沿っていない
  • bootstrap.cssに一部でしか使われていないスタイルが大量に定義、さらに個別サービス用 CSSにも重複したスタイル定義
  • CSS構文エラーが大量に存在する
  • HTML テンプレート内の style 要素に直書き
  • !importantの嵐。そして !importantをオーバーライドする為、別箇所でさらに !importantの嵐...

要するに、長年運用されてきたサービスによくある技術的負債が積もった状態でした。

HTML テンプレートの DOM を見直したい、CSSも見直したい、UI も見直したい。しかし量が多い。
やりたいことは幾つもありましたが、問題を切り分け、Bootstrap3 に対応した新しい HTML テンプレートへの移行を少しずつ進めつつ、サイトデザインやボタン類に利用されている CSS部分を、既存のこんがらがった bootstrap.cssや他 CSSからサルベージし、新規に CSSを構成し直すことにしました。

そして、折角なのでこのタイミングで Sass へ移行する舵取りを行いました。
CSSをスクラッチで大量に書き換える必要がある為、CSSプリプロセッサを導入してもっと楽で管理しやすい形で書きたいよねというのが動機でした。

Bootstrap は v4 から less から Sass になったことと、デザイナからゆくゆくは Compass Helper Functions も使いたいとの要望を考慮に入れ、less ではなくSass 導入を決めました。

Sass 導入プラン

導入プランとして、以下の3案がありました。

1. *.cssをすべて *.scss に一括置換し、リポジトリから CSSはすべて無くす。

1.は、リポジトリから一括で CSSを排除するプランです。
CSSはそのままでも SCSS Syntax として読み込み可能なので 、機械的に一括置換し後々 SCSS を整理していこうというプランです。

しかし、既存コードの中には CSS構文エラーやブラウザハック系の CSSが含まれており、置換後の SCSS から生成した CSSが当初の CSSと異なるケースがありました。
さらに、機械的に構文エラーを修正すると、適用されていなかったスタイルが適用されるようになり、思わぬ所でデザイン崩れが発生する可能性もありました。
構文エラーの種類によってはブラウザ側で許容し適用してくれていた箇所もあり、これもまた当初のデザインと異なる可能性が否定できません。

一括置換しただけではスタイル定義重複などの煩雑な構成が解決されないことからも、このプランは見送りとなりました。

2. CSSをすべて SCSS に手動で全部一度に書き換える。エンジニアとデザイナは死ぬ。

2.も、リポジトリから一括で CSSを排除するプランです。 HTML も CSSも問題があるから両方全部直したい!というよくあるプランですが、一度に複数のことをやろうとすると大抵破綻します。 他の開発案件も当然ありますし、作業量が膨大になることからこのプランも見送りとなりました。

1, 2共に E2E テストを併用した HTML, CSS同時改修も検討したのですが、この記事の執筆時点で MedPeer のリポジトリには、HTML テンプレートが451ファイル・53701行、CSSが123ファイル・47294行があり、ユーザの属性ごとに出し分けするコンテンツも多く存在する為、一度に行うのは困難だと判断しました。

3. 移行期間中、一時的に CSSと SCSS が混在することを許容し、各サービス単位で徐々に移行する。

最終的には無難なプランでの導入となりました。

CSSと SCSS がリポジトリ上に混在することで、SCSS から生成された CSSを別の人が直接編集し、SCSS と CSSの整合性が取れなくなる懸念がありますが、SCSS から生成する際には compressed されたワンライナーCSSにすることで、誰が見ても生成された CSSと分かるようにしています。
最初の段階で共通 CSSはすべて SCSS にしておき、新規ページも原則 SCSS で作成することで、徐々に CSSを減らしていきます。

CSSリポジトリから無くすのが最終目標になります。

Sass 導入

導入プランも決まった所でいよいよ Sass 導入です。
導入に際して以下を実施しました。

  • Node.js, gulp 環境の構築
  • コーディングスタイルガイドの策定
  • scss-lint の導入
  • Browsersync の導入

Node.js, gulp 環境の構築

Sass 導入にあたって、デザイナでも簡単に環境構築できる環境が必要です。
極力 $ npm install一発で済むように package.jsonに gulp も含め管理するようにしました。 global でなく local にインストールした gulp を使うようにしておくことで、環境差異をできるだけ少なくしています。

local の gulp 実行は、package.json"scripts"に以下のように記述するとOKです。

"scripts": {"start": "gulp default",
    "gulp": "gulp",
    "scss": "gulp scss",
    "scss-lint": "gulp scss-lint"},

上記の場合、 $ npm startgulp defaultが実行されます。

コーディングスタイルガイドの策定

社内には PHPのコーディングスタイルガイドはあったのですが、CSSに関してはありませんでした。 過去の技術的負債はサービス開発全体に於けるコーディング流儀が統一されていなかったことにも要因がありますので、この問題にも対応しなければなりません。

ちょうど弊社の凄腕フロントエンドエンジニアがスタイルガイド策定準備を進めていてくれたので、このタイミングで社内公開を行いました。
あくまで社内向けスタイルガイドの為この記事では公開しませんが、概ね以下の著名なスタイルガイドを継承したものになります。

しかし、コーディングスタイルガイドを策定したからといって、すべてのコードがスタイルガイドに従ったものになるとは限りません。

scss-lint の導入

すべてのコードをスタイルガイドに合わせるべく、 scss-lint も併せて導入しました。

  • 後で導入すると、既存コードの修正は放置されがち
  • 早い段階で一定のフォーマットに整えておき、後程の新たな技術的負債を防ぎたかった
  • 過去に既存リポジトリPHP CodeSniffer と PHP Mess Detector の導入を試みたが、なかなか徹底されず頓挫してしまった
    • 一方、当初からCircle CIと共に導入した別プロジェクトでは問題なくlint活動が行われていた

などが主な動機です。

SCSS の lint ツールは幾つかありましたが、コード内で特定箇所のみルールの除外が可能だった為 Ruby実装ではありますが scss-lint にしました。
できれば Node.js だけで完結したかったのですが、Node.js 実装の lint ツールは現時点ではルールの除外ができなかったり、CLI出力やオプション設定などで総合的に満足いくものが現状ありませんでした。

gulp-scss-lint と併用し、gulp watchで *.scss 変更時に自動で lint 警告を gulp log に流すことで lint の徹底を行っています。

GitHub - brigade/scss-lint: Configurable tool for writing clean and consistent SCSSgithub.comgithub.com

インデントやシングル/ダブルクォートなど、細かいフォーマットを指摘は lint ツールに任せた方が角が立たずオススメです。
前述のスタイルガイドは .scss-lint.ymlに反映し、自然とスタイルガイドに沿ったコーディングになるようにしています。

また、 csscombも適宜利用し、自動での修正もサポートしています。
PropertySortOrder などの修正は面倒ですからね。

Browsersyrc の導入

このタイミングで以前から個人的に使っていた Browsersync も配布しました。
大量に SCSS を書き換える際に抜群の効果を発揮します。

Browsersync については以前こんな記事を書きました。

tech.medpeer.co.jp

Browsersync 利用時の WebFont の Cross-Origin Resource Sharing 設定

defalut では Browsersync は localhost:3000で動作します。
その為、CDN に WebFont を置いている場合には Cross-Origin Resource Sharing policy( CORS 設定) の為に読み込みが行われません。

とはいえ、CloudFront や S3 の CORS 設定に localhost:3000を許可してしまうのはセキュリティ上好ましくありません。
そこで今回は、Browsersync 用の hosts を追加し、CORS 設定で許可することで WebFont も読み込み可能にしました。

127.0.0.1 browsersync.example.com
browserSync({
  host: 'browsersync.example.com',
  open: 'external',
});

(個人的な話) Vim

個人的に Vim上でも scss-lint を実行するようにしました。

Vimでの Syntax Check Plugin は Syntasticが著名ですが、同期実行の為 Vimが固まりがちなのが課題でした。
vim-watchdogsは非同期実行の為、この懸念がありませんのでこのタイミングで乗り換えました。

おおよそ以下のような設定を書くことで、指定の .scss-lint.yml を読み込んで実行が可能です。

letg:quickrun_config = {
  \'watchdogs_checker/_': {
  \'hook/time/enable': 0,
  \'hook/back_window/enable_exit' : 1  \ },
  \'scss/watchdogs_checker': {
  \'type': 'watchdogs_checker/scss-lint',
  \'cmdopt': '-c $HOME/.config/.scss-lint.yml'  \ }
  \}

letg:watchdogs_check_BufWritePost_enable =1letg:watchdogs_check_CursorHold_enable =1letg:watchdogs_check_BufWritePost_enable_on_wq =0call watchdogs#setup(g:quickrun_config)

詳しくは vim-watchdogs/README.mdを参照してください。

また、CSScomb も vim-csscombを利用することで :CSScombで実行可能にしています。

社内周知と公開

さてさて、上記のような環境を用意しましたが、突然「使ってください!!!!」といっても、Sass 経験のないエンジニアや Terminal に慣れてないデザイナにはとっつきにくいかもしれません。

メリットを上手く説明し、納得して使ってもらう普及活動が必要です。
恐らく、新しい環境を導入する際に、最も重要で、最も難しいポイントです。

どうやって周知するか悩んでおりましたが、ちょうど弊社では月一でピザを食べつつ LT を行う、通称ピザ会を開催しているので、この時に Sass と開発環境の説明を行いつつ、社内公開を行いました。

なお、突然 LT で「merge しろ!!」「使ってくれ!!」 といっても「何言ってんだこいつ」となるかなと思ったので、関係者ほぼ全員に事前に承認ラリーを行いました。

tech.medpeer.co.jp

マイクロソフトVisual Studio Code をイベント中にライブで GitHubに公開したように、その場で Pull Request に LGTM を貰いカッコ良く公開したかったのですが、カッコ良くは行かなかったのが反省点です。

とはいえコレで Sass 環境の配布が完了しました。

まとめ

以上の流れで MedPeer では Sass 導入を実施しました。 Slack を眺めて振り返ってみたところ、大体1ヶ月程度で導入まで漕ぎ着けることができました。 導入しようと決めてからは、あれこれ決めることが多かったのですが、思ったよりすんなり進められた印象です。
今後は Sass 移行を進めつつパーシャルファイルに分け、modularize を進めていくことになりますが、CSSに比べ格段に書きやすいので以前より開発が進めやすくなったと実感しています。

Sass 導入を検討されている方の参考になれば幸いです。

問題を問題として認識するためにしたこと

$
0
0

こんにちわ、エンジニアの井原です。

どの会社も同じだとは思いますが、自社サービスをフルスクラッチしていた最初のころというのは突貫で作っている部分が少なからずあると思います。 弊社もそういった部分が多々あり、特にフロントエンドは手付かずな状態でした。

この混沌としたフロントエンドに漠然とした不安はあるものの、ルールが全くないため全員が同じ思いになってなく、何をもって悪いと言えるのかといった状態になっていました。 そのため、何が悪いのか、どうしたらいいのかという方針作りからはじめました。

本稿では、方針を作るプロセスとできた方針をいかにまもってもらうかという流れを記載します。

現状を確認する

  • インデント..?
  • styleプロパティ
  • 重複したid
  • 大量のcssファイル
  • ロード目的以外のscriptタグとstyleタグ
  • HTML4のころのタグ
  • invalidなcss
  • etc...

あるべき論なルールを作る

現状がすごすぎて列挙すると切りがないため、 まずは現状は気にせず世の中のデファクトスタンダードを踏襲したルールを作りました。

例えば、JavaScriptAirbnbのコーディングスタイルガイドcssではCyberAgentGoogleの公開しているコーディングスタイルガイドのような、メジャーなものを参考にしました。

Lintツールの導入

上記で定義したルールを人間の目で見るのは限界があるので、各種Lintツールを導入して機械の力に助けてもらいます。

ルールを設定

各Lintにあるべき論ルールを設定しました。

ただ、既存のプロジェクトにあるべき論をそのまま適用するとエラー量が大変な事になります。多すぎるエラーはほとんどの人のやる気を失わせ、やがて放置され、形骸化するのが常です。

ですから、共通のルールを適用しつつ、プロジェクト毎にルールを変えられるようにしました。 (もちろん最終的にはあるべき論に少しずつ近づけていきます)

ESLint

ESLintはルールをnpmで取得して、それを継承する形で設定ができます。 その機能を利用するため、まず社内リポジトリに上記ルールを定義したeslint-config-medpeerを作成します。各プロジェクトはeslint-config-medpeerを継承した上でプロジェクトにあわせた妥協ラインを設定するようにしました。

scss-lint

scss-lintはESLintのような継承の仕組みがないですが、明示的に設定しなかったルールはデフォルトのルールが適用されるという仕様があります。

そこで、scss-lint本体のリポジトリをforkし、デフォルトの設定をあるべき論ルールにあわせて書き換えて社内リポジトリに設置しました。 これをインストールして利用することで、ESLintの設定の継承に近いことが実現できました。

html

htmlに関しては妥協を許さないスタンスで共通の設定ファイルを導入。 Nu HTML Checkerは https://validator.nuで確認するのではなく、ローカルで動かしているものを利用するようにしました。動作環境はdockerfileを作り配布しました。

Runnerを導入する

書き換えるたびに手動で実行するという運用では、面倒でやらなくなる、もしくは実行するのを忘れるので、ファイル監視して書きかわるたびにLintを通すようにします。

ESLintとscss-lint

gulp-watchで監視します。

html

htmlに関しては静的なhtmlファイルであれば同じくgulpでいいのですが、弊社はphpで作っているサービスが多いためテンプレートファイルです。 ビルドするまではhtmlの全貌が見えないので、ビルド済みのものを各種Lintツールに流す必要があります。

できればプロジェクトのセッティング(npm installとかcomposer installとか)しただけで環境が構築されてて欲しかったので、以下の2つをつくってみました。

  • BrowserSyncにmiddlewareで挟んでBrowserSyncのログに流すようにした
  • PHP debugbarにCheckerの結果を表示するタブを増やす

ただBrowserSyncは裏で勝手に動いているのでスルーしがちです。 debugbarはdebugbar自体入れていないプロジェクトもあるので、全てのプロジェクトで使えるわけでもない。

別途インストールが必要ではありますが、ブラウザの拡張ツールにしたほうが良さそうな気がしてます。htmlについてはまだベストな方法を模索しているところです。

今後の展開

ルールや仕組みはできたので次は実際に書き換えていく作業になります。 その際レイアウトが崩れていないかを人間の目ですべてを確認するのは難しいので、画面側のテストを導入しようとしています。

さらに実際に運用されているものの監視も不十分なため、エラートラッキングツールの導入も検討しているところです。

ほかにもjQueryバージョンバラバラ問題とかjQueryプラグインたくさん問題とかロードしているファイル多すぎ問題とかまだまだ問題は多く抱えています...

問題は多く抱えつつも、方針を決めることで今まで空中で漂っていた何かが問題として設定できるようになりました。これで問答無用で斧が投げられるようになったので、今後は改善していくしかないです。

ぼくらのたたかいはまだまだこれからだ!

2泊3日の開発合宿 in 熱海に行ってきました!!

$
0
0

はじめまして。4月からメドピア新卒一期生として入社する栢割(カヤワリ)です。今回は初めてメドピアの開発合宿に参加してきたので、その時の様子を書かせて頂きます。よろしくお願いします!!!!!!

どんな開発合宿なの??

メドピアの開発合宿は、年に2,3回程度定期的に行われているエンジニアのためのイベントです。その都度合宿のテーマを決めて、開発に取り組みます。 今回の合宿は「技術研鑽」をテーマに、熱海で2泊3日の開発合宿となりました。個人でもチームでもいいので、何か一つ学びたいテーマや課題を決め、それらについて知識を深めるといった具合です。

  • 数年前に作られた社内既存システムの深い部分を再解析。そして新技術にどう組み替えるかを考える方

  • Slack APIやNotification APIを使った通知機能を作る方

  • 最近のJSフレームワークAngular2とかReactとか)を学ぶ方(Reactってもうv15.0ってやつが出てるんですね…ひえー)React v15.0 Release Candidate | React

  • AMP(アンプ)googleが発表したモバイル端末でのWebページ表示高速化の規格)について調べる方
    、、、、などなど。いろんなテーマを各自決めて取り組みました。

合宿当日はどんな様子だった??

1日目

宿泊先は熱海温泉 山木旅館です。昔ながらのおもてなしがある、心安らぐ和風の宿でした。
メドピア開発合宿では、有名な伊東の山喜旅館さんに過去何回か行ったことがあるそうですが、今回は違う「やまき」旅館さんに宿泊することになりました。www.yamakiryokan.co.jp

16:00頃に品川駅を出発し、18:00くらいには旅館に到着。
f:id:MedPeerEngineers:20160302142147j:plain

f:id:MedPeerEngineers:20160301095054j:plain
「ふう…着いたー。よっしゃ!!温泉入ろーーー!!!」、、、、ではなく、部屋に入ったら無線環境整えて、すぐに開発に取り組みます。

「3日間もあれば、すげーモノ作れそう!!!余裕っしょ!!」って思う方もいるかもしれません。でも、初日の夜〜最終日の午前中の2泊3日で、食事や睡眠時間などを考えると、実質開発にあてられる時間は、、、
20~24時間くらい?!?!
徹夜は考慮してません。かなりアバウトですが、多分このくらい。楽しみだった半面、開発スピードやスキルがまだまだな自分は「やべぇ、意外と時間ない?!大丈夫か?!」と、行きの新幹線で内心焦ってましたw。

夕飯を食べた後、
「今回の合宿で私は〇〇やります!!」
と宣言してから本格スタート!!!畳や布団で寝転がりながら、、、。座椅子座りながら、、、。温泉に入りながら、、、(<= さすがにコレやってる方はいなかった)。各自好きなスタイルで開発に取り組みました。こんな風に落ち着いた旅館でリラックスして開発に取り組めるのが、開発合宿の魅力だと思います。
ちなみに僕はReactを勉強したかったので、以前メドピアでのインターン時に開発した社内Webアプリのフロント部分を、Reactに置き換えることを目標にしました。

2日目

朝起きて旅館のご飯を食べたら、すぐに開発に没頭!! ちょっと眠かったですが、カタカタ…カタカタ…。

お昼は旅館の隣にある鰻屋さんに行って、高そうな鰻の蒲焼きを食べました。めっちゃ美味しかったです(写真撮るの忘れた…)。

昼食後、各自の進捗状況をみるため、部屋に集まって中間発表!!!!
この時点で、既に開発物が形になっている人もいました。先輩方の成果を見て、「よっしゃ!頑張るぞ!!」と気合を入れ直し、残り時間集中します。3日目は午前中に宿を出てしまうので、あまり時間がありません。
つまり、この2日目が開発の頑張り所です!!!

途中、他の先輩社員の方が差し入れを持って、様子を見に来てくれました(^0^) 差し入れありがとうございます(_ _ )

夜は差し入れを食べて、雑談もしながら、先ほど同様、開発!開発!
12時頃までやった時点でとてつもない眠気が襲ったので、宿の温泉に入ってリフレッシュ。すぐにお風呂入ってリフレッシュできるのも温泉合宿の魅力です。
温泉に入った後も明日のために少しやったのですが、結局2時くらいにダウンしてしまいました。zzzz
中には追い込みで朝方5時くらいまでやってた方もいたそうです。

3日目

最終日!!
最終日は、各自の成果を発表する場があります。 朝食を食べて、午前中はそれぞれの部屋で最終調整。と言っても、2日の時点で完成していた方が殆どだったので、部屋でゆっくり…という感じ。

そしてお昼頃、昨日と同じ部屋で最終成果発表しました!!写真は発表後に各自の成果物に対してディスカッションしてる様子。 f:id:MedPeerEngineers:20160301093331j:plain

当たり前ですが、先輩方とのレベル差を痛感。奥で縮こまって座ってるのが僕ですが、テンション落ちてる感じですねwww。次回はもっと成長して参加します!!!

最後は打ち上げランチして、熱海駅まで徒歩でお土産買いながら帰りました!(また食事の写真撮るの忘れた…ごめんなさい)
帰りに買ったおみやげは、、、 猫の舌!!!!!!
www.mikiseika.com
を買いました。
学校の後輩から「コレ買ってきて!!!」と要望があったので。
洋風なクッキーって感じです。なぜ猫の舌というのかは不明。熱海に来た際は、是非買ってみて下さい。

合宿参加してみてどうだった?? 

「普通に楽しい」「技術研鑽できる」「これから一緒に働く先輩方の雰囲気等も知れる」などなど、、、自分にとって有意義な時間を過ごせたと思います。
2泊3日という短い期間で、特定の技術研鑽や成果を出すというのは、学生時代に特に時間に縛られずダラダラと開発をしていた自分にとっては、なかなか難しかったです。でも、このような開発だけに没頭できるイベントは自身のスキルアップにおいて重要ですし、2泊3日でどの程度自分が出来るのか?確認できるいい機会であるとも思いました。後は今回の合宿で初めてお会いする方や、まだあまりお話したこと無かった方とも親睦を深めることができるのも、合宿の良い所だと思います。 次回も是非参加したいです。
これからメドピアの一員として一生懸命頑張っていくわけですが、初めて参加した今回の合宿を思い出して「あ〜、オレもこんな時期あったな〜」と思えるように成長していきたいと思います。
最後は個人的な抱負になってしまいましたが、以上で2泊3日のメドピア開発合宿 in 熱海の様子を報告させて頂きます。

TerraformでCloudWatch EventsのEBSスナップショット定期作成機能を設定する

$
0
0

はじめに

世の中の医療・ヘルスケア情報を医師たちが実名で解説するWEBメディア、イシコメ開発担当の大谷です。 イシコメは少数の開発者で開発・運営しているため、省力化のためTerraformなどのツールによりインフラ管理を自動化しています。

今回は、TerraformでEBSのスナップショットをサーバレスで定期的に作成する方法について調査したため、その方法を共有します。

本稿ではTerraform自体の解説は行いません。 Terraformを使ったことが無い方は、公式サイトのチュートリアルが分かりやすいので是非試してみて下さい。

尚、使用したTerraformのバージョンはv0.6.16です。

手順

CloudWatch EventsのEBSスナップショット作成機能をTerraformで設定します。 必要なのは.tfファイル一つですが、その中に記述する要素について順を追って説明します。

1. AWSのproviderを準備する

いつもの設定です。

variable "access_key" {}
variable "secret_key" {}
variable "aws_account_id" {}
variable "region" {
  default = "ap-northeast-1"
}

provider "aws" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region = "${var.region}"
}

2. 取得対象のEBSのIDを指定する

ハードコードにしてもいいですが、実用的ではないので変数化してしまいます。

variable "ebs_id" {}

3. IAM Roleを作成する

CloudWatch EventsがEBSを作成できるように権限を設定します。

resource "aws_iam_role" "cloudwatch_events_automatic_execution" {
  name = "cloudwatch_events_automatic_execution"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Action": "sts:AssumeRole",
          "Principal": {
            "Service": "events.amazonaws.com"
          },
          "Effect": "Allow"
      }
  ]
}
EOF
}

resource "aws_iam_role_policy" "cloudwatch_events_automatic_execution_policy" {
  name = "cloudwatch_events_automatic_execution_policy"
  role = "${aws_iam_role.cloudwatch_events_automatic_execution.id}"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:Describe*",
                "ec2:CreateSnapshot"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
EOF
}

4. EBSスナップショット作成のスケジュールを設定する

aws_cloudwatch_event_ruleリソースでCloudWatch Eventsのルールを作成します。 ここでは主に先程作成したIAM Roleの指定と、実行間隔を設定することになります。 今回は、1日1回実行するようにしてみましたが、変更したい場合はCloudWatch Eventsの仕様に従ってschedule_expressionオプションを変更して下さい。

resource "aws_cloudwatch_event_rule" "take_snapshots_every_day" {
  name = "take_snapshots_every_day"
  description = "Fires every day"
  schedule_expression = "rate(1 day)"
  role_arn = "${aws_iam_role.cloudwatch_events_automatic_execution.arn}"
}

5. EBSスナップショット作成用のイベントターゲットを設定する

ここでスナップショット作成対象EBSボリュームのIDを${var.ebs_id}ではなく${aws_ebs_volume.your_volume.id}のような形でtfファイルの他の場所で作成したEBSボリュームのIDで置き換えるとインフラ一式の作成にあわせてIDが自動で設定されるので便利だと思います。

resource "aws_cloudwatch_event_target" "take_snapshots_every_day" {
  rule = "${aws_cloudwatch_event_rule.take_snapshots_every_day.name}"
  arn = "arn:aws:automation:${var.region}:${var.aws_account_id}:action/EBSCreateSnapshot/EBSCreateSnapshot_${aws_cloudwatch_event_rule.take_snapshots_every_day.name}"
  input = "\"arn:aws:ec2:${var.region}:${var.aws_account_id}:volume/${var.ebs_id}\""
}

実行してみる

今回はaccess_keyなどの設定をsecrets.tfvarsファイルに書いて実行しました。

aws_account_id = "<< substitute me >>"
access_key = "<< substitute me >>"
secret_key = "<< substitute me >>"
ebs_id = "<< substitute me >>"

plan

% terraform plan -state=terraform.tfstate -var-file=secrets.tfvars
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_cloudwatch_event_rule.take_snapshots_every_day
    arn:                 "" => "<computed>"
    description:         "" => "Fires every day"
    is_enabled:          "" => "1"
    name:                "" => "take_snapshots_every_day"
    role_arn:            "" => "${aws_iam_role.cloudwatch_events_automatic_execution.arn}"
    schedule_expression: "" => "rate(1 day)"

+ aws_cloudwatch_event_target.take_snapshots_every_day
    arn:       "" => "arn:aws:automation:ap-northeast-1:xxxxxxxxxxxx:action/EBSCreateSnapshot/EBSCreateSnapshot_take_snapshots_every_day"
    input:     "" => "\"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/vol-xxxxxxxx\""
    rule:      "" => "take_snapshots_every_day"
    target_id: "" => "<computed>"

+ aws_iam_role.cloudwatch_events_automatic_execution
    arn:                "" => "<computed>"
    assume_role_policy: "" => "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n      {\n          \"Action\": \"sts:AssumeRole\",\n          \"Principal\": {\n            \"Service\": \"events.amazonaws.com\"\n          },\n          \"Effect\": \"Allow\"\n      }\n  ]\n}\n"
    name:               "" => "cloudwatch_events_automatic_execution"
    path:               "" => "/"
    unique_id:          "" => "<computed>"

+ aws_iam_role_policy.cloudwatch_events_automatic_execution_policy
    name:   "" => "cloudwatch_events_automatic_execution_policy"
    policy: "" => "{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Action\": [\n                \"ec2:Describe*\",\n                \"ec2:CreateSnapshot\"\n            ],\n            \"Effect\": \"Allow\",\n            \"Resource\": \"*\"\n        }\n    ]\n}\n"
    role:   "" => "${aws_iam_role.cloudwatch_events_automatic_execution.id}"


Plan: 4 to add, 0 to change, 0 to destroy.

apply

% terraform apply -state=terraform.tfstate -var-file=secrets.tfvars
aws_iam_role.cloudwatch_events_automatic_execution: Creating...
  arn:                "" => "<computed>"
  assume_role_policy: "" => "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n      {\n          \"Action\": \"sts:AssumeRole\",\n          \"Principal\": {\n            \"Service\": \"events.amazonaws.com\"\n          },\n          \"Effect\": \"Allow\"\n      }\n  ]\n}\n"
  name:               "" => "cloudwatch_events_automatic_execution"
  path:               "" => "/"
  unique_id:          "" => "<computed>"
aws_iam_role.cloudwatch_events_automatic_execution: Creation complete
aws_iam_role_policy.cloudwatch_events_automatic_execution_policy: Creating...
  name:   "" => "cloudwatch_events_automatic_execution_policy"
  policy: "" => "{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Action\": [\n                \"ec2:Describe*\",\n                \"ec2:CreateSnapshot\"\n            ],\n            \"Effect\": \"Allow\",\n            \"Resource\": \"*\"\n        }\n    ]\n}\n"
  role:   "" => "cloudwatch_events_automatic_execution"
aws_cloudwatch_event_rule.take_snapshots_every_day: Creating...
  arn:                 "" => "<computed>"
  description:         "" => "Fires every day"
  is_enabled:          "" => "1"
  name:                "" => "take_snapshots_every_day"
  role_arn:            "" => "arn:aws:iam::xxxxxxxxxxxx:role/cloudwatch_events_automatic_execution"
  schedule_expression: "" => "rate(1 day)"
aws_iam_role_policy.cloudwatch_events_automatic_execution_policy: Creation complete
aws_cloudwatch_event_rule.take_snapshots_every_day: Still creating... (10s elapsed)
aws_cloudwatch_event_rule.take_snapshots_every_day: Creation complete
aws_cloudwatch_event_target.take_snapshots_every_day: Creating...
  arn:       "" => "arn:aws:automation:ap-northeast-1:xxxxxxxxxxxx:action/EBSCreateSnapshot/EBSCreateSnapshot_take_snapshots_every_day"
  input:     "" => "\"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/vol-xxxxxxxx\""
  rule:      "" => "take_snapshots_every_day"
  target_id: "" => "<computed>"
aws_cloudwatch_event_target.take_snapshots_every_day: Creation complete

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

awsコマンドで確認

コマンドラインで確認すると、CloudWatch EventsのルールとIAM Roleの一式が作られています。

% aws iam get-role --role-name cloudwatch_events_automatic_execution
{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "events.amazonaws.com"
                    }
                }
            ]
        },
        "RoleId": "XXXXXXXXXXXXXXXXXXXXX",
        "CreateDate": "2016-05-31T12:54:18Z",
        "RoleName": "cloudwatch_events_automatic_execution",
        "Path": "/",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:role/cloudwatch_events_automatic_execution"
    }
}
% aws iam get-role-policy --role-name cloudwatch_events_automatic_execution --policy-name cloudwatch_events_automatic_execution_policy
{
    "RoleName": "cloudwatch_events_automatic_execution",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": [
                    "ec2:Describe*",
                    "ec2:CreateSnapshot"
                ],
                "Resource": "*",
                "Effect": "Allow"
            }
        ]
    },
    "PolicyName": "cloudwatch_events_automatic_execution_policy"
}
% aws events list-rules
{
    "Rules": [
        {
            "ScheduleExpression": "rate(1 day)",
            "Name": "take_snapshots_every_day",
            "RoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/cloudwatch_events_automatic_execution",
            "State": "ENABLED",
            "Arn": "arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/take_snapshots_every_day",
            "Description": "Fires every day"
        }
    ]
}
% aws events list-targets-by-rule --rule take_snapshots_every_day
{
    "Targets": [
        {
            "Input": "\"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/vol-xxxxxxxx\"",
            "Id": "terraform-xxxxxxxxxxxxxxxxxxxxxxxxxx",
            "Arn": "arn:aws:automation:ap-northeast-1:xxxxxxxxxxxx:action/EBSCreateSnapshot/EBSCreateSnapshot_take_snapshots_every_day"
        }
    ]
}

おわりに

今回は、TerraformでCloudWatch EventsによるEBSボリュームのスナップショット作成機能を設定してみました。 マネジメントコンソールからなら簡単に設定できるのにTerraformでやろうとすると色々調べて回らないといけないので大変でした。 しかし、一度コードに落としてしまえば使いまわせるのがTerraformの良いところですね。

ところで、この方法は設定しなければいけない要素が少なくて手軽ではあるんですが、古いスナップショットを手動で作成しなければいけなかったりと万能ではありません。 より細かい制御がしたい場合はLambdaを使うことになります。 Lambdaも同様にTerraformで設定できるので、興味が湧いたら調べてみて下さいませ。

それでは

ソースコード

今回実行したtfファイルのサンプルコードです。

variable "access_key" {}
variable "secret_key" {}
variable "aws_account_id" {}
variable "region" {
  default = "ap-northeast-1"
}
variable "ebs_id" {}

provider "aws" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region = "${var.region}"
}

resource "aws_iam_role" "cloudwatch_events_automatic_execution" {
  name = "cloudwatch_events_automatic_execution"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Action": "sts:AssumeRole",
          "Principal": {
            "Service": "events.amazonaws.com"
          },
          "Effect": "Allow"
      }
  ]
}
EOF
}

resource "aws_iam_role_policy" "cloudwatch_events_automatic_execution_policy" {
  name = "cloudwatch_events_automatic_execution_policy"
  role = "${aws_iam_role.cloudwatch_events_automatic_execution.id}"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:Describe*",
                "ec2:CreateSnapshot"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
EOF
}

resource "aws_cloudwatch_event_rule" "take_snapshots_every_day" {
  name = "take_snapshots_every_day"
  description = "Fires every day"
  schedule_expression = "rate(1 day)"
  role_arn = "${aws_iam_role.cloudwatch_events_automatic_execution.arn}"
}

resource "aws_cloudwatch_event_target" "take_snapshots_every_day" {
  rule = "${aws_cloudwatch_event_rule.take_snapshots_every_day.name}"
  arn = "arn:aws:automation:${var.region}:${var.aws_account_id}:action/EBSCreateSnapshot/EBSCreateSnapshot_${aws_cloudwatch_event_rule.take_snapshots_every_day.name}"
  input = "\"arn:aws:ec2:${var.region}:${var.aws_account_id}:volume/${var.ebs_id}\""
}

事業拡大に伴いコーポレートサイトのリニューアルを行いました!そして、コーポレートサイトだから色々技術的に挑戦してみました

$
0
0

Introduction

こんにちは、元Sake部長@shinofara(篠原)です。
Golang(Go言語)を採用して、たった二人で基盤となるAPIゲートウェイを開発した話以来の登場です。

今回調子に乗って、各セクションを英語にしてみたのですが、途中で力尽きてしまいました。。

それはさておき、みなさんお気づきでしたか?
実は2016年11月10にフルリニューアルした、弊社コーポレートサイト(以下、COJP)を公開していました。

f:id:shinofara:20161125205953p:plain:w300

今までと比べると、とても最近感がありますよね!
ちなみに、11月9日時点までのサイトは、以下の画像です。
f:id:shinofara:20161125205635p:plain:w300

デザイン刷新しただけ?の様に思えますが、技術的な観点でも旧COJPと全く違う物になりました!!
今回は、その裏側を紹介したいと思います。
デザイン観点でのブログはこちら

※ちなみに僕がアサインされたのはリリース3週間前

Background of renewal.

今までのCOJPには様々な課題がありました。
あるあるなモノから、メドピアだけのものだとか

  • エンジニアがいないとデザイン修正が出来ない
  • 1台のサーバに WordPressが複数稼働している状態で、冗長構成に出来ない。
  • そもそも WordPress魔改造されていてセキュリティ対応し難いし、脆弱性多発しやすい
    CVEレポートがおおい
  • そもそも構成を理解している人が居ない(外注)為、ローカルで再現出来ず開発出来ない
  • そもそもデザイン自体が古いので、新しくしたい

Renewal overview.

リニューアルに対しての、企画要望と課題、そして対応方法

Requirement.

リニューアルを行う際に、下記の様な要望がありました。

  1. 更新のある記事は、WordPress等のCMSで入稿したい
  2. TOPページとか、更新がめったにないサイトは、デザイナが直接更新したい
  3. 問い合わせに対して、自動返答+社内に履歴を残したい
  4. 旧社長ブログや、プレスリリースなどのSNSシェア数などは可能な限り引き継ぎたい(可能な限りとはあるが、これは限りなく100%に近い可能な限り)

という感じで、ざっくりまとめると今より色々な面で良くしたいけど、過去は全部引き継いでね♡という感じでした。

Issue.

あがった要望に対して、見えてきた課題もあります。

  1. WordPress本番運用はセキュリティ的な問題で、NG
  2. 運用者が、FTPクライアントなどでupload出来る場所が必要
  3. 問い合わせ処理と管理は動的な物なので、サーバサイドの仕組みが何かしら必要
  4. 旧サイトのURLは動的URL (https://medpeer.co.jp/?p=1632

How to respond?

そんな課題に対して、今回行った対応です。

  1. ステージング環境に構築した WordPressから本番用S3に静的ファイルの書きだしを行う事で対応する。(StaticPressを利用) *Updateが止まってるのが悩み
  2. S3に専用バケットを作成して、運用者ごとのIAMでAccess出来る様にする
  3. 会社としてRails化を進めているのでPOSTを受けてメール送信を行う処理をRails5で作成
  4. StaticPressを使う関係上URLは、静的な物(https://medpeer.co.jp/blog/1632.html)になってしまう為、この場合でも旧URLのソーシャルスコアを維持できるように対応

Technical point of view

技術的な取り組みとしても幾つか新しい事をやったりもしています。 今回のリニューアルでは、技術的に以下の取組を行いました。

1. All applications are operated by Docker

最近では珍しくなくなってきたDockerをフル活用しています。 Dockerを使う事で、再現性のある環境を共有・展開出来るようになり、とある環境では動かないと言った問題が起こりにくくなります。

2. Blue Green Deployment(Operation not using SSH)

DockerとECSを使う事で、EC2に対してSSHを用いる事無く、コンテナの作り捨てが出来るようになります。 また、イメージが存在していればトラブルが発生していても、過去の動いていたバージョンにロールバックする事も容易に出来ます。

3. Engineerless operation

エンジニアにデプロイ依頼が発生しているとスピード感が損なわれる為、動的部分以外はエンジニア介入しない公開フローと公開できる仕組みを整えました。

4. Infrastructure as Code

Terraformでインフラ構成管理、Dockerでアプリケーションサーバ構成管理を行う事で、環境構築の再現性を確保 また、コード化する事で、PRによるレビューも行い、インフラ事故を軽減

Infrastructure

今回のCOJPでは、開発環境は、 docker-composeで、ステージング、本番は AWSを使っており、更にステージングではWordPressPHPで動いているのに対して、本番ではS3からの静的配信になっています。
その為、各環境で使っている物が少しずつ変わってきます。

各環境毎に利用しているサービスの違い

本番/ステージングがAWSですが、開発環境の構成も可能な限り同じものを用意してます。

要素 本番 ステージング 開発
SSLAWS Certificate ManagerAWS Certificate ManagerオレオレCA認証SSL証明書
Load Balancer ALBALB HAProxy Container
WordPressS3WordPress Container Wordpress Container
Static File Storage S3S3 Nginx Container
RailsRails(Puma) Continer Rails(Puma) Continer Rails(Puma) Continer
SMTPSESSES MailCatcher Container
Mail Log Data Storage DynamoDBDynamoDB DynamoDB Local Container
Wordpress Data Storage None RDS (MariaDB)MariaDB Container
Log Cloud Watch LogsCloud Watch Logs Stdout

How about services provided by AWS?

ALB,DynamoDB,etc..と言ったAWS提供サービスは、ローカルには存在しませんので、 HAProxy, DynamoDB-local, MailCatcherで代用してます。

ALBの代わりに使ってるHAProxyって何?

HAProxyは、簡単に書くと多機能プロキシサーバで、ALBの様にPATH Routingが出来ます。

DynamoDBの代わりに使ってるDynamoDB-localって何?

RDSの場合は、MySQLなどで代用は可能ですが、AWS提供のDynamoDBは代わりが効きませんが、DynamoDB Localが公式提供されていますので、こちらを使います。

SESの代わりに使ってるMailCathcerって何?

MailCatcherは、SMTPサーバとメールクライアントを同時に提供してくれるアプリケーションになります。 開発環境に導入する事で間違えて本番に.....という事故を防ぐ事ができます。

それぞれの環境毎のinfrastructureについて

環境 ツール
本番・ステージング Terraform / ECR / ECS
開発 Docker Compose

本番/ステージングと開発環境では、ECS/ECRとdocker-composeと違いはあるものの 各アプリケーションは同じDocker Imageを使いまわせる為、便利ですね。

全ての環境の構成図

f:id:shinofara:20161115141208p:plain:w300

About AWS configuration

今回はAWSのインフラに関して、更に深く書いてみたいと思います。

Services

  • ALB
  • ECS
  • ECR
  • S3
  • DynamoDB
  • RDS ( stg only )
  • Cloud Watch Logs

Policy

どのように接続したか

EC2から各サービスを利用するにあたって、IAM Userを作るのでは無く、EC2にIAM ROLEを割り当てました。

なぜ?

  1. IAMの管理がめんどくさい
  2. 何かしらの事情で、キーやシークレットが流出したら削除、もしくは変更しないといけない
  3. そもそも秘密情報の管理方法を考えなくてはいけない

など管理面でワズらしい事がおきてしまいます。

EC2に紐付けるとどうなるの?

EC2に紐付けると上記問題は解決できますし、この割当てられたEC2からしか各サービスへのアクションを行えなくなります。 これだとEC2に入れれば許可された範囲で何かできてしまうのでは?となりますが、この点に関してはキーを発行した場合でも同様の事が言える為、別の問題という事になります。

公式ドキュメントはこちら

ECSに対して付与したPolicy

{
  "Version": "2012-10-17",
  "Statement": [
      {
      "Action": "elasticloadbalancing:*",
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Action": "ecs:*",
      "Effect": "Allow",
      "Resource": "*"
    },
    {
    "Action": ["logs:CreateLogStream","logs:PutLogEvents"],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": [
          "arn:aws:s3:::<Bucket Name>",
          "arn:aws:s3:::<Bucket Name>/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "dynamodb:PutItem",
      "Resource": [
             "arn:aws:dynamodb:ap-northeast-1:<AWS Account ID>:table/<Table Mame>",
      ]
    },
    {
      "Effect": "Allow",
      "Action": ["SES:SendEmail", "ses:SendRawEmail"],
      "Resource": [
             "arn:aws:ses:us-west-2:<AWS Account ID>:identity/<Domain>",
      ]
    }

  ]
}

TerraformとECS、ECR

Terraformでも、ECS/ECRの操作は出来ますが、今回は2重管理は避けたかったので、 ECS Clusterを作成するところまでをTerraform、 ECS Service作成、Task Difinition作成などは、自作のスクリプトで行っています。 理由としては、task.jsonをterraform管理ではなく、デプロイスクリプト管理にしたかったという理由です

Terraform

Terraformは、開発も活発で結構頻繁にバージョンがあがるのですが、たまにバージョンアップの影響で forces new resourceと強制再構築されそうになる時がたまにあります。
過去の例としては、0.6.14から 0.6.15にあげる際に、 security_groupsではなく vpc_security_group_idsに書き換えないといけない変更が入っていて、planせずに applyすると再構築に....
今例に上げた問題は、CHANGELOG.md#0615-april-22-2016にも書かれているので、見ていれば気付けるのですが、見逃すと怖いですね。

aws_instance - if you still use security_groups field for SG IDs - i.e. inside VPC, this will generate diffs during plan and apply will recreate the resource. Terraform expects IDs (VPC SGs) inside vpc_security_group_ids.

更に言えば、複数のサービスに関わっていると、それぞれを実行したバージョンが違ったりと、毎回最新に追従できていれば問題無いのですが、なかなか出来ない事もあります。

ですので、弊社では誰がどこで実行しても、バージョンによる問題が起きないように、Terraformの実行にも Dockerを使っています。
Dockerイメージは、Docker Hubで公開されている hashicorp/terraformを使っています。

簡単な使い方は、下記のとおりです。

OPTION=''
docker run --rm -v ~/.aws:/root/.aws:ro -v ${PWD}:/work -w /work hashicorp/terraform:0.7.13 plan ${OPTION}
docker run --rm -v ~/.aws:/root/.aws:ro -v ${PWD}:/work -w /work hashicorp/terraform:latest plan ${OPTION}

その他利用可能なバージョンはこちら

このようにして置くことで、違うバージョンで実行してしまって再構築が走ったりするリスクは回避できます。 ※でも頑張って追従しましょうね。

ECS with ALB

いいツールが見つからなかったので、自作しました。

Finally

このような感じで、COJPのリニューアルが完了しました。 Dockerイメージ管理下の、WordPressや、Railsはエンジニアの手が必要ですが、日々運用に置いてはエンジニアレスを実現する事ができました。
また、全てのコンテンツを静的配信化することで、スケールしやすくもなり、負荷に怯える日々もなくなりました。(S3のお金は...)

それに、ECSを使うことで、厳密にはDockerを使う事で、CVEなどの対応時に、稼働中のサーバ更新・再起動して動くか分からないと怯えるという事があったのが、動く事を保証した上で、イメージ再作成も行えるようになりました。 罠は多いですが、

では、皆さんよきECS/ECR生活を(๑•̀ㅂ•́)و✧

レガシーな独自フレームワークから脱却してRailsへ徐々に移行している話

$
0
0

みなさんこんにちわ。 メドピアでエンジニアをやっている内田と申します。

現在メドピアではPHPで作られたレガシーな独自フレームワーク (以下FW) からRailsへと移行するプロジェクトが進んでいます。 今回は移行に向けて行ったことについて共有したいと思います。

移行の計画

メドピア株式会社では、医師限定のコミュニティサイト「MedPeer」を運営しています。 「MedPeer 」サービス内では、薬剤評価掲示版、症例相談、Forum、ニュースなど、医師同士が情報交換をするための、機能の異なる複数のサービスを提供しています。

それらサービスの内部では7年前に作られたPHPの独自FWが採用されており、コードが肥大化したことで機能の変更や追加がとても困難になっていたことが課題でした。

そうした課題を解決するために、アーキテクチャの見直しを含めたリプレースがエンジニアの主導で計画されました。 様々な言語を比較する中で、生産性の高さやコミュニティが活発なところに魅力を感じて言語はRubyへと決め、FWは独自で作るよりもOSSの方がより多くの開発者によって開発が行われているため、新しい機能が使えたり安定性が高いといったメリットがあるだろうと考え、Railsへと移行することに決めました。

しかし、移行するにしても「MedPeer」はとてもモノリシックな上に、機能によってはもはや誰も知らないコードなども存在しており、一筋縄ではいきません。 一度に全てを移行するという方針だと数年はかかりそうです。その間ビジネスを止めるわけにはいかないので、移行の間も新サービスの開発だったり機能の追加を常に行っていく必要があります。

そこで、既存の機能追加は今まで通りレガシーな旧環境で開発しつつ、新サービスやサービスごと移行したいものに関してはRailsの新環境で開発していく、という方針を取ることにして、両環境を運用しながら徐々に移行することにしました。そのためにまずは旧環境と新環境2つを連携して並列稼働させる仕組みが必要でした。

やったこと

具体的にやったことは以下の3つです

  • urlごとの振り分け

  • 共通処理の切り出し

  • データの同期

urlごとの振り分け

urlの振り分けは、nginxを使ったリバースプロキシー先の振り分けです。ドメインは両環境で同じにしてたとえば、/aaaときたurlは旧環境に、/bbbときたurlは新環境にと振り分けるようにしました。 幸いMedPeerは前述の通り複数のサービスで成り立っておりサービスごとにurlが異なり依存度も少ないためこの方法は合っていました。 注意点としては両環境でurlがバッティングしないように設計する必要があったり、同一ドメインであるためcookieの扱いに気をつける必要があることです。

f:id:yutauchida:20170130140305p:plain

共通処理の切り出し

両環境にて共通に処理する必要のある機能が存在します。たとえば認証です。認証したら環境をまたいでもセッションをセキュアに維持できる仕組みが必要です。 これに対しては既にAPIゲートウェイとして認証部分を別のアプリケーションとして切り出していたためそれを新環境でも利用するようにしました。こういった共通処理を切り出しておくと他サービスでも連携しやすいといったメリットがあります。実際にメドピアの他サービス(キャリア、イシコメ)でも認証部分はAPIゲートウェイを利用しており、サービスのスケールに寄与しています。APIゲートウェイを開発した際の話についてご興味ある方はこちらをご参照ください。

Golang(Go言語)を採用して、たった二人で基盤となるAPIゲートウェイを開発した話 - メドピア開発者ブログ

データの同期

両環境ではユーザーテーブルなど共通のデータを利用する必要があります。 当初は現行のDBを共用することも考えましたが以下の理由でやめました。

  • テーブルの設計がRailsに最適化されていない

  • 移行を機にDB構造を見直したい

RailsはDBに対してある一定の規約があります。例えば主キーはidが望ましいといったことです。 旧環境のDBでは主キーがid以外に設定されていたり、テーブル名が複数形になっていないなどRailsを利用する にあたって最適化されていないという問題がありました。また、長年DBを運用していて設計上見直したい部分が多々ありました。例えば 以下の内容です。

  • 不要なテーブル、カラムが存在している

  • カラム名が適切でないため理解しにくい

  • 正規化が適切にされていない

  • indexやunique制約が適切に貼られていない

移行をいい機会としてこれらを見直して再設計したいこともあり、DBは新しく作るという方針を立てました。 そのために2つのDBのデータを同期する必要があったため、両環境のDBを橋渡しする役割のアプリケーションを作りました。 それがDB-SYNCです。

f:id:yutauchida:20170130152525p:plain

DB-SYNCは旧環境のデータを新環境でも使いたい場合にデータを任意の形にコンバートします。例えば旧環境であるユーザーのEmailが更新されたとします。 その際に更新があったことを通知するテーブルに登録されます。DB-SYNCはcron&キューの処理としてそのテーブルをreadして更新のあった対象のテーブル とデータを判別します。その後、旧環境のDBから対象のデータをFetchして、新環境のDBに新しく設計されたテーブルのどのカラムに対応するのか を変換ルールが定義されたファイルを参照しながらUPSERTを行います。変換ルールが定義されたファイルは例えば以下のような内容が記載されます。

user:
  old_table: portal.users
  column_name_mappings:
    id:              old_user_id
    last_login_time: last_logged_in_at
    account_id:      email
    state_cd:        registration_status
    profession_id:   profession_type
    physical_deletable: true
    unsync_columns:
      - id

DB-SYNCでは命名の変更だけでなく、2つのテーブル を統合して再設計した一つのテーブルにデータを登録していくといったことも可能になっています。その他のポイントとしてはデータの流れは 旧環境から新環境への一方向のみとしています。理由としては、両環境でデータが行き来すると煩雑になり障害発生時に原因の究明に時間がかか るのではと考えたのと、逆方向へのデータ同期によりそれを使う機能を開発する要件があるならば、それは新環境での開発を促すべきだというポリシ ーのもとで、そのようにルール化しました。

まとめ

以上のような施策を行い2016年の夏にRailsで作られた新システムがリリースされており、現在は2つの環境が安定して動いています。 徐々に移行することで比較的小さいリリースとなるので大きな障害が起きにくいといった安心感があるのと、Railsでの開発ができるようになったので楽しんで開発しているエンジニアが見られるようになったのはポジティブな要素だと感じています。

また、Railsアプリケーションの立ち上げについてはパーフェクトRuby on Rails共著の前島さんを始め、株式会社 万葉のエンジニアさんなどRailsに知見があるメンバーに推進してもらいました。今後規模が大きくなると予想されるアプリケーションで新しい言語やFWを採用するに あたって経験豊富なエンジニアにしっかりとした土台を作ってもらい、その上で開発していくことが長く運用していくうえで大事だと考えています。

近頃、大規模アプリケーションをRails化しているという案件を聞くことが多くなった気がします。そういった計画を考えている方に弊社 の事例が一つでも参考になれば幸いです。


イマドキのジョブスケジューラについて考える

$
0
0

こんにちは。Ruby化をすすめるメドピアをお手伝いしている@willnetといいます。

メドピアではPHPからRubyに移行するにあたり、単純に言語を置き換えるだけではなく、言語以外の仕組みについても適宜見直しを行っています。今回はそのうちジョブスケジューラを見直した件について書いていきます。

言語を置き換えた話はこちらを参考にしてください。

レガシーな独自フレームワークから脱却してRailsへ徐々に移行している話 - メドピア開発者ブログ

そもそもジョブスケジューラってなに

「毎日1時になったら前日のアクセスログを集計して統計データとしてまとめる」などといった定期的に実行するジョブを登録するためのものです。

ウェブサービスを作るときのジョブスケジューラといったらやっぱりcronですよね。メドピアでもこれまでcronを活用していました。しかしサービスが小さいうちはcronでもそれほど問題ないのですが、サービスが育つにつれだんだん問題が顕になってきます。

cronの問題ってなに

以下列挙します

スケジュールをバージョン管理できない

crontab -eなどで直接crontabファイルを編集した場合、変更の履歴が残りません。つまり以前のバージョンに切り戻したり、過去の履歴を振り返ったりすることができません。

余談ですがcrontab -eを打とうとしてeの隣のrを押してしまい、crontab -rでcrontabの設定を消去してしまった経験のある人はチームに一人くらいいるんじゃないでしょうか。

間違えやすい記法

crontabの記法は一見してわかりづらく、設定ミスをしやすいものになっています。

例えば次のようなcronを設定したとします。

5 2 1 * * /your/batch/command

これは毎月1日の2時5分に/your/batch/commandを実行するcronジョブですが、ぱっと見ただけで理解するのは難しくはないでしょうか。あとは*/10とか1-5,10-20とかの特殊記号を使ったときも、本当にこの書き方で想定通りの時間に実行できるのか不安になります。

cronサーバを分散できない

ウェブサービスで負荷が高まったときには、仮にアプリケーションサーバボトルネックであれば同じサーバを追加(スケールアウト)することで負荷を分散することができます。しかしcronサーバで同じことをすると複数のサーバで同じジョブが同時に実行されることになってしまいます。そのため大抵の環境においてcronサーバは1台だけで運用されているはずです。

開発が進みcronジョブが増えてくると、時間帯によっては複数のジョブが1台のcronサーバで並列に実行されて負荷が異常に高まり、予想しない挙動や障害につながっていきます。

デバッグしづらい

cronの環境と、普段使っているシェルの環境との違いでジョブが失敗することはよくありますが、特にそれを示唆するような出力はないことが多いです。そのためこの手の経験がないと「手元で実行すると動くのになぜかcronだと動かない!なぜ!?」のようにハマります。

ログを追いづらい

cronを実行した際の出力はメールで送信されますが、2017年の現在その機能を使っている方はあまりいないのではないでしょうか。普段は自前でログを頑張って出力したものを保持しておき、エラーが起きたときにはAirbrakeなどのエラー管理システムと連携して通知、ログを漁って原因を究明する…というケースが多そうです。

ジョブごとに成功/失敗のステータスやログがまとまっていると便利ですが、そこまで自前で実装するのは大変ですね。

どうやって問題を解決するか

cronの問題についていろいろ書きましたが、どのようにしたらこれらの問題を解決できるのでしょうか。cronそのものを改善させる方向と、cronをやめる方向で考えてみます。

whenever で cron を改善させる

これまで挙げたcronの問題のうちいくつかはwheneverというgemを利用することで改善可能です。使っている方も多いのではないでしょうか。

wheneverを使うと次のような記法でジョブをファイルに定義し、crontab用の記法に変換して登録することができます。

every 1.day, :at => '4:30 am'do
  runner "MyModel.task_to_run_at_four_thirty_in_the_morning"end

このファイルをバージョン管理しておき、デプロイ時に自動でcrontabを更新するようにします。これで、バージョン管理の問題と、記法の問題を解決できました。

しかし、他の問題は変わらず残っています。

cron をやめる

調べると、cronを置き換えようとするプロジェクトはたくさんあることがわかります。

上記はなるべくRubyで作られたツールの中から選びました。つまりRubyにこだわらなければもっとたくさんの選択肢があるわけです。悩みますね><。

全ての問題を完全に解決するツールは存在しなかったので、次のような観点でツールを選定しました。

  • 適度に問題を解決していること
  • Rubyを使ったプロジェクトであること
  • メンテナンスが続いていること
  • 最悪メンテナンスが停滞しても自分たちでなんとかできること
  • 導入のしやすさ

結果として、sidekiq-cronを採用することにしました。

sidekiq-cron

sidekiq-cronはバックグラウンドジョブを扱うgemであるsidekiqを拡張し、cron的な機能を追加してくれるgemです。

主なメリットとしては、

  • sidekiqに相乗りする形で利用できるので導入が楽
  • ジョブ失敗時にリトライさせることができる
  • sidekiqのワーカーを増やすことで負荷分散できる
  • yamlでスケジューリングを定義するのでスケジュールのバージョン管理ができる
  • sidekiqがweb uiを用意しているので、ジョブの状態を確認できる*1

などがあります。慣れているsidekiqをそのまま使う感覚でいけるのがいいですね。

時間を指定する記法はcrontabと同じだったり、詳細なログ出力は独自で頑張る必要がありますが、cron単体の時よりはだいぶ前進できた気がします。

将来的にどうするか

sidekiq-cronは本番導入されており、今の段階では特に問題はないのですが、遅くても数年したら乗り換えを検討する必要がありそうです。

例えば複数のRailsアプリを管理するようになった場合に、複数アプリを横断したジョブスケジューラを管理したいという要望にはsidekiq-cronでは応えられません。また、ジョブの数が数十〜百になった場合にジョブをどうやって管理するかも悩ましいところですし、ログの出力が弱いことが問題となるケースも今後出てくるでしょう。

と、そんな折に昨年OSS化されたkuroko2を軽く触ってみたところ、なかなか良さそうでした*2。次回乗り換えを検討する際の有力な候補となりそうです。

kuroko2の詳細は以下のリンクを参考にどうぞ。

まとめ

開発しているアプリケーションの規模によって適切なツールは変わってきます。小規模なアプリケーションのジョブスケジューラであればcronはまったく問題ないと思いますが、アプリケーションが成長していくにつれてより適したツールに乗り換えていく必要があるでしょう。このエントリが次のツール選定の参考になれば幸いです。

*1:sidekiqの仕様上、WebUI上にログはほぼ残らないので、sidekiq-failuresを利用して失敗したジョブのエラーを閲覧できるようにしています。

*2:ツール選定当時はOSS化されていなかった

2泊3日の開発合宿 in 湯河原 おんやど 恵に行ってきました!!!!

$
0
0

こんにちは。メドピアに入社してもうすぐ1年の栢割(カヤワリ)です。
そろそろ開発合宿の時期がやって参りました!!!!弊社は2013年から開発合宿を定期的に行っており、今回で第8回目になります。
(自分は第6回目にも参加し、ブログも書かせて頂きました。)

tech.medpeer.co.jp
今回も私の方から合宿の様子を書かせていただきます。よろしくお願いします!!!!

合宿テーマ

開発合宿のテーマは毎回異なり、技術研鑽や技術的負債の解消、他にも新規サービス立ち上げ等を行ったこともあります。過去の開発合宿では新規サービス立ち上げをテーマに実施され、弊社のサービスとして事業化されているものもあります。

合宿に行く前に各自、取り組む内容について宣言してから開発します。今回はこんな感じ。

  • 社内の業務改善ツール開発
  • これまで懐に温めてきた医療系サービス開発
  • メドピア既存サービスに新機能追加
  • 話題の新技術を使って開発

取り組み方は自由でチームを組んでも良し。個人でモクモクと取り組むも良し。特に取り組み方について制約は無いので、自身が取り組みたかった開発がしやすいです。
しかし、ただ開発するだけでは自己満足で終わってしまうので、今回は最終日に会社へ戻り、成果発表LTをすることになりました。

エンジニアに嬉しい旅館「湯河原 おんやど 恵」さん

f:id:takayukikayawari:20170314143607j:plainさっそく足湯に浸かっている写真ですが、これを見てピンときた方いるのではないでしょうか?
今回の合宿は湯河原の「おんやど 恵(めぐみ)」さんにお邪魔しました。SE出身の社長さんが経営されている旅館で、エンジニアに嬉しい「開発合宿プラン」などが用意されています www.onyadomegumi.co.jp
当日は湯河原駅に直接集合!湯河原駅からタクシーで5~10分程度で到着しました。 f:id:takayukikayawari:20170314143650j:plain

お部屋が空くまで少し時間があったので、先に会議室へ…。
かなり広い会議室でした。合宿参加者は7人でしたが、この人数には広すぎるくらい。電源タップ、スピーカー、USB充電器、おやつ、ホワイトボード、腰が痛くならないグッズ等、設備品も充実してました。食事をしてる最中も、旅館の方が会議室の飲み物やお菓子の補充してくれます。ありがたや〜。(余談ですが、ディスプレイのレンタルプランもあるそうです。) f:id:takayukikayawari:20170315100928j:plain

基本的にお食事、お風呂、寝る時以外は開発に没頭します!!レッドブルかまして夜遅くまで開発するメンバーもいました。 f:id:takayukikayawari:20170314180547j:plainf:id:takayukikayawari:20170315104859j:plainお菓子も摘みながらモクモク…。 f:id:takayukikayawari:20170314180820j:plain

開発の合間には戦士の休息…。足湯でリフレッシュします。気持ちいいべ〜。もちろん温泉もあります。露天風呂は最高でした。 f:id:takayukikayawari:20170314181211j:plain

美味しいご飯レポート

旅館のご飯も美味しかったです。
(※ココから美味しそうな写真が続くので、ご飯時に見るとお腹が空いてきます。ご注意ください。)f:id:takayukikayawari:20170314181544j:plainf:id:takayukikayawari:20170314182243j:plain:w290f:id:takayukikayawari:20170314183252j:plain:w290f:id:takayukikayawari:20170314183653j:plain:w290f:id:takayukikayawari:20170315183027j:plain:w290f:id:takayukikayawari:20170314183733j:plain:w290f:id:takayukikayawari:20170314183755j:plain:w290
宿の近くにあったラーメン屋さん。あさりラーメンが美味しかったです。 f:id:takayukikayawari:20170314182140j:plainf:id:takayukikayawari:20170314182005j:plain
お店を探している道中……
弊社のRailsプロジェクト開発で大変お世話になった株式会社 万葉さんが…!!??   f:id:takayukikayawari:20170314184527j:plain
……………お茶屋さんでした…。

そして最終日…成果発表へ!!

2泊3日の合宿を終え、成果発表のために会社へ戻ります。 f:id:takayukikayawari:20170314184148j:plain

お昼過ぎに会社に到着し成果発表LTをしました。弊社代表の石見をはじめ、エンジニア以外の方々も見に来てくれました。 f:id:takayukikayawari:20170315175325j:plain

まとめ

開発合宿は普段の業務でなかなか取り組めないタスクや負債を潰したり、新しい技術に集中して取り組める絶好の機会だと思います。 また、2泊3日という短い時間で成果を出す必要があるので、自身がこの短期間にどこまで出来るかを確認できる良い機会でもあると思います。次回も是非参加して、成果発表LTで皆さんに「おぉ!!」と驚かれる物を作りたいです…。

以上!2泊3日のメドピア開発合宿 in 湯河原 おんやど 恵の様子でした。

form objectを使ってみよう

$
0
0

こんにちは。メドピアのRuby(Rails)化をお手伝いしている@willnetです。

Ruby化のプロジェクトが始まって1年が過ぎました。新しいメンバーも入り、Railsのコード量は日に日に多くなっています。可読性を保ちつつアプリケーションを大きくしていくために、使える知見をチームメンバーに効率よく伝えていくのが大事だと感じる今日このごろです。

普段メドピア内ではコードレビューや社内勉強会などで知識のシェアを行っています。そんなとき、ブログ記事や書籍などのまとまった文章があると「これ読んでおいて」と言うだけで良くなるので楽です。先日form objectを使ったほうがいいですよーという内容でレビューコメントをつけようとしたところ、日本語で詳しくまとまった文章が見当たりませんでした><なければ自分で書くしかありません。そこで今回はRailsにおいて可読性を保つための知見である、form objectについてまとめたいと思います。

form object って何?

form_withのmodelオプション*1にActive Record以外のオブジェクトを渡すデザインパターンです。form_withのmodelオプションに渡すオブジェクト自体もform objectと呼びます。

利点は大きく次の2点です。

  • DBを使わないフォームでも、Active Recordを利用した場合と同じお作法を利用できるので可読性が増す
  • 他の箇所に分散されがちなロジックをform object内に集めることができ、凝集度を高められる

具体例を見ていきましょう。

具体例

Rails 5.1.0でサービス管理者にフィードバックを返すフォームを作ってみます。フィードバック内容をデータベースに保存したい場合、素直に書くと次のようになるでしょう*2

classFeedback< ApplicationRecord
  validates :title, :body, presence: trueendclassFeedbacksController< ApplicationControllerdefnew@feedback = Feedback.new
  enddefcreate@feedback = Feedback.new(feedback_params)
    if@feedback.save
      redirect_to home_path, notice: 'フィードバックを送信しました'else
      render :newendendprivatedeffeedback_params
    params.require(:feedback).permit(:title, :body)
  endend
<%= form_with model: @feedback, local: truedo |f| %><%if@feedback.errors.any? %><%@feedback.errors.full_messages.each do |message| %><%= message %><%end%><%end%><%= f.label :title%><%= f.text_field :title%><%= f.label :body%><%= f.text_area :body%><%= f.submit %><%end%>

ここでフィードバックをデータベースに保存せずに、サービス管理者へメールを送信するようにコードを変更してみましょう。データベースに保存しないので、ApplicationRecordを継承したモデルは使用しません。

そうしたときにこんな感じのコードを書いてしまう人が多いのではないでしょうか。

classFeedbacksController< ApplicationControllerdefnewenddefcreateif params[:title].present? && params[:body].present?
      AdminMailer.feedback(params[:title], params[:body]).deliver_later
      redirect_to home_path, notice: 'フィードバックを送信しました'else@error_messages = []
      @error_messages<< 'タイトルを入力してください'if params[:title].blank?
      @error_messages<< '本文を入力してください'if params[:body].blank?
      render :newendendend
<%= form_with url: feedbacks_path, local: truedo%><%@error_messages&& @error_messages.each do |message| %><%= message %><%end%><%= label_tag :title%><%= text_field_tag :title, params[:title] %><%= label_tag :body%><%= text_area_tag :body, params[:body] %><%= submit_tag %><%end%>

少し乱雑なコードになりました。まず、コントローラにロジックを記述してしまっています。また、独自のお作法でフォームを作成したため、ビューとコントローラを両方読まないとどのように動くのか、すぐに理解することができなくなりました。例えばビューとコントローラで@error_messagesという変数を利用していますが、どのような形式で情報が格納されているのか、どのように使用したらいいのかはビューとコントローラ両方を読まないと判断できません。

今回の例は項目数が少なく簡単なため、この程度であれば問題ないと判断する方もいるかもしれません。しかし、フォームの入力項目数が増えたり種類(セレクトボックス、チェックボックスラジオボタン)が増えたときのことを想像してみてください。コントローラが肥大化して読みづらくなり、ビューにどんなときに何が描画されるのかもわかりづらく、修正するのが大変になりそうですね。

これを解決するためにform objectを利用してみます。

form object を利用した例

form objectとして、ActiveModel::Modelをincludeしたクラスを用意します。 Active Modelは、Active RecordからDBに依存する部分を除いた振る舞いを提供しているライブラリです。これを利用することにより、DBを利用しないフォームでもActive Recordを利用したときと同じような記述をすることができます。

コードを見てみましょう。

classFeedbackincludeActiveModel::Modelattr_accessor:title, :body

  validates :title, :body, presence: truedefsavereturnfalseif invalid?
    AdminMailer.feedback(params[:title], params[:body]).deliver_later
    trueendendclassFeedbacksController< ApplicationControllerdefnew@feedback = Feedback.new
  enddefcreate@feedback = Feedback.new(feedback_params)
    if@feedback.save
      redirect_to home_path, notice: 'フィードバックを送信しました'else
      render :newendendprivatedeffeedback_params
    params.require(:feedback).permit(:title, :body)
  endend
<%= form_with model: @feedback, local: truedo |f| %><%if@feedback.errors.any? %><%@feedback.errors.full_messages.each do |message| %><%= message %><%end%><%end%><%= f.label :title%><%= f.text_field :title%><%= f.label :body%><%= f.text_area :body%><%= f.submit %><%end%>

Feedbackクラスを除けば、Active Recordを利用した例と全く同じコードになりました。

このようにform objectを利用すると、Active Recordを利用した場合と同じお作法でビューとコントローラを記述することができて大変便利です。

個人的には、form_withでは必ずmodelオプションを利用する(form_tagを使わずにform_forを使う)という規則をチーム内に作ってしまってもよいのではないかと考えています。

その他の利用例

ここまでの文章を読むと「form objectはActive Recordを使わないフォームでだけ利用する」という理解をしてしまう方が多いのではないでしょうか。しかしform objectはActive Recordを利用する場合にも使えます。

例えば次のようなUserモデルがあるとします。

classUser< ApplicationRecord
  has_secure_password

  validates :email,
            presence: true,
            format: { with: /\A.+@.+\z/ }
  validates :password, length: { minimum: 6 }, on: :create

  after_create_commit :send_welcome_maildefsend_welcome_mailUserMailer.welcome(self).deliver_later
  endend

よくある形だと思いますが、passwordのバリデーションやコールバックは、ユーザ作成時以外は必要のないものです。emailのバリデーションもユーザ作成時とメールアドレス変更時だけ必要なものなので、常に必要というわけではありません。

こんな時にform objectを利用してUserモデルに書かれたロジックを外出しすることができます。

classSignupincludeActiveModel::Modelattr_accessor:email, :password, :password_confirmation

  validates :email,
            presence: true,
            format: { with: /\A.+@.+\z/ }
  validates :password, presence: true, length: { minimum: 6 }, confirmation: { allow_blank: true }

  defsavereturnfalseif invalid?

    user = User.new(email: email, password: password, password_confirmation: password_confirmation)
    user.save!
    UserMailer.welcome(user).deliver_later
    trueendend
classUser< ApplicationRecord
  has_secure_password
end

Userモデルがスッキリしましたね。

他にも、複数のActive Recordを一度に保存するようなフォームなどでもform objectを活用することができます。

form objectをもっと活用する

これまでの例では、すべてのform objectにActiveModel::Modelをincludeしてきました。簡単なコード例なのでこれで十分だと思いますが、実際の現場でのコードはもっと複雑になります。そんなときActive Modelだけだと少し面倒に思う部分も出てくるはずです。

例えば先程のユーザ登録用のform objectに、サービス運営側からのダイレクトメールを受け取るか否かのチェックボックスを追加したとしましょう。Railsのビューヘルパーであるcheck_boxメソッドはチェックした場合は文字列の"1"、チェックしない場合は"0"がパラメータとして送信されてきます。Active ModelはActive Recordとは異なり、属性の自動的なキャストは行いません*3。booleanとして扱いたい場合、キャストの処理は自分で書く必要があります。

classSignupattr_accessor:email, :password, :password_confirmation, :accept_dm# 略defaccept_dmActiveRecord::Type::Boolean.new.cast(@accept_dm)
  endend

signup = Signup.new(accept_dm: '1')
signup.accept_dm #=> true
signup = Signup.new(accept_dm: '0')
signup.accept_dm #=> false

独自にキャストしないといけない属性の数が多かったり、種類が豊富だったりしたら面倒ですね。そんなときに使える、属性の型を定義できるgemやform objectのためのgemがいくつか存在するので、form objectをバリバリ使ってみたくなった方は一度目を通しておくと良いと思います。

まとめ

form objectの利点や使いどころについて一通り解説しました。

form objectという概念自体は4~5年ほど前から広まっているはずなのですが、日本語でのまとまった文章がないことから、日本ではあまり普及していないような気がします。このエントリがform object普及の一助になって、読みやすいRailsのコードが少しでも増えることを願っています。


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

*1:もしくはform_for

*2:ビューテンプレートのデザインは省略しています

*3:カラムの型情報がないので当たり前ですが

【初心者向け】レビュワーをイライラさせるRSpec集と解決方法

$
0
0

こんにちは。メドピアにjoinして3ヶ月目の保立です。
毎週のように新しい開発が進むため、毎日楽しくソースコードを書かせてもらっています。

テストコードを制するものは、Railsを制す!!!
ということで、今回はメドピアのRSpecについてです。
メドピアでは、RSpecを用いてテストコードを書いており、
- 1) models配下に記載するビジネスロジックに対するUnitTest
- 2) 機能ごとのEndToEndTest (E2E Test)
の2種類のテストコードを書いています。

RSpecについて、書こうと思ったきっかけ

RSpecは(というかRuby自体が)様々な書き方で動かすことができるため、統一したルールがないと、書いた人によってバラバラなテストコードになります。
私も初めてRSpecを書いた際に、参考書やソースコードによって書き方がバラバラで、どのRSpecを参考にすればよいのか迷いました。
RSpecソースコードの仕様を理解できると、レビュワーや後から開発する人のストレスが軽減できますが、テストコードごとのフォーマットがバラバラだと、テストコードを理解するのに、時間がかかってしまいます。(イライラする人が出てきちゃいます)
そこで、今回は、メドピア内部のルールや、今まで指摘を受けたこと、私が気をつけていることをまとめていきます。


目次

  1. describe/context/itのフォーマットが統一されていない
  2. インスタンス変数を使用している
  3. letで定義した変数名が、何を表しているかわからない
  4. beforeブロック内でテストを行う
  5. テスト対象が同じ、複数のテストケースで、subjectが使われていない
  6. 環境や時間によって失敗する

1. describe/context/itのフォーマットが統一されていない

テスト対象、条件、振る舞いが決まったところに決まったフォーマットで記載されていれば、そのテストコードが何を表しており、実際のソースコードがどのような仕様なのかを簡単につかむことができます。
下記は、フォーマットが統一されていない例です。

# contextが日本語だったり英語だったり式だったり
describe 'valid?'do
  context '非公開のとき'
  context 'publish'
  context 'expired < Time.now'end# itに条件が書かれている
describe 'valid?'do
  it '◯◯で△△のとき、回答が登録されること'end

テストの条件や結果のフォーマットが異ならないように、メドピアでは、以下のように記載しています。

内容 書き方
describe テストの対象 #インスタンスメソッド名
.クラスメソッド名
〜画面 〜処理(E2Eテスト)
context テストの前提条件 〜のとき
it テストの結果 〜されること 〜となること

また、なるべくcontextやitでは主語を書くようにしています。
context '非公開のとき'ではなく、context '質問が非公開のとき'のように書いています。

2. インスタンス変数を使用している

RSpecでは「インスタンス変数」(@から始まる変数)を使わず、「let / let!」を使って、テストで使用する変数を定義します。

# インスタンス変数を使用する
describe 'valid?'do
  before do@question = create(:question)
  end

  it 'is valid'do
    expect(@question).to be_valid
  endend

「let / let!」を使うと、以下のように書き換えることができます。

describe 'valid?'do
  let(:question) { create(:question) }

  it '質問が登録されること'do
    expect(question).to be_valid
  endend

インスタンス変数を使わない方がいい理由については、以下のリンクをご参考ください。


3. letで定義した変数名が、何を表しているかわからない

letで「インスタンス変数」の代わりに、テストを行う変数を定義できます。 しかし、定義した変数名が何を表すかが一目でわからないと、わかりやすいテストコードとは言えません。

describe 'valid?'do
  let(:question_1) { create(:question, title: 'タイトル') }
  let(:question_2) { create(:question, title: nil) }
  
  it 'タイトルが設定されている質問が、登録できること'do
    expect(question_1).to be_valid
  end

  it 'タイトルが設定されていない質問が、登録できないこと'do
    expect(question_2).to be_invalid
  endend

このように、モデル名_1, 2 … というように変数名を設定すると、後から読んだ人は、どの変数が何を表しているのか理解するのに時間がかかってしまいます。
letで宣言する変数名には、以下のように何を表しているのかわかりやすい変数名を選ぶことを心がけています。

describe 'valid?'do# 基本パターンの場合、model名をそのまま変数名にすることが多いです。
  let(:question) { create(:question, title: 'タイトル') }
  # 例外パターンの場合、model名の後に例外内容を表す変数名にします。
  let(:question_without_title) { create(:question, title: nil) }
  
  it 'タイトルが設定されている質問が、登録できること'do
    expect(question).to be_valid
  end

  it 'タイトルが設定されていない質問が、登録できないこと'do
    expect(question_without_title).to be_invalid
  endend


4. beforeブロック内でテストを行う

beforeは、テストの前提条件を用意するためのブロックです。
しかし、beforeがやるべきでないテストの実施を、beforeブロックに書かれていることもあります。

context '登録ボタンをクリックしたとき'do# before内でテストしているケース
  before do
    within '#button'do
      expect(page).to have_css '.disabled'# 非活性であることをチェックend
    fill_in 'name', with: '山田太郎'
    within '#button'do
      expect(page).to have_css '.enabled'# 活性であることをチェックend
    click_on '登録する'end# 登録処理のテストが続く 
  it '登録されること'
  it '画面遷移すること'end

上記の例のように、beforeでテストをしてしまうと、テストの前提条件がどこで何をテストしているのかわかりづらいだけでなく、before内のテストで失敗した際に、後続のテストが行われなくなってしまいます。

5. テスト対象が同じ、複数のテストケースで、subjectが使われていない

subjectを使用すると、そのメソッド内のテスト内容を一括で設定することができます。

# subjectを使わない
describe '#human_time_distance'do
  context '現在時刻と一致するとき'do
    let(:from_time) { now }
    it { expect(helper.human_time_distance(from_time)).to eq '' }
  end

  context '現在時刻より前の時刻のとき'do
    let(:from_time) { now - 1.second }
    it { expect(helper.human_time_distance(from_time)).to eq '過去' }
  end

  context '現在時刻より後の時刻のとき'do
    let(:from_time) { now + 1.second }
    it { expect(helper.human_time_distance(from_time)).to eq '未来' }
  endend

expect(helper.human_time_distance(from_time))subjectにまとめると以下のようになります。

describe '#human_time_distance'do
  subject { helper.human_time_distance(from_time) }
  
  context '現在時刻と一致するとき'do
    let(:from_time) { now }
    it { is_expected.to eq '' }
  end

  context '現在時刻より前の時刻のとき'do
    let(:from_time) { now - 1.second }
    it { is_expected.to eq '過去' }
  end

  context '現在時刻より後の時刻のとき'do
    let(:from_time) { now + 1.second }
    it { is_expected.to eq '未来' }
  endend

特に、メソッドの返却値のテストやバリデーションのテストでは、積極的にsubjectを用いて、DRYなテストコードを心がけています。

6. 環境や時間によって失敗する

ローカルでテストした時には動いたけど、特定の環境ではたまに上手くいかないテストケースもよくあると思います。
メドピアでも、ローカルではうまくいくのに、CI上では3, 4回に1回くらい失敗するテストがありました。 以下の例は、メドピアでも比較的多かったケースです。

# let!でmodel作成後、テストまで時間がかかるケース
let!(:question_published) { create(:question, published_at: Time.current) }
let!(:question_non_published) { create(:question, published_at: Time.current + 1.second) }

before do# 色々処理が書かれる# 色々処理が書かれるend

it '質問が表示されること'do# 色々テストが書かれる# 色々テストが書かれる

  expect(question_published).to be_valid
  expect(question_non_published).to be_invalid
end

上記の例では、published_atが現在時刻以降のquestionのみ表示するテストですが、1秒後に公開されるquestion_non_publishedを作成してからテストするまでにタイムラグが生じ、たまにうまくいかないことがあります。
このような場合は、テストを分割するか、1.secondをもっと大きな値に変更することで、解決できます。
常に成功するテストでないと、プロジェクト全体の生産性に影響を与えるので、現在の環境で常に動くテストコードを書く必要があります。



おわりに

今まで指摘されたこと、気をつけていることをまとめました。
他にも、Better Specsなどを見て、勉強しています。 たかがテストコードですが、テストコードが読みやすいかどうかは、企業の文化によって大きく違うと思います。
RSpecのテストコードを書いたり、レビューする一助になれば幸いです。


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

NextCloudを導入してみた

$
0
0

皆様こんにちは。
今回は、社外の取引先様等に重要な情報を安心して受け渡しするツールが決まってなかったので、NextCloudを導入してみたエントリーとなります。

以下、これまでの課題となります。

  • 手渡しとか郵送はコストが掛かりすぎてしまう
  • メールは平文のため危険、添付ファイルを暗号化してパスワードは別メールにて送信も危険となる
  • 各種インターネットサービスは適切に管理されてるか監督が難しい、機能が多すぎて誤操作と脆弱性が心配、受け取り側がアカウントを作成して頂かなければいけない場合等々

NextCloudとは、ファイルホスティングサービスを構築するための、PHPJavascriptで書かれたオープンソースソフトウェアとなります。機能的に似たようなサービスとして比較されるのはdropboxとなります。元々ownCloudという名前で開発されていたのですが、方針の食い違いによりフォークされてNextCloudとなったようです。

要件

はじめに、要件のまとめとなります。

  • 弊社社員ユーザーが認証され、オフィスからブラウザーでアクセスしてファイルをアップロードできる
  • アップロードされたファイルは暗号化され、アップロードした社員と指定された(取引先様などの)社外ユーザだけがアクセスできる
  • (取引先様などの)社外ユーザは認証され、ブラウザーでアクセスして復号されたファイルをダウンロードできる
  • ファイルのダウンロードは指定された日数をすぎると無効になる

構成

下記、構成の詳細となります。

Amazon Elastic Load Balancing

  • ポート443:インターネットからのアクセス(SSL)を受け入れ、nginxにフォワードします
  • ポート8000:オフィスからのアクセスを受け入れ、nextcloudにフォワードします

Amazon EC2

nginx・fail2ban

https://nextcloud.hogehoge.co.jp/index.php/s/IWpva4t3FoYgnkSのようなNextCloudのURLリンクでの共有で、ブラウザーでアクセスされるURLリンクのパスだけをプロキシして、NextCloudに投げます。nginxのコンフィグファイルの内容は以下となります。

server {
    【省略】
    # /index.php/heartbeat
    # /index.php/s/*
    # /index.php/s/*/authenticate
    # /index.php/s/*/download
    # /index.php/core/js/*.js
    # /index.php/apps/files_sharing/ajax/publicpreview.php
    # /core/img/*
    # /core/css/*.css
    # /core/fonts/*.woff
    # /core/js/*.js
    # /core/vendor/*.css
    # /core/vendor/*.js
    # /apps/encryption/*.js
    # /apps/files/*.js
    # /apps/files_sharing/*.css
    # /apps/files_sharing/*.js
    #
    location ~* ^(/index\.php/(heartbeat|s/[A-Za-z0-9]+(/authenticate|/download)?|core/js/.+\.js|apps/files_sharing/ajax/publicpreview\.php)|/core/(img/.+|css/.+\.css|fonts/.+\.woff|js/.+\.js|vendor/.+\.(css|js))|/apps/(encryption/.+\.js|files/.+\.js|files_sharing/.+\.(css|js)))$ {
        proxy_pass http://nextcloud;
        proxy_set_header Host $http_host;
    }
}

これで共有されたファイルがブラウザーでダウンロードできるギリギリのパスだけを許可となります。それと、fail2banでnginxに来たアクセスを監視して、不自然なアクセスが有ればブロックとなります。

NextCloud

NextCloudで行った設定は下記となります。ブラウザーにて管理者のアイパスでログインし、管理画面から設定が可能となります。

  • 共有
    • URLリンクでの共有を許可する
      • 常にパスワード保護を有効にする
      • 有効期限のデフォルト値を設定する
  • 暗号化
    • サーバーサイド暗号化
      • サーバーサイド暗号化を有効にする
  • 追加設定
    • Password policy
      • Minimal length
      • Forbid common passwords
      • Enforce upper and lower case characters
      • Enforce numeric characters
      • Enforce special characters

この画面にて、ユーザーがパスワードを忘れてしまった場合に、ファイルを復元するためのリカバリキーを設定できるのですが、このNextCloudはあくまで一時的なファイル置き場のため、リカバリキーの漏えいのリスクを心配してあえて設定しません。それと、アプリの管理画面から不要と思われる大量のプラグインを無効化しました。NextCloudは、モバイル・デスクトップクライアントからアクセスできたり、外部ストレージに接続できたり、リッチなファイル閲覧・編集など、豊富な機能が特徴の一つですが、要件以外の機能は、リスクを減らすため徹底して排除となります。

Amazon RDS

NextCloudからログインする普通のMySQLデータベースとなります。

利用シナリオ

利用時のシナリオとなります。

【社員ユーザー】
①会社のパソコンのブラウザーでhttps://nextcloud.example.com:8000/を開きます
②IDとパスワードを入力してログインします
③画面内の「+」をクリックし「アップロード」を押下します
④ダイアログボックスで、アップロードするファイルを選択します
⑤共有するファイルの「共有アイコン」を押下します
⑥「URLで共有」をチェックします
⑦「URLによる共有のパスワード」を入力します
⑧表示されたURLリンクを共有先にメールで通知します
⑨パスワードをメール以外の安全な方法で共有先に通知します

【受け取り側ユーザー】
①通知されたURLリンクをブラウザーで開きます
②通知されたパスワードを入力してログインします
③ファイルをダウンロードします

Amazon EC2インスタンスのアップデート

yum-cron-securityパッケージをインストールしておくことで、自動的に1日1回、インストール済みのパッケージにセキュリティーアップデートがないかチェックされ、有れば、自動的にインストールとなります。それで、システムにトラブルが起きる可能性も有りますが、社内用ツール&一時的ファイル置き場ということで割り切ります。 カーネルがアップデートされてたら、再起動が必要なので、こちらからお借りしたスクリプトで実施となります。

おまけ

https://nextcloud.com/security/advisories/のページの情報を監視して、更新があったら、対応します。

以上となります。

弊社にてNextCloudを導入した件について紹介させて頂きました。導入後は社内のニーズに対し、取り急ぎこのツールに誘導できるようになりました。


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

Viewing all 210 articles
Browse latest View live