TECH PLAY

株式会社スタメン

株式会社スタメン の技術ブログ

247

はじめに こんにちは、スタメンで iOS/Android アプリのエンジニアをしている @temoki です。 昨年の10月にスタメンにジョインしてからの私の最初のミッションは、 TUNAG iOS アプリのチャット機能の開発プロジェクトでした。本記事ではこのチャット機能開発プロジェクトにおいて採用した VIPER というアーキテクチャについて紹介し、チャット機能の初版リリースからいくつかの機能改善を経た現在における所感をお伝えしたいと思います。 VIPERの概要 VIPERとは? 私が VIPER を知ったのは objc.io の Architecting iOS Apps with VIPER という記事です。これを読めば VIPER について一通り理解することができますが、端的に説明するために次の文を引用します。 VIPER is an application of Clean Architecture to iOS apps. The word VIPER is a backronym for View, Interactor, Presenter, Entity, and Routing. Clean Architecture divides an app’s logical structure into distinct layers of responsibility. This makes it easier to isolate dependencies (e.g. your database) and to test the interactions at the boundaries between layers: つまり次のとおりです(日本語に翻訳しただけですが)。 VIPER は iOS アプリケーションに Clean Architecture を適用したもの VIPERという言葉は、 V iew、 I nteractor、 P resenter、 E ntitiy および R outing を表す バクロニム アプリケーションの論理構造を責任の異なるレイヤーに分割することで(データベース等の)依存関係を分離し、レイヤー間の境界で相互作用をテストすることが簡単になる VIPERのメインパーツ VIPER は次の5つのパーツに分類され、それぞれが明確な責務を持ちます。 V iew Presenter からの指示を表示する ユーザー入力を Presenter に伝える I nteractor アプリケーションの中の一つのユースケース(画面単位など)を表現する データ操作とビジネスロジックを含む P resenter Interactor から受けとったデータを表示するための準備を行う ユーザー入力に反応して Interactor に新しいデータを要求する このようなビューロジックを含むが、iOS の UIKit には依存しない E ntity Interactor のみに使用される単純なモデルオブジェクト R outer 画面がどのように表示されるかの画面遷移ロジック その表示する画面の生成(依存性注入) これは Passive View 方式の MVP (Model View Presenter) アーキテクチャがベースになっており、大きくなりがちな Presenter から画面遷移と生成の責務を Router に、そして画面固有のユースケースを Interactor に分離しているのが特徴的です。 それぞれのパーツのつながりは下図のようになります( objc.io の記事からの引用)。 TUNAG アプリへの適用 ここからは TUNAG iOS アプリへの VIPER 適用についてお話していきます。 なぜ VIPER を採用したのか? TUNAG のチャットは複数の画面で構成されますが、メインとなるチャットルーム画面のスクリーンショットがこちらです。 メンション、絵文字、スタンプ、写真投稿、ファイル添付などたくさんの機能があります。TUNAG のチャットは簡易的なものではなく、メンバー間の日常コミュニケーションやビジネス用途としても利用されるものですので、この1つの画面だけでもとても多くの表示パターンと入力パターンがあります。そして TUNAG という事業は長く継続していくものですので、今後の変更などのメンテナンスのことを考えると最初から適切にモジュールを分割していく必要があります。 VIPER は特定のプラットフォームやライブラリに依存しているものではなく、オブジェクト指向でアプリケーションを適切に設計していくためのガイドラインのようなものです。そのため、開発チーム内でモジュール分割の方針を共通認識として開発を進めていけると感じたため、VIPER を採用することにしました。 また、開発チームには開発経験の少ない若手メンバーもおり、VIPER での構築を実践していくことでメンバーの設計力向上につながるのではないかという期待もありました。 TUNAG アプリの VIPERによる構成 最終的に、TUNAG アプリの構成は下図のようになりました。各パーツで <~> と記述しているものは、そのパーツが準拠する Protocol です。それぞれのパーツは、その Protocol により接続されることになります。 View, Presenter, Interactor, Router は基本的に画面単位に用意します。画面を開発するときは、その画面における各パーツの役割を Protocol で定義し、それらを Contract (契約) としてまとめたファイルを作るようにしました。こうすることでこの画面の機能がまとまったカタログができます。以下が、タスクリスト画面を作るとした時の Contract の例となります。そして、Router がそれぞれの依存性を注入し、まとめて一つの画面モジュールとして組み立てます。 /// タスクリスト画面 View protocol TaskListView : class { // Dependency var presenter : TaskListPresentation! { get } /// タスクリストの読み込み中の表示をする func showLoadingState () /// タスクリストを表示する func showTaskList (_ taskList : [ TaskCellModel ] ) /// タスクリストが一つもない状態を表示する func showEmptyState () } /// タスクリスト画面 Presentation protocol TaskListPresentation : Presentation { // Dependency var view : TaskListView? { get } var interactor : TaskListUseCase! { get } var router : TaskListRouter! { get } /// タスクセルがタップされた func taskCellDidTap (taskId : Int ) /// タスク追加ボタンがタップされた func addTaskButtonDidTap () } /// タスクリスト画面 UseCase protocol TaskListUseCase : class { // Dependency var output : TaskListInteractorOutput? { get } /// すべてのタスクリストを取得する func fetchAllTaskList () } /// タスクリスト画面 InteractorOutput protocol TaskListInteractorOutput : class { /// すべてのタスクリストを出力する func outputAllTaskList (_ taskList : [ TaskModel ] ) } /// タスクリスト画面 Wireframe protocol TaskListWireframe { /// タスクリスト画面のビューコントローラー var viewController : UIViewController? { get } /// タスク作成画面を表示する func presentCreateTaskView () } VIPER による基本構成に加え、データレイヤーには Repository パターンを適用しています。TUNAG のチャット機能におけるデータソースは、 Firebase の Cloud Firestore、TUNAG の REST API、ローカルストレージと多岐にわたるため、Repository パターンによりそれらを隠蔽しています。Repository は特定のユースケースに依存せず、例えばユーザー情報やチャットメッセージ情報のようなシンプルなデータの CRUD のみを責務とするため、複数の画面から使用されます。 また、Cloud Firestore にあるユーザー情報やチャットメッセージ情報などのリアルタイムアップデートを受け取り、それらをマージして Interactor に流していくために RxSwift を使用しています。MVVM アーキテクチャーを採用しているわけではないですし、OS 標準ではない特定のライブラリにアプケーション全体が依存することを避けるため、RxSwift は Interactor より先のプレゼンテーション層では使用しないようにしています。 良かった点/悪かった点 このような構成で2018年10月からチャットの開発をはじめ、2019年1月初に初版をリリースしました。その後細かいアップデートをしつつ、この4月にはチャットメッセージの検索のような大きな機能追加の開発を行なっています。VIPER を採用した開発を始めて半年経った現時点における、個人的に良かった・悪かったと感じている点を箇条書きでまとめます。 良かった点 画面実装時に前述の Contract ファイルを用意することで、どのパーツは何に関与して、何に関与しないのか?と常に考えながら実装していくことになり、自分もチームメンバーもオブジェクト指向での設計力が格段に上がったという実感があった(よくよく考えてみると SOLIDの原則 を忠実に実践していくことになっていた) 各パーツの責務が明確で、それぞれの間が Protocol により疎結合になっていることで、ユニットテストがとても書きやすい チャットの開発は最低限の機能を実装し、徐々に肉付けしていくスタイルで行なっていったが、足し算が非常にやりやすい 機能追加後のコードの Diff も何の機能追加や変更を行なったかがとてもわかりやすかったため、PR のレビューもしやすい 初版リリースの前にQAチームによるテストや社内でのβテストを実施したが、機能のボリュームのわりには不具合検出数が非常に少なかった 悪かった点 1つの画面を作るのに多くのクラスのファイルが必要であるのはやはり面倒だった( 例えば Swift-VIPER-Module のような自動生成ツールを試すべきだった) これは VIPER の問題ではなく私の問題だが、初版リリース時に部分的にしかユニットテストを書くことができなかった(ただし、その後徐々にテストを追加していっているが、実装コードを変更せずともテストを書けていけているのは VIPER のメリット) コード量で見る成果 チャット機能の初版リリースの直前に、VIPER 適用の振り返りの意味もこめて、開発着手から初版リリースの間に書いた Swift コード量を集計してみたました。結果は次のとおりです。 新規ファイル数: 約200 (≒クラス数) 追加/変更LOC: 約10,000 単純に計算すると、1ファイルあたり 50 LOC くらいになっています。VIPER で他のパーツの Mediator として働く Presenter は比較的にコード量が多くなりがちなのですが、チャット機能でもっとも複雑なチャットルーム画面(「なぜ VIPER を採用したのか?」でお見せしたスクリーンショットの画面)の Presenter においても 500 LOC 程度で収めることができていました。 これも VIPER を採用したことで設計がうまくいった成果だと思っています。 おわりに スタメンでの私の最初のミッションはまさかのチャット機能でした。とても大きな機能なのでやりきれるかという不安をなくすため、最初にこのような VIPER による設計方針を固め、それにしたがって1画面作り終えた頃に VIPER の良さを実感し、それ以降は特に不安なく淡々と機能開発を進めていくことができました。この開発期間は私のエンジニア人生の中でも特に濃密なものになりましたので、その振り返りをしながらこの記事を書いています。 TUNAG iOS アプリのチャット開発は一旦落ち着きましたが、TUNAG の iOS/Android アプリとしてやるべきことはまだまだ多く、引き続きより良いアプリにしていくために一緒になって開発してくれるエンジニアが必要です。もし興味がありましたら こちら からご連絡ください。一緒に濃密なエンジニア人生を送りましょう!
こんにちは! スタメンで iOS / Android アプリエンジニアとして インターン をしているカーキ @khaki_lit です! スタメン開発ブログに登場するのは初めてになります.現在は主にTUNAGの Android 版を開発していますが,以前は iOS 版のTUNAGチャットの開発にも少し関わっていました. try! Swift とはTUNAGの iOS アプリでも使用されているSwiftという言語の国際的なカンファレンスとなります.私は2日目(26日)の方に参加してきました.初日のレポートは @temoki が こちら の記事で書いています. 近くて遠い世界 自分はtry! Swiftのような国際的なカンファレンスに参加するのは初めてだったのですが,今回感じたのが 世界の近さと遠さ です. 世界の近さ 自分たちと同じようにSwiftという言語を使って iOS アプリを開発しているエンジニアが世界中にたくさんいるんだと認識し,世界の近さを感じました.私は普段 OSS などに参加していないため,アプリを開発していて世界を感じることはありませんでしたが,Swiftのコミュニティは世界中に広がっていて,自分もその一員なんだと今回参加して実感しました.言葉も国籍も違う人々と『Swift』という共通言語を通じて話せることに感動しました! 世界の遠さ その一方で世界への遠さも同時に痛感しました. 世界で活躍するエンジニアの発表は本当にどれもすごくて,今の自分には理解できないようなレベルの話もあり,自分の未熟さを突きつけられました. 個人的に衝撃を受けたスピーカーが18歳で発表時高校を卒業したばかりという @kateinoigakun さんです. 自分は大学生で若いしまだまだこれからだとばかり思っていましたが,自分より若い世代が世界的なカンファレンスで英語で堂々と発表しているのを生で見たことで自分自身もっと力をつけてこのようなカンファレンスに立ちたい!と強く思いました. 印象深い発表 どれも素晴らしかったのですが,特に印象に残っているのが Mayuko Inoue さんの発表です.彼女はSwiftの技術的な話ではなく iOS デベロッパ ーの多様性 の話をされていました. iOS デベロッパ ーの多様性?と思われるかもしれませんが,事実 iOS アプリは Mac でしか開発することができず, デベロッパ ー登録にも多額なお金が必要な iOS 開発者は欧米とアジアにしかほとんどいないそうです.格差問題は個人の力ではどうしようもできませんが,彼女は自らのエンジニアとしての生活を YouTube を通じて紹介することで エンジニアの生態 を知ってもらう取り組みをしています. シリコンバレー のエンジニアの1日を紹介した こちらの動画 など 自分たちを紹介することで知らない人にとっては謎に満ちたエンジニアという職業を少しでも知ってもらい,エンジニアに対するハードルを下げる取り組みです. スタメン自身もこの技術ブログを通じてスタメンのプロダクト部及びエンジニアの日常や使っている技術などを紹介し,少しでもスタメンに興味を持ってもらえたらと考えているためリンクする部分がありました.そしてこれから個人でも発信していきたいと改めて感じました. 最後に 私が参加したのは2日目と3日目のワークショップでしたが普段名古屋では得られないような刺激をたくさん受けました.2日目のアフターパー ティー や3日目のワークショップで東京や海外のエンジニアの方とお話しできたのもエキサイティングな経験でした!
はじめに 4/18〜4/20に開催されたRubyKaigiに、スタメンエンジニア @mmoto99299415 (写真左) と @uuushiro (写真右)の2名で参加してきました。そのレポート記事になります。 セッション いくつか気になったセッションを紹介します。 1日目 Building Serverless Applications in Ruby with AWS Lambda AWS SDK for Ruby チーム の@alexwwoodさんによるセッションでした。 柔軟性・スケーラビリティ・高可用性・セキュア・従量課金制といったサーバーレスの利点の説明から、実際に AWS SAM CLI ・ AWS SDK for Ruby ・ aws -record を用いたサーバーレスアプリケーションのライブデモの実施まで丁寧に説明してくれました。スタメンでも分析基盤の コンポーネント の一つに Ruby を実行する AWS Lambdaがあります。別の機会にテックブログで紹介したいと思います。 https://speakerdeck.com/awood45/building-serverless-applications-in-ruby-with-aws-lambda GraphQL Migration: A Proper Use Case for Metaprogramming? Square Incのエンジニアである@gao_shawneeさんによるセッション。 GraphQLを扱うにあたり、サーバーサイド側では、モデルに対応したType・Fieldを定義する必要があります。加えて、Queryによりデータを取得するため、QueryType内に必要となるFieldと、それに対応したメソッドを定義します。対象とするモデルが増えると、Type・Field・メソッドの定義も一緒に増加し、だんだんFatになっていきます。 メタプログラミング を利用することで、動的にType・Field・メソッドを定義してこの問題を防ぐという内容のセッションでした。 スタメンではGraphQLを採用したサービスはまだありませんが、今後導入する場合は同じ問題に直面するので、とても参考になりました。 https://speakerdeck.com/shawneegao/ruby-kaigi-slides 2日目 Yabeda: Monitoring monogatari eBaymag.com を運用しているエンジニアの@Envekさんによるセッションでした。 eBaymagでは大規模なSidekiqの運用をしており、そこで学んだ経験についての トーク でした。例えば、Sidekiqのジョブが遅くなったとき、それがなぜ遅くなったのか、どこから考えるべきか、原因を見つけるにあたって必要な理論はなにか?といったような現実世界での課題に対して具体的な方法を知ることができました。スタメンではSidekiqの数値の監視にはSidekiq標準の ダッシュ ボードしか利用していませんでしたが、このセッションでは、prometheus-client という gem を利用してアプリケーションに簡単に Prometheus で監視するためのメトリックを追加し、Grafanaによってクラフを作成し可視化するということを実践していました。アプリケーションが成長に伴って、監視すべきメトリクスも増えていくことは今後の参考になりそうでした。 https://docs.google.com/presentation/d/1i8N_OcnQJ9SE6wdqzqV-vp_IYRxQRpEtJ0tpluYtCvo/edit#slide=id.g5675f30aae_1_230 3日目 Cleaning up a huge ruby application Cookpad Incのエンジニアである@riseshiaさんのセッション。 不要なコードを削除することで、コードの可読性が高まり、アプリケーションの読み込み時間も改善できたという内容でした。 具体的には次のような取り組みが紹介されていました。 - コードを削除するための仕組みづくり(どのようにメンバーに アサイ ンするか) - ソースコード が実行されているかどうかの調査方法 → logの取得 - 詳細なlogの取得方法(InstructionSequense, oneshot coverage) コードを削除することは、成果がすぐに現れないため、どうしても優先順位が後ろになります。でもその中で、仕組み化し、実践していく方法は参考になりました。 1年間で実際に数万行のコードを削除できた。という成果をみて、毎日の積み重ねの重要さを感じました。 https://speakerdeck.com/riseshia/cleaning-up-a-huge-ruby-application ブース 各企業のブースを見て回りました。 普段、RubyMineを使って開発しているのですが、そのエディタを提供しているJetBrainsもブースを出していました。様々な ノベルティ が有り、面白かったです。 同じ名古屋からは エイチーム がブースを出しており、はるばる福岡で話をすることができて嬉しかったです。 他にもブースを回ったのですが、エンジニアの方とゆっくり話ができて、有意義な時間が過ごせました。 感想 松谷(@uuushiro) 初参加のRubyKaigiとても楽しかったです。特に印象に残ったのは、3日目の「 Ruby Committers vs the World」という、 Ruby コミッタの方たちがステージ上でこれからの Ruby について様々な議論を繰り広げるといったコーナーです。そのコーナーの中では、コミッタの皆さんの Ruby を良くしたいという思いと、絶え間ない努力を重ねてきたという過程を垣間見ることができ、改めて支えてくださっている方々に感謝の気持ちが湧きました。 コミッタの方同士の掛け合いが日本語で交わされるのも日本発祥の プログラミング言語 ならではだと思いましたが、グローバルな仲間たちとコミュニケーションをとり、RubyKaigiをもっと楽しめるように英語の壁を乗り越えて来年のRubyKaigiを迎えたいです。また、 Ruby を支えている側と利用している側の間に大きい技術的な壁を感じました。私自身、 Ruby を支えている方々と同じ目線で Ruby の未来を考えられるように、エンジニアとしてもっと成長したいと思いました。 スタメンの創業事業「TUNAG」は、 Ruby を利用し成長してきました。スタメンとしても Ruby にお世話になるだけではなく、恩返しをしてきたいと思います。 来年のRubyKaigiは 松本市 ということで、名古屋から近いのでスタメンのエンジニアみんなで参加します。 RubyKaigiでお世話になった皆さん、本当にありがとうございました。来年もよろしくお願いします。 ミツモト(@mmoto99299415) RubyKaigiに参加し、充実した時間を過ごすことができました。各セッションを通じて、普段の開発では知ることのできない内容に触れ、深く掘り下げるきっかけが得られました。(ただ、日本語通訳が無いのは辛かった汗) 最も心に残ったセッションは、自分も松谷と同じ「 Ruby Committers vs the World」です。 互換性を捨ててでもアグレッシブに仕様を変えていくべきか?という議題や、version upで追加したい新しい機能(右代入、イテレートのブロックパラメーター)において、様々な意見が交わされていました。 Ruby を開発する上での議論を目の当たりにして、1Rubyistとして興味深かったです。 これまで、 Ruby という言語で当たり前のように開発していましたが、その裏側にいるコミッタ、安定版のversionを提供するメンテナーの方々のおかげで、普段の開発が成り立っていることを肌で感じることができました。 加えて、RubyKaigiを通じて、他のエンジニアと交流を持てたこともすごく嬉しかったです。アプリケーションを作るにあたり、 アーキテクチャ 、技術選定、開発体制など、一緒に議論できる仲間が増えたのはすごく良かったです。 おまけ スタメンはちょうど4月に福岡支社を立ち上げたので、福岡支社のメンバーとめんたい重を食べてきました! 福岡でもスタメンをよろしくお願いします。 おわりに 今回、RubyKaigiには業務として参加しており、宿泊費と交通費を会社に負担してもらいました。 スタメンでは Ruby を書く機会がたくさんあります!絶賛 Rubyist を募集中です!!興味がある方は Wantedly よりご連絡ください。
こんにちは、Web アプリケーションエンジニアのミツモトです。 普段は TUNAG という、企業やコミュニティを対象としたサービスの開発しています。 今回のブログでは、TUNAGのユーザー登録を実装するときに採用した、Rails の FormObject を取り上げます。 目次 はじめに FormObject 採用例 ActiveModel::Model おわりに はじめに ユーザー登録にあたり、ユーザーだけでなく、その付属情報を扱う Model (所属など)も同時に保存する必要がありました。 それらを各々で処理すると、同じような処理を Controller で繰り返し書くことになり、見通しが悪くなります。 Rails の FormObject を採用することで、複数の Model を一緒に保存でき、 Controller の肥大化を防ぐことができました。 この記事では FormObject とその採用例をご紹介させていただきます。 FormObject 単一のフォーム送信で複数の ActiveRecord モデルを更新したい場合に、その永続化ロジックをカプセル化できるデザインパターンです。 ActiveModel::Model というモジュールを include することで利用できます。 メリット ビジネスロジックが Controller に出ないため、各レイヤーの可読性が良くなる 種類の異なる複数 Model を1つの Model として扱える validation がかけられる 渡ってきた parameter を FormObject のクラス内で parse できる DBに依存しないインスタンスでも、Active Recordと同じインターフェースで扱える(通知など) 採用例 採用する前 ユーザーのみを新規作成する場合、Controller は以下になります。 # ユーザーの新規作成フォームを表示 def new @user = User .new end # ユーザーを作成 def create @user = User .new(user_params) if @user .valid? @user .save end end 扱う Model が増えると... # ユーザー、所属、住所の新規作成フォームを表示 def new @user = User .new @department = Department .new @address = Address .new . . . end # ユーザーを作成 def create @user = User .new(user_params) @department = Department .new(deparment_params) @address = Address .new(address_params) . . . if @user .valid? && @department .valid? && @address .valid? ... @user .save @department .save @address .save . . . end end 同じような処理が増えて、Controller が見辛くなります。 採用した後 User, Department, Address...をまとめるため、Form::Registration という FormObject のクラスを作ります。 Controller の処理 # 登録の新規作成フォーム def new @registration = Form :: Registration .new end # 登録の実行 def create @registration = Form :: Registration .new(registration_params) if @registration .valid? @registration .save # User と Department と Address が create される end end Form::RegistrationのFormObjectクラス class Form :: Registration include ActiveModel :: Model attr_accessor :user , :department , :address validate :validate_user validate :validate_department validate :validate_address def initialize ( params : {}) @user = User .new(params[ :user_params ]) @department = Department .new(params[ :department_params ]) @address = Address .new(params[ :address_params ]) end def save @user .save @department .save @address .save end . . . end まとめてvalidation をかけたり、parameter を FormObject のクラス内で parse できます。 また、各 Model の attributes ではないけど、保存するかどうかの判定に必要な parameter もここで受け取り、 Form::Registeration の validation として追加することができます。 このように FormObject を使うには、Form::Registration で include している ActiveModel::Model が必要です。 ActiveModel::Model ActiveModel::Model を include することで、自分で定義したクラスを FormObject として Model のように扱うことができます。 中を見ると、 module ActiveModel module Model extend ActiveSupport :: Concern include ActiveModel :: AttributeAssignment include ActiveModel :: Validations include ActiveModel :: Conversion included do extend ActiveModel :: Naming extend ActiveModel :: Translation end . . . end end ActiveModel 関連のモジュールが include , extend されています。 errors, valid? などのインスタンスメソッドがを扱える ActiveModel::Validations や、 エラーメッセージを翻訳できる ActiveModel::Translation が定義されています。 Model のようにインスタンスメソッドやクラスメソッドを呼ぶことができます。 おわりに Rails の FormObject を取り上げました。 MVC アーキテクチャだけでは綺麗にコーディングできない部分を、別のレイヤーに切り出すことで、可読性を高めることができます。もし機会があれば、試しに使ってみてください。 最後まで読んでいただき、ありがとうございました。 スタメンでは エンジニア を募集しています。興味がある方はぜひご連絡ください。
こんにちは、スタメンエンジニアの松谷です。 組織のエンゲージメントを高めるプロダクト TUNAG(ツナグ) を開発しています。 開発・運用に関わる中で日々思うのは、アプリケーションの管理をよりシンプルにし、セキュアで信頼性が高く、スケーラブルに運用することを容易にしたいということです。 AWS Systems Manager には、これらのニーズを満たす多くの機能があることを知り実際に導入してみました。簡単に導入することができ、運用上大きな効果を得ることができたので共有させていただきます。 TL;DR (概要) スタメンのSystems Managerを活用することで、 SSH レスで安全なオペレーションを実現し、監査可能な操作ログを保存し、現状のサーバーの状態を簡単に把握できるようになりました。具体的な活用事例とともに紹介します。 目次 スタメンにおける活用事例 セッションマネージャーでSSHアクセスの廃止 オートメーションで日常タスクを1クリックで自動化 イベントリでインスタンスの状態を簡単に把握 まとめ スタメンにおける活用事例 AWS Systems Managerが持つ機能のうち、スタメンで活用している機能とその運用について説明します。 セッションマネージャーで SSH アクセスの廃止 セッションマネージャーの機能の説明は以下の公式ドキュメントをご確認ください。 セッションマネージャー を使用して、 インタラクティブ なワンクリックブラウザベースのシェル、または AWS CLI を介して Amazon EC2 インスタンス を管理できます。セッションマネージャー は、インバウンドポートを開いたり、踏み台ホストを維持したり、 SSH キーを管理したりすることなく、安全で監査可能な インスタンス の管理を提供します。セッションマネージャー は、 Amazon EC2 インスタンス への簡単なワンクリックの クロスプラットフォーム アクセスをエンドユーザーに提供しつつ、 インスタンス への制御されたアクセス、厳格なセキュリティプ ラク ティス、 インスタンス アクセスの詳細を含む、完全に監査可能なログを必要とする企業ポリシーに準拠することを容易にします。 AWS Systems Manager Session Manager - AWS Systems Manager 簡単に説明すると、 SSH 接続せずに、 AWS マネジメントコンソール(もしくはAWSCLI)上でEC2 インスタンス に対してオペレーションができる機能です。 適切なロールがEC2にアタッチされていて、SSMエージェントがインストールされている インスタンス が以下のように、一覧で表示されます。 インスタンス を選択して、セッションを開始をクリックすると、セッションが開始され以下のようにシェルライクな画面がブラウザで表示されます。 任意の操作が完了した後、終了をクリックするとセッションが終了します。 そして、セッション履歴のタブをクリックすると、過去に起動したセッションの履歴を確認することができ、ここでは、いつ、だれが、どの インスタンス のセッションを起動したのか、そして、そのセッションでどのようなオペレーションを実行していたのかは、設定画面で「S3 バケット ストレージの有効化」または、「CloudWatch Logs ストリームの有効化」をONにしていれば確認することができます。ローカルのターミナルでオペレーションをしていた頃は、問題が起きた時に過去のログを遡ることができない場合があり、困ったことがありましたがこれで安心です。 また、新しいセッションが始まった時点で SNS 通知をすることも可能です。 セッションマネージャーの導入により、各開発者からの SSH アクセスが不要になり、踏み台サーバーを廃止することができました。セキュアになるだけでなく、踏み台サーバ代も節約できて良いこと尽くしです。 オートメーションで日常タスクを1クリックで自動化 オートメーションの機能の説明は以下の公式ドキュメントをご確認ください。 Systems Manager オートメーションを使用して、一般的なメンテナンスとデプロイのタスクを自動化します。オートメーションを使用すると、 Amazon Machine Image の作成と更新、ドライバーとエージェントの更新プログラムの適用、 Windows インスタンス でのパスワードのリセット、 Linux インスタンス での SSH キーのリセット、および OS パッチまたはアプリケーション更新プログラムの適用が可能になります。 オートメーションドキュメントは、Systems Manager がマネージド インスタンス および AWS リソースで実行するアクションを定義します。ドキュメントは JavaScript Object Notation ( JSON ) や YAML を使用し、これにはユーザーが指定するパラメータおよびステップが含まれます。ステップは順番に実行されます。 AWS Systems Manager オートメーション - AWS Systems Manager Systems Managerで実行する各種操作はドキュメントとして定義されています。 AWS が定義済みのドキュメントや、自分で作成するカスタムドキュメントがあります。 スタメンでのオートメーションの活用例の一つに、ステージング環境へのデプロイがあります。以下は、特定 インスタンス に対して、デプロイ スクリプト を実行する、カスタムドキュメントです。 --- description: "deploy staging" schemaVersion: "0.3" parameters: BranchName: type: "String" description: "(Required) Deploy target Branch" InstanceId: type: "String" description: "(Required) InstanceId to run command" default : "instance id" S3BucketName: type: "String" description: "(Required) S3BucketName" default : "tunag-systems-manager" S3KeyPrefix: type: "String" description: "(Required) S3KeyPrefix" default : "staging/deploy" mainSteps: - name: "deployStaging" action: "aws:runCommand" inputs: DocumentName: "AWS-RunShellScript" InstanceIds: ["{{ InstanceId }}"] OutputS3BucketName: "{{ S3BucketName }}" OutputS3KeyPrefix: "{{ S3KeyPrefix }}" Parameters: commands: - su operator -c "source ~/.zshrc; cd /home/app; ./bin/deploy_staging.sh {{ BranchName }}" これは、ステップ数が1つのシンプルなドキュメントです。parametersで、パラメータ化したいキー・バリューを指定しています。 aws :runCommandは、指定されたドキュメントを実行するアクションです。 aws :runCommandでは、1つ以上の管理対象 インスタンス でコマンドを実行するためのオプションが指定可能です。DocumentNameで、Systems Manager が実行するドキュメント( AWS -RunShellScript)を指定しています。 なお、commandsオプションで実行されるコマンドリストは、1つ1つssm-userというユーザーで実行されます。弊社のデプロイフローでは、特定 Linux ユーザーに限定してデプロイをしていたので、suコマンドのcオプションを利用することで、ユーザーを切り替えた状態でコマンドの実行をしています。 また、オートメーションの実行リンクを、チームに共有すれば簡単に特定タスクの実行画面にアクセスすることができ便利です。下図を例にすると、デプロイしたいブランチ名を入力パラメータに入れて実行をクリックするだけでデプロイを開始することができます。 今までアプリケーションエンジニアは、デプロイを実行したいだけなのに SSH 鍵の登録が必要になっていました。今回、オートメーションを使うことで、アプリケーションエンジニアに特定リソースへの SSH アクセス権限を付与する必要なく、特定のリソースの特定のタスクに対するアクセス権限をIAMによって提供できるようになりました。これはセキュリティ的に大きな前進でした。 また、Systems Managerによるドキュメント化によって、チーム内で属人化していた運用のベストプ ラク ティスを リージョンやIAMグループ間で簡単に共有ができるようになりました。また、ドキュメントが受け入れる許可パラメータ値のバリデーションも行うことができ、ミスオペレーションを防ぐことができます。 今回はとてもシンプルなタスクをSystems Manager上で管理できるようにしましたが、Systems Managerは、 インスタンス だけでなく周辺のサービスも含めて自動化できる フレームワーク なので、積極的に複雑なタスクを簡素化・自動化していきたいと思います。 オートメーションには数多くのアクションがあるのでぜひ公式ドキュメントをご確認ください。 Systems Manager Automation アクションのリファレンス - AWS Systems Manager イベントリで インスタンス の状態を簡単に把握 イベントリの機能の説明は以下の公式ドキュメントをご確認ください。 インベントリを使用して、 Amazon EC2 インスタンス およびオンプレミスサーバー、またはハイブリッド環境の 仮想マシン ( VM ) から、 オペレーティングシステム (OS)、アプリケーション、 インスタンス の メタデータ を収集できます。 メタデータ を照会すると、ソフトウェアポリシーに従ってソフトウェアと設定を実行している インスタンス と、更新が必要な インスタンス をすばやく把握できます。 AWS Systems Manager インベントリ - AWS Systems Manager インベントリ情報を取得することで、 インスタンス の全ての構成要素(OS, ミドルウェア , ライブラリ)の一覧とそのバージョンを簡単に管理することができます。OSのセキュリティ設定に問題はないか、不要な ミドルウェア は存在しないかなど、各 インスタンス の状況を把握することができます。また、取得した情報は、以下のように ダッシュ ボード形式で表示することができます。 まとめ まだまだSystems Managerの一部機能しか使いこなしていませんが、この時点で既に感じているメリットは以下の4つです。 1. 権限の可視性と制御性の向上 IAMロールで制御可能になるので、誰がどのサーバーにどのアクセス権限をもっているのかが AWS 上で簡単に管理が可能 2. セキュリティと コンプライアンス の向上 踏み台サーバーが不要 SSH 用のインバウンド用ポート開放が不要 SSH キーペアの管理不要 サーバーのインベントリ情報の管理が簡単に アクセス履歴がS3やCloudTrailで可能になり、サーバーアクセスなど監査可能なログを必要とする企業ポリシーに準拠が可能 3. 運用業務の自動化と定型化 繰り返し作業の自動化と定型化ができ、コスト削減とミスオペレーションの削減が可能 4. 無料で利用可能 AWS Systems Manager自体は無料 Systems Managerによって管理される AWS サービスを使用している場合、そのサービスの料金は発生 サービスを運営するにあたって必要なセキュリティ要件も、 AWS のサービスを利用することで、ほとんど 工数 を掛けず、サービス全体のセキュリティを高めることができました。これからも適切な AWS サービスを選択し、運用コストを下げていこうと思います。 スタメンでは一緒にシステムの信頼性を向上させていく仲間を募集しています。ぜひ こちら からご連絡ください。
こんにちは、スタメンで iOS / Android アプリのエンジニアをしている @temoki です。 TUNAG の iOS アプリはすべて Apple 発の プログラミング言語 Swift で書かれていますが、この3月にその Swift の国際カンファレンス try! Swift 2019 Tokyo が開催されました。 アプリ開発 の情報収集やスキル向上のために、昨年からスタメンのエンジニアもこのカンファレンスに参加しており、今回は私が参加してきましたので、その様子をお伝えしたいと思います。 国際色豊かな会場 開催場所は ベルサール 渋谷ファーストです。到着すると緑色のTシャツを着たたくさんのスタッフと、try! Swift キャ ラク ターの Riko が出迎えてくれ、地下の会場の方に案内していただきました。 こちらがセッション会場です(1日目最後の写真撮影の時のものですが)。国内外からなんと900人の参加があったようです。海外からの参加者も非常に多く、国内でこれだけ国際色豊かな プログラミング言語 に関するカンファレンスは希なのではないかと思います。 スピーカーによる素晴らしい発表の数々 このカンファレンスは主に招待スピーカーによる発表と、CFP の募集から選ばれたスピーカーによるライトニング トーク が中心となります。司会進行は日本語と英語両方で行われ、発表も同時通訳が入るなどオーディエンスへの配慮も万全でした。特に同時通訳の質が高く、よくこれだけ専門的な内容をすぐに変換できるものだなと驚きました。おかげで英語が得意ではない私も発表内容に専念することができました。 個人的に刺さった発表の1つは @1024jp さんの native macOS application、またはAppKitの世界 です。以前に渡邊恵太さんの 融けるデザイン という書籍に感銘を受けたのですが、その内容が macOS のアプリケーションという世界で具体化されたような素晴らしい発表でした。@1024jp さんが開発されている CotEditor は masOS に最適化されてとても使いやすく、今も愛用しています。 そしてもう一つ、最も興味深かったのが @terhechte さんの Introduction to Swift Keypaths です。Swift の KeyPath 機能については私の知識は Swift 3 時代のもので止まってしまっていたので、型情報が保持される点など KeyPath が非常に強力なものになっていることに驚きましたし、それを ジェネリクス や プロトコル とは異なる抽象化の手段として用いてしまった点は 目から鱗 でした。 個人スポンサーがとてもオススメです これは個人的な活動となりますが、このような良いコミュニティが今後も継続していくことに少しでも貢献できればと思い、個人スポンサーとして登録していました。すると、この写真のように開会時に紹介していただいたり、公式ウェブサイト、公式アプリ、そして会場の入り口のスポンサーボードなど様々なところでアイコンを掲載していただきました。スポンサーボードには皆さんサインされていたので私も書いてきましたよ。また、これによって他の参加者との会話のきっかけにもなりましたので、try! Swift の個人スポンサーはオススメです! 多種多様なスポンサーブース try! Swift にはたくさんのスポンサーがついており、各社工夫をこらしたブースを出されています。写真は Cyber Agent さんの投票コーナーです。Application Architecture のところで私は VIPER に最初の一票を入れてきました。TUNAG の iOS アプリは VIPER アーキテクチャ ーを採用しています。このあたりはまた今度このブログで詳細を書きたいと思います。 このようなスポンサーブースで各社のサービスについて聞いたり、それを支えるエンジニアなどとも交流することができました。その中のトレタさんのブースで説明されていた、今月にリリースしたばかりの トレタnow という飲食店を当日予約で最短10分後に入れるというサービスが素晴らしかったです。飲食店等の予約管理をしているトレタさんだからこそできることですね。この日は参加者10人くらいで夕食に行くことになったのですが、早速このトレタnowで予約して、本当にすぐにお店に入れてしまいました。二次会会場探しなどにすごく役立ちそうですし、店舗としては空きを極力減らせて良いサービスだなと思いました。 最後に 私は都合により1日目だけの参加となりましたが、この1日だけで非常に多くの刺激を受けましたし、たくさんの素晴らしい発表によって多くの知見も得ることができました。スタメンはまだ創業期であるにもかかわらず、こういったカンファレンスにエンジニアを積極的に送り出していただけるのはとてもありがたいですね。 スタメンではこのような自己研鑽を奨励する文化があり、それをサポートする環境も整っているため、エンジニアが成長し、それがプロダクト開発に活かされるという良いサイクルが生まれていると思います!こんな環境の中で一緒に切磋琢磨してみたいと思っていただけたら、ぜひ こちら からご連絡ください。
こんにちは、スタメンのエンジニア、津田です。以前、弊ブログでも「 TUNAGの全文検索を支える Elasticsearch × Rails 」として紹介させていただいたように、TUNAGでは検索機能の実装にElasticsearchを利用しています。検索クエリとしては主にMulti Matchを利用しているのですが、RDBに登録されているレコードを利用しやすい形でElasticsearchのドキュメント化する方法について試行錯誤したため、共有させていただきます。 TL;DR (概要) Elasticseachでは、 Multi Match Query を使って、複数のフィールドを検索対象とするクエリを発行できます。検索時に対象とするフィールドを指定することができるため、一つのインデックスを、検索対象フィールドの異なる複数の用途で共用することができます。ただし、一つのインデックスに含められるフィールド数には制限があるため、注意が必要です。 実現したかったこと Elasticsearchを検索用途で導入した場合、RDBには1:Nで格納されている情報を、Elasticsearchには1つのドキュメントとして登録するケースがあります。たとえば、「1つの商品 : N個の商品情報」、を、「1つの商品情報」ドキュメントにまとめてから、Elasticsearchに登録するような場合です。「N個の商品情報」を「1つの商品情報」に折りたたむ際には、「検索時にどのような情報を検索対象とするか」を考える必要があります。 検索の用途として、 店舗利用者が、購入時に参照可能な商品情報のみを検索する 店舗担当者が、店舗が参照可能な商品情報のみを検索する システムの管理者が、すべての商品情報を検索する という3つのユースケースがあったとします。やり方として、二通り考えられます。 ドキュメント"登録時"に必要な情報を予めまとめてしまう ドキュメント"検索時"に必要なフィールドのみを検索する 順に説明します。 ドキュメント登録時に必要な情報を予めまとめてしまう この場合は、たとえば、以下のように3つのフィールドに情報をまとめる、具体的には必要な文言を結合して登録することで、情報を折りたたむことが可能です。 商品情報 店舗利用者用フィールド: 「商品名、商品の特徴、保証について、etc...」 システム管理者検索用フィールド : 店舗利用者用フィールド +「販売時の注意点、店舗での管理方法、etc...」 店舗担当者用フィールド : システム管理者用フィールド + 「システム管理上必要な情報、etc...」 このようにドキュメントを登録した場合、検索時には1つのフィールドを対象とすればいいので、 Match Query で検索することができます。 ただし、この方法では、検索時により細かい調整をすることができません。たとえば、「このページでの検索は、商品名、特徴のみから検索しよう」となった場合、利用できるフィールドがないため、ドキュメントの再登録をしない限り対応できません。 ドキュメント検索時に必要なフィールドのみを検索する そこで、以下のようにドキュメントを登録します。 商品情報 商品名 商品の特徴 保証について 販売時の注意点 etc... このように構築したインデックスに対して Multi Match Query を使えば、検索時に対象とするフィールドを変更して対応することが可能です。 Multi Match Query では、複数のフィールドに対してMatchクエリを実行するようなクエリです。以下のタイプがあり、タイプによって挙動が異なります。 best_fields most_fields cross_fields phrase phrase_prefix 今回のような用途では、cross_fiedsを使います。 cross_fields タイプの検索では、「指定されたフィールドを大きな一つのフィールドとして扱い、与えられたクエリと一致しているものを検索する」ような動きをします。 たとえば、「金槌 丈夫」で検索した場合、マッチする情報を持つのは「商品名」フィールドと、「商品の特徴」フィールドです。cross_fieldsタイプのクエリであればこれはうまくマッチしますが、たとえば「best_fields」ではヒットしません。「best_fields」も複数のフィールドを対象として検索するのですが、ヒットするためには、同じフィールドに「金槌 丈夫」が含まれている必要があるからです。 cross_fieldsを指定したmulti_matchクエリは、以下のような形になります。 { " query ": { " multi_match " : { " query ": " 金槌 丈夫 ", " type ": " cross_fields ", " fields ": [ " 商品名 ", " 商品の特徴 ", etc ... ] , " operator ": " and " } } } 問題点 このように便利なcross_fieldsですが、今回は上記のやり方では使いませんでした。 今回、登録するドキュメントは、インデックス単位で見ると、「商品の特徴」がN個あり動的に増えるものでした。さらに複数のアナライザ(ngramとkuromoji)でフィールドを分けていたため、 商品情報 商品特徴1_kuromoji 商品特徴1_ngram 商品特徴2_kuromoji 商品特徴2_ngram ... となっており、フィールド数の上限を設定するのが難しかったからです。 Elasticsearchには「1つのフィールドに含まれるフィールド数の上限(デフォルトでは1,000)」が存在しています。これは、インデックス作成時に、 index.mapping.total_fields.limit を設定することで変更可能ではあります。 しかし、そもそもこの制限は、 Add limit to total number of fields in mapping にあるように、クエリ実行時に Mapping Explosion が発生するのを避けるためのものです。 実際に、動的にフィールドが増加する形でドキュメントを登録する場合は、事前に予想されるフィールド数上限と、上限に付近に達した状態での動作検証が必要と思われます。 まとめ Elasticsearchでの検索は非常に高速で、クエリも高機能なものがありますが、それを活かすためにはINDEXの設計からきちんと行っていかなければいけないものだと感じました。 スタメンではエンジニアを募集しています。もし興味がありましたら こちら からご連絡ください。
こんにちは、スタメンCTOの小林です。 2019年3月1日に、スタメンのエンジニア全員で合宿を行い、スタメンのエンジニアチームが目指している姿(VISION) と 個々のエンジニアの価値観や行動指針(VALUE) を全員で話し合って決めました。 スタメンのエンジニアが作っている『TUNAG』の技術的な解説 と合わせると、スタメン開発チームの現在と将来をご理解いただけると思います。 スタメンの開発チームに興味をもってくださったら、ぜひ @lifework_tech に気軽にご連絡ください。オフィス紹介や詳細をお伝えさせてください。 なぜ合宿で決めたのか 現在、スタメンには、10名のエンジニアが在籍しています(入社予定者含む)。この10名は、プログラミングが好きで、成長意欲に溢れ、スタメンで大きな仕事をしたいと思っているなど、多くの共通点があります。 とはいえ、各自のスタメンに入社するまでの経歴は様々ですし、それぞれ異なったすばらしい個性もあります。現在スタメンで担当している役割・領域も異なります。 今後、TUNAGの大規模化により技術的な難易度は上がり、新規事業では一層スピードが求められ、エンジニアメンバーが増加することで多様性が増していく、など様々な課題が待っています。 これらの課題に対応するためには、各エンジニアは一層の成長を求められ、開発チームとしてさらに結束する必要があります。そこで、開発チームとして目指す姿と、個々のメンバーに求められる考えや方針を固め、団結したチームで非連続な成長をしていきたい。こんな背景で VISION と VALUE を定めることにしました。 また、下記の理由で、創業期から現在に至るまでスタメンを支えてきたエンジニア全員が集まり、合宿という形でしっかり議論して決めることにしました。 上意下達のチームではなくて、当事者意識に溢れたチームでありたい。 チームの規模的に、個々の強さに加え、チームの強さで勝負する時期に来ている。 アイデアをみんなから募ることで、少しでも良いアウトプットにしたい。 議論に加わることで、今後加わるメンバーに自分の言葉で伝えていってほしい。 前提となるスタメンの企業理念 と Star Way 株式会社スタメンの企業理念は『 一人でも多くの人に感動を届け、幸せを広める。 』です。 また、すべての部門の全員が大切にしている下記の5つの行動指針「 Star Way 」があります。 Finish Promise ・・・ 約束を守り、最後までやりきる Work Bravely ・・・ 大胆に攻め、挑戦や失敗を讃える Take Ownership ・・・ 当事者意識を持って、自らが率先する Say Niceplay ・・・ 素直に認め合い、強みでチームを引き上げる Enjoy Together ・・・ ワクワクを大事に、一体感を楽しむ 今回、合宿で決めた VISION と VALUE は、上位概念として この企業理念 と Star Way が存在し、エンジニアに合わせてさらに具体的にしたものになります。 スタメン開発チームのVISION (目指している姿) エンジニアリングで事業の成長を牽引する 経営理念に掲げた『一人でも多くの人に感動を届け、幸せを広める。』を実現するために、目指している開発チームの姿がこの VISION です。 エンジニアが事業を作り、事業によって多くの人に感動を届け、幸せを広めたい。また、開発チームが提供する「ものづくり力」が事業の成長を牽引する存在でありたい。 エンジニアのみんなで話しましたが、目指している姿にずれはなく、シンプルで当たり前な内容になりました。 スタメン開発チームのVALUE (価値観と行動指針) VISION を実現するためには、高い技術力と強いチームワークを持った開発チームが必要で、メンバーに求められる価値観や行動指針を定めた内容が 以下の VALUE になります。 ユーザー目線で考える 失敗に向き合う 本音で伝える 誇りに思えないなら十分でない わくわくを心に 順に説明させてください。 ユーザー目線で考える 多くの人に感動を届け、幸せを広めるためには、ユーザーが抱くプロダクト(事業)に対しての期待値を超える必要があります。 そのためには、プロダクトに作り手としての意思を込めつつも、常にユーザー目線で考える必要があります。合宿で話した全員が「ユーザーファースト」な考えを、価値観として最優先していました。 失敗に向き合う エンジニアの仕事には、不具合や障害など失敗はつきものです。でも、その失敗から目をそらしてはだめで、正面から向き合わなくてはなりません。 できる限り失敗しないよう、根性ではなくエンジニアとして創意工夫する。それでも失敗したときには、真摯に失敗に向き合って対応し、失敗から学びを得られるエンジニアでありたい。 本音で伝える どれだけ優秀なエンジニアであっても一人では限界があります。エンジニアリングで事業の成長を牽引するためには、チームで結束する必要があり、スタメンの開発チームでは非常にチームワークを重んじています。 チームとしての結束を強くするために何をVALUEに加えるべきか。議論した結果が「本音で伝える」でした。 レビューや議論など様々な場面において、遠慮して本心を伝えないチームよりも、厳しいことも本音で伝え合えるチームでありたい。そのためには、お互いに一人のエンジニアとして対等に向き合い、リスペクトし合うことが大切と考えています。 誇りに思えないなら十分でない エンジニアとして、プロフェッショナルな仕事をしたい。そのためにも、常に高いレベルの仕事を目指し、一つ一つの成果物に対して誇りを持てる仕事をしたい。「誇りに思えるか」と問われたときに躊躇するようであれば、それは十分な仕事ではないと考えています。 また、成果物に限らず、チーム、プロダクト、実績など、何に対しても誇りに思えるぐらいの仕事をしたいと考えています。 わくわくを心に エンジニアリングは本当に楽しい仕事です。技術で困難な問題を解決したり、プロダクトを多くのユーザに使ってもらえることは、本当にわくわくします。わくわくを常に感じ、エンジニアという仕事を楽しみたい。 わくわくなエンジニアが集まって、わくわくなチームになる。そんな現場でありたいです。 合宿の風景 記念に写真を撮ったので、合宿の風景をご紹介します。 各自事前に考えてきた、VISION と VALUE の案とその考えをそれぞれ発表し、全員でカテゴライズや優先順位を付けて集約していきました。 いつもどおり和やかな雰囲気で始まりましたが、思いのこもった発表と議論は真剣でした。 合宿後は懇親会でした。本音で語り合った後で、みなさん良い笑顔です。 最後に 今回は、スタメンのエンジニアチームの VISION (目指している姿) と VALUE (価値観や行動指針) をご紹介させていただきました。 スタメンのエンジニアが作っている『TUNAG』の技術的な解説 と合わせて、スタメン開発チームを、少しでも知って、ご理解いただけたら嬉しいです。 VISON や VALUE は、掲げただけでは意味がありません。経営理念に掲げた『一人でも多くの人に感動を届け、幸せを広める。』を実現するためには、常に VISION や VALUE を意識して、日々の仕事の中で実践していきたいと思います。 スタメン開発チームは、世界中に誇れる素晴らしいチームです。本当に頼もしい仲間たちが集ってくれました。でも、まだ10名です。もっと頼もしい仲間を集めて、エンジニアリングで世の中に感動と幸せを広めたいと思っています。 こちら から、募集中の職種が確認できます。 こんな環境でいっしょに働きたいなと思っていただけましたら、 @lifework_tech か Wantedly から、ぜひご連絡いただけないでしょうか。
こんにちは、スタメンCTOの小林です。 最近、面接や勉強会などで社外のエンジニアの方と話した際に、スタメンのエンジニアチームの詳細について、思ったより面白そう、まともそう、やりがいがありそうとの感想をいただくことが続き、スタメンの中の人たちの詳細が外の人たちに伝わっていないと感じることがありました。 ちょうど先月の1/29、株式会社スタメンは、設立3周年を迎えて4年目に入ったこともあり、4年目を迎えたスタメン開発チームの現在を紹介する記事を書いてみます。 まずは、創業以来スタメンのエンジニアチームが作り続けている TUNAG について、技術的な側面から解説します。 スタメンのエンジニアチームの VISION (目指している姿) と VALUE (価値観や行動指針) と合わせて読んでいただけると、スタメン開発チームをより理解していただけると思っています。 興味をもってくださった方は、 こちら からご連絡いただき、気軽にスタメンのオフィスに遊びにきてください。詳細をお伝えさせていただきます! TUNAG について スタメンは、 TUNAG(ツナグ) という、エンゲージメント経営を推進する社内SNSを運営しており、私を含めてエンジニアが在籍しているプロダクト部は、TUNAG の 企画、開発 を担当しています。 TUNAG が生まれた背景 TUNAG ミッションは、エンゲージメントの向上によって、明るく強い組織作りの支援を通じて、クライアントの成長を牽引することです。 スポーツにおいてメンバーが相互に信頼しあっているチームは強いのと同じように、ビジネスにおいても経営陣と従業員、従業員間で信頼関係を築けているチームは強く、様々な業界において競合他社に勝り、継続的な成長を遂げている企業があります。 この、企業においての経営陣と従業員、従業員間で信頼関係の度合いを「エンゲージメント」と呼び、スタメンは「エンゲージメント」を重視した経営を行っている企業に対し、TUNAG による社内制度を軸としたコミュニケーションによって、エンゲージメントの醸成を支援しています。 TUNAG が生まれた背景の詳細や思いについて、代表の加藤が ビジョン に書いています。また、 導入事例 にて TUNAG を導入いただいている企業様を紹介しています。スタメンのエンジニアたちが、世の中にどのように影響しているかを知ることができます。ぜひ読んでいただけないでしょうか。 TUNAG の主な機能 TUNAG は、Ruby on Rails で実装され、Amazon Web Services で運用された SaaS(サース、Software as a Service) です。また、iOS/Android 向け アプリ と Chrome などの PC のブラウザで利用でき、導入頂いている各企業の社内コミュニケーションを担っています。 TUNAG は、規模や業界の異なる様々な企業の社内制度の運営に対応できるように、非常に多くの機能を備えていますが、各機能は4つのグループに大別されます。 SNS SNS については、Facebook に代表されるような、タイムライン、プロフィール、効果的なコミュニケーションに必要な 絵文字、スタンプ、画像・動画の投稿に加え、ビジネスで利用できるチャットを備え、フル機能をもつ社内SNSサービスになっています。 社内制度の作成と利用 社内制度の作成と運用については、Google Form や SurveyMonkey に代表されるような 任意のフォーム を作成できます。制度は、特定の部署や役職のみに利用を制限したい場合がありますし、書籍の購入補助など利用回数に制限(例:月に3回)を設けるなど、利用に際して様々な条件を設定することができます。 書籍の購入補助など管理部門による確認が必要な制度もありますし、投稿前に上長の確認が必要な制度もあります。よって、TUNAGでは、承認経路の設定や承認状態の管理といったワークフロー機能を備えています。 さらに、ワークフローを提供するためには各ユーザーの所属や上長といった組織及び役職の情報が必要となります。TUNAG では、会社全体の各部門の組織図、各ユーザの所属と役職といった、人事マスターを管理することができ、制度の利用設定や、投稿の公開範囲、チャットのルーム設定などを組織単位で設定することができるようになっています。 共通基盤 TUNAG の目的は、エンゲージメント経営の推進です。よって、各企業の運営担当者にとって、それぞれの社内制度の利用状況や、各部署やユーザのログイン状況など、TUNAG の利用状況を把握することは非常に大切で、管理者画面には、グラフ等のダッシュボード機能や、各種CSVエクスポート機能が提供されています。 また、TUNAG 全体を横断的に検索できる 全文検索機能を Elasticsearch を用いて構築しています。 外部連携 他要素認証などの強固なセキュリティ や アカウントの一元管理などを目的に、Google G-Suite や Microsoft Azure Active Directory のアカウントを用いた Single Sign-on を希望されるお客様もいます。よって、TUNAG では、SAML 2.0 に対応しています。 また、PCにダウンロードすることなく、データをエクスポートできるように Google SpredSheet と連携していたり、 ネイティブアプリへPUSH通知を送るために、ニフクラ mobile backend と連携したりと、様々な外部サービスを連携する機能を実装しています。 TUNAG の テクノロジースタック 前述したように多くの機能を実装した TUNAG は、コード量も多く、また、日々データが蓄積されサイト規模が拡大しているため、開発効率、保守性、運用性を重視して技術選定しています。 創業時こそ、CTOの小林がまとめて技術選定しましたが、チームが大きくなった現在では、各分野で専門的な知見を持ったエンジニアが存在し、各々が新しい技術やライブラリをチームに提案して、TUNAG に取り入れています。 技術選定の考えとしては、各分野/技術のベストプラクティスを積極的に導入することで開発/運用を効率化し、確保した時間でTUNAGのコア部分の実装に注力する方針をとっています。 2019年2月時点の主なテクノロジースタックとしては、下記の図のようになります。順に解説させてください。 言語・フレームワーク TUNAGの サーバーサイドは、Ruby on Rails で実装されたモノリシックなWebアプリケーションとなっています。TUNAG のコード量がさらに大きくなり、開発チームも複数になった場合は、機能単位でマイクロサービス化をするかもしれませんが、現時点では開発効率を重視してモノリシックに作っています。 また、タイムライン や チャット など 複雑なUI は React を用いて実装しています。ネイティブアプリは、iOS は Swift で実装されており、Android は Java から Kotlin へ移行中です。 開発支援 エンジニアの日々は、コーディング、デバッグ、テストが大部分を占めます。よって、日々の開発効率は非常に重要です。そのため、スタメンのエンジニアは、全員 RubyMine を用いて開発しており、快適に Rails や React を書いています。 また、GitHub Flow で開発を行っており、GitHub で PullRequest を作成し、リリースに際しては、チームメンバーがレビューを必須にしています。リリース前に必ずレビューを行うことで、バグを未然に防ぐことはもちろん、設計方針やコーディングテクニックなどの知識の共有なども積極的に行っています。 自動化も積極的に行っており、テスト や PullRequest がマージされた際の Deploy などは、CircleCI により自動化されています。Sider は、rubocop によるコーディング規約のチェックや Rails Best Practices による自動レビューに用いています。 なお、開発に用いているハードウェアは、MacBook Pro 15-inch, Apple Magic Keyboard, Magic Trackpad もしくは Magic Mouse に、27インチ 4Kモニターとなっています。スタメンのオフィスは机が広く、肩こり対策でキーボードを2台並べているエンジニアが多く、やたらキーボードがたくさんあるオフィスになっています(笑 プロジェクト管理 ベストプラクティスを積極採用するスタメンですが、社内コミュニケーションにおいては少し違います。 スタメンでは、多くのITベンチャーが利用している Slack を利用しておらず、TUNAG の チャット機能で日々のコミュニケーションを行っています。 プロジェクト管理は、Trello によるカンバン方式を2週間単位のスクラムで行っており、 DocBase に仕様やマイルストーンなどを記載して社内共有と知識の蓄積を行っています。 Amazon Web Services (AWS) AWS の使い方は、Webアプリケーションのテンプレート的な構成となっています。EC2 上の Amazon Linux で nginx + puma の上で rails を動かしていますが、より効率的な運用を目指して夏にかけて AWS ECS を用いた構成に変更する予定です。 最近は、ログの蓄積と分析の基盤を準備中で、Kinesis Firehose、Glue、Athena といったサービスを用いて構築中です。これらのデータは、BI Tool の Metabase で分析したり、機械学習を用いたタイムライン等の最適化などに用いる予定です。 その他のサービス TUNAG では、主要な部分は AWS で運営されていますが、いくつかの分野ではより高機能なクラウドサービスを利用しています。 アプリケーションの監視とパフォーマンス・チューニングは CloudWatch に加えて、NewRelic を用いています。 Bugsnag も多様しています。Rails や JavaScript で 例外が発生した場合は、Bugsnag によって通知されますが、例外発生時 の 環境変数 や Stacktrace などの実行環境が保存されるため効率的なデバッグに欠かせないツールとなっています。 また、メールの配信には SendGrid、画像の配信には imgix を用いています。これらのサービスは、メールのバウンス処理や、画像のリアルタイム変換など便利な機能備え、開発リソースの節約につながるだけでなく、費用面でも効率が良く助かっています。 最後に Cloud Firestore ですが、TUNAG のチャット機能のネイティブアプリ向けのバックエンドとして利用しています。チャットは、利用頻度も高くスケーラビリティ及びパフォーマンスが非常に大切となります。FireStore を用いることで、AWS の S3 のように パフォーマンスとスケーラビリティをすぐに獲得でき、また、Cloud Firestore のネイティブ向けSDKに含まれるローカルキャッシュ機能により、データ通信量の削減やレスポンス向上にも寄与しており、今後 Cloud Firestore の利用頻度は向上しそうです。 まとめ ここまで読んでくださってありがとうございます! 今回、スタメンのエンジニアたちが作っている TUNAG について、技術的な側面からご紹介させていただきました。 TUNAG は、SNS、チャット、ワークフロー、制度管理(フォームビルダー) といった機能を備えた 大規模 Webアプリケーションで、Rails の他にも、React を用いたフロントエンド開発、Swift/Kotlinでの 向けネイティブアプリに加え、AWSを中心とした SRE にも取り組んでいます。 UI/UX、パフォーマンス、安定性、スケーラビリティ と 課題は山盛りで、当面飽きそうにありません。 また、TUNAG の導入社数は増え続けており、利用いただいているユーザ企業様からは多くの要望と期待を頂いています。ユーザーの皆さんの会社を良くすることに貢献できるのは、やりがいも社会的意義も感じます。 このように、スタメンの開発現場は、頼もしい仲間と、歯ごたえのある課題に向き合い、多くのユーザに使われる、大規模なアプリケーション開発に、没頭できる環境です。 これだけでも、それなりに幸せなことなのですが、もっと技術的なチャレンジをしてみたいし、もっと良いサービスにして、もっと多くの人に使っていただきたいと思ってます。何よりも、たくさんの頼もしい仲間を集めて、さらにすばらしい開発チームを作っていきたいと思っています。 ちょっとでも興味をもってくださったら、 株式会社スタメンの募集一覧 ご覧いただき、ぜひご応募いただけないでしょうか。
1. はじめに 明けましておめでとうございます。エンジニアのミツモトです。 年末は皆さんいかがお過ごしでしたでしょうか? 年末、私は家でゆっくりしながら、リーダブルコードを読んでいました。 リーダブルコード この本は私がスタメンに入ってから最初に勧められて読んだ本です。 スタメンがリーダブルコードを推奨している理由 スタメンはチームでの開発が前提になるため、誰もが読みやすいようにコーディングすることを大事にしています。そのため、人によって読みやすさの基準に差がでないよう、リーダブルコードを参考にしています。 あわせてRubocopも採用し、コーディングスタイルを統一しています。 リーダブルコードを読みながら、Webアプリケーションエンジニアとしての1年目を振り返ったので、今回は、この1年でコードの書き方がどう変わったかを紹介します。 2. 読みやすいコードを書く 変数・メソッドの 命名 に気を付ける 変数・メソッド名で処理の中身をわかりやすくするため、次のことをしています。 品詞の扱いを気をつける メソッド名で何が起きるのか/できるのかをわかりやすくするため 他でどういう 命名 がされてるか調べる プロダクト/チーム全体で用語を統一する Facebook など類似のプロダクトのURLや CSS の変数を参考にする 最初の頃は品詞を意識していなかったため、名詞・形容詞が入り混じっていました。 (最近でもどの単語にするか迷う) 昨年、社内で話題になったのは ghkw という コマンドライン ツールです。 github 上のキーワードを検索し、利用件数を知ることができます。複数のキーワードを同時に検索できるため、比較するのに便利です。 実装の長いメソッドを分ける メソッドが長くなると、コードが読みづらくなり、テストも複雑になります。 ユニットテスト が書きやすい粒度にコード量を保つことで、可読性が増え、不具合も減ります。 メソッドを分けるため、具体的には次のことをしています。 どういう処理を行っているか箇条書きにする。 箇条書きにした処理の中で、別メソッドとして切り出せるものは分ける。 別メソッドにすることが難しければ、メソッドの中で段落に分ける。 こうすることで、1つのメソッドの読みやすさが向上します。 それでも分かりづらい場合はコメントをつけます。本来は「コードのみで処理がわかる」状態が一番良いですが、他の人や未来の自分からすると、「一体何をしてるんだ?」となり得るものはコメントを付けるようにしています。 コメントを付ける際は、背景やメンテナンス時の注意事項など、あとで読む人に伝えたいことをシンプルに書くように気をつけています。 条件式がネストしていたら早めに return する 条件式がネストしていると、コードが読みづらくなります。 if post.present? if post.image? puts ' hoge1 ' else puts ' hoge2 ' end else puts ' hoge3 ' end 階層の深い条件式はスタックが多くなり、「最初の条件って何だっけ?」となります。 他の人にとっても読みづらくなるので、早めにreturnするのはオススメです。 return puts ' hoge3 ' if post.blank? if post.image? puts ' hoge1 ' else puts ' hoge2 ' end 3. Rails での設計の考え方 Model に対しての ロジックを View と Controller に持たせない スタメンはバックエンドに Ruby on Rails を採用しており、 MVC の アーキテクチャ がベースになります。 昔のコードを見返すと Model にあるべきロジックが Controller や View に出ていました。 Modelにロジックが定義されていないと、 Controller や View でコードが重複する可能性があり、DRY ( Don't repeat yourselfの略。コードの重複を防ぐプログラミングの原則 )から遠ざかります。 View でよくあるのが Model の状態に関連した条件式です。 if @post .present? && @post .image? && ( @post .image.width > 1024 || @post .image.height > 1024 ) # サムネイル表示する end こういう場合は Model の インスタンス メソッドにします。View は スマホ 版とPC版、メールなど、同じような内容で複数のテンプレートを実装することも多く、Modelにメソッドとして実装することでDRYになります。 class Post def has_large_image? image.present? && (image.width > 1024 || image.height > 1024 ) end end Controller でよくあるのが、 scope を使っていないパターンです。 @posts = Post .where( user : current_user, type : %i(image video) ) というような where 句を 昔は Controller に直書きしていました。 こういう時は Model に scopeを定義することで、他のController や View でも呼び出すことができます。 View と Controller は、 Model に定義したメソッドを最低限呼び出すだけにしましょう。(ただし、最近 FatModel になりつつあるので、 Model 内の整理も必要) private メソッドで カプセル化 する 昔は深く考えずに全て public メソッドで定義していたのですが、外部から参照してほしくないメソッドは private メソッドとして定義するようになりました。これによって外部から呼ばれないことが保証され、 リファクタリング がしやすくなります。 private メソッドは、それを用いる public メソッドのテストで動作が確認されるため、private にすることでテストを省くことができます。 validation でエラーを返す インスタンス として無効なのであれば、 インスタンス メソッド内でエラーハンドリングするのではなく、 validation としてエラーを返します。エラーメッセージも扱いやすいし、 validation をかけるタイミングも設定できます。 ActiveModel::Validator を継承した custom validator を使えば、 複数のモデルに共通した validation を DRY に実装することができます。 4. メンテナンス性を高める YAGNI だけど、拡張しやすく YAGNI とは、"You ain't gonna need it"の略で、「機能は実際に必要となるまでは追加しないのがよい」という エクストリーム・プログラミング における原則です。 新しい機能を実装するとき、「こういう機能も付加した方が良いのでは?」という話が出ます。要望に沿って付加機能を実装したとしても、使われなければ意味がありません。また、gemや他サービスでそれを補うものが出てくる可能性もあります。(実際に google drive の gem を扱ったとき、作り過ぎなくてよかった!ということがありました。) 作り過ぎは禁物ですが、予見できる要望を後で作りやすくするために設計段階に考慮しておくのは大事です。 DB のテーブル構成を考える時など、あとで修正する時にコストが高くつくものは拡張性を意識します。 コードの共 通化 既存の機能と似たものを作るとき、そのままコピーして部分的に変更するだけで、実装できるものがあります。ですが、これをするとロジックの修正が必要になった際に修正箇所が多くなり、メンテナンス性は大きく下がります。 スタメンに入ってからも何度かそういう場面がありました。最初の頃はコピーして実装することが多かったですが(スタメンの皆、すみません...。)、レビュー等で指摘を受け昨年の終わり頃から共 通化 を意識するようになりました。 Model と Controller は共通部分を Module に切り出すことで、次に機能を追加する時に楽になります。 View についても template を共 通化 します。 その瞬間は面倒だと思いますが、後で全部つくり直すより今やった方が良いです。 5. おわりに エンジニアになったばかりの人の参考になればと思い、今回の記事を書きました。 こうやって書き並べると、まだ十分にできてない部分があるため、今後もより良いコードを書けるよう頑張ろうと思います。2019年は Rails の基礎を固めつつ、フロントエンドにも幅を広げていきます。 スタメンではエンジニアを募集しています。もし興味がありましたら こちら からご連絡ください。 オフィスで、話ができるのを楽しみにお待ちしています。
こんにちは。桑原です。 2度目の投稿となります。 今回は TUNAG のフィードに埋め込まれている Markdown の仕組みについて解説します。 ※ ソースコード は部分的な公開になるため、ご了承ください。 前置き Markdown とは、テキストで HTML を表現するための マークアップ 言語です。 下記の画像のように、タイトルやプログラムコード、画像の埋め込みなど、多彩な表現が可能になります。 ところがこの Markdown 、一般的な記法はあれど、標準化・規格化されていないことをご存知でしょうか? エンジニアの間では、 Markdown への対応はまさに 地獄 と評されてきました。 何故 地獄 と言われているのか、 Markdown の仕組みと散々苦しんだ私の愚痴話をしたいと思います。 網羅するべき多くの記法 ざっとあげると下記の通りです。 文字の強調、イタリック、もしくはその両方 Strongタグ 取り消し線 ヘッダー(h5〜h1) 区切り線(4種類) HR区切り線(2種類) Codeタグ Blockquoteタグ 点リスト 数値リスト チェックボックス (チェック入り、無し) 絵文字 画像 リンク(URLそのまま、文字列にHrefのアタッチ) メール and 電話番号 改行 テキストに 正規表現 の走査・走査・走査... TUNAG ではフロントエンドの実装に React を採用しています。 Markdown での アルゴリズム では、テキストを 正規表現 で記法を検知し、 Markdown 用の React Component に置き換える、と言う方法で実装しています。 これを行う MarkdownParser の動きは下記の通りです。 Markdown の記法には大きく分けて下記の5パターン存在します。 太字のようなそれ単体で成立するもの Hタグのようにその記法の中でさらに別の記法が成立するもの Codeタグのように行をまたいで成立し、かつタグの中で別の記法が成立するもの 改行 1〜4のどれにも属さない文字列 これらのパターンに分けて、走査の アルゴリズム について説明します。 1. Delタグのようなそれ単体で成立するもの これは最も単純なパターンです。 該当する記法としては下記の通りです。 文字の装飾や取り消し線 HR区切り線 絵文字 画像 該当する文字列が存在する場合、下記の コンポーネント が実行されます。 decoratedText には該当した文字列が引数として与えられるため、この文字列を del タグでくくって DOM を return するだけです。 const Del = ( { decoratedText } ) => { const textRegExp = new RegExp (`(?: \\ ~) { 2,2 } ( [ ^ \\ x00 ] +?)(?: \\ ~) { 2,2 } `); const textMatch = decoratedText.match(textRegExp); if (!textMatch) { return null ; } return ( <del className= "markdown__del" > { textMatch [ 1 ] ? textMatch [ 1 ] : null } </del> ); } ; 2. Hタグのようにその記法の中でさらに別の記法が成立するもの このパターンの場合は 1. の応用で実現します。 decoratedText が該当する文字列になるため、これを更に 正規表現 (二重が許される記法)で走査する、 replaceToComponentsメソッド を利用しています。 replaceToComponentsメソッド により、該当する 正規表現 があれば コンポーネント に置き換わった戻り値が得られます。 const H1 = ( { decoratedText } ) => { const textRegExp = new RegExp (`# ( [ ^ \\ x00 ] *)`); const textMatch = decoratedText.match(textRegExp); if (!textMatch) { return null ; } return ( <del className= "markdown__h1" > { textMatch [ 1 ] ? replaceToComponents(textMatch [ 1 ] ) : null } </del> ); } ; 3. Codeタグのように行をまたいで成立し、かつタグの中で別の記法が成立するもの Markdown では画像のような行をまたいだ記法が存在します。 これに対応するため、改行コードを UTF-8 の \x00 に変換して、テキストすべてを1行の文字列に整えてから 正規表現 の走査を行います。 ※ \x00 を利用するのは、ユーザーのキーボード入力では入力ができないためです ※ 一般的に利用される 文字コード ではバグの原因となります 下記の 正規表現 は1行に変換された文字列から Blockquote の記法を検知するためのものです。 これを1行となったテキストに実行することで、 Blockquote の Component に置き換えます。 '> Blockquoteだよ> 二行目> > 段落1> > 段落2'.match(new RegExp(`((^|\\x00)(\\> )+(.*?)(\\x00|$)){1,}`)); しかしこのままでは Blockquote は一行の文字列になってしまうため、先ほど変換した \x00 を使って改行を行います。 Blockquote の場合は、 \x00 ごとに div タグでくくる処理を加えます。 そしてCodeタグの中でさらに別の記法が成立するため、2. と同様に二重で走査を行います。 4. 改行 マークダウンは複数改行を行なっても、一行に変換されるものが一般的ですが、 TUNAG では複数改行をそのまま有効にするように作られています。 これは単に走査された \x00 を コンポーネント に置き換えるだけの処理になります。 const Line = () => { return ( <span className= "markdown__line" /> ); } ; 5. 1〜4のどれにも属さない文字列 1〜4 のどれにも属さない文字列はそのまま span タグでくくっただけの コンポーネント に置き換えます。 const String = ( { decoratedText } ) => { return ( <span className= "markdown__str" > { decoratedText } </span> ); } ; Markdown だけじゃ足りない、更なる要望 マークダウンの実装だけではなく、下記のような要望を社内やユーザーさんから頂いたため、機能の拡張を行いました。 「...もっと見る」によるフィードの省略 「ナイスプレーしたメンバー」など、マークダウンでないパーツの埋め込み 複数改行をしたい まだこれだけでなく、たくさんの要望が積もっているため、随時対応していきたいと思います。 最後に いかがだったでしょうか。本当にざっくりでしたが、 Markdown の仕組みを解説しました。 しかし実際にはこの説明ほど単純ではなく、更に地獄が待ち受けているのが現実です。 👻 もし挑まれるなら心して掛かるように... 💀  
はじめに こんにちは! スタメンのシュール ( @shule517 ) です。 RuboCop って便利ですよね! 僕は Ruby を書き始めた頃に、「君の書いたコードは Ruby っぽくない。Rubocopを入れると良いよ!」とアド バイス をもらってから、ずっと愛用しています。Rubocopは Ruby の一般的な書き方を教えてくれたり、もっと効率の良い書き方を教えてくれるので、 Ruby を学ぶ中でとても頼りになりました。 そんな僕の Ruby の師匠(?)でもある Rubocop について紹介します! Rubocopとは? Rubocop は、 Ruby のコードをチェックしてくれるGemです。 上の例だとロケットハッシュ { :key => 1234 } は Ruby の古い構文なので、書き換えてね! と指摘されています。 Ruby のコーディングスタイルで有名な「 Ruby Style Guide 」を基準に、ルール違反した書き方をしていると指摘してくれます。また、「 Rails Style Guide 」を元に Rails に特化したルールまでチェックしてくれます。チェックしてくれるルールは、400以上あります。詳しくは 公式マニュアル で! Ruby を始めたばかりの人には、 Ruby の書き方を勉強するためのヒントに。プロジェクトで Ruby を使ってる人には、コーディング規約をプロジェクトに浸透させるために。 個人で開発している人にも、プロジェクトで使う人にも、おすすめです! Rubocopを導入してみる Gemをインストールする ターミナルで gem install rubocop を実行する。 Rubocopを実行してみる rubocop と実行すると、現在 ディレクト リ配下のコードをチェックしてくれます。 ハッシュの書き方がルールと違うよ!と4箇所指摘されています。 この方法だとコマンドを実行するのが手間なので、次はRubyMineと連携してみましょう! RubocopをRubyMineと連携しよう! RubyMineと連携すると、コードを書いた瞬間にRubocopの指摘が表示されるようになります。コマンドを手動で実行しなくても良く、その場でコードを修正できるので、とても便利です! RubyMineでRuboCopを有効にする RubyMineのPreferencesメニューを開きます。[Editor → Inspections] の Rubocop を有効にしてください。 RubyMineのエディタにコードを書くと、リアルタイムでRubocopの指摘が表示されるようになりました。 連携完了です! どんどんコードを書いていきましょう! Rubocopの指摘を自動修正しよう! AutoCorrect機能を使うと、指摘内容を自動で修正してくれます。この機能がかなり強力で、大半の指摘は自動修正が可能です。Rubocopを導入した時点で大量のエラーがあると、手動で修正するのはとても大変なので、自動修正していきましょう! rubocop -a ファイル名 で、指定したファイルの指摘内容を自動修正できます。 ※-a は、 --auto-correct の略です。 いろんな書き方のハッシュが全て同じ書き方に統一されました! ←が修正前 →が修正後のコードです。 さいごに 最後まで読んでいただいて、ありがとうございました! 次は、実際のプロジェクトにRubocopを導入した時の体験談について書こうと思います! プロジェクトにRubocopを導入した時点では、大量のエラーが出てビビリました。。。 プロジェクトにRubocopを浸透させていくまでの方針や、工夫。 Sider の導入について書こうと思います! 次回もお楽しみに〜!
1. はじめに こんにちは。エンジニアのミツモトです。 普段は TUNAG という 社内SNS サービスのバックエンドを担当しています。 フロントエンドの業務も行いますが、触れる機会が少なく、たまには動くものを作ってみたい!と思い、今回は SVG でloaderを作りました。 2. つくったもの See the Pen svg loader by mitsumoto ( @mmoto ) on CodePen . 3. SVG とは Scalable Vector Graphicsの略 ベクター 画像( JPEG や PNG などのラスター画像と異なり、伸縮しても画質が劣化しない)を XML で記述するための言語 W3C ( World Wide Web Consortium)により勧告 図形(ex. 直線・曲線・円)、画像、 テキストを扱い、それらのオブジェクトをグループにまとめたり、スタイリングできる onmouseover や onclick などの多彩な イベントハンドラ を、任意の SVG オブジェクトにあてることができる Scalable Vector Graphics (SVG) 1.1 (第2版)概要 Can I Use どのブラウザでも、最新のversionであれば SVG に対応しています。 4. 図形を描画する 4-1. SVG タグ svg タグは、他の XML である XHTML やMathMLタグとの名前の衝突を避けるため、 xmlns="http://www.w3.org/2000/svg" と記述します。 これにより 名前空間 を指定し、自分と子要素が svg であることを示します。( HTML5 では省略できます。) SVG プロパティ 説明 width 描画領域の幅 height 描画領域の高さ viewBox "x y width height" x:表示領域の左上角 x座標 y:表示領域の左上角 y座標 width:(描画領域に対する)表示領域の相対的な幅 height:(描画領域に対する)表示領域の相対的な高さ viewBoxの設定は分かりづらいため、今回は表示領域の位置を(x,y)=(0,0)とし、描画領域と表示領域のサイズを合わせます。 数値に単位がない場合、 SVG ではpxになります。 4-2. 線の描画 線の起点と終点を座標で指定し、描画します。 See the Pen line by mitsumoto ( @mmoto ) on CodePen . lineプロパティ 説明 x1 起点のx座標 y1 起点のy座標 x2 終点のx座標 y2 終点のy座標 stroke 線色の指定 4-3. 矩形の描画 矩形の幅や高さ、座標、角の丸みを指定して描画します。 See the Pen rect by mitsumoto ( @mmoto ) on CodePen . rectプロパティ 説明 width 幅 height 高さ x 左上角 x座標 y 左上角 y座標 rx 角のx方向丸み ry 角のy方向丸み fill 塗りつぶし色の指定 4-4. 円の描画 See the Pen circle by mitsumoto ( @mmoto ) on CodePen . circleプロパティ 説明 cx 円中心 x座標 cy 円中心 y座標 rx 円の半径 4-5. パスの描画 指定した座標を順番に線で結び、描画します。 See the Pen path by mitsumoto ( @mmoto ) on CodePen . pathコマンド 説明 M x,y パスの起点 座標 h x 水平方向にx座標まで直線を描画 v x 鉛直方向にy座標まで直線を描画 l x,y x,y座標まで直線を描画 c x1,y1 x2,y2 x,y 3次 ベジェ曲線 を描画 x1,y1:曲線の起点に対する制御点の座標 x2,y2:曲線の終点に対する制御点の座標 x,y:終点の座標 コマンドは大文字だと絶対座標、小文字だと1つ前の座標からの相対座標になります。 上記以外にも、 ベジェ曲線 を描く q , t , sコマンドや、円弧を描く a といったコマンドがあります。 5. loaderをつくる 「2. つくったもの」の内、右側のドミノ倒しのようなloaderのつくり方を説明します。 はじめに矩形を描画します。 See the Pen domino1 by mitsumoto ( @mmoto ) on CodePen . これにアニメーションをつけていきます。 まず水平方向に移動させます。 animate attributeName="x" values="10;100" dur="1.2s" begin="0.8s" repeatCount="indefinite" animateプロパティ 説明 attributeName アニメーションの対象属性 values 変化させる属性値( ; で区切ります) dur アニメーションの1ループにかける時間 begin アニメーションの開始時間 repeatCount 繰り返し回数(indefinite:無限に繰り返し) 0.8sec経過後にアニメーションが始まり、1.2secかけて水平方向にx=10から100へ移動するアニメーションが繰り返されます。 See the Pen domino2 by mitsumoto ( @mmoto ) on CodePen . 次に矩形を回転させ、不透明度を変化させます。 animateTransform attributeName="transform" type="rotate" from="0 10 70" to="-60 100 70" dur="1.2s" begin="0.8s" repeatCount="indefinite" attributeName="opacity" values="0;1;0" dur="1.2s" begin="0.8s" repeatCount="indefinite" animateTransformプロパティ 説明 type 変化の種類 from アニメーションの開始時の属性値 type:rotateの属性値(d, x, y) d: 回転角 x: 回転の中心点x座標 y: 回転の中心点y座標 to アニメーションの終了時の属性値 不透明度はattributeNameプロパティを"opacity"とし、valuesを0〜1の間で指定します。 See the Pen domino3 by mitsumoto ( @mmoto ) on CodePen . 最後にアニメーションの開始時間をずらして複数表示させることで、ドミノが倒れているようなアニメーションにします。 See the Pen domino4 by mitsumoto ( @mmoto ) on CodePen . 6. おわりに SVG を用いてloaderをつくりました。細かな部分へのこだわりがサービスへの愛着に繋がると思うので、デザインには一層気を配りたいと思います。スタメンではエンジニア、デザイナーを募集しています。興味がある方は こちら をご覧ください。
こんにちは。スタメンの 小林 ( @lifework_tech )です。 スタメンの社内勉強会で障害対応について話しましたので、資料を公開させていただきます。 障害対応への考え方と備え どんなに対策を行っていても障害は必ず発生します。 地震 のようにいつ発生するのかはわかりません。でも、予め障害発生を想定して、準備しておくと、影響の低減、復旧までの時間短縮など多くのメリットがあります。 障害に向き合い再発防止策を積み重ねることで、システムもチームも強くなることを目指し、普段意識していることをまとめてみました。 障害の検知から対応まで 障害を把握したら まずは落ち着く 障害を知ったらまずは深呼吸して落ち着きましょう。慌てて対応してもいいことは何もありません。 役割を分担する 原因調査をしつつ、応急処置を行い、社内に連絡するなど、障害対応は同時並行で進みます。また、普段の デバッグ と一緒で、一人よりも、誰かと話しながら対応すると、スムーズに進むものです。 まずはチームメンバーに連絡を取り障害対応を分担できるようにしましょう。 原因を把握する アラートや社内からの報告など、障害を把握することになった情報があるはずです。まずは、その情報を起点として何が起きているかを把握することが大事です。 このときアラート、ログを精査することが多いと思いますが、「原因」と「現象」を意識することが大事です。 例えば、 AWS の CloudWatch ALARM を用いて監視を行っている場合、Elastic Load Balancing (ELB) の HTTP応答の Latency が しきい値 を超えたというアラートによって、障害発生を知ることがあります。この場合、何らかの原因があって、ELB の遅延が発生しているのであって、現象としての負荷に対応しようとして、サーバーを増設しても「原因」は解消されません。 また、 MySQL への接続数が上限に達したときに発生する Too many connections の場合、 Rails 等の クライアント から 新たに接続できないのは「現象」であって、滞留しているクエリなど接続数を増やす「原因」は別にあります。 なので、「この現象を起こしている要因は何か?」と考えながら、ログやアラートを見ると良いです。 また、それぞれのログには非常に重要な情報が含まれています。なんとなく流し読みするのではなく、一つ一つメッセージ (エラー) の意味を理解しながら調査してください。 加えて、時系列にメモを取りながら調査すると、状況の整理、チームへの共有、障害報告への記載などに役立ちます。 テキストエディタ でも良いですし、Slack 等のチャットへ貼っていくのでも良いでしょう。 こういうときに、 MySQL や bash / zsh の プロンプトに現在時刻が入っていると、出力結果をコピーするだけで時刻も記載されるため便利です。また、iTerm2 等 の Terminal Scrollback Buffer を 大きな数字にしておくと、スクロールすれば履歴を追えるため便利です。 社内と顧客への共有 障害対応に集中するあまり、社内やユーザーへの共有が疎かになることがあります。 障害対応中は、常にユーザーへの悪影響が発生します。何が起きているかわからない状況ではストレスは強くなります。障害が続くようであればユーザー向けの告知を検討しましょう。 このとき、社内のサポートチームへ告知をお願いできると、技術的な障害対応に専念できます。エンジニア以外でも告知を掲載できるツールを用意しておくと良いです。 社内への共有について、顧客対応に加えて、 Google や Facebook 等へ広告を出稿している場合は、広告出稿を止めることで余計な出費を抑えることができます。 たまたま、障害発生 と Google の クローラー の巡回が重なって、一時的に検索順位がさがることもあるかもしれません。エンジニアが想定していない影響がある場合もあります。社内共有はしっかり行いましょう。 復旧作業 障害の要因がわかり、障害が継続している(しそうな)場合は、復旧作業をすることになります。原因よって対応は異なりますので、ここでは個別ケースでの対応方法は割愛し、注意すべき点について記載します。 復旧作業をする際には、障害によってデータの不整合が発生していないか、または、応急処置によって不整合が新たに発生しないかを必ず確認してください。特に、 MySQL 等のDBで、クエリを 中断(Kill) するとき、 Sidekiq/DelayedJob Jobを中断するときは、そのオペレーションによって何が起きるのかを把握してから実行すべきです。 不用意な中断によって、database の破損 や データ不整合などが起きることはよくあります。障害時に焦ってオペレーションしたばかりに、さらに大きな障害を招いた例は、たくさん経験してきました。 障害対応時のオペレーションは、普段よりもミスしやすいです。まずは落ち着いて、誰かに目視で確認してもらったり、できる限り実行前にバックアップを取るようにしてください。 障害収束後にすること 顧客向け障害報告の掲載 ユーザーからのお問い合わせへの返信、サイトへの告知の掲載など、お客様に対して障害の報告とお詫びを掲載します。どれくらい詳細に記載するかは、障害の範囲、時間、影響など多くの要素が絡みます。ユーザー向けサポートを担当している部門と連携して文案を作成して掲載しましょう。 この際、過去の掲載内容が履歴として残っているとスムーズです。 社内向け障害報告の提出 障害から時間が経つと記憶も曖昧になるし、危機感も薄れます。障害報告は、当日か遅くても翌日には提出するようにしましょう。 社内の過去の障害報告を雛形にして記載すると良いです。可能ならドキュメントツールのテンプレートにしておきましょう。 障害報告を記載する際は、誰に対して報告するのかを明確にしましょう。誰に報告するかで、影響範囲や原因などの説明内容が変わります。以前勤めていた会社で、エンジニアしかわからない専門用語ばかりの障害報告を全社向けにメールして、叱られたこともありました。 スタメンの障害報告フォーマットは下記です。 スタメンでは、障害発生時から対応完了まで 社内のチャット で状況を随時共有しながら障害対応をしているため、障害報告は、エンジニアチーム内での履歴として作成しており、社内向けには「概要」に記載する程度にしています。 ## 報告 [年月日] [報告者]  (誰がいつ報告したか) ## 概要 (発生した障害についての概要。エンジニア以外にもわかりやすく簡潔に) ## 対応履歴 (障害発生、検知、対応、収束までの履歴を時系列に箇条書きで) ## 顧客対応 (顧客への連絡 や 影響に応じた対応など) ## 発生原因 (障害が発生した原因について) ## 再発防止 (再発防止策とタスクリスト) ## 関連コミット (障害対応時のコミット、再発防止策のコミットへのリンク) ## 参考資料 (障害対応や再発防止実施時に参考にしたドキュメント等へのリンク) 障害報告で重要なのは、再発防止 です。 障害の原因となったことへの対策はもちろんですが、間接的な要因となった事象についても再発防止を行うこと大切です。 直接的な原因への対策 不具合の理由となったコードを修正する、 アクセス障害の原因となったサーバーを入れ替えるなど、発生原因に対しての対応です。 間接的な原因への対策 不具合であれば、なぜテストで検知・予防できなかったのか。過負荷によるシステムダウンであれば、設計上の課題や、ステージング環境での検証で事前に把握できなかったのか? など。 また、再発防止策として悪い例としては、下記があります。 気をつけます。 ダブルチェックします。 マニュアルに記載します。 エンジニアは、コードによって、再現性、確実性のある再発防止策を実施できるのが強みです。 気をつけます という気持ちも大事ですし、ダブルチェック もときには必要ですが、どちらも再現性も、確実性はありませんし、マニュアルについては読まれるとは限りません (たいてい忘れ去られる)。 事前の準備 障害は突然やってきますし、素早い判断や対応を求められるため、事前に準備(備え)ておくことが大切です。 システム構成の把握 今からホワイトボードにシステム構成図をかけますか? ログの場所は把握していますか? CloudWatch 等での監視 異常を検知できるような仕組みになっていますか? 異常が起きる前の状態も監視していますか? データのバックアップ 大切なデータが失われないように定期的にバックアップが取得されていますか? DB はもちろん、画像など失われるデータはないでしょうか? コマンドライン に慣れておく mysql ps, top, tail, grep などの代表的なコマンドとそのオプションを把握しておく。 連絡先の把握 休日や夜間に、助けを求められるように連絡先をしっていますか? Slack や LINE だと夜間通知が抑制されるときがあるため、電話できると良いです。 まとめ はじめから障害対応できる人はいません。みなさん、小さな経験を積み上げて、 スキルアップ していくものです。ですので、障害に遭遇したらいい経験が詰めるチャンスだと思って、飛び込んでみてください。 先輩がテキパキ障害対応しているときに、飛び込む勇気がないときがあるかもしれません。そのときは「できることないですか?」と聞いても良いでしょう。 過去に、集中して対応しているときに社内調整や告知掲載を引き受けてくれて助かったこともありますし、ランチ前に障害が起きて対応しているときにおにぎりの差し入れをもらって一息つけたこともありました。 私が新人だったころ、障害対応後に喫煙ルームにいって一服している上司を追いかけて、何を考えながら対応していたのか質問していました。また、上司の shell の history を見て、 トラブルシューティング 方法を学んでいました。 今晩、一人アラートに気づくかもしれません。そのときに、何かしらの対応ができるように今日から準備していきましょう。
こんにちは、スタメンの松谷( @uuushiro )です。 「 Rails スタートアップがやってよかったこと」 というタイトルで、 銀座Rails#1 で発表してきました。その時の資料を公開します。 発表内容 https://speakerdeck.com/uuushiro/yin-zuo-rails-number-1-uuushiro スタメンが創業してから約2年間のやってよかったことをまとめているので、以下のような内容に興味のある方はぜひスライドをご参照ください。 入れてよかったgem aasm (オブジェクトの状態遷移管理が簡単にできる) awesome _nested_tree ( ActiveRecord でツリー構造を扱える) elasticsearch-model / elasticsearch- dsl (Elasticsearchを Rails で簡単に扱える) xray- rails (ブラウザ上でviewの構造を可視化してくれる) delayed_job / sidekiq (非同期処理) 使ってよかったツール・サービス imgix (画像専用 CDN ) Bugsnag (クラッシュレポート管理ツール) SendGrid (メールデリバリーサービス) Mailtrap (ダミー SMTP サーバー) Metabase (BIツール) 取り入れてよかった開発スタイル 定期bundle update レビューの自動化 発表の背景 今回、私が発表しようと思った理由としては2つあります。 1つ目の理由は、TUNAGの開発には Rails やその他多くのgem使っていてますが、単に使うだけでなくフィードバックをして、 Rails コミュニティの発展に少しでも貢献したかったからです。スタメンが創業した2016年に、「 Rails ベストプ ラク ティス」のような内容の資料を参考にさせて頂き、とても助かりました。今回公開する発表資料で、どなたかの2018年版の Rails 技術選定の参考になれば幸いです。 2つ目の理由は、技術的な知見を発表することで、名古屋に閉じがちな弊社も、社外の多くの Rails エンジニアからフィードバックを得ることができ、それをもとに技術力の向上に繋がるのではないかと考えたからです。実際、発表中にも Twitter の ハッシュタグ で沢山のフィードバックを頂きました。ありがとうございました。懇親会でも多くの Rails エンジニアの方々と技術ノウハウの交換ができ、名古屋ではなかなかできない充実した時間を過ごせました。 まとめ スタメンとして初めて東京の技術イベントで発表させていただいたのですが、想像以上にその場の空気や熱、そこで出会う人たちとの交流が魅力的で、本当に楽しかったです。これからどんどん東京のイベントにも参加したいと思います。このようなきっかけを作ってくださった運営スタッフの皆様、ありがとうございました。
こんにちは。スタメンでデザイナー兼フロントエンドエンジニアをしているナガキです。 弊社が提供している「TUNAG」 には(オプションで)ビジネスチャット機能があります。 App版チャット PC版チャット ご覧のように、 スマホ アプリ版は非常にシンプルな画面構成ですが、PC版はひとつの画面内に様々な情報を表示する必要があり、どうしても複雑になりがちです。 そんな複雑で大量の情報を CSS の Flexbox を利用してレイアウトしています。 今回は「TUNAG」PC版チャットを例に、 Flexbox の便利な使い方をご紹介します。 `Flexbox` とは Flexible Box Layout Module のことで、 Flex Container と呼ばれる親要素と Flex Item と呼ばれる子要素を使って、柔軟なレイアウトが実現できます。 CSS Flexible Box Layout - MDN web docs 仕様が何度も変わったり、ブラウザが対応していないなどの問題もありましたが、現在ではほとんどすべてのモダンブラウザで安心して利用することができます。 Can I use 最近徐々に利用が広がっている Grid Layout は2次元のレイアウトを定義しますが、 Flexbox は1次元、つまり横か縦、どちらか一方向だけのレイアウト(並びや余白)を定義します。 これだけ聞くと Grid Layout のほうが便利なように思えますが、 Flexbox をうまく活用することで、その名のとおりフレキシブルなレイアウトが実現できます。 それでは早速、実際のチャット画面を参考にしながら、どのように Flexbox を利用しているか説明していきましょう。 固定幅 + 可変幅の横方向カラムレイアウト PC版チャットのベースレイアウトは 位置 コンテンツ 横幅 ウィンドウサイズによる拡縮 左 ルーム一覧 固定 なし 中央 選択中ルームのメッセージ一覧 可変 あり 右 選択中ルームに関する情報 固定 なし となっており、左右のカラムが固定幅、中央のメッセージ一覧部分だけがウィンドウ幅にあわせて変わります。 これを CSS で実装しようとすると以下のようになります。 See the Pen flexbox sample 1 by ngk works ( @ngk-works ) on CodePen . ` flex ` プロパティでカラムの拡縮をコン トロール する ポイントとなるのは子要素の flex プロパティ。これでカラム幅の拡縮の有無( flex-grow , flex-shrink )や基準幅( flex-basis )をまとめて設定しています。 flex: flex-grow flex-shrink flex-basis; サンプルでは左右のカラムを 100px の拡縮しない要素とすることで、中央のカラム幅だけがウィンドウ幅に応じて変わるようになります。 このように Flexbox を利用することで、複数カラムのレイアウトが簡単に実現できました。 固定幅 + 可変幅の縦方向カラムレイアウト Flexbox は横方向だけでなく、縦方向にも利用できます。 例えば、左カラムにあるチャットルーム一覧部分のレイアウトを見てみると、 コンテンツ 高さ ウィンドウサイズによる拡縮 スクロール ヘッダ(タイトル) 固定 なし なし 検索フォーム 可変(なりゆき) なし なし ルーム一覧 可変(なりゆき) あり あり となっています。 先程のベースレイアウトと比べると、 コンテンツが並ぶ方向が縦 拡縮はしないが、コンテンツの内容によって幅(高さ)が決まるコンテンツがある ウィンドウ幅やコンテンツの内容によって幅(高さ)が変わる という違いがありますが、これも Flexbox であれば簡単に実装できます。 See the Pen flexbox sample 2 by ngk works ( @ngk-works ) on CodePen . ` flex -direction` で並べる方向を指定する flex-direction を column に指定するだけで Flex Item を縦方向に並べることができます。 また、幅をコンテンツ内容に合わせたい場合は flex-basis に auto を指定します。 .flex-container--column { display : flex; flex-flow: column nowrap ; // 縦方向に並べる } .component-header { flex: 0 0 60px ; // 拡縮なし / 固定幅 } .search-form { flex: 0 0 auto ; // 拡縮なし / コンテンツに応じた幅 } 縦方向に並べる場合に気をつけなければならないのは、ウィンドウの高さとコンテンツ量の関係です。 コンテンツ量がそれほど多くなく、ウィンドウ内に収まる場合は問題ありませんが、ウィンドウの高さを超える量のコンテンツがある場合は注意が必要です。 「TUNAG」のチャットでは1画面内にすべての情報を収める必要があるため、チャットルーム一覧部分が画面外に広がっていかないよう .room-list { overflow-y : auto ; } を指定することで、状況に応じてリスト部分だけをスクロールさせています。 サンプルのウィンドウサイズを変えたり、リストアイテムの数を増減させたりして挙動を確認してみてください。 `Flexbox` を利用して、思い通りのレイアウトを実現する 右カラムにはチャットルームの様々な情報が表示されています。 コンテンツ 高さ ウィンドウサイズによる拡縮 スクロール 配置 ルームアイコン 固定 なし なし 上 メンバー一覧 可変(なりゆき) あり あり 上 ファイル一覧 可変(なりゆき) なし なし 下 (※ 各コンテンツには、指定幅のヘッダ(タイトル)と実コンテンツが含まれます) チャットルーム一覧とは 下寄せで配置される コンポーネント がある 大きさの違う複数個の コンポーネント が混在する という違いがあります。 flex プロパティを利用してカラム位置をコン トロール する コンポーネント の下寄せ配置は、拡縮可能なコンテンツを利用することで実現します。 See the Pen flexbox sample 3 by ngk works ( @ngk-works ) on CodePen . 上記のサンプルでは .members { flex: 1 1 auto ; } により、中央のメンバー一覧部分がウィンドウ領域を埋めるように拡縮するため、ファイル一覧が最低限の高さを確保した状態で、最下部に押しやられます。 `margin` プロパティを利用してカラム位置をコン トロール する また別の手段として、 margin を利用する方法もあります。 例えば、下記のようなメンバー一覧部分が拡縮しない場合であっても、 .members { flex: 0 0 auto ; } margin を利用することで、下寄せ配置が実現できます。 .files { flex: 0 0 auto ; margin-top : auto ; } Flex Container をネストすることでスクロールをコン トロール する 「TUNAG」PC版チャットでは、コンテンツの高さがウィンドウの高さを超えた場合にメンバー一覧だけをスクロールさせる必要がありました。 See the Pen flexbox sample 4 by ngk works ( @ngk-works ) on CodePen . ポイントとなるのは、 .members が .flex-container--column の Flex Item であり、 .member-list の Flex Container でもある点です( .files も同様)。 Flexbox は Flex Container をネスト( 入れ子 構造)することができるため、うまく利用すると、 拡縮の可否 スクロールの有無 配置(寄せ) などを自由自在に組み合わせることができます。 サンプルでは、メンバー一覧のリスト部分だけを拡縮・スクロール可能な要素とすることで、1画面に収めつつ、状況に応じてスクロールバーを表示しています。 `Flexbox` で柔軟なレイアウトを 冒頭に `Grid Layout` のほうが便利なように思えますが、 `Flexbox` をうまく活用することで、その名のとおりフレキシブルなレイアウトが実現できます といった理由が、前項の 自由自在な組み合わせ にあります。 Grid Layout は ピクセル や割り合いを事前に定義する必要があり、(縦/横)幅が異なるコンテナが混在する場合はレイアウトが難しくなってしまいますが、 Flexbox であれば、 ピクセル や割り合いを事前に定義することはもちろん、コンテンツに応じた大きさにもできます。 それらを柔軟に組み合わせることができるため、非常に自由度が高いレイアウトが実現できます。 See the Pen flexbox sample 5 by ngk works ( @ngk-works ) on CodePen . レイアウトの実装に困ったら、ぜひ Flexbox を使ってみてください。
こんにちは。スタメンでエンジニア インターン をしております河井と申します。 本記事では、TUNAGにおけるユーザーネットワークの可視化の取り組みについて紹介いたします。 はじめに 弊社では TUNAG という社内制度をコミュニケーションの起点とする 社内SNS を開発しています。 TUNAG から社内制度を利用すると Facebook のような タイムライン に、制度を利用したことが共有されます。 そして同じ社内のユーザーは投稿に対してコメントやスタンプなどで コミュニケーションを取ることができます。 投稿とそれに付くコメントは社内での人間関係をある程度反映しているとの仮説と、 交流の度合いをひと目で確認できると嬉しいというニーズから、ネットワーク図に落とし込んで可視化しようと考えました。 使用ツールについて 今回はRとigraphを用います。 igraphはネットワーク解析・可視化用のライブラリで、R、 Python 、 C/C++ で使うことができます。 将来はサービスに組み込みたいと考えていますが、まずは手元で簡単に実行できるという理由で今回の組み合わせを選定しました。 実装の詳細 対象とする入力 ネットワーク(グラフ)はノード(点)とリンク(ノードを結ぶ線)で構成されています。 今回で言えば、ノードはユーザーリスト、リンクはコメントのリストです。 igraphに渡すデータとして、ノードとリンクでそれぞれ CSV を作成します。具体的には以下に示すような形になっています。 nodes. csv id name department_id 1 Alice 1 2 Bob 1 links. csv from to weight 1 2 1 2 1 1 テーブルのヘッダーの内容は以下のとおりです。 id :ユーザーID name :ユーザー名 department_id :所属部署のID from :コメントをしたユーザーのID to :投稿にコメントをもらったユーザーのID weight :コメント数。テーブル作成時はすべて1とし、後で集計します。 以上のような CSV を2018年の6、7、8月と1ヶ月ごとに作成しました。 データの読み込みからプロットまで まずパッケージと CSV をファイルを読み込みます。 library ( igraph ) nodes <- read.csv ( 'nodes.csv' , header = T ) links <- read.csv ( 'links.csv' , header = T ) 次に取り込んだデータの前処理です。 links <- aggregate ( links [ 3 ], links [ - 3 ], sum ) net <- graph.data.frame ( d = links , vertices = nodes , directed = T ) net <- simplify ( net , remove.multiple = F , remove.loops = T ) 1行目 `aggregate(links[3], links[-3], sum)`では、`links`の3列目(`weight`)について、`links`の3列目以外(`from`と`to`)が一致するもの同士を足し合わせる(`sum`)という操作をしています。 2行目 graph.data.frame()にリンクとノードの情報を渡してグラフオブジェクトを作成します。 3行目 今回、自分の投稿へのコメント情報は不要なため省略したいです。 自身に対するリンクをもたず、かつ同じノード間に複数のリンクをもたないグラフを単純グラフといい、任意のグラフを単純グラフ化する関数である`simplify()`を適応しています。 ネットワークをプロットする前に、図の設定をしておきます。 # ノードの設定 V ( net )$ size <- 4 V ( net )$ color <- V ( net )$ department.id # 所属部署で色分け V ( net )$ frame.color <- 'white' V ( net )$ label = NA # ラベル非表示 # リンクの設定 E ( net )$ width <- E ( net )$ weight / 5 # コメント数に応じてリンクの太さを変える E ( net )$ arrow.size <- 0.2 edge.start <- ends ( net , es = E ( net ), names = F )[, 1 ] E ( net )$ color <- V ( net )$ color [ edge.start ] # リンクをコメントしたユーザーの所属部署の色に 最後にプロットです。 set.seed ( 1 ) plot ( net , layout = layout_with_fr ) ネットワーク図のレイアウトを決めるためにFruchterman-Reingold アルゴリズム を採用しました。力学モデルと呼ばれる計算方法の一種で、繋がりのあるノードほど近くに配置されるようになります。結果として、密に繋がっているノードが中心近くに集まります。 結果 1ヶ月ごとのコメントを可視化した結果は以下のようになりました。 ノードは各ユーザーに相当し、その色は所属部署を示しています。 リンクの色はコメントを送ったユーザーと同じ色を示しており、リンクの太さはコメントを送った数に比例して太くなっています。 6月 7月 8月 考察 上の図はスタメン社内のデータを可視化したものになります。 図から読み取れることとして、 黃、緑、ピンク、水色の部署は横断的にコミュニケーションをとっている オレンジのグループは中心の集団から少しずれた場所にいる 最初遠くにいたノードが時間の経過とともに中心に寄ってきている(離れているノードもありますが。。) などがあります。 さらに、追加的な情報を使うことでもう少し込み入った考察をすることができます。 6月の時点で外側にいる青色のノードは内定者です。 7月になると、6月の塊はほぼそのままで外側に新たなノードが出現します。(2つのオレンジのノードと1つの青色のノード) こちらも7月にTUNAGに登録した内定者です。 8月になると、6月7月時点で遠くにいたノードが中心の集団に近づいていることがわかると思います。 近づいているということは繋がりが生まれているということなので、順調に社内に馴染んできていると言えるのではないでしょうか。 このようにネットワーク図を社内コミュニケーションのスナップショット的に毎月取得していくと、新しいメンバーが最初は遠くにいたところから近づいて来ているか、部署をまたいだ交流があるかどうか、などを分析することができます。 まとめと展望 今回はRとigraphでネットワークの可視化を行いました。 TUNAG上でのコミュニケーションは決して現実のコミュニケーションと等価ではないですが、コメントのみでその実態を反映できている可能性が示されました。 また今回は触れませんでしたが、ネットワーク解析という分野では、ネットワークのつながりの度合いやコミュニティで影響力の強い人物などを 定量 的に求めることができるので、今後はそのような分析も行っていきたいと思います。 最後に今回用いたコードをまとめておきます。 library ( igraph ) nodes <- read. csv ( 'nodes. csv ' , header = T ) links <- read. csv ( 'links. csv ' , header = T ) links <- aggregate ( links [ 3 ], links [ - 3 ], sum ) net <- graph.data.frame ( d = links , vertices = nodes , directed = T ) net <- simplify ( net ) V ( net )$ size <- 4 V ( net )$ color <- V ( net )$ department_id V ( net )$ frame.color <- 'white' V ( net )$ label = NA E ( net )$ width <- E ( net )$ weight / 5 E ( net )$ arrow.size <- 0.2 edge.start <- ends ( net , es = E ( net ), names = F )[, 1 ] E ( net )$ color <- V ( net )$ color [ edge.start ] set.seed ( 1 ) plot ( net , layout = layout_with_fr )
こんにちは。 春からスタメンでフロントエンドエンジニアをしております、桑原と申します。 初めての投稿となります。 今後のフロントエンド開発の紹介を意識して、今回は入口となるファイル構成に ついて紹介させていただきます。 前置き デファクトスタンダード から乖離することに悩み踠き苦しみ、何が正解なのかを 日々模索しながら開発をすることは、フロントエンドエンジニアの宿命とも言えます。 ファイル数が肥大化しやすい React-Redux での開発ならば尚のことです。 私のモットーは 可読性の高いコードはファイル構成から作るべし です。 そしてここにエンジニアのファイル構成3大訓を示します。 ファイル構成3大訓 可読性の高いファイル構成は開発を効率化する 可読性の高いファイル構成は肥大化を防ぐ 可読性の高いファイル構成はバグを減らす 現時点までで導き出した、 React-Redux でのファイル構成のあり方を示そうと思います。 ファイル構成を決める要因 ファイル構成を決める要因としては主に下記の3点になります。 TUNAG を例に出しながら、各項目について説明します。 アプリケーションの規模 アーキテクチャ 思想・好み → ノウハウ アプリケーションの規模 x アーキテクチャ このお題だけでひとつの記事になりそうですが、だいぶ簡略化して書きます。 アプリケーションの規模を決める要素として、下記の3点が要点になります。 ビジネスロジック の複雑性 技術的な複雑性 ページ数 下記のような例では1~2の実装方法により複雑性が増します。 ログイン情報をフロントで保持し、 API を利用する度に トーク ンを利用する フォームの入力情報を storage に保存し、再訪しても内容を表示したい ユーザーのタイプによってページレイアウトの色を変更したい これらの例ではフロントの ビジネスロジック をどのような技術で実装するのかを 検討しますが、 API や Helper 関数、Middleware、 Utilities を追加したりなど、 さまざまな要素が絡み始めます。 TUNAG ではバックエンドの Rails で生成した View に、 React-Redux を埋め込む実装をしているため、1~2の大部分を Rails で解決しています。 3.のページ数は、1ページあたりのパーツ数によって大きく左右します。 TUNAG では1ページだけでもかなりの複雑性を持ち、画像1. の タイムラインでは40〜50個の Component から作られております。 そして今後の更なる機能追加や リファクタリング により、より複雑なロジックが 絡んでくるため、それに耐えられるファイル構成を検討する必要がありました。 図1. TUNAGのタイムライン   思想・好み → ノウハウ デファクトスタンダード が必ずしも最適にならない場合も多々あり、プロジェクトによって工夫を凝らします。 しかし思想、好みを議論に加えると、ファイル管理に限らず、 アーキテクチャ ーも最終的には 宗教戦争 に発展しかねません。 思想、好み、ではなく、ノウハウと呼べるものとして 、良いモノづくりをするための 開発環境を追求することは永久のテーマですね。 ファイル構成 さて、ここから本題に入ります。 下記の4つのポイントが既読性をあげるための要点です。 ページ単位でファイル構成そのものをグループ化する ボタンやテキストフィールドなど、パーツを Materials と 命名 して共 通化 する 共有して利用するモジュールは1層目に配置する CSS in Module で実装する CSS をまとめる  1. ページ単位でファイル構成そのものをグループ化する コンポーネント 同士を組み合わせて作成するデザイン手法の Atomic Design という 概念があります。 この Atomic Design ではパーツを5レベルの Atoms 、 Molecules 、 Organisms 、 Templates 、 Pages に分類しますが、 パーツのレベルによってファイルの管理方法も明確化すると 大量の Container 、 Component に混乱せずにまとめることができます。 図2.のファイル構成では、Atomic Designの Pages と Templates 概念を取り入れる ことによって、ページ単位でファイル構成そのものをグループ化しています。 図2. modulesとtemplatesによるファイル構成 Pages Pages は modules と定義し、 module 単位で React-Redux の構成を管理することによって 、ページごとに Flux の流れを独立させます。 Templates 画面上部のヘッダーやサイドバーなどが Template にあたります。 図3. Templateの例 テンプレートに該当する Container がひとつなら modules の中に 含めて良いかと思います。 複数存在するならば、1階層に templates フォルダを作って管理します。 2. ボタンやテキストフィールドなど、パーツを Materials と 命名 して共 通化 する Organism、 Molecules、 Atoms レベル3以下の要素は同じ階層としてファイル管理しています。 このときに気をつけなくてはならないのは、これらの要素は大きく2分されることです。 各 modules から共有されるケース modules の中でのみ利用するケース 1.の場合は、 modules の各ページから参照されるため、1階層の materials に含めます。 2.の場合は、特定の module 内でのみ使用するため、 materials ではなく、 module 内の components に含みます。 図4. 共通パーツを含めたファイル構成 3. 共有して利用するモジュールは1層目に配置する 汎用的に利用する api や config、 middleware、 utilities、 vendor は src 層に配置します。 図5. 共通して利用するモジュールを含めたファイル構成 4. CSS in Module で実装する CSS をまとめる 従来の CSS はスコープの概念がないため、アプリケーションの規模が 大きくなればなるほど管理が困難になります。 CSS in Module は コンポーネント 単位で CSS を独立管理することができるため、 この問題を解消することができます。 CSS を コンポーネント 単位で実装することによって、さまざまな恩恵がありますが、 それは CSS in Moduleでググってください。 本題である CSS の管理の方法ですが、大きく下記の3通りなのではないかと思います。 1階層に stylesheets フォルダを作り、 materials modules のすべての Container 、 Component の CSS をここで管理する 各 コンポーネント と同じ階層に配置する materials で利用する CSS は materials 以下に、各 module で利用する CSS は各 module 以下で管理する 1.のメリットは、1箇所で CSS をまとめて管理できるのでわかりやすいですが、 コンポーネント が多いと雑多になるというデメリットがあります。 また、2.は CSS も コンポーネント と共に管理できるのでわかりやすいですが、 一箇所で管理することができないため、煩わしさがあります。 3.はある程度数カ所でまとめた上で、 materials 、 modules 単位で独立性を保つことができるため、私なら 3.を選択します。 図6. CSS を含めたファイル構成 終わりに 今回は React-Redux のファイル構成についてお話ししました。 React-Redux のノウハウはネット上のブログでは見つけにくい情報が多いので、 引き続き発信をしていこうと思います。  
こんにちは。6月からスタメンに入ったエンジニアの滿本(ミツモト)です。 スタメンではタスク管理ツールとして Trello 、バージョン管理サービスとして GitHub を採用しています。 今回はそれらを連携する Integromat というサービスについて紹介します。 Integromatとは? Integromat アプリケーションを連携できるサービス。 インターネットの接着剤(glue)と呼ばれている。 アプリ間のデータ転送や変換を自動で処理することができる。 例えば Slack を利用している場合、次のようなことができます。 ・ メールが届いたら Slack でも通知される。 ・ Dropbox にファイルが追加されたら Slack に通知される。 ・ Google Calender の明日の予定を Slack で知ることができる。 導入するきっかけ これまではタスク管理ツールとして Waffle を採用していました。Waffle は GitHub の Issue に連携しており、便利ですが、Waffle にアクセスするためには GitHub リポジトリ へのアクセス権限が必要で、エンジニア以外にも リポジトリ を解放する必要があり、セキュリティ的に課題がありました。 サービス改善に繋がるア イデア ( Issue )をエンジニア以外の社員も積極的に出し合い、プロダクト開発に参加できるように、 GitHub のアクセス権限に依存しないタスク管理ツールを検討していました。ツールの使い勝手の良さなどから Trello を選択しましたが、Trello のカード と Issue の連携を手動で行う必要があるという課題が残っていました。 その課題を解決するため Integromat を導入しました。 類似サービスに Zapier 、 IFTTT 等がありますが、無料でできることの多さ、UIの良さから Integromat にしました。 導入手順 Trello のカード作成と Issue を連携する事例をもとに導入手順を説明します。 用意するもの Trello アカウント、ボード・リスト GitHub アカウント、 リポジトリ 1. Integromat アカウントの取得 始めにアカウントを作成します。 2. 対象サービスと連携フローの選択 今回やりたい「 Trello のカード作成と Issue の連携」はテンプレートで用意されています。 サイドメニューの Templates から Trello・ GitHub を検索します。 Integromat では一つの連携フローを「Scenario」と呼びます。 「Create GitHub issues from new Trello cards」を選択して Scenario を作成しましょう。 3. Trello の設定 Scenario が作成できたら、Trello のアカウント、ボード・リストの設定を行います。 Connection フォームに Trello のアカウント名を入力します。 Trello 側で「Integromat があなたのアカウントを利用することを許可しますか?」と聞かれるので、許可を選びます。 その後、連携したいボード・リストを選びます。一度に連携するカードの数もここで設定します。 4. GitHub の設定 次に GitHub の設定です。 まず GitHub のアカウント名を入力します。 GitHub 側の認証画面が表示されるので、「Authorize Integromat」をクリックしましょう。 連携したい リポジトリ を選択し、Issue のタイトルとボディを設定します。デフォルトは ・ カードの名前   : Issue のタイトル ・ カードの詳細説明 : Issue のボディ になります。 5. 連携の頻度と アクティベーション どのくらいの頻度で連携する( Scenario を実行する)かを設定するため、左下にある SCHEDULING の時計マークをクリックします。フリープランだと最短で15分ごとに連携させることができます。 最後に時計マーク横のスイッチを ON にして Scenario をアクティベートします。 これで Trello と GitHub の連携ができるようになりました。 6. 実際の動作 試しに Trello でカードを作成してみましょう。 GitHub に Issue が自動で追加されました👍 おわりに 今回は Integromat を用いた Trello と GitHub の連携について紹介させていただきました。Integromat は便利なサービスですが、Trello や GitHub には豊富な API が用意されており、 API を使うことでより高度な連携を実現することができます。今後は、 API による Trello と GitHub の同期タイムラグの解消や、 GitHub の Merge に連動した Trello の操作など、快適なプロジェクト運営に向けて、より便利な連携をしていきたいと思います。 スタメンでは現在、 エンゲージメント経営コンサルティング「 TUNAG 」 というサービスを開発しており、より良いサービスにするため エンジニア を募集しています。「ちょっと話を聞いてみたい」という方は是非ご連絡ください。お待ちしております!
こんにちは、スタメンの松谷です。 弊社は「TUNAG」という 社内SNS を提供しています。TUNAGではアプリケーション フレームワーク として、 Ruby on Rails を使用しています。TUNAGの主要機能に Facebook のニュースフィードに該当する「タイムライン」があり、社員同士のコミュニケーションや、会社からのお知らせが共有されます。 タイムラインに投稿が蓄積されるにつれ、過去の投稿を振り返りたいというニーズが増えたので、 全文検索 (Elasticsearch)を導入して検索を可能にしました。 導入に際して、 Rails から Elasticsearch を扱う方法をまとめました。これから Rails にElasticsearchを導入しようとしている方の参考になれば幸いです。 TL;DR 概要 Ruby on Rails で作られた TUNAG に Elasticsearch で、 全文検索 を導入した。 対象読者 これから Rails に作られたサービスに Elasticsearch を導入しようとしている方 動作環境 Ruby on Rails 5.1.6 Amazon Elasticsearch Service (Elasticsearch 6.0) なぜ Elasticsearchか? 以下の理由でElasticseachを選択しました。 形態素解析 や n-gram など 自然言語 的な解析をサービスに合わせて選択することができる ノードを増やすことで簡単にスケールアウトができる 検索が高速でパフォーマンスが高い 検索クエリの表現力が高い インデックスを複数もつことでマルチテナント対応が可能 スコアリングして検索順位のチューニングが可能 利用したgem 今回 Rails で 全文検索 を実現する上で以下の2つのgemを利用しました。 elasticsearch-model elasticsearch-dsl elasticsearch-model は、 Rails の MVC の「モデル」とElasticsearchの統合を簡素化することを目的としています。 このgemのモジュールを検索対象のクラスにincludeすることにより、 ActiveRecord を継承したモデルに対応するデータをインデックスにインポートする機能が用意されるなど、Elasticsearchに関連する機能が拡張されます。 elasticsearch- dsl は、Elasticsearchに対するクエリの作成と実行のサポートを目的としたgemです。表現豊かなElasticsearchのクエリを Ruby の DSL で感覚的に構築することができます。 運用上気にしたこと TUNAG のクライアント企業様毎にインデックスを分けてデータの分離を実現 サービス公開後でも、サービス停止することなくインデックスの変更を実現 実装の詳細 以下で Rails にElasticsearchを導入した際の全体的な流れを紹介していきます。 検索対象モデル 「タイムライン」は複数のモデルから構成されています。一部を紹介すると、 タイムラインへ投稿したユーザ(User) 投稿フィード(Feed) 投稿フィードに対するコメント(Comment) 投稿に関連するデータ(例: 利用した社内制度 Menu) などが存在します。 全文検索 では、これらのモデルのデータを検索対象とし、横断的に走査してキーワードに合致した投稿(Feed)を取得します。 # == Schema Information # Table name: feeds # # id :integer not null, primary key # content :text(65535) # status :integer default("active"), not null class Feed has_many :comments belongs_to :user belongs_to :menu end データのインポート ActiveRecord ::Base を継承したモデルに、elasticsearch-model の Elasticsearch::Model::Importing を Mix-In することで、 インスタンス のデータを Elasticsearch::Model::Importing#import で、Elasticsearch に インポートすることができます。 TUNAG では、Feed に関連するアソシエーションを含めてインデックスにインポートしたいので、 Elasticsearch::Model::Importing#import で中で、 JSON にシリライズする際に呼ばれる Elasticsearch::Model::Serializing#as_indexed_json をオーバーライドしています。 下記は、Feedクラスがincludeするモジュール Serchable を定義しています。(実際の実装を省略しています。) module Searchable extend ActiveSupport :: Concern <200b> included do include Elasticsearch :: Model <200b> def as_indexed_json (options = {}) as_json( only : [ :content , :status ], include : { comments : { only : [ :content ] }, menu : { only : [ :title ] }, user : { only : [ :name ] }, }) end end end Feed .first.as_indexed_json # => { # "content" => "お疲れ様でした!来週は新入社員歓迎会をします!", # "status" => "active", # "comments" => [ # { "content" => "参加します!" }, # { "content" => "最近会ってないですね。来週楽しみです。" }, # { "content" => "お疲れさまです!参加予定です。" } # ], # "menu" => { "title" => "イベント出欠" }, # "user" => { "name" => "松谷 勇史朗" }, # } インデックスの更新 初回のインデックスへのインポートが完了した後も、新しい投稿が発生したり、既存の投稿が編集されたタイミングで、該当のインデックスを更新させる必要があります。 ActiveRecord ::Base を利用したモデルの場合、after_commitコールバックを使用してインデックスの更新を行うことができます。TUNAGでは、ActiveJobを使用して、非同期でインデックス操作を行っています。 module Searchable extend ActiveSupport :: Concern <200b> included do after_commit ->(record) { Elasticsearch :: IndexerJob .perform_later( ' index ' , record.id, ElasticsearchIndex .alias_name(record.company))}, on : :create after_commit ->(record) { Elasticsearch :: IndexerJob .perform_later( ' index ' , record.id, ElasticsearchIndex .alias_name(record.company))}, on : :update after_commit ->(record) { Elasticsearch :: IndexerJob .perform_later( ' delete ' , record.id, ElasticsearchIndex .alias_name(record.company))}, on : :destroy end end class Elasticsearch :: IndexerJob < ApplicationJob def perform (operation, record_id, index_name) logger.debug [operation, " ID: #{ record_id }" ] case operation when / index / record = :: Feed .find(record_id) Elasticsearch :: Model .client.index index : index_name, type : ' feed ' , id : record.id, body : record.__elasticsearch__.as_indexed_json when / delete / Elasticsearch :: Model .client.delete index : index_name, type : ' feed ' , id : record_id else raise ArgumentError , " Unknown operation ' #{ operation } ' " end end end マッピング の定義 マッピング とは、Elasticsearch の インデックスにおいて、ドキュメントをどのような構造で表現するかを定義することです。 具体的には、フィールドとその型、Analyzerなどを設定します。 Elasticsearch::Model::Indexing.settings メソッドで設定することができます。 ここではFeed、Comment、Menu、Userの4つのテーブルそれぞれのフィールドに、それぞれAnalyzerとしてkuromojiを設定しています。 module Searchable extend ActiveSupport :: Concern included do settings index : { number_of_shards : 1 , number_of_replicas : 0 } do mappings dynamic : ' false ' do indexes :content , analyzer : ' kuromoji_analyzer ' indexes :status , type : ' keyword ' indexes :comments do indexes :content , analyzer : ' kuromoji_analyzer ' end indexes :menu do indexes :title , analyzer : ' kuromoji_analyzer ' end indexes :user do indexes :name , analyzer : ' kuromoji_analyzer ' end end end end end 複数テーブルの横断的検索 elasticsearch- dsl を使ってクエリを構築します。ElasticsearchのBoolクエリを使えば複雑な検索も可能になりますBoolクエリは以下の4種類あります。 must: AND条件 filter: AND条件 should: OR条件 must_not: NOT条件 例として、 (statusがactiveなFeed) && (FeedのcontentフィールドまたはCommnetのcontentフィールドまたはMenuのtitileフィールドまたはUserのnameフィールドにキーワードが含まれる)の条件をelasticsearch- dsl で構築しました。 multi_matchクエリは、複数フィールドへのマッチを許可するクエリです。Boolクエリを用いて「投稿したユーザから検索(例: 自分の投稿)」、「投稿したユーザの属性(例: 所属部署)による検索」など、複雑な検索を実現することができます。 このように、Elasticsearch側で 関連する複数のモデルを対象とした、複雑な検索が実現できるため、 Rails の Controller 等で検索結果をさらに絞り込む必要性が無く、シンプルな実装と高いパフォーマンスを得ることができます。 Elasticsearch :: DSL :: Search .search do sort do by :created_at , order : ' desc ' # 新しい順 end query do bool do must do term ' status ' : ' active ' # 有効なフィード end must do multi_match do query keyword fields %w( content comments.content menu.title user.name ) operator ' and ' end end end end end マルチテナント対応 Elasticsearchではインデックスを複数もつことが可能です。TUNAGでは、データ分離のためクライアント企業毎にインデックスを分割しており、検索結果に他クライアント企業の情報が紛れ込む可能性はありません。 class Feed < ApplicationRecord include Searchable belongs_to :company # クライアント企業毎にインデックス名を割り振る index_name = " company_ #{ company.id }" end インデックスの無停止再構築 サービスを運営していると、機能の仕様変更などで、モデルとElasticsearch インデックスの マッピング 定義の変更や、検索対象フィールドの変更が発生し、インデックスを再構築する必要性がでてきます。この場合、インデックスを0から再構築するのに時間もかかりますし、インデックス切替の度にメンテナンスをすることもユーザに迷惑をかけるため、サービスへの影響を与えずに、手軽にインデックスを更新することが求められます。 TUNAGでは、 Index Aliases and Zero Downtime | Elasticsearch: The Definitive Guide [2.x] | Elastic を参考にして、Elasticsearch が提供する Index Aliases 機能を用いて、無停止でのインデックスの再構築を行っています。 Index Aliases は、Elasticsearch の インデックスに対し、 エイリアス (別名)をつける機能です。具体的な手順としては下記となります。 現在有効なインデックス(company_01_feed_20180420) への エイリアス として company_01_feed_current を作成しておき、各種コードからは company_01_feed_current を参照するようにしておきます。 新しいインデックスを構築する際は、company_01_feed_20180423 を作成し、データインポート後を行った後、company_01_feed_current の参照先を company_01_feed_20180420 から company_01_feed_20180423 へ切り替えます。アプリケーションから参照している エイリアス 名は同じですが、Elasticsearchの内部的にはAliasが指しているインデックスは異なるという点を活かしています。 切り替え後に、古いインデックス(company_01_feed_20180420) を削除して、メンテナンス完了です。 エイリアス の切り替えは、 Elasticsearch::API::Indices::Actions#update_aliases を利用しており、update_aliases は、Elasticsearch側でアトミックに処理されることが保証されているため、安全に切り替えを行うことができます。 また、TUNAG では、ElasticsearchIndex という ActiveRecord ::Baseを継承したモデルを作成し、インデックスを管理しています。 namespace ' elasticsearch:feed:rebuild ' do desc ' 企業毎にインデックスを新たに作成し、古いインデックスから切り替える ' task feed : :environment do Company .all.each do | company | ElasticsearchIndex .rebuild!(company) end end end class ElasticsearchIndex < ApplicationRecord include Elasticsearch :: Model def self . rebuild! ( company :, model :) next_index = create!( model : model, company : company, rebuild_at : Time .current) next_index.create_index next_index.import if current_exists?(company) current_index = current(company) switch(current_index, next_index) current_index.delete_index else next_index.add_aliases end next_index.active! end def self . switch (current_index, next_index) raise ArgumentError " Not allowed switch index other company index " if current_index.company != next_index.company __elasticsearch__.client.indices.update_aliases body : { actions : [ { remove : { index : current_index.name, alias : alias_name(current_index.company)}}, { add : { index : next_index.name, alias : alias_name(current_index.company)}} ] } end def self . current (company) company.elasticsearch_indices.active.last end def current_exists? (company) current(company).present? && __elasticsearch__.index_exists?( index : current(company).name) end def create_index self .class.__elasticsearch__.create_index!( force : true , index : name) end def import company.feeds.import( force : true , index : name) end end 終わりに 今回は、Elasticsearchを Rails アプリで実運用したときの事例を紹介しました。elasticsearch-modelで、 ActiveRecord とElasticsearchの連携がスムーズに実現し、elasticsearch- dsl でElasticsearhの機能を Ruby で簡単に操作できてとても開発効率が向上しました。今後は、スコアリング、アナライザの選定、検索結果のハイライトなど未だ使っていない機能の調査をし、サービスの質を向上させてTUNAGをもっと良くしていこうと思います! スタメンでは一緒に働くメンバーも募集しております!是非 Wantedly をご覧ください。