デザイン指針の必要性

こんにちは、ラクスルでUXデザイナーをやっていますガーワルロビンです。6月にオランダのアムステルダムで開催されたUXデザインのカンファレンス2つに参加してきました。UX戦略とUXデザインに特化した「UX STRAT Europe 2018」が6月11日・12日に開催され、またフロントエンドやテックの話も少し入った「CSS DAY 2018 UX Special」が6月14日に開催されました。

左:UX Strat Europe 2018の会場;右:CSS Day 2018の会場

ラクスルは2012年からネット印刷のサービスを提供し続けてしました。この6年間の間でサービスの形は変わり、ユーザーとのタッチポイントも変わってきました。変わり続けるウェブサービスを開発するにはプロダクトの形をリードする指針が必要だと思われます。ラクスルにそう言った指針がなかったため、ユーザー体験の一貫性がなくなってきました。ユーザーにより良い体験を提供するためには、どのようにプロダクトを作ればいのか、どのような組織でどのような開発を進めれば良いのか。いままでラクスルで進めてきた方法が正しいのか?もしくは別のアプローチがあるのか?その答えを探すため、カンファレンスに参加しました。

その答えになりうる勉強になった事項のひとつは、デザインプリンシプル(以下デザイン指針)でした。このブログでは主にデザイン指針とは何か、なぜ会社に必要かと最後に良いデザイン指針を作るためのコツを紹介します。では早速本題に入って行きましょう。

デザイン指針とは

CSS DAYの登壇者の一人で「Design Systems」の著者 Alla Kholmatova はデザイン指針を次のように説明しています。

デザイン指針は、特定の製品やチームにとって良いデザインが何を意味するかという共通の基準です。

ウェブ上で出てくるデザイン指針には様々な定義があるかもしれませんが、私にとってはこれが単純でストレートです。[参考までにInteraction Design foundation

デザイン指針の必要性がどこにあるのか

デザイン指針は、プロダクト開発において組織の基準を一方向に定めるために重要です。

Brio toys

Brioが色々なおもちゃを作っている中で、一目瞭然でBrioのおもちゃであることがわかります。

複数のプロダクト間の一貫性を保つ

デザイン指針があることによって、同じサービスやブランドに含まれるプロダクトにユーザーが接するときに迷わずに動作をすることができます。 ユーザーがプロダクト間で一貫性のある経験を持ち、同じ機能が違うプロダクトで複製することができれば、タスクをスムーズに進めることができます。 この一貫性は、Atlassianの幅広い製品で見られます。 Atlassianは3つのデザイン指針を定義し、プロダクトの必要に応じてそれぞれの度合いを微調整して行きます。

アトラシアンが定義している3つのデザイン指針

アトラシアンが定義している3つのデザイン指針

時間依存性を減らす・長期間で一貫性を保つ

事業会社では同じサービスを何年も、もしくは何十年も市場に出すことがあります。 ビジネス制約の変化やチームの変化によってプロダクトに対する良いデザインの定義も変わることがあり、長期間経つとサービスはユーザーにとって一貫性のない体験になりがちです。 デザイン指針を中心にプロダクト開発することによって長期的にものを同じ軸で評価することができ、組織やビジネス制約に依存性を減らすこともでき、一貫性の高い体験を提供することができます。

共通言語化で効率化

デザイン指針を明確に定義することで、組織のメンバーがプロダクトの開発や評価に関して同じ言語でコミュニケーションを行い、効率的に話を進めることができます。 開発ペースが早いウェブサービスにおいては短時間で精度の高いコミュニケーションを取り、デザイン判断を行えることがキモになると思います。

良いデザイン指針を定義するには

良いデザイン指針を定義するにはAlla Kholmatova民の「Design Systems」に戻って、話をしたいと思います。良いデザイン指針は以下の4つのパラメータで測ることができるそうです。
1. 本質的:実際の具体的な施策で説明をすることができること
2. 反意語が成り立つ:別の会社がそれの反意語でデザイン指針を定義することができること
3. 行動に起こせる:プロダクトの評価軸になること
4. 記憶に残る:シンプルで覚えやすい、いつでも思い出せること

またデザインプロセスをご存知の方はイテレーションを回す重要性がわかると思います。デザイン指針も同じように定義したもをでプロダクトを作ってデザイン指針のFine-tuningをする必要があります。これによってデザイン指針が会社のミッションや顧客価値からズレていないかを確認することができ、より洗練されたデザイン指針・プロダクト・ユーザー体験を作ることができます。

良いデザイン指針を定義するためのTIPSをいくつかあげます!
・会社のミッションから始める
・横断的なテーマを見つけ出す
・誰に向けてのデザイン指針なのかを明確にする
・仮説検証をおこなう  

サマリー

今回はデザイン指針について書かせていただきました。デザイン指針とは何か、それがあることによって会社へのメリットはあるのか、に加えて良いデザイン指針を作るためのTipsをいくつかあげてみました。

この二つのカンファレンスに参加することによって、確かにデザイン指針があることによって長期的により良い・洗練されたUXを設計することができると感じました。ラクスルでは現在以下のようデザイン指針を定義しています。私たちもデザイン指針の運用が初期段階にいるので、これからイテレーションを回して、改善する必要を感じています。
・シンプル:ユーザーがタスクを実行するうえで不要な機能やビジュアルエレメントが含まれていないか?
・明瞭:ユーザーがタスクを実行するうえで重要なアクション項目はひとつに絞られているか?
・一貫性:同じ機能で異なるビジュアルや性能をユーザーに提供し、混乱を招いていないか?
・速い:ユーザーは最短のスピードで必要な情報にアクセスできているか?

ラクスルでは、このデザイン指針を決めるためのワークショップをデザイナーやプロダクトマネージャーを交えて行う予定です!

私たちはユーザーにより良い体験を提供していくことに努めています!
良い体験づくりで世界をもっとよくしてきませんか?
ラクスルでは様々な職種の方々を募集しています。ラクスルの採用ベージへ
ラクスルについてもっと知りたい場合はこちらをご覧ください。

RubyKaigi 2018にSticker Sponsorとして参加します

こんにちは。エンジニアの三瓶です。

ラクスルは、2018/05/31 ~ 2018/06/02に仙台国際センターで開催される RubyKaigi 2018 にSticker Sponsorとして参加します。

RubyKaigiは、プログラミング言語Rubyに関する国内最大級のイベントです。
Rubyはラクスルを支える中心技術であり、今後も積極的に活用していきたいと考えているため、このような機会を通じて少しでもRubyの発展に貢献していきます。

今回のRubyKaigiではSticker Sponsorとして、毎年配られているRubyKaigiのステッカーをラクスルにてスポンサリング・納品させていただきました。

また、ラクスル自身のステッカーや便利でかわいいノベルティもご用意していますので、是非ノベルティ置き場にお立ち寄りください。

新卒エンジニアとしてのラクスルでの1年間を振り返る

こんにちは、サーバサイドエンジニアの岸野です。

17卒の新卒2期目として入社してから1年が経った今、この1年間を振り返ってみようと思います。

1. 入社初日から開発着手

2017年4月3日、入社式で歓迎していただいて私は社内のオペレーションシステムの開発チームにジョインしました。開発環境のセットアップを終えた後、まずは簡単なタスクからでしたが入社初日から開発に着手しました。

新卒といえば1〜3ヶ月ほどの新人研修が設けられるイメージでしたが、勉強よりも実際に目的に向かって手を動かすほうが好きな私にとっては向いていたと思います。

もちろん研修期間がないからと言って放置されていたわけではなく、コードレビューや質問を通して多くのことを学びました。特に始めの数ヶ月では自分で調べる力と何が分からないかを伝える力が身についたと思います。

2. リアルな産業を相手にしているから面白い

ジョブローテーションで8月からは新聞折込やポスティングなどの印刷を利用した集客活動を支援する集客支援チームにジョインしました。

新聞折込やポスティングの大きな特徴としてはバリューチェーンが長いことがあげられます。

配布エリアを選ぶ、購入する、データを入稿する、データの審査、印刷、配送、配布

大雑把にあげただけでもこれだけ多くのプロセスがあります。この長いバリューチェーンを扱う時に重要だったのはプログラミングの知識はもちろんですが、それ以上に各プロセスに対する解像度でした。

配布エリアってどうやって決まっているんだ?

審査って何をしているんだ?

配布って誰がどこで何をして行っているんだ?

例えば、新聞折込の場合は印刷物を印刷した後に各地域の新聞販売店へチラシを差配する納品センターへ納品するのですが、納品センターや販売店によって納品日が早かったり遅かったりします。そこを各販売店ごとに納品日を最適化することで新聞折込の配布にかかる時間を短縮する事ができました。

このような各プロセスに対する解像度を高めることで新聞折込やポスティングの短納期化を実現することができました。また、この新聞折込の短納期化では社長賞をいただきとても感慨深かったです。

入社前は「印刷ECなんてそんな大して難しいことしてないでしょ」なんて正直思っていました。ところが蓋を開けてみると、現実の世界の複雑なプロセス、仕様をコードに落とし込む難しさがありました。そしてそこがラクスルの面白い部分なんだなと感じました。

3. ペアプロ

ラクスルの開発チームの一部ではペアプログラミングを取り入れています。実際にペアプロを体験して分かったメリットとデメリットをあげてみます。

メリット

  • パートナーがいるので集中できる
  • 知識の共有ができる
  • 属人化を防げる
  • キャッチアップが早い
  • 自信を持って作業を進められる

デメリット

  • 体力的にも精神的にも疲れやすい
  • コードレビュー以上に恥ずかしい
  • 二人共スタックしてしまうと効率が落ちる

メリットとしては生産性が上がったことがあげられると思います。2人で1つの作業を行うため生産性が半分になってしまいそうですが、自分が触ったことがない領域でも早くキャッチアップできることやレビューのコストが下がることでむしろ生産性は上がったと思いました。

また、学びが多いこともあげられると思います。プログラミングのテクニックや設計方法を学べることはもちろんですが、IDEなどのツールの効率的な使い方やドメインの知識などペアプロをするからこそ学べることも多かったです。

デメリットとしては集中して作業すれば体力的にも疲れますし、人に見られながらプログラムを書くことは恥ずかしいので精神的にも疲れやすいことです。なので疲れたときは休憩を入れたり、オフィスにある卓球台でリフレッシュを計るようにしていました。

まとめ

18卒入社式の写真

1年前は分からないことだらけで社会人としてうまくやっていけるか不安でしたが、今は温かい目で見守られながらのびのびと働くことができています。

また4月からは新卒3期目が入社したり別のチームにジョインしたりと新しい刺激の中、「世界をもっと良くする」ために2年目も頑張っていきたいと思います。

ラクスルでは新卒エンジニアインターンを大募集しています。一緒に世界の仕組みを変えていきませんか?

LTとコードゴルフで大盛り上がり!Hackers Partyレポート

hackers party

こんにちは。ラクスル内定者(2018年4月入社予定)の後藤です。
今回は、3月に開催されたラクスルエンジニアイベント『Hackers Party』についてレポートします。

Hackers Partyは、ラクスルのエンジニア・PMがカジュアルに交流できるイベントです!美味しいご飯とお酒を片手に、ライトニングトークやコードゴルフで盛り上がりました。


チキンや生春巻きなど、食事の準備も万端です。

catering
お寿司もありました!

イベントのコンテンツは大きく2つ。ライトニングトークとチーム対抗のコードゴルフ大会です。
これらについて順番にレポートしていきます。

ライトニングトーク

5名のスピーカーによる5分ずつのライトニングトーク。勉強になるお話や思わず笑ってしまう裏話で会場が盛り上がりました

スピーカー CTO 泉

LT

CTOののトークテーマは個人で裁定取引を使って暗号通貨で利益を出そうとしていたことについて。証券会社での勤務経験からいくつかのパラメーターに着目し、取引所の動きを追って適切なタイミングで暗号通貨を取引するプログラムを構築していたとのことでした。
新たなパラメーターを増やしプログラムの再度構築を試みたところ、パラメーターの関係がかなり複雑になって挫折したそう。利益獲得は一朝一夕にはいかないようです・・・

スピーカー 石田

LT

2018年入社の石田は1月にwebアプリ製作会社からラクスルに転職したエンジニアです。今回のトークは軽量マークアップ言語のasciidocの利便性について。途中からはライブコーディングを利用して、利用シーンをわかりやすく伝えてくれました。

スピーカー 加藤

LT

女性エンジニアの加藤は大手精密機器メーカー、食料品ECサイト運営、FinTechベンチャーを経て1年前にラクスルに入社しました。現在はプロダクト開発部に所属するサーバーサイドエンジニアです。担当業務の抱えている課題とそれに対する取り組み、今の苦悩と今後の理想の姿について話してくれました。

他にもpdfファイルの構造に関する話や、これまでのラクスルの裏話を発表するメンバーもいました。

 

チーム対抗コードゴルフ大会

コードゴルフとは、出題されたアルゴリズムを最も短いコードで書くことを競うものです。
出題は以下の3問です。※1
1.三項演算子をさらに短縮する問題
2.アスキーアートで円を描くコード
3.最短経路を探すコード

5名程度のチームに分かれ、メンバーと話し合いながら制限時間内で出題されたコードを短縮していきます。CTOの泉は、CTO力を試すためにチームではなく個人で参戦しました。
普段の業務とは異なる書き方に戸惑うメンバーもちらほら。果たして、その結果は・・・?

codegolf
コードゴルフの
準備をしていると、チームで協力しやすいようにデスクからディスプレイを持参する社員が!気合が入っています。

codegolf
新たな切り口を探して頭を抱えるCTO泉。
ぶつぶつと考え込むCTOの姿を見るのは初めてだったので新鮮でした!

codegolf
ディスプレイを覗き込んで相談するチーム。先ほどのディスプレイが大活躍しています。

codegolf
短縮できる箇所を入念に確認。
半年前に新卒で入社したゴン(写真左)のまなざしから真剣さが伝わってきます。

白熱する中、優勝したのはプロダクト開発部部長、水島率いるチーム!!喜びのガッツポーズです。
ちなみに、一人で戦ったCTOは見事2位。孤独な戦いを勝ち抜きました!

codegolf
優勝した水島チームは2台のパソコンで分担をしながら、こまめに話し合いを行っていた姿が印象的でした。
全員で同じ画面を見ているチームもあれば、各々が自由に解いて答えを確認しあうチームもあり、戦法にチームの特色が色濃く出ていたことに驚きました!

まとめ

コンテンツが終了した後も、コードゴルフの回答をさらに短縮しようと試みるメンバーがいたり、お酒を片手に話に花が咲いたりと大盛り上がり。
業務で多く関わらないメンバー同士、リラックスした表情で会話を楽しんでいたました。コミュニケーションが増えるだけでなく、リフレッシュにもなったHackers Partyは大成功でした!

talkingcodegolftalking

 

ラクスルでは現在、新しい仲間を募集中です!

今回のHackers Partyの他にも、毎月エンジニアのチームをシャッフルしたランチなど、エンジニア交流の機会があります。
次回のHackers Partyにはあなたも参加しているかも?


ラクスル採用ページ https://recruit.raksul.com/guideline/career

 

※1 コードゴルフ出典:プログラマのためのコードパズル ~JavaScriptで挑むコードゴルフとアルゴリズム(柳井政和著/技術評論社)

ノンエンジニアでも無線綴じ冊子の背幅計算ツールはできるのか?!

みなさん、こんばんは。
ラクスルの甲木です。

ノンエンジニアの私がふとしたニーズから超基礎のhtml/javascriptを利用して「無線綴じ冊子の背幅計算ツール」を作った話をしたいと思います。
(この記事、公開していいのかすらわからないレベルの初歩のオンパレード ※初心者なのでクソコードなのは勘弁してください。)

今回作ったもの

題名のとおり、「無線綴じ冊子の背幅を計算するツール」です。
まず冊子には大まかに中綴じと、無線綴じがあります。

  • 中綴じ冊子
    本を開いた状態で、本文を何枚も重ね針金で綴じた冊子。
  • 無線綴じ冊子
    本の背の部分全体に糊 (のり)を塗布(とふ)して表紙に貼り付けた冊子。

※中綴じ冊子・無線綴じ冊子の詳細は冊子ページからご確認ください。

このうち無線綴じで問題となっていたのが、表紙と本文の紙の種類・厚さ(斤量)・ページ数で、冊子になった際の厚さが決まるので、注文時に「これ1冊あたりの厚さ何mmなんだ??」とお客様が困っていたという点です。

エンジニアに頼めば15分くらいで作ってくれるかもしれませんが、頼むのも忍びないので作ってみました。

 

実際に作ってみる

まず最初にhtmlから。紙の種類・厚さ(斤量)の選択肢に対して、valueでミリ数を定義します。

<form name="keisan">
  <select name="hyoshi">
    <option value="0">用紙を選択してください。</option>
    <option value="0.065">光沢紙(コート) 薄手73kg</option>
    <option value="0.077">光沢紙(コート) 標準90kg</option>
    <option value="0.100">光沢紙(コート) 厚手110kg</option>
    <option value="0.132">光沢紙(コート) 最厚手135kg</option>
    <option value="0.072">マット紙(マット) 薄手70kg</option>
    <option value="0.107">マット紙(マット) 標準90kg</option>
    <option value="0.130">マット紙(マット) 厚手110kg</option>
    <option value="0.170">マット紙(マット) 最厚手135kg</option>
    <option value="0.098">普通紙(上質) 薄手70kg</option>
    <option value="0.120">普通紙(上質) 標準90kg</option>
    <option value="0.148">普通紙(上質) 厚手110kg</option>
    <option value="0.178">普通紙(上質) 最厚手135kg</option>
  </select>
  <select name="honbun">
    <option value="0">用紙を選択してください。</option>
    <option value="0.065">光沢紙(コート) 薄手73kg</option>
    <option value="0.077">光沢紙(コート) 標準90kg</option>
    <option value="0.100">光沢紙(コート) 厚手110kg</option>
    <option value="0.132">光沢紙(コート) 最厚手135kg</option>
    <option value="0.072">マット紙(マット) 薄手70kg</option>
    <option value="0.107">マット紙(マット) 標準90kg</option>
    <option value="0.130">マット紙(マット) 厚手110kg</option>
    <option value="0.170">マット紙(マット) 最厚手135kg</option>
    <option value="0.098">普通紙(上質) 薄手70kg</option>
    <option value="0.120">普通紙(上質) 標準90kg</option>
    <option value="0.148">普通紙(上質) 厚手110kg</option>
    <option value="0.178">普通紙(上質) 最厚手135kg</option>
  </select>
  <p><input type="text" name="page" value="0"></p>
  <input type="button" value="計算" onclick="keisan()">
  <output size="8" type="text" name="result">
</form>

次にこれだけだと、ただただ表示をしているだけなので、動的な処理をいれてみます。

 

無線綴じ冊子の背幅の計算式は下記となるので、この計算式に従ってjavascriptを記述します。

(表紙の厚さ(斤量) × 2) + (((本文の厚さ(斤量) × (ページ数-4)) ÷ 2 ) = 背幅の厚さ

※本文もvalueで定義している紙の厚さ(斤量)は1枚あたりです。入力してもらうページ数は2ページ = 紙1枚分なので最後に本文を2で割って紙の厚さにしています。
※本文を除いた背幅を計算するため4ページ(紙2枚分)差し引くようにします。

<script type="text/javascript">
  <!--
    function keisan(){

    var paper1 = eval(document.keisan.hyoshi.value) * 2;
    var paper2 = ((eval(document.keisan.honbun.value)) * ((eval(document.keisan.page.value)-4)));
    var calculated = Math.round(paper1 + (paper2 / 2));

    document.keisan.result.value = calculated;

  }
  -->
</script>

先程のhtmlにjavascriptを記述すると以下のようになります。
表紙・本文・ページ数を選択・指定すると何mmなのか計算することができました。

 

 

 

 

 

 

これで完成です。皆さんも冊子の背幅計算が必要になった場合は参考にしていただけると幸いです。

最後に

ラクスルには事業とものづくりが大好きなメンバーが揃っています。

そのメンバーと一緒に働いてくれる方を絶賛募集中ですので興味がある方は是非。

ラクスル採用ページ https://recruit.raksul.com/

【Ruby】Array から Hash を作る方法7選(a.k.a. やっぱり Array#zip はかわいいよ)

ラクスルでサーバサイドエンジニアをやっている小林です。

最近の業務では、主に Ruby を書いています。

さて、Ruby の組み込みライブラリにはいろいろな便利メソッドがありますが、
みなさん推しメソッドはありますか?

個人的推しメソッドは Array#zipHash#transform_values です。

Hash#transform_values について少し紹介すると、
もともと Rails4.2 の ActiveSupport で実装され、
Ruby2.4 で組み込みライブラリに移植されました。
移植の際は、名前をどうするかという議論でとても盛り上がったようです。
また、Ruby2.5 からは姉妹メソッドとも言える Hash#transform_keys が実装されました。

Hash#transform_values の話はこの辺にして、今回は Array#zip の推しポイントを紹介するため、
Array から Hash を作る方法について考えてみようと思います。

コーディングをしていると、下記のようなコードを書きたいことはないでしょうか?

  • ActiveRecord で取ってきて、id をkey、インスタンスを value にしたHash を作りたい
    • 例) [AR1, AR2, …] ⇒ { id1: AR1, id2: AR2, … }
  • 文字列のArrayに対し、元の文字列をkey、正規化後の文字列を value にしたHash を作りたい
    • 例) [Str1, Str2, …] ⇒ { Str1: NormalizeStr1, Str2: NormalizeStr2, … }

このようなときの実装方法をいくつかあげ、後半で性能比較をしてみようと思います。

 

以下、ActiveRecord で User 一覧を取得し、id を key、インスタンスを value とするHash を作成する場合を考えます。

空Hashに追加していく

他の言語でも実装できる、一番オーソドックスな方法かと思います。

array = User.all
hash = {}
array.each do |user|
  hash[user.id] = user
end
hash

 

Array#to_h を利用する

Ruby 2.1 から Array#to_h というメソッドが追加になっています。

レシーバを[key, value] のペアの配列として、Hash を返します。

これを利用すると、下記のように書くことができます。

array = User.all
array.map { |user| [user.id, user] }.to_h

 

Array#zip & Array#to_h を利用する

[key, value] のペアを作るのであれば、 Array#zip が便利です。

メソッドチェインですっきりと書けるところが、個人的気に入っています。

これだけでも、Array#zip がかわいいと思えます。

array = User.all
array.map(&:id).zip(array).to_h

 

Array#transpose & Array#to_h を利用する

レシーバを二次元配列として、転置配列を作成する Array#transpose を利用しても、
同じことができます。

array = User.all
keys = array.map(&:id)
[keys, array].transpose.to_h

 

Enumerable#each_with_object / Enumerable#inject を利用する

Array#to_h がない時代は Enumerable#each_with_objectEnumerable#inject を使うことが
多かった気がします。

array = User.all
array.each_with_object({}) do |user, hash|
  hash[user.id] = user
end

 

Enumerable#index_by を利用する(ActiveSupport)

よくあるパターンなので、ActiveSupport に Enumerable#index_by という、
まさになメソッドがあります。

ただ、こちらは Proc の返り値を key とする Hash を返すので、Array の要素を key、
Proc の返り値を value とする Hash を作りたい場合は、Hash#invert で一手間加える必要があります。

array = User.all
array.index_by(&:id)

 

Enumerable#reduce & Hash#merge を利用する

Lisp 的な発想で、 Enumerable#reduce と Hash#merge を使って畳み込みを行うことで 、
Hash を作ることもできます。

ちなみに、Ruby の Enumerable#inject とEnumerable#reduce は違う名前ですが、
同じ挙動をします。

少し話がそれますが、なぜ同じ挙動で名前が違うメソッドがあるのかについては、
るびまに書かれているので、読んでみると面白いかもしれません。

array = User.all
array.map {|user| {user.id => user} }.reduce(&:merge)

 

比較

みなさん、どの方法で実装することが多いでしょうか?

好みやコードの読みやすさなどで意見が分かれそうですが、一指標として、
各実装方法の性能評価をしてみたいと思います。

今回は、Array から Hash に変換する性能のみを評価するため、変換やメソッド呼び出しはせず、
Array から key と value が同じ Hash に変換する場合の性能を比較してみようと思います。

検証コード

ベンチマークの取得には、 Ruby on Rails Guides にも紹介されている
benchmark-ips gem を利用したいと思います。

検証コードは以下のとおりです。

#!/usr/bin/env ruby

require 'active_support/all'
require 'benchmark/ips'

array = (1..10_000).to_a

Benchmark.ips do |r|
  r.config(time: 20)

  r.report "Empty Hash" do
    hash = {}
    array.each do |num|
      hash[num] = num
    end
    hash
  end
  r.report "to_h" do
    array.map { |num| [num, num] }.to_h
  end
  r.report "zip & to_h" do
    array.zip(array).to_h
  end
  r.report "transpose & to_h" do
    [array, array].transpose.to_h
  end
  r.report "each_with_object" do
    array.each_with_object({}) do |num, hash|
      hash[num] = num
    end
  end
  r.report "index_by" do
    array.index_by { |num| num }
  end
  r.report "reduce & merge" do
    array.map { |num| {num => num} }.reduce(&:merge)
  end

  r.compare!
end

実行環境は以下のとおりです。

ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin16]
activesupport (5.1.4)
benchmark-ips (2.7.2)

 

結果

Warming up --------------------------------------
          Empty Hash    80.000  i/100ms
                to_h    73.000  i/100ms
          zip & to_h   108.000  i/100ms
    transpose & to_h    98.000  i/100ms
    each_with_object    70.000  i/100ms
            index_by    63.000  i/100ms
      reduce & merge     1.000  i/100ms
Calculating -------------------------------------
          Empty Hash    773.943  (±10.3%) i/s -     15.280k in  20.013161s
                to_h    733.483  (± 8.9%) i/s -     14.600k in  20.090466s
          zip & to_h      1.065k (±10.3%) i/s -     21.060k in  20.027320s
    transpose & to_h      1.005k (± 8.5%) i/s -     19.992k in  20.067946s
    each_with_object    719.383  (± 7.1%) i/s -     14.350k in  20.063602s
            index_by    654.471  (± 8.6%) i/s -     12.978k in  19.999714s
      reduce & merge      0.962  (± 0.0%) i/s -     20.000  in  20.834702s

Comparison:
          zip & to_h:     1065.3 i/s
    transpose & to_h:     1004.8 i/s - same-ish: difference falls within error
          Empty Hash:      773.9 i/s - 1.38x  slower
                to_h:      733.5 i/s - 1.45x  slower
    each_with_object:      719.4 i/s - 1.48x  slower
            index_by:      654.5 i/s - 1.63x  slower
      reduce & merge:        1.0 i/s - 1107.19x  slower

Array#zip 早いですね!!メソッドチェーンですっきりかける上、処理も早いという、
これは推さざるをえない感じがしませんか?

Array#transpose も Array#zip とほぼ同じくらいの性能ですが、
やはり個人的にはメソッドチェーンで書ける Array#zip のほうが好きですね。

他の手法についても見てみると、Enumerable#index_by が思ったより遅いです。
実装を見たところ、空Hashに追加していく実装と同じなので、
yield の呼び出し分オーバーヘッドがかかっている感じでしょうか。

Enumerable#reduce と Hash#merge を利用する方法は、
配列長分 Hash#merge が実行されるため、かなり遅くなっています。

 

ただ、実際のコードでは、key と value が同じということはなく、
key や value に対して何かしらの処理を行うため、Hash の作成コストより、
他の処理のオーバーヘッドが大きくなります。

別途、key を Integer#to_s して Hash を作成する場合のベンチマークも取ってみましたが、
空Hash に追加する方法が一番早く、Enumerable#reduce & Hash#merge を除く
実装方法については、それほど変わらないという結果になりました。

#!/usr/bin/env ruby

require 'active_support/all'
require 'benchmark/ips'

array = (1..10_000).to_a

Benchmark.ips do |r|
  r.config(time: 20)

  r.report "Empty Hash" do
    hash = {}
    array.each do |num|
      hash[num.to_s] = num
    end
    hash
  end
  r.report "to_h" do
    array.map { |num| [num.to_s, num] }.to_h
  end
  r.report "zip & to_h" do
    array.map(&:to_s).zip(array).to_h
  end
  r.report "transpose & to_h" do
    [array.map(&:to_s), array].transpose.to_h
  end
  r.report "each_with_object" do
    array.each_with_object({}) do |num, hash|
      hash[num.to_s] = num
    end
  end
  r.report "index_by" do
    array.index_by(&:to_s)
  end
  r.report "reduce & merge" do
    array.map { |num| {num.to_s => num} }.reduce(&:merge)
  end

  r.compare!
end

=begin
Warming up --------------------------------------
          Empty Hash    17.000  i/100ms
                to_h    15.000  i/100ms
          zip & to_h    19.000  i/100ms
    transpose & to_h    18.000  i/100ms
    each_with_object    19.000  i/100ms
            index_by    18.000  i/100ms
      reduce & merge     1.000  i/100ms
Calculating -------------------------------------
          Empty Hash    192.959  (± 8.8%) i/s -      3.825k in  20.060582s
                to_h    178.314  (± 9.0%) i/s -      3.525k in  20.023982s
          zip & to_h    181.005  (±10.5%) i/s -      3.572k in  20.065550s
    transpose & to_h    176.782  (± 9.1%) i/s -      3.510k in  20.039329s
    each_with_object    192.932  (± 5.2%) i/s -      3.857k in  20.054975s
            index_by    182.739  (± 4.4%) i/s -      3.654k in  20.036235s
      reduce & merge      0.905  (± 0.0%) i/s -     19.000  in  21.030102s

Comparison:
          Empty Hash:      193.0 i/s
    each_with_object:      192.9 i/s - same-ish: difference falls within error
            index_by:      182.7 i/s - same-ish: difference falls within error
          zip & to_h:      181.0 i/s - same-ish: difference falls within error
                to_h:      178.3 i/s - same-ish: difference falls within error
    transpose & to_h:      176.8 i/s - same-ish: difference falls within error
      reduce & merge:        0.9 i/s - 213.18x  slower
=end

ちなみに、空Hash に追加するという Enumerable#index_by の実装も、
リファクタリングされて現在の形になっています。

まとめ

コーディングの際よくあるパターンとして、Array から Hash を作成する実装方法を7つあげ、
性能比較をしてみました。

個人的推しメソッドである Array#zip のかわいさが少しは伝わったでしょうか?

 

iframe で複数の管理画面を1つの統一されたサイトに見せるパターンのまとめ

こんにちは。エンジニアの二串です。普段はサーバサイドの開発をしていますが、今日は最近仕事で取り組んだフロントよりの話になっています。

iframe を使って複数の管理画面サイトをあたかも1つの統一されたサイトとして見せるパターンをまとめます。

iframe を使って別サイトをはめ込むことは簡単ですが、実際複数ページあるサイトをはめ込もうとすると、ただ iframe を使うだけでは力不足です。例えばiframe内でのページ遷移、高さ調整、パーマリンク等で問題を感じます。

今回紹介する tips のいくつかはインターネット上で見つけられますが、今回の複数管理画面を1つにマージするという視点でまとまった記事は見つけられなかったので、まとめたら便利なのではとおもい記事にまとめました。

コードスニペットを以下で掲載していますが、より詳しくはデモアプリも併せて見ていただければとおもいます。

背景

移行イメージ

ラクスルに存在する最もレガシーなシステムは admin と呼ばれるいわゆる管理画面です。PHPで作られています。そのレガシー管理画面を素早く新システム(Ops Rails製)へ移行する必要がありました。

当時の状況としては

  • 新管理画面はまだ機能が少ないが、今後の新規開発は新管理画面でしたい
  • 旧管理画面には多くの機能がまだあり、理想的には新管理画面に移植したいものの、工数的にさくっと移行できない
  • オペレーションの中心は旧管理画面から新管理画面に移行してしまいたい

というような状況でしたので、iframe を使って新管理画面の1機能として、旧管理画面の各ページを埋め込んでしまって、ユーザー目線での移行については少ない工数で完了させることにしました。

iframe埋め込みによる問題点

単に iframe の src 属性に URL を指定すると、以下の点が課題になります。

  • 旧管理画面のページ数は多いので、ページごとに iframe を設置するのは大変
  • iframe の高さ動的に変えれない(初期ページや子側でページ遷移した場合)。結果的にページが切れる
  • ブラウザアドレスバー問題
    • 子側でページ遷移を何回かした後に F5 を押すと元に戻る。またURL を他の人にシェアできない。
  • 子側ページ内のリンクが 旧管理画面URL 問題
    • リンク先 URL を Slack 等でシェアできない(できるけど iframe が外れる)
    • 別タブで開くと iframe が外れる
  • そもそも、デザインの統一性がない(子側のヘッダやサイドバーメニューが邪魔)

さらに補足すると、今回の2画面はドメインが異なるため、iframeの親・子間で window をまたいで javascript や css で操作しようにもクロスドメイン(CORS)のため DOM 操作できません。

仕組み全体像

以上の問題を踏まえ、以下の指針を立てました

ページ表示の流れ

  • 新管理画面側(Rails)では `GET /proxy?path=path/to/page` というエンドポイントを作り <iframe src=”https://旧管理画面/path/to/page”> をレンダリングする。つまりどんなページもはめ込むことができる。
  • 親・子それぞれに今回のための javascript  を設置し(parent.js, child.js)、window間通信は window.postMessage() API を使う。これによりクロスドメインであってもwindow間で通信可能
  • この通信路を使って子の page height などを 親に伝える。高さ以外の情報もあるので、メッセージタイプ(type)、メッセージデータ(data)を含めた object をデータペイロードに入れて送ることにする。
  • iframe内のリンクの諸々の問題は、a タグの href の値を力技で新管理画面のproxy URL に書き換える。
  • サポートブラウザは Google Chrome のみとする。社内利用のツールなので工数削減も踏まえ割り切れる点と、そもそも社内で Google Chrome が標準で使われているので。

以下、長くなりますが、1つずつ見ていきます。

GET /proxy?path= エンドポイント

# app/controllers/proxies_controller.rb
class ProxiesController < ApplicationController
  def show
    path = params[:path] || ''
    @src = 'https://旧管理画面FQDN/' + path
  end
end

# app/views/proxies/show.html.erb
<iframe id="proxy-iframe" src="<%= @src %>" height="1024" style="overflow: hidden; border: none;" scrolling="no" frameborder="no"></iframe>

# config/routes.rb
get '/proxy', to: 'proxies#show'
  • GETパラメタの path から旧管理画面の URL を構築し iframe の src 属性へセット
  • height=”1024″ は初期値として。後述の方法で別途ページ毎に動的に変える。
  • その他、iframeのボーダー枠線。スクロールバーを消す。これはiframeっぽさを無くすための工夫
  • id属性 `proxy-iframe` は後述の javascript の処理で利用

これで旧管理画面の任意のページを新管理画面で扱えるようになりました。

なお、旧管理画面側が X-Frame-Options を返却していたので iframe 内で表示させるため nginx で消しこみました。

親・子window間通信

新・旧管理画面それぞれに以下の javascript を置きます。今回のケースでは、子から親に一方通行でメッセージを伝え親側でアクションさせればよかったので、子と親の通信はいわゆるクライアントサーバモデルになっています。

子側

const postMessage = (type, data) => {
  const rawData = JSON.stringify({ type: type, data: data })
  window.parent.postMessage(rawData, '*')
}

// iframe内でレンダリングされた場合にのみ発火させる
if (window !== window.parent) {
  window.addEventListener('load', () => {
    // 子の高さを親に伝え、親の iframe の heigit を変更
    postMessage('PageHeight', {height: document.body.scrollHeight})
  }, false)
}
  • iframe 内でレンダリングされた場合にのみ実行している
  • on load 時に高さを送る。メッセージには type と data がありJSON シリアライズして送る。

iframe 内でレンダリングされた場合にのみこの仕組が発動するようにすることで、移行をやりやすくしました。旧管理画面直アクセスでは発動しないので、旧管理画面直アクセスへのパスを残しつつ、緩やかに移行を進めれます。

親側

const messageHandlers = {
  PageHeight: (data) => {
    // iframe の height を調整
    let elm = window.document.getElementById('proxy-iframe')
    elm.style.height = `${data.height}px`
  }
}

window.addEventListener('message', (event) => {
  console.log('[ops] message received', event)

  // On development environment webpack-dev-server sends message, so skip message from it.
  if (/^https?:\/\/(localhost|127.0.0.1)/.test(event.origin)) return

  // Verify received message from for security manner of postMessage() API,
  const originRe = /旧管理画面URL/
  if (event.origin.search(originRe) === -1) {
    console.log('Blocked the message from ' + event.origin)
    return
  }

  const data = JSON.parse(event.data)
  if (data.type === null) throw new Error('[ops] Message received, but no type specified')
  if (!messageHandlers.hasOwnProperty(data.type)) {
    throw new Error(`[ops] Message received, but type "${data.type}" is invalid`)
  }
  messageHandlers[data.type](data.data)
})

細かいエラーハンドリングも載せましたが、中心は以下のとおりです。

  • window.postMessage() で送信されたデータは ‘message’ イベントで受け取れる
  • type に対応するメッセージハンドラ関数へディスパッチする
  • PageHeight ハンドラでは iframe の height 属性を変更

iframe高さの動的調整

1つ前のセクションのとおりです。

ブラウザアドレスバー問題

iframe内でページ遷移を繰り返してもブラウザアドレスバーのURLは最初のURLのまま問題。実際のカスタマーサポートなどの現場では、「あ、この注文、サポートが必要そうだから Slack で注文詳細のURLをシェアしておいて…」 といったことありますので、iframe内で遷移したあとの URL も気軽に貼り付けれる必要がありました。

そこで、子の a タグのクリックイベントを prevent default  で止めて、PageMove メッセージを送り、親側windowでページ遷移させてしまうことにしました。詳しくはサンプルアプリのコードを参照ください。

なお、a タグ以外に POST 等の後のサーバサイドでのリダイレクト処理される場合にも同様の課題はあります。管理画面の特性を検討した結果、リダイレクトについては特に何も対策せずとも運用上なんとかなると判断してます。

子側ページ内のリンクが 旧管理画面URL 問題

リンク先をシェアされたり、別タブで開こうとすると iframe が外れてしまう。

これに対しては、子側の DOMContentLoaded で hrefの属性値を 新管理画面の proxy URL に挿げ替えてしまうことにしました。

const fakeAnchors = function () {
  document.querySelectorAll('a').forEach(function (elm) {
    // getProxyUrlFromAdminUrl() は https://旧管理画面FQDN/foo/bar を
   // https://新管理画面FQDN/proxy?path=foo/bar に変換
    elm.href = getProxyUrlFromAdminUrl(elm.href)
  }
}

// iframe内でレンダリングされた場合にのみ発火させる
if (window !== window.parent) {
  // ...
  window.addEventListener('DOMContentLoaded', function () {
    // ...
    fakeAnchors()
  }, false)
}

なお、javascript を使わず、旧管理画面の view の各aタグを手で全て書き換える方が素直ですが、そうしなかった理由は、数が多いのと、暫くの間旧管理画面にダイレクトにアクセスできるパスは残しておきたかったためです。管理画面だから許される力技…です。

ページ内リンク問題

<a href=”#foo”>foo</a> のようなリンクです。iframe のオプションで scrolling=”no” している、また href をすげ替えた都合でスクロールしてくれないので、このようなリンクがクリックされた場合は、子から親に要素のポジションを計算して親側でスクロールさせるようにしました。詳細はサンプルコードを…といいたいところですがこの部分サンプル書けてないです :pray:

なお、production のコードではこの部分も window.postMessage() で 親にスクロール位置を通知させました。

ヘッダ・サイドバー消し

新管理画面の吸収されてしまった旧管理画面。もう旧管理画面のヘッダやサイドバーメニューは不要なので消します。旧管理画面側で DOMContentLoaded のタイミングで 追加の css ファイルを入れて、消し込むことにしました。

子側

const applyOpsCss = function () {
  const style = document.createElement('link')
  style.rel = 'stylesheet'
  style.href = '/css/additional.css'
  document.getElementsByTagName('head')[0].appendChild(style)
}


// iframe経由でのみ発火させる
if (window !== window.parent) {  
  applyOpsCss()
  window.addEventListener('DOMContentLoaded', function () {
    postMessage('PageDOMContentLoaded', {})
  }
}

  • iframe内でレンダリングされた場合に限り additional.css を適用する。additional.css 内では消したい要素に display: none; を適用している
  • DOMが読み込まれたら親に通知(次を参照)

親側

const messageHandlers = {
  // ...
  PageDOMContentLoaded: (_data) => { window.document.getElementById('proxy-iframe').style.visibility = 'visible' },
  // ...
}
  • スタイルを当てるまでの非常に短い間はヘッダ等が見えてしまうので画面がチラつく。これを防ぐために ngCloak 的なアプローチ、すなわち、iframe に `visibility : hidden;` で予め非表示にしておいて、追加のスタイルが適用されたら PageDOMContentLoaded メッセージを送信しiframe のdisplay visibility を `visible` に変更する

CORS でなければ親側だけで iframe 内の load を検知できそうな気はするのですが、CORS だったのでこのようにしました。

メニューのマージ

このタイミングでメニューバーを整理しました。新・旧それぞれのメニューバーにあった項目を整理し、新管理画面のメニューバーにマージしました。格段に見やすくなってこちらは評判です。

まとめと所感

iframe を使って複数の管理画面サイトをあたかも1つの統一されたサイトとして見せるパターンをまとめました。特に CORS 環境おいては window.postMessage() API を利用することで様々な課題を解決することができました。

この方式で本番運用して4ヶ月ほど立ちますが、大きなトラブルなく稼働できています。管理画面という特性上そのあたりは柔軟に対応できているのと、対応ブラウザを Chrome 1本に割り切ったので、導入後のメンテ工数は今のところほぼなく上手く動いてます。

本番の切り替えについては、旧画面と内容は変わらないので、導入調整コストがほとんどかからず、また利用者=オペレーターさん達の心理的負荷や学習コストも低く抑えることができました。これは本アプローチの最大のメリットではないかとおもいます。

ちなみに、このアイデアを思いついた翌週が運良く Hack It Day だったので、1日つかってプロトタイプを作って、どのあたりに技術課題があるか、工数感などつかむことができたのが良かったとおもいます。 * Hack-It Dayは月に一度、ラクスルのエンジニアが自由に開発することができる日)

さて、長くなりましたが最後までお読み頂きありがとうございます!!

ラクスルでは、絶賛エンジニア募集中です。
「仕組みを変えれば世界はもっとよくなる」
世界が変わっていく瞬間を一緒に体験したいエンジニアの方、お待ちしています☆

【Tableau】DesktopとOnlineの複合で簡単・安価にレポーティング

みなさん、いかがお過ごしでしょうか?
ラクスルの安井一浩です。

データアナリストではない私ですが、Tableauをいじるのが好きなので共有させていただきます。
「昨日、売上が下がったんだけどなぜだかよくわからない」
「あの数値を毎日見たいんだけど、あまり時間が無い」
こんなことが日常的に起こると思います。

それを解決するTableau Desktop&Tableau Onlineをご紹介したいと思います。

システム構成

Tableauレポートシステム図
図を解説します。

各ソフトで役割が違います。

Tableau Desktop:主に分析ツール
役割・・・データ解析、グラフ制作、分析用DBの制作
特徴・・・分析がとてもやりやすい。フィルタと数式がとても使いやすく動作も速い。
頻繁にバージョンアップしているので、複数DBをJOINできるなどできることがどんどん増えている。

Tableau Online:クラウドおよびサブスクリプションツール
役割・・・DB更新、定期レポート配信、グラフデータバックアップ
特徴・・・クラウドから、データ更新やメールのサブスクリプションなどを自動でしてくれる。
バックアップデータを保持してくれるのでTableau Desktopでデータを触っている時に
壊れてしまった場合などは非常に助かる。

Tableau Desktopが得意なところとTableau Onlineが得意なところを分けて、2つのソフトでレポーティングを実現するという構成になっています。
データベースエンジニアやデータサイエンティストの力を借りずとも、レポートが作れてしまうのがメリットですね。
通常のTableau Desktopの使い方の延長でできてしまいます。

システム導入後の作業手順

①データベースに接続して、分析用DBを制作します。※今の画面はMySQL
データベースに接続

②作ったデータベースを抽出する。※ここがポイント抽出画面

③グラフを制作する。※ご自由に
グラフ制作

④データベースをTableau Onlineにアップロードして、アップロードしたデータベースに置き換える
データベースをアップロード

⑤Tableau Onlineでデータベース更新の設定をする※昔はアメリカ時間でしたが、今は日本時間になっています。データベースの更新スケジュールを設定する

⑥Tableau Onlineにグラフをアップロード(パブリッシュ)ワークブックのアップロード

⑦Tableau Onlineでビューに移動してから、メールの定期配信(Subscription)の設定をする。

ビューに移動します。Tableau onlineの画面

ビューからサブスクリプションを設定。ビュー単体や、ワークブック全体などを指定できます。サブスクライブというのが、決定ボタンです。サブスクリプション設定

どこにメールが配信されるかというと、マイアカウントで設定したメールアドレスにメールが飛びます。

1アカウント1メールアドレスと言う設定になっていますので、
複数のメールアカウントに送りたい場合はアカウントを増やすか、
メール側で転送設定をすれば、複数の人に送信することも可能ですのでご安心を。
※ただし、Tableau Onlineで細かい数字は見れませんが・・・

最小構成だと、なんと年間162,000円でレポートシステムができてしまいます。
Tableau Desktop 102,000円/年
Tableau Online 60,000円/年
とは言っても、触っているとどんどんアカウントが欲しくなってくるのが常なのでこれで終わることはマレなんですけどね(;´∀`)

細かいレポーティングが簡単に設定できるのでとてもおススメです。
ラクスルはこのような形でデータ分析とレポーティングは一部Tableauで行っています。

以前はエクセルでの分析がメインだったのですが、データ量が増えてくると厳しいですよね。
また、行数制限の問題等もあります。
Tableauなら、エクセルデータも取り込めますし、DBとのJoinもできる優れもの。
みなさんも試してみてください。

おまけ

Tableauの発音方法はイチロー、サブローみたいにタ → ブ ↑ ロ ↑ ー →って読んでいたのですが、Tableauの人と会話した時はマズローみたいに、タ → ブ ↓ ロ ↓ ー →という読み方でした。後者が正しいようです。

ラクスルでは、絶賛一緒に働いてくれる方を募集中です!
新しいことにチャレンジしたい方は是非。

ラクスル採用ページ https://recruit.raksul.com/

そうだ、ラクスルを作り直そう!

10月からラクスルにジョインさせていただいた水島です。

新参者ですが、宜しくお願いいたします。

さて、入社して間もなくCTOの肝いりでスタートした 「Raksul Platform Project」 のプロマネを拝命したため、今日はその全体感の話をしたいと思います。

なにをするのか

「スタートアップあるある」だなんて言わないでください。

ラクスルをフルスクラッチで作り直そうとしています。

でもそれはあくまで手段です。

目的は、

  • 技術負債と思われている部分を根本的に解消して開発しやすい状態にする(エンジニアを幸せに)
  • システムに柔軟性を持たせて経営戦略の選択肢が増えている状態にする(経営を幸せに)

の大きく2つです。

特に前者の「エンジニアを幸せに」という目的に対する経営陣の温度感が不思議と高いのはポジティブに感じています。短期的な投資対効果とかではなく、「ものづくり」を大切にする会社になるんだという意思が強く感じられるので、長い道のりにはなりそうですが、エンジニアとデザイナーがさらに輝けるようにするために是非やりきりたいと思っています。

後者の「経営を幸せに」という目的は、プロマネである私のバランサーとしてのプライドみたいなものです。ただ作り直してキレイになりました、幸せです!だけでは虚しさがあるので、経営の潜在ニーズを今回のプロジェクトで一定先回りして、いつかドヤ顔したいと思っているのです。

つまりは、単純に他言語、他フレームワークで作り直すということではなく今後のアーキテクチャーから見直していくプロジェクトです。

 

今までのいろんなアンチパターンを思い返して方針を考える

ラクスルの現在のシステムの状態を見たり、私が今まで経験してきた様々なサービス、プラットフォーム、受託システムの開発を思い起こすと、やってはいけないアンチパターンの連続が強烈な反省と共に頭の中を駆け巡ります。

それらをベースに今回はどのようなアーキテクチャーで進めていこうとしているかをいくつかご紹介したいと思います。

アンチパターン1: サービスのコア機能が汎用化されていない

サービスはユーザーのニーズや市場環境を見ながら進化していくものなのですが、再利用可能なコア機能が主力サービスの中に閉じ込められていることがあります。

私の経験を思い返しても、

  • 会員体系の認証、認可機能
  • 決済、経理、資金決済法遵守のための仮想通貨管理などの機能
  • データ分析基盤機能
  • 校閲、投稿内容チェックなどの機能
  • 画像管理、変換機能
  • CMSと広告配信管理機能
  • ファイルシステムみたいな機能

などがありました。

つまりは社内外で今後再利用される可能性があるものを主力のサービスとは疎結合に作っておけばよかったと後悔し、後で掘り起こして、汎用サービス化することを繰り返してきました。

社外のクラウドサービスで置き換え可能な機能も増えているので、要件がほぼフィットするならそれらを活用すべきというのは言うまでもなく、実際にFirebaseなどに載せ替えた部分もありましたが、自社の強みや特殊要件が眠っている場合は、早めの段階で社内クラウドサービスのように汎用的な設計にして再利用可能な状態にしておくべきです。

現在のラクスルにおいて、主力サービスの中に閉じ込められている汎用的なコア機能としては、

  • ラクスル社の他事業(ハコベル事業等)で共通に使える汎用機能レイヤー
    • 個人向け、法人向け決済、経理処理などの機能
    • メール送信とテンプレ管理機能
  • 印刷関連事業で共通に使える汎用機能レイヤー
    • 印刷SCMと発注基盤機能
    • スピードチェック入稿を含むDTP機能
    • オンラインデザイン制作サービス機能
  • raksul.com で共通に使える汎用レイヤー
    • ラクスル会員の認証、認可機能
    • ECの基本機能

と分析しています。

ハコベル事業でも物流の輸配送管理システムなどは、物流全般で共通に使える汎用機能レイヤーと言えるかもしれません。

裏を返すとこれらの機能の汎用化ができていないために、既存事業とシナジーのある多角的な取り組みの開発スピードが落ちていたり、顧客体験の低下や、事業の機会損失が出始めているようにも見えます。

今回のプロジェクトでは、これらの機能を各レイヤーにおいて、社内外の他サービスと接続しやすく切り出して作り直し、そしてそのアーキテクチャーを継続していけるような開発体制を作っていくことが重要となります。

raksul.comの印刷サービスや集客支援サービスは、これらの汎用機能の上に成り立つ一つのサービスという位置づけになります。後付にはなりますが、これがRaksul Platform Projectと呼ばれている所以です。

 

アンチパターン2: データベースとアプリケーションの切り方を間違える

よくあるアンチパターンな構成としては、モノリシックなデータベースが中心にあり、ユーザー向け、管理画面やAPIなどの多数のWebアプリが読み書きしている状態です。

立ち上げ期はスモールな構成なのでいいのですが、そのまま多角的に機能追加を行ってしまった結果、データベースとメインのWebアプリ、管理画面などが肥大化していき、辛くなって対処療法的に小さなアプリだけを切り出し始めます。

 

こうなってしまうと、

  • 各アプリケーションで似て非なるモデルが冗長に開発され、コピペしはじめる(DRYでない)
  • 各アプリケーションがそれぞれの事由でスキーマを変更、書き込みをしていき、影響範囲が広くなる
  • 新しいエンジニアやオペレーターがシステムをキャッチアップするのに時間がかかる

といった弊害が出てきます。

開発の現場から悲鳴が上がり始め、内部にインターナルAPIを置き、データベースやビジネス・ルールを一定ラップして凌ぐことになります。ラクスルにも内部にAuth API, Order APIといった比較的新しくて小さな内部APIが存在していて、一時的な対処はしている状態ですが、抜本的な解決には至っていません。

本質的には適切な関心事であるドメイン毎に、データベースを分割して、各ドメインのアプリケーションが、

  • エンドユーザー向けWeb画面
  • 管理者向けWeb画面
  • 他アプリケーション向けAPI

の3つのプレゼンテーション機能を持ち、一つのデータベースと一つのストレージ(AWS S3 のバケット等)の保全に責任を持つ、という構成にすることで、機能の凝集度を高めるべきです。

早期にこういった発想でデータベースとアプリケーションの切り出しを意思決定したかったと後悔することがよくあります。

 

アンチパターン3: マイクロサービス化しすぎて逆に効率が落ちる

上記で説明した、データベースとアプリケーションの切り方についてですが、柔軟性を求めすぎて細かく切りすぎてしまい、ピタゴラスイッチ状態になるとまた問題が出てきます。

  • dockerなどを使っても環境セットアップするのが辛くなる
  • データを非正規化しすぎて不整合が起きやすくなる
  • 障害対応やチューニングの難易度が上がる

ラクスル内部でも、Auth APIがつらい、直接usersテーブル参照したい、joinしたいといった声が聞こえることがありますが、まさにといった弊害かと思います。それは近くにあるべきはずのものが遠くに切り出されてしまっている可能性があります。

一定仕方のない部分はありますが、なんでもかんでもAPIにして分割していくということではなく、逆に統合すべきドメインを見定め、的確にケーキカットしていくことが非常に重要だと思っています。

ラクスルの場合は、

  • 全社共通で使われるであろう機能レイヤーに2アプリケーション
  • ラクスルのコア技術で印刷事業共通に使われるであろう機能レイヤーに3アプリケーション
  • raksul.comのECサイトのレイヤーに2〜3アプリケーション

の合計7〜8アプリケーション構成程度になってくると思っています。
これでも少し多いですが、これ以上切り出すのは多すぎると思っています。

 

アンチパターン4: テストしにくい状態で身動きが取れなくなる

フレームワークや言語のバージョンアップをするというプロジェクトを今まで何度も経験していますが、ここで辛いのが、テストがないアプリケーションのマイグレーションです。

例えば、Rails5が出ている時期に、 テストが不十分なRails3の巨大なモノリシックなアプリがあってなかなかマイグレーションできませんでした。「Rails3 なんですねえ。。。」と採用面接で何度言われたことでしょう。

持続可能な事業を支えるアプリケーション、開発組織であればここは妥協すべきではありません。

ラクスルでも、PHP + テストがないアプリケーションなどが一部あります。かつ、それがとても事業上重要なアプリケーションだったりします。日々の開発では、End2Endテストと人力のQAで品質を担保していますが、もう少し細かいレベルでのテスティングに力を入れなければ長くアプリケーションの品質を保つのは難しいのは言うまでもありません。

思い切ってこれらのアプリケーションをRuby on Railsでテストフレームワークも含めて標準化し、テストを書きながら作り直していく必要があると考えています。

 

まとめ

さて、ご紹介してきたアンチパターンを踏まえた上で、今のところのRaksul Platform Projectの基本的な方針は以下のような形です。

  • レイヤーとドメインを定義し、コア機能を汎用的に切り出して社内外と接続可能な状態にする
  • 1アプリケーションに対して、1データベース、1ストレージ構成のアプリにする
  • アプリケーションを細かく切りすぎないように7〜8アプリケーションぐらいに再分割して切り出していく
  • Ruby on Railsでアプリケーションを標準化してreadable、testableな状態にする

ラクスルの開発現場はこれから大きく変わろうとしています。また、これによりシステム構成と事業・組織構成の間の歪を最小化することができる考えています。

印刷業界をリードできるコア機能の開発が段々できるようになってきた今、今度はそれらをモダンなアーキテクチャーに載せ替えていき、ワクワクするような経営・事業戦略と共にエンジニアもさらに高いレベルに成長できると思います。

ラクスルでは、絶賛エンジニア募集中です。
「仕組みを変えれば世界はもっとよくなる」
世界が変わっていく瞬間を一緒に体験したいエンジニアの方、お待ちしています♡

スピードチェック入稿リリース秘話

ラクスルの数少ない女子エンジニアの加藤です。

今回は、8月末にリリースしたスピードチェック入稿のリリースまでのプロセスを書いてみたいと思います。

スピードチェック入稿とは

そもそも、チラシや名刺などの印刷をインターネットで注文したことのない方もいらっしゃるかもしれません。

ラクスルは、印刷物を扱うECサイトです。通常のECサイトと違って、商品を選んで配送先と決済方法を選んでもらうだけでは注文は完結しません。お客様が印刷するデータを入稿するという印刷ならではのステップがあります。

また、印刷用データはラクスルで印刷に適しているかどうかチェックして、適していない場合、お客様は再度入稿する必要があります。

これまで、ラクスルではデータが印刷に適しているかどうか、オペレーターが一つ一つチェックし、場合によっては微修正をしていました。人手でやっているため、印刷データが確定して実際に印刷工程に進む準備ができるまでに1日以上かかってしまうこともあり、急いで印刷物が欲しいというお客様の要望になかなか答えられていませんでした。

そこで、サイトを訪問している最中にその場でチェック結果がわかるようなシステムを開発しようということになりました。それが「スピードチェック入稿」です。

スピードチェック入稿画面

スピードチェック入稿画面

プロジェクトの始まり

入社してしばらく経ちそろそろ会社に慣れてきたかと思っていた4月末、プロジェクトのミーティングに呼ばれました。データチェックを自動化する理由が共有されて、成功させるぞという強い意志が感じられましたが、参加メンバーのドメイン知識が十分ではない状況で、議論も少し混乱気味。私個人としては不安がありつつも、とにかくやってみようという気持ちでミーティングを終えました。

プロジェクトは、ラクスルで提供しているデータチェック業務理解から始まりました。実際に、オペレーターの方が作業している所をメンバーみんなで見させてもらう時間もとりました。

・どんなデータが入稿されるのか
・どんなデータのどんな点を確認しているのか
・どんな修正を行っているのか
・どんなメールをお客様に送っているのか

最初はこのような基本的なポイントを一つ一つ確認していき、データチェックのドメイン知識の理解を深めていきました。

デザインと設計のプロセス

スピードチェック入稿のデザインチームはデザインスプリントを実施。毎スプリントごとにお客様からのフィードバックを受けてデザインが進みました。

使う人はどんな印象を受けるのか、どんなところで操作に詰まったり、戸惑ったりするのか。私自身はデザインチームではありませんでしたが、週ごとのデザインとフィードバックのシェアや、お客様へのインタビューの同行を通じで、ユーザ理解を深めることもできました。

ビジネスチームによるデータチェック要件の詳細化とデザインと同時並行で、システムの設計も始めました。

システムは、すぐにWebで時間のかかる処理を実装するときのオーソドックスな構成に決定。
・フロント:データチェック用のAPIを呼び出し結果を描画
・データチェック用のAPI: フロントから受け取ったリクエストを受けてジョブをキューイング
・バックエンドジョブ:データチェックやPDF変換を実施

システム構成

システム構成(模式図)

デザインスプリントが進んでいる間に、最小単位で連携して動くものを実装していきました。

初めて一緒に仕事をするメンバーで構成されたチームでしたが、REST APIなど共通した設計スタイルがあると設計検討も早く進むのを改めて感じました。

自走チームの開発スタイル

メンバーが各作業を進める中、試行錯誤したのはコミュニケーションとタスク管理でした。

ビジネスメンバー、デザイナー、エンジニアそれぞれに違うバックグラウンドを持ち、比較的自由に仕事をするメンバーが揃ったため、仕事自体はすごく速く進むのですが、みんなが協力しないと一つ一つの機能が動きません。

どんな風にコミュニケーションするか、どんな風にタスクの状況を共有したり管理したりするか。朝会や毎週の振り返りで、率直に意見やアイデアを言い合って、改善して進めました。

振り返り

毎週末の振り返り

以前のブログ記事では、開発スプリントやペアプロの紹介もありましたが、今回はペアプロは実施せず、また開発スプリントは回しつつも運用は比較的ゆるめ。それぞれのメンバーのやりやすさを優先させつつ、アクションが滞らないようにコミュニケーションをとって乗り切りました。

やっぱり何か起こる結合とテスト

いくつかのコンポーネントが連携するシステムの開発では、結合とテストの段階になって様々な考慮漏れが判明してスケジュールが遅れるというのが開発でよくある光景ではないでしょうか。

今回のプロジェクトでは、かなり前段階で様々なケースのテストデータを準備したりエラーケースの検討をしてきましたし、結合して動かすこともその都度行なっていましたが、それでもやはり本格的な結合やテストの段階で色々な問題が起こりました。

例えば、初めは入稿している原稿の縦横の方向を自動判別しようとしていましたが、チラシでは判定できるけれども、名刺では判定できないケースがあるということが間際になってわかったりしました。

もちろん前倒して考慮できればベストだと思いますが、初めての要素があるプロジェクトではなかなかそうもいかないものです。このプロジェクトでは見つかった時にその都度、チームで学習しながら、一つ一つアクションを決めていきました。

リリース!

風邪でチームメンバーが次々と休んでスケジュールが遅れ気味になったり、間際で解決が危ぶまれる課題が発生したりと、様々なことがありましたが、8月末に当初の予定通りリリース。日程的にタイトな状況になっていても、みんなでランチに行ったりとチームの雰囲気を保ち、コミュニケーションを密に取れるようにしていたのも、逆に予定通りのリリースに不可欠だったかもしれません。

リリース後も、スピードチェック入稿の状況をチームでモニタリングしながらの対応や改善、対象商品の追加が続き、多くのお客様に使われるサービスに育ってきました。フェーズが移り変わるとチームの状況ややることも変わっていきますが、これからもビジネスメンバー・デザイナー・エンジニアの垣根を超えて、お客様の体験がよくなっていくような開発を続けていきたいと思います。

 

ラクスルでは、絶賛エンジニア募集中です。
「仕組みを変えれば世界はもっとよくなる」
世界が変わっていく瞬間を一緒に体験したいエンジニアの方、お待ちしています♡