当社のクラウド移行とSREについて講演をしました 2019/1/30にitsearch+様で当社のクラウド移行とSREについて講演をしました。 news.mynavi.jp 発表資料はこちらです。ぜひ、ご覧ください。 speakerdeck.com 昨年11月に書いた以下の記事の内容に具体的な事例を交えつつ、当社のSREの取り組み方について発表をしました。 user-first.ikyu.co.jp 発表にも書きました通り、今後もコンテナ技術等、新しい技術を活用しつつ、ビジネスの成長を支える技術基盤開発、SREを実践していきたいと思います。We are hiring!! hrmos.co hrmos.co おまけ 最近勉強になったSRE関連のリソース SRE - 次世代Webカンファレンス #nwc_sre - YouTube 3人の経験者が語るReal World SRE。とても啓発的な内容で勉強になりました。 Seeking SRE 洋書ですが、SREについて非常に幅広いトピックを扱っています。 この記事の筆者について システム本部CTO室所属の 徳武 です。 サービスの技術基盤の開発運用、開発支援、SREを行なっています。
宿泊事業本部でフロントエンド開発をしている宇都宮です。 昨日(2019/1/24)、 LODGE で開催された、 Bonfire Frontend #3 に登壇させていただきました。 Bonfire Frontend #3のテーマは「パフォーマンス改善」で、各社がパフォーマンス改善ネタを持ち寄って発表する会でした。 私は「一休.comのフロントエンドパフォーマンス改善」というタイトルで、フロントエンド全般のパフォーマンス改善についてお話ししました。 slides.com フロントエンドのパフォーマンス改善においては、デファクトスタンダードな計測ツールである Lighthouse の使い方に習熟することが重要だと思っています。 また、2019年1月現在、Lighthouseを利用した計測環境として最もアクセスしやすいのが PageSpeed Insights です。ただ、PageSpeed Insightsの計測には前提条件や若干の癖があるため、その辺の話も交えて、PageSpeed Insightsの話を厚めにしました。 その後は一休で行っているパフォーマンス改善の大まかな戦略や、今後のフロントエンドパフォーマンス改善に向けたアーキテクチャの構想などをお話ししました。 具体的なパフォーマンス改善の施策については、先月のIkyu Frontend Meetupで発表した下記スライドもあわせてご覧ください。 slides.com speakerdeck.com 一休では、使いやすい予約サービスの提供のため、引き続きパフォーマンス改善活動を進めていきたいと思っています。 hrmos.co
一休.comでWebフロントエンドを開発している宇都宮です。 先日、一休.comホテルページのスマホ版から、jQueryを取り除きました。jQueryを取り除いた経緯、やったこと、結果について書きます。 ちなみに、ホテルページには以下のURLでアクセスできます(スマホで開くか、PCの場合はUAをスマホに偽装する必要があります) https://www.ikyu.com/sd/00001290/ なぜjQueryを取り除いたのか? どうやったのか 何をやったのか jQuery.ajax() => fetch に置き換え fetchのpolyfillを採用した理由 DOM操作を標準APIに置き換え 要素の取得 show/hide addClass/removeClass html/text アニメーション $.ready() イベントフィルタリング jQueryの使用を防ぐ目印 jQuery削除の効果 まとめ 告知 なぜjQueryを取り除いたのか? JavaScriptサイズの削減のためです。一休.comホテルページは、以前は合計で約300KBのjsファイルを読み込んでいました(300KBはgzip後の転送量なので、実ファイルはもっと大きいです)。 よくいわれる「jsは170KB以内ルール」は、回線速度のベースラインが400Kbpsという前提 1 です。一休.comの平均的ユーザはもっと良質の回線 2 を使っているので、170KBまで切り詰めようと思っているわけではありません。 しかし、jQueryで実装されている処理は、最近のDOM APIを使えば代替可能です。ブラウザAPIの統一が進みつつある現在、jQueryを使う理由はないのでは? と考え、jQuery依存を取り除くプロジェクトを進めました。 どうやったのか jQueryを使用している箇所は多かったため、細かくプルリクエストを切って、都度masterにマージしていく方針で進めました。 結果的に、修正プルリクは12個、総変更行数は±2500行程度になりました。 また、メインプロジェクトと並行して進めていたため、去年の8月頃から着手して、完了は先週でした。約4ヶ月かかった計算になります。 何をやったのか ここからは、やったことを細かく書いていきます。 jQuery.ajax() => fetch に置き換え jQuery.ajax() を、ブラウザの標準APIである fetch に置き換えました。fetchが利用可能なのはiOS 10.3以上なので、polyfillも導入しました。 github.com ※ライブラリをバンドルすると、全てのユーザにpolyfillを配信することになります。パフォーマンス観点からは、 polyfill.io で fetchが使えない場合のみpolyfill を使うのも良いと思います。 基本的には、Promiseを使っているところはそのまま置き換え、コールバックを使っているところはPromiseベースに書き換えました。1カ所だけ同期のajaxを使っているところがあったので、そこは非同期に書き直しました。 $.ajax( 'https://www.ikyu.com/api/...' , {} ).then(data => data); const data = await fetch( 'https://www.ikyu.com/api/...' ).then(res => res.json()); fetchのpolyfillを採用した理由 比較したのはXMLHttpRequest(XHR)と axios ですが、 XHRと比べると、 Pros fetchはPromsieベースで、高レベルなAPIになっている Cons iOS 10.2以下ではpolyfillの読み込みが必要 axiosと比べると、 Pros fetchはWebの標準APIであるのに対して、axiosはjQuery.ajax風の独自API polyfillなので将来的にライブラリの読み込みをなくせる whatwg-fetchはaxiosよりもサイズが小さい axiosが提供しているような高度な機能(Universal JS、リクエストのキャンセル、transform/intercept等)は今のところ必要ない Cons 機能が少ない(たとえば、タイムアウト機能がない) という感じかなと思います。 fetchのConsについては、 XHRを生で使うのは可読性の観点からはありえない fetchに足りない機能は必要に応じて補うことができる という理由から実質的に問題ないと考えて、fetchのpolyfillを採用しました。 DOM操作を標準APIに置き換え jQueryで行っていたDOM操作を、全てブラウザの標準APIに置き換えます。jQuery => DOM APIの置き換えに関する包括的なドキュメントは以下がおすすめです。 github.com ここでは、今回のプロジェクトで実際に使った置き換えのみ紹介します。 要素の取得 jQueryの $() は単体の取得とリストの取得を透過的に扱えるようになっていますが、DOM APIでは区別が必要です。 $(selector); // 1個だけ取る document .querySelector(selector); // 要素のリストを取る document .querySelectorAll(selector); // 要素のリストを取って、一括操作する(NodeList.forEachはiOS 9では使えないので配列化している) [ ... document .querySelectorAll(selector) ] .forEach( /**/ ); 注意が必要なのは存在しない要素へのクエリです。jQueryは、存在しない要素に対するクエリを発行して、返却されたオブジェクトにメソッドを発行しても、エラーにはなりません。存在したりしなかったりする要素に対する処理をjQueryで行っている場合、DOM APIへの置き換えは一手間必要です。 // エラーにならない $( 'こんな要素はない' ).show(); // document.querySelector()の結果はnullなので、styleへのアクセスでエラー発生 document .querySelector( 'こんな要素はない' ).style.display = 'block' ; show/hide $el.show(); $el.hide(); $el.toggle(); el.style.display = '' ; el.style.display = 'none' ; if (el.ownerDocument.defaultView.getComputedStyle(el, null ).display === 'none' ) { el.style.display = '' ; } else { el.style.display = 'none' ; } 実際に使う際は、関数化したほうがよいでしょう。 addClass/removeClass class操作は classList で置き換え可能です。IE 10以上対応なので安心。 $el.addClass( 'class' ); $el.removeClass( 'class' ); $el.hasClass( 'class' ); el.classList.add( 'class' ); el.classList.remove( 'class' ); el.classList.contains( 'class' ); html/text $el.html(html); $el.text(text); el.innerHTML = html; el.textContent = text; アニメーション jQueryのアニメーションAPIは手軽に使えて高機能なので、完全な置き換えは難しいです。 ユースケースに合わせて、CSSアニメーションに置き換えていくのがよいでしょう。 これについても https://github.com/nefe/You-Dont-Need-jQuery が参考になります。 You Don't Need jQueryには載っていない、アニメーションを伴うスクロールは以下のように実装しました。 /** * 指定した要素までスクロールする * @param {string} selector スクロール対象のHTML要素のCSSセレクタ * @param {number} step スクロール幅(px) * @param {number} timeout スクロールを行う間隔(ms) */ export function scrollToElement( selector, { step = 100, timeout = 16 } = {} , ) { const target = document .querySelector(selector); if (!target) return ; // 目的地のY座標 const destY = target.offsetTop; // 目的地が現在位置より上にある場合は上(負のstep)、下にある場合は下(正のstep)にスクロール const stepWithDirection = destY < window .scrollY ? -step : step; const scrollByStep = () => { if (Math.abs( window .scrollY - destY) > step) { // step よりも距離が開いているときはscrollByで近づく window .scrollBy(0, stepWithDirection); setTimeout(scrollByStep, timeout); } else { // step 以下の距離まで近づいたらscrollToでピッタリ移動する window .scrollTo(0, destY); } } ; setTimeout(scrollByStep, timeout); } $.ready() $.readyはブラウザの対応状況にあわせて load と DOMContentLoaded を使い分けてくれます。が、すでにDOMContentLoaded未対応ブラウザ(IE 8以前)は滅びているので、DOMContentLoaded のみでOKでしょう。 $.ready( function () { // 処理 } ); $( function () { // 処理 } ); document .addEventListener( 'DOMContentLoaded' , () => { // 処理 } ) イベントフィルタリング jQueryだと、「doument配下のclickイベントを全てキャッチし、そのクリック対象、およびクリック対象の親要素が特定の属性をもつ場合にだけハンドラを実行する」という処理が、以下のように簡単に書けます。 $( document ).on( 'click' , '[data-xxx]' , eventHandler); これをDOMの標準APIで実装すると、少々面倒です。 function findParentByAttribute(target, attributeName) { let el = target; while (el.parentNode) { if (el.getAttribute(attributeName)) { return el; } el = el.parentNode; } return null ; } document .addEventListener( 'click' , event => { if (!findParentByAttribute( event .target, 'data-xxx' )) return ; // handle event } ); jQueryの使用を防ぐ目印 jQueryを取り除く作業をしたファイルには、先頭に以下の記述を追加して、jQueryを使ってはダメなことがわかるようにしました。 // このファイルではjQuery使用禁止! const $ = undefined ; このコードは、ローカル変数の $ を定義して、undefinedで初期化します。これによって、グローバルな $ はローカルの $ でシャドウされます(グローバルな $ は上書きされませんが、シンボルの探索ではローカルの $ が優先されます)。さらに、 $ の値はundefined なので、 $() などの呼び出しを行うとエラーが発生します。constなので再代入もできません。 これでも、 window.$ 、 window.jQuery 、jqueryのimportなど、jQueryにアクセスする手段は残されています。が、一休.com開発チームの規模やスキルを考えると、この方法で十分と判断しました。 なお、上記コードはES Modules(またはwebpack)環境での動作を前提にしています。ES Modulesはファイル毎のスコープを切ってくれますが、ES Modulesを使っていない場合も即時関数でスコープを切ることで同じことができます。 ( function () { // ファイルの先頭 // このファイルではjQuery使用禁止! const $ = undefined ; ... } )(); // ファイルの末尾 jQuery削除の効果 jQuery削除前 jQuery削除後 ↑は、jQuery削除前後のPageSpeed Insightsのスコアです。どちらも71点。Time To Interactive/First CPU Idleは改善していますが、SpeedIndexは悪化しています。この程度の変動は何も変更しなくても起きるので、スコアが変わるほどのインパクトはなかったということですね。 パフォーマンス改善の観点からは、 jQuery削除は、コスパが悪かった という結論になるかと思います。たぶん、同じ時間を別のタスクに使えば、もっと改善できたはず…。 なお、今回この結論に達したのは、既存コードのjQueryへの依存度が高かったからという理由もあります。サクッと取り除けるような状態なら、もっとコスパは良かったと思います。また、一休.comのホテルページスマホ版においては効果がなかったということであり、条件が異なれば、別の結果が得られると思います。 まとめ パフォーマンスの観点からは、ロードするJSの量を減らすことは重要です。一方で、JSライブラリ30KB程度の削除だと、誤差の範囲程度の改善効果しか得られない、ということもわかりました。塵も積もれば山となるので、無駄ではないと思いますが、もっとコスパの良い改善施策を実施していきたいところです。 告知 Bonfire Frontend #3が、1/24に開催されます。テーマは「パフォーマンス改善」です。今回、Yahooグループのよしみで(?)お声がけいただき、登壇する機会をいただきました。今回の記事のような、一休.comで進めているパフォーマンス改善のお話しをしようと思っているので、是非ご参加ください!(すでに満席ですが、1/18に抽選なので、まだ間に合います) yj-meetup.connpass.com http://infrequently.org/2017/10/can-you-afford-it-real-world-web-performance-budgets/ ↩ 一休.comでは回線速度のベースラインを1.4Mbpsで考えています。LighthouseのSlow 4G相当です。 ↩
データサイエンス部・大西 id:ohke です。 一休の1 to 1マーケティングを支えるプラットフォームについてお話したいと思います。 1 to 1マーケティング 一休の主力である宿泊予約サービスは今年で19年目、レストラン予約サービスも13年目を迎え、会員数も800万人を超えました。 一休のサービスを「知らなかった」から「知っている」という成熟フェーズに入ってきますと、集客に加えて、1 to 1マーケティングがより重要になってきてます。 一休の1 to 1マーケティングで大事にしていることは3点です。 施策に必要なデータは全てデータウェアハウス (DWH) へ集約する オートメーションツール (内製のWebアプリケーション) でターゲットの抽出、コンテンツの作成、配信を一元管理する ユーザごとにコンテンツを最適化する (レコメンド) DWHとETL 1 to 1マーケティングでは、ユーザの過去の行動に基づいて施策を実施していきます。 そのため、予約情報 (サービスのテーブルデータ) 、アクセス情報 (Google Analyticsと内製のリアルタイムログ収集ツール 1 ) 、およびそれらと紐付くマスタデータなどは、全てDWHに集約しています。 DWHには、SQL Server (RDS) を使っています。SQL Server Management StudioやRedashでSQLを書けば、誰でも集計・分析を行えるようになります。 DWHへの日々のETL (抽出・加工・格納) は極めて重要になります。 ETLが失敗・遅延すると、データにアクセスできなくなり、1 to 1マーケティングに支障を来してしまいます。マーケティングの文脈以外にも、DWHは、CEOをはじめ各事業部長や集客チーム・UI/UXチームの定点観測や意思決定に利用されるため、データが無い・間違っていることによる影響は甚大です。 一休ではETLを管理するツールとして、 Apache AIrflow を導入・運用してきました 。 user-first.ikyu.co.jp 日次だけで約400タスク (1タスクが1つのテーブルのエクスポートなどと対応します) 、さらに週毎・時毎などのタスクもあわせると、そこそこ大規模なETLとなります。 DWHとETLを健全な状態に維持し続けるために、以下の改善・運用も行ってます。 社内の各部署から依頼されるDWHへのデータ追加や計算方法変更への対応 各部署で自然発生的に持っていたETL処理もAIrflowへ統合 事業担当者 (マーケタや営業) が実行するクエリのチューニング 滞留クエリの自動検知・除去 オートメーションツール 1 to 1マーケティングでは、その名の通り、ユーザ一人ひとりに寄り添ってなければなりません。しかし一方で、アプローチするユーザ数も減らしたくないので、必然的に、少数のユーザに深くターゲットした施策をたくさん実行する必要があります。 1 to 1マーケティング施策の実行にあたっては、大まかに、ターゲットの抽出、コンテンツの作成、配信という3段階の作業を行います。 従来は他社のマーケティングメール配信サービスを使ってきましたが、数十以上の施策を実施するにあたって、大きな壁にぶつかりました。 ターゲットやコンテンツに埋め込む値の抽出は、マーケタの日々の手運用に頼られていたため、スケールしない SQLで抽出したCSVファイルをファイルサーバにアップロードする、ということを施策ごとに行っていました 配信は他社のサービス任せとなる (一休でコントロールできない) ため、事故無く運用するためには習熟が必要 また、メールだけではなく、サイトに来訪しているユーザにもアプローチするために、以下の新しいチャネルも要件として生まれていました。これらのチャネルをサービスに密に組み込んでしまうと、施策のたびにマーケタと事業部のエンジニア・デザイナが協同せざるえないため、クイックに施策を実行できなくなります。 サイトメッセージ: サイト上でのお知らせやクーポン配布に使われます ポップアップ: サイトメッセージよりも訴求力が強く、クーポン配布に使われます そこで、 ターゲットの抽出・コンテンツの作成・配信を一元管理するオートメーションツール (Webアプリケーション) を内製 することで、上の課題の解決を図りました。 ターゲットやコンテンツに埋め込む値の抽出を、DWHへ発行するSQLで記述できるようにする 2 テキストやHTMLを編集できるようにすることで、自由にデザインを変更・プレビューできるようにする 配信のチャネル (メール or サイト上のメッセージ or サイト上のポップアップ) 、日時、優先度などを確認・変更できるようにする 配信は全てオートメーションツールで行うため、基本的には事業部のエンジニア・デザイナとコミュニケーションする必要が無い オートメーションツールの導入と、その後のマーケタの細やかな施策設計によって、現在では毎日100近くの施策がこのツールから実行されています。 日々の効果測定は、Google Analyticsなどを使って収集し、Redashでビジュアライズして、Slackに通知するというカジュアルな方法を採ることが多いです。 オートメーションツールそのものは以下のアプリケーション構成となっています。 サーバサイドは、Python + Flask + SQL Alchemy フロントエンドは、React CI/CDは、CircleCI + CodeDeploy オートメーションツールからの配信は、チャネルごとに異なる機構で行っています。 メールは、サービスと同等の構成をマーケティング用に構えました 3 サイトメッセージとポップアップは、RDS (SQL Server) に配信リストを渡し、API (Go) 経由でサービスからアクセスすることで実現しました それぞれの配信結果もDWHへETLすることで、開封率の集計や効果測定を行ってます レコメンド いくらオートメーションツールで簡単にユーザへ配信できるようになったとしても、そのユーザにとって嬉しい情報でなければなりません。 ホテルやレストランの予約を提供している一休にとって、「そのユーザがどのホテル or どのレストランをおすすめされると喜ばれるか」というのは、サイトでの検索結果やリマーケティング施策では重要なテーマです。特に、ユーザの直近の行動を反映できないリマーケティング施策 (例えば、数ヶ月前に宿泊したユーザを対象としたメール) では、興味を引くコンテンツとなっていないとユーザの心が離れる (要するにウザいと思われる) 要因となってしまいます。 一休では、サイトでの検索結果やリマーケティングメールに表示されるホテルやレストランのリストは、各ユーザの過去の行動を反映したものとなってます。 レコメンド と呼んでいます。 具体的には、ホテルやレストランでの行動 (予約やアクセスなど) を元に、アイテムベースの協調フィルタリングを用いて、類似したホテルやレストランをおすすめするというロジックになっています。これらのロジックもDWH上のデータを使って計算しています。 サイトでの検索結果にも同様のロジックでレコメンドしてますが、リアルタイムな情報を使う必要があること、および、サービスに組み込むために堅牢性が求められることから、APIとして提供してます。 このAPIは以下のような構成となっています。こうしたサービスから利用されるアプリケーションの開発・運用もデータサイエンス部の役割です。 実装はPython + Flaskで、全てオンメモリに展開してリクエスト都度で計算してます インフラは、ECS + ElasticBeanstalk CI/CDは、CircleCI 課題は山積みです 今回お話した中でも、まだまだ取り組めていない課題はたくさんあります。 DWH/ETLやオートメーションツールの安定化 営業時間前にデータが出揃ってないと、集計・分析や施策の精度が落ちてしまうので、ETLの品質を担保し続けていかないといけません そこまで熟達していないマーケタがSQLやデザインを自由に記述できるので、大きな事故につながらないようにシステムで細やかに予防する必要があります 「検証配信」や「計算する」(件数チェック) はそのための仕組みとなってます 配信タイミングの最適化 このユーザには朝8:00、そのユーザには夜7:00など、ユーザごとにチャネルに触れる時間帯が異なる そのユーザがチャネルに触れる時間帯を狙ってアプローチしたい 協調フィルタリングによって売り上げのリフトに成功していますが、更なるUX向上のために別のアプローチを模索中 協調フィルタリングでは、ライトユーザの場合には情報が不足しているため、レコメンドによるリフトが小さくなってしまいます (いわゆるコールドスタート問題) ヘビーユーザでも、ユーザの嗜好が常に一定では無く、ユーザのコンテキスト (目的、一緒に行く人、気分など) を汲み取る必要があります 以前イタリア料理店に行ったからと言って、未来永劫イタリア料理店に行くとは限りません リアルタイムのユーザ行動を反映しながら、その時点でそのユーザにとって最もフィットするホテル or レストランをおすすめできるようになることを目指してます 上に加えて、データサイエンス部としては他にも様々な課題に取り組んでいます。 急増するレストランへの営業活動を最適化する 抽象的なワードでも良い感じにレストランを検索できるようにする クーポン施策の最適化 (損益分岐点の予測など) などなどなどなど... データサイエンス部では、こうした取り組みを4人で遂行しています (2018/12現在) 。 一緒に取り組んでいただける仲間を募ってます DWHやETLの改善に燃える! 一休の経営や施策の根幹を成すデータの安定供給のために、自ら設計・構築・運用できます。 データプラットフォームエンジニア(正社員) | 株式会社一休 内製アプリケーションをクイックに開発・改善したい! フロントエンド、バックエンド、インフラ、CI/CDを自分で考えて開発できます。 CEO直轄 アプリケーションエンジニア (正社員) | 株式会社一休 統計モデリングや機械学習でもっとかっこよく課題を解決したい! 高価格帯のサービスECサイトですので、他のサービスと一線を画したテーマの問題に取り組めます。 データサイエンティスト (正社員) | 株式会社一休 機械学習エンジニア (正社員) | 株式会社一休 自ら考えた施策でもっとユーザに愛されるサービスへ育てたい! CEO直下で施策の立案・遂行・改善に取り組めます。 www.wantedly.com 詳しい経緯はこちら → データ分析基盤、その後 - 一休.com Developers Blog ↩ 一休のマーケタはSQLを書けます。 ↩ 詳しくはこちら → 新メール配信基盤への移行 /ikyu-mail-platform - Speaker Deck ↩
こんにちは。今日はイベント登壇のお知らせです。 1/30(水) にマイナビさんが主催する「ITSearch+」のイベントに弊社エンジニアの 徳武( id:s-tokutake ) が登壇します。 一休com on クラウド ~ 急成長を支える技術基盤とSRE ~ 今回は「技術基盤、SRE」をテーマとして 一休.com / 一休レストランのクラウド移行 を軸に、以降前後でインフラ、技術基盤の仕事がどのように変わってきているか、サービスの成長を支えながら開発生産性をどう上げているのかなどを具体的な事例をもとにお話する予定です。 お申込みはこちらから。 news.mynavi.jp ご興味のある方はぜひご参加ください。 またイベント開催後に、発表スライドをもとにレポートを書きたいと思いますので、こちらもお楽しみに!
この記事は一休.com アドベントカレンダーの25日目の記事です。 レストラン事業部エンジニアの id:ninjinkun です。 一休.com及び一休.comレストランはユーザー向けのシステムだけではなく、店舗や一休内の管理者向けの業務システムという性格も持っています。 業務システム経験の無かった自分が一休に転職して最初に驚いたのが、DBに履歴を保持するための 履歴テーブル が大量にあることでした。 そこから履歴テーブルの存在に興味と疑問を持ち、社内外のエンジニアと履歴テーブルについて議論してきました。このエントリではそれらの議論をまとめた結果について書いていきます。 履歴テーブルのパターン まず以下の図をご覧ください。 込み入った図かつ事例が一休特化で恐縮ですが、左上の起点から始まって、右のオレンジの部分が最終的な実装パターンです。 図にあるとおり、たいていのユースケースでは以下の3パターンの実装に落とし込めるのではないかと考えています。 バージョンテーブル ログテーブル ElasticSearchやBigQueryなどに放り込む いきなり「バージョンテーブル」や「ログテーブル」などの単語が出てきましたが、これは自分が「履歴テーブル」を細分化するために使っている言葉です。以下でそれぞれについて説明します。 1. バージョンテーブル アプリケーションから特定のバージョンを参照する必要があるので、以下の様にバージョン番号を保持するテーブルを作ります。 元データテーブル 価格が変わる商品の例です。IDと最新のバージョン番号を保持しています。 id version price 1 2 1,000 2 1 2,000 3 3 10,000 バージョンテーブル 今までの価格変動がバージョン毎にすべて記録されています。 id item_id version price 1 1 1 1,100 2 2 1 2,000 3 3 1 11,000 4 3 2 10,500 5 1 2 1,000 6 3 3 10,000 2. ログテーブル 基本的にアプリケーションから参照しない前提であり、参照する場合も一覧で確認用に表示するくらいなので、バージョン番号などは不要です。 元データテーブル id price 1 1,000 2 2,000 3 10,000 ログテーブル 元データテーブルと同じカラム + ログ用の作成日を持つ。更新があったものをどんどん放り込んでいきます。最新かどうかは作成日で判断します。 id item_id price log_created_at 1 1 1,100 2018-11-10 12:00:00 2 2 2,000 2018-11-10 12:01:00 3 3 11,000 2018-11-10 12:02:00 4 3 10,500 2018-11-15 12:00:00 5 1 1,000 2018-11-18 11:00:00 6 3 10,000 2018-20-10 13:00:00 3. ElasticSearchやBigQueryなどに放り込む こちらのパターンについては一休では採用していないので詳細がないのですが、社外のエンジニアに履歴について相談した際に「うちではこうしているよ」という事例として聞いたものです。 リーズナブルな解決策だと思うので、どこかで採用したいと考えています。 テーブルの詳細 テーブル設計の詳細についても触れておきます。 別テーブル vs イミュータブル 自分が最初に履歴テーブルを見た時に感じたのは「履歴として独立したテーブルは必要なのか?」という疑問でした。履歴として独立したテーブルが無くても、同じテーブルに変更を全て残すように設計すれば、別テーブルはなくても履歴は作ることができます。自分はこれをイミュータブルパターンと呼んでいます。 これの議論については以下のエントリが参考になります。 自分が見ている範囲では、上記エントリの2のパターン、つまりイミュータブルでは無く、別テーブルに元テーブルと同じカラムを作ってを記録するのが最終的に一番穏当な落としどころになるケースが多いようです。 自分が考えるそれぞれのメリット、デメリットは以下の通りです。 メリット デメリット イミュータブル テーブルが1つで済んで綺麗に見える 書き込みが一回で済む select する際にmax()で最新の行を絞り込む必要がある(activeフラグなどで解決は可能だが、テーブルロックを掛ける必要が発生すると思われる) 別テーブル 見た目で履歴であることがわかりやすい 2つのテーブルに同時に書き込む必要がある イミュータブルパターンにもメリットはあるのですが、結局わかりやすさと実装のしやすさから別テーブルパターンが採用されることが多いようです。 データベーストリガー vs アプリケーション側でコピー 別テーブルとして設計する場合、データをコピーする実装にはデータベーストリガーとアプリケーションコード(要トランザクション)の2つの選択肢があります。メリットとデメリットは以下の通りです。 メリット デメリット トリガー DB側でコピーが自動的に走るために漏れが無い アプリケーションとトリガーに実装が分かれてしまうので後から動きが追いづらい トリガのデプロイを先にしないとエラーが発生するので、デプロイ時に気を遣う必要がある コード データの管理が全てコードに集中するので、動きが追いやすい テストが書きやすい トランザクションを張り忘れると大変 バージョンテーブルのパターンでは、バージョンテーブルはアプリケーションから参照されるので、アプリケーションに属する機能だと考えられます。この場合はアプリケーションコードで実装する方が自然だと思います。 一方でログテーブルのパターンでは、アプリケーションの機能として捉えるか、ただのログとして捉えるかは解釈が分かれるところです。実装方法もトリガーとコード、どちらも選択肢に入ると思います。自分はトリガはDB側の設定を見に行かないと挙動が把握できないので、極力アプリケーション側で実装したいと思っています。ただ、現場では後から証跡を残して欲しいと言われる事も多く、その場合に短時間で証跡テーブルを実装するにはトリガが選択されることも多いです。 おわりに 初めは違和感を感じていた履歴テーブルですが、これは時系列データのモデリングなのだと考えることで、今では素直に受け入れられるようになりました。 このエントリでお伝えしたいことのほとんどは最初の図に入っていますので、もし良ろしければもう一度ご覧ください。 ここで漏れているパターンも当然あると思いますので、ぜひ皆さんの考える最強の履歴テーブルについても教えていただけると嬉しいです。うちはイベントソーシングで全部解決しているよ!などの事例も知りたいです。
この記事は一休.com アドベントカレンダーの24日目の記事です。 qiita.com 社内情報システム部の大多和( id:rotom )です。 一休には2018年8月に入社し、情報システムエンジニアとして、IT を活用した業務改善、オフィス環境の構築を中心とした社内の「情シス」業務全般を担当しています。 本エントリでは、表立って登場することの少ない「情シス」が普段何をしているか、ご紹介していきます。 情シスのお仕事 社内情報システム部は「システム本部」に所属しており、現在 6人 のメンバで業務を行っています。 一休における情シスは以下の2つの側面を持っています。 コーポレートエンジニアリング :社内ツールやシステムの導入及び管理運用、bot やスクリプト開発による業務の効率化などの業務改善の他、オフィスの IT インフラ環境の構築、改善など、IT を活用し、より社員がよりパフォーマンスを発揮できる環境を構築する、いわゆるコーポレートエンジニアとしての業務を行う ヘルプデスク :入退社に伴う PC やiPhone、iPad などのモバイルデバイスの調達、キッティング、ライセンスの管理や、新入社員への PC のオリエンテーションの他、社員からのPC や社内システム、IT ツールに関する問い合わせや、トラブルシューティング、修理依頼などの短期的な課題解決を行う IT サポートを行う それぞれの側面から見た一休における情シスの強みと弱み、そして、これから進めていくことについて紹介します。 コーポレートエンジニアリング 強み 一休で利用しているほとんどの社内ツール/システムでは、シングルサインオンかつ2段階認証(2FA)を導入済であり、社員の利便性とセキュリティを両立させています。 オフィスでは高速なネットワークを整備しており、全国の支社においても同一の SSID/パスワードで Wi-Fi を利用することができ、東京本社と同等の環境で業務を行えるように構築しています。 弱み 一休.com や 一休.com レストラン を始めとする一休のサービスは全てクラウド(AWS)上で稼働しています。 一方で、レガシーな社内システムや一部のファイルサーバはオンプレミスで構築されており、クラウドに移行ができていません。 災害などで東京本社が機能しなくなった際も業務が止まらないよう、社内システムにおいても「脱オンプレ」を行う必要があります。 これからやること 前述の通り、現状行えていない社内システムのクラウド移行を進めています。 また、営業現場からの要望の多い LINE WORKS の導入や、 システムによる受付業務の刷新など社内業務フローの改善に取り組んでいきます。 line.worksmobile.com ヘルプデスク 強み PC のキッティングに伴う単純なセットアップ作業はバッチにより自動化されています。また、メーリングリストの作成や PC やモバイルデバイスの MAC アドレス登録などの細かい作業についても、Slack bot にクエリを投げるだけで行えるようになっています。 以前は口頭、電話、Slack と複数の窓口があり煩雑だった問い合わせ対応については、Google フォームによる投稿に一本化し、 必要事項を記入すれば専用の Slack チャンネルへ自動投稿されるように改善されており、情シスメンバが早急に状況を判断し対応できるようになっています。 弱み PC のセットアップについては多くが自動化されているものの、一部人の手を介す必要のあるものがあります。 現状はキッティング作業を外部リソースへのアウトソースを行っていますが、こちらも自動化を進めなければなりません。 これからやること 今後も事業急成長に伴う社員増大が見込まれます。PC 1台にかかるセットアップの工数を少しでも減らす為の自動化、効率化を継続的に進めていきます。 社内で最もユーザの多い Windows においては Windows Autopilot の導入も視野に入れています。 docs.microsoft.com 社内ツール/システムの紹介 一休では多くのツール/システムが活用されています。その中からいくつかご紹介します。 Slack slack.com 一休では全社で Slack を導入しており、エンジニアやデザイナーに限らず、営業や事務のメンバともチャット上でコミュニケーションを取っています。 各チームで業務上のやりとりを行うチャンネルの他、勤怠連絡、PC や書籍、オフィス備品の購入依頼や名刺の発注、総務や情シスへの問い合わせを行う専用チャンネルなども用意されています。 また、多くの Slack bot が稼働しており、情シスにおいてはメーリングリストの作成や、入退社に伴うアカウントの作成/停止などの業務は bot により自動化されています。 障害通知を始めとする多くの情報も Slack に連携される仕組みとなっており、社内インフラとして日々利用されています。 ぽ 「ぽ」 は一休で最も多く使われている Slack bot のひとつです。 社員情報検索、会議室予約や、オフィスの座席表や内線番号、Wi-Fi のパスワードなど、今知りたい情報をすぐに検索できる辞書ツールとなっています。 詳しくは以下のエントリで解説されています。 user-first.ikyu.co.jp G Suite gsuite.google.com 一休では全社で G Suite を導入している為、 Gmail や Google カレンダー、Google ドライブが業務で利用されています。 また、多くのスプレッドシートが Google Apps Script によりスクリプトが組まれており、 Slack への連携や業務自動化に活用されています。 情シスにおいては、スプレッドシートに入力された人事情報をトリガーとしたアカウントメンテナンスの自動化や、 更新期日の迫ったライセンスや保守契約についてリマインドを行うスクリプトなどを実装し業務の効率化に取り組んでいます。 Azure Active Directory azure.microsoft.com Slack や G Suite を含む、一休で利用している多くの社内ツール/システムは Azure Active Directory によるシングルサインオン(SSO) 構成となっています。 社内ツール/システムごとにアカウントの ID/パスワードの管理が異なると、社員がパスワードを忘れて問い合わせが発生する、社員の離職に伴うアカウントメンテナンスの工数が大きい、ヌケモレが発生するといった問題が想定されます。 一休では PC にサインインする ID/パスワードでほぼ全ての社内ツール/システムを利用することができます。 また、離職者に対しては Azure Active Directory のアカウントを停止することで、社内ツール/システムへのアクセスを防ぐことができます。 Microsoft Authenticator Microsoft Authenticator – オンライン アカウントの安全なアクセスと管理 一休では Azure Active Directory による SSO に加え、Microsoft Authenticator による二段階認証(2FA)を行っています。 SSO は複数の ID/パスワードを管理しなくても良い、というユーザの利便性が高い一方で、1つの ID/パスワード が盗まれたときのリスクが高いとも解釈できます。 そこで、一休では各自のスマートフォンに 2FA 用のアプリケーションをインストールして頂いており、アプリケーション側からの承認を得ない限り、 ID/パスワードを知っていても社内ツール/社内システムにはログインできない仕組みをとっています。 Vimeo vimeo.com 一休では毎月の月初に全社員が東京本社ラウンジに集い、月次の実績報告や社員同士の交流を行う共有会・パーティを開催しています。 各支社のメンバも月初には東京に集まっていましたが、業務都合が付けられずに参加ができないメンバもいました。 また、東京本社で定期的に開催される社内勉強会や、社員向けの説明会にを各支社のメンバに向けてもリアルタイムに共有したいという強いニーズがありました。 そこで、情シスでは動画配信プラットフォームである Vimeo を導入し、東京本社の様子を生中継することにしました。 同様のサービスとしては YouTube Live が挙げられますが、パスワード保護機能を搭載し、よりセキュアである Vimeo を選定しました。 配信機材としては Cerevo 社の LiveShell.PRO を購入することで、HD 画質の高品質なストリーミング配信を実現しました。 liveshell.cerevo.com Chromebox for meetings gsuite.google.com 一休では事業急成長に伴い、東京、大阪、名古屋に続き、沖縄、福岡、京都、横浜と次々とブランチオフィスが開設していきました。 事業所が増えることにより、東京本社と各支社間でリアルタイムに Web 会議を行いたいというニーズが高まりました。 Web 会議システムは多種多様にありますが、一休では Chromebox for meetings を導入しました。 この製品は Chrome OS を搭載した Chromebox という PC を Web 会議(Hangout) 専用にカスタマイズしたものです。 選定理由として一休では G Suite を導入していることから全社員が Hangout を利用できる状態にあること、 Chromebox for meetings は Hangout に機能を絞ったシンプルなユーザインタフェースであることから導入を決定しました。 IT 部門の担当者が社内ツール/システムを選定する際は、極力安い製品を選んでしまったり、自分たちの IT リテラシー基準で選んでしまったりと、IT 部門目線になってしまうことがあります。 ユーザファーストのカルチャーとする一休の情シスにおいてはユーザである社員にとって使いやすいか、という点を最重視して機器選定を行いました。 尚、現在は Chromebox for meetings は生産を終了しており、後継種の Chrome devices for meetings が販売しています。 最後に 情シスのエンジニアが行っている業務は、事業部でプロダクト開発を行っているエンジニアと比べると地味かもしれません。 しかし、ユーザ(= 社員)と最も近い距離で直接ニーズやフィードバックを聞くことのできる、非常にやり甲斐のある立場であると考えています。 一休においては、多くの業務が社内ツール/システムや bot により自動化、効率化されていますが、未だ人の手を介している業務フローも多く存在します。 そうした社内の業務課題の解決、改善を継続的に行い、社員が 「本来やるべき業務に時間を使える」 環境を作り上げるのが情シスのミッションです。 これからも一休 情シスは社内の業務改善、サポートを続けていきます! 明日は今年最後のアドベントカレンダー id:ninjinkun による 「 履歴テーブルについて 」 です!
この記事は 一休.comアドベントカレンダー2018 の23日目です。 一休.com の開発基盤をやっています akasakas です。 長いタイトルですいません。 本日のお話 本番リクエストを開発環境に投げて、エラーを検知し、修正するというサイクルで開発をすると品質が上がっていくというのを最近実感しました という話です。 図にするとこんな感じのイメージです やっていること 本番リクエストログから抽出 開発環境に対して、リクエストを投げる バグみつかる なおす を 細かく 繰り返す これを繰り返せば、 ちゃんと試験考えなくても、勝手に 品質上がっていくようになるのかなと思いました。 安心してリリースができるのはエンジニアの心理的に非常にいいことなのかなと思います。 具体的にどうやっているのか? 一休ではLogentriesでアクセスログを管理しています。 LogentriesのAPIを使い 本番リクエストを取得 開発環境にリクエストを投げる HTTPステータスコードを確認 というところまでやりたいと思います。 参考実装の前に諸注意 LogentriesのAPIは呼び出し制限があるので、用法用量を守って、正しくお使いください。 詳しくはこちらになります。 docs.logentries.com 参考実装 参考実装がこちらになります。 import logging import datetime import requests import time import re import json settings = { 'dev_host' : 'dev.hostname.com' , 'sampling_seconds' : 180 , 'logentries' : { 'endpoint' : 'https://rest.logentries.com/' , 'apikey' : 'logentries api key' , 'logs_query' : 'query/logs' , 'query_api' : 'query' , 'query_statement' : 'where(' \ + ' path = /query_statement(regular_expression)/ ' \ + ')' , 'logs' : [ 'logenrties log set key id' ] } } logging.basicConfig( format = '[%(asctime)s] %(message)s' ) logger = logging.getLogger( 'real-request-to-development' ) logger.setLevel(logging.DEBUG) def calc_query_time (): now = datetime.datetime.now() from_time = datetime.datetime.now() - datetime.timedelta(seconds=(settings[ 'sampling_seconds' ])) return int (time.mktime(from_time.timetuple())) * 1000 , int (time.mktime(now.timetuple())) * 1000 def le_query (from_unixtime, to_unixtime): headers = { 'x-api-key' : settings[ 'logentries' ][ 'apikey' ]} body = { "logs" : settings[ 'logentries' ][ 'logs' ], "leql" : { "during" : { "from" : from_unixtime, "to" : to_unixtime}, "statement" : settings[ 'logentries' ][ 'query_statement' ]} } res = requests.post(f '{settings["logentries"]["endpoint"]}{settings["logentries"]["logs_query"]}' , headers=headers, json=body) return res.json()[ "id" ] def le_longtime_query ( id ): headers = { 'x-api-key' : settings[ 'logentries' ][ 'apikey' ]} res = requests.get(f '{settings["logentries"]["endpoint"]}{settings["logentries"]["query_api"]}/{id}' , headers=headers) return res.json() def get_request_url (json_data): dev_url_list = [] for log_row in json_data[ "events" ]: log_data = re.search( r'{.*}' , log_row[ "message" ]) log_json = json.loads(log_data.group( 0 )) path = log_json[ "path" ] query = log_json[ "query" ] useragent = log_json[ "useragent" ] dev_url = f 'https://{settings["dev_host"]}{path}{query}' dev_url_dict = { "dev_url" : dev_url, "ua" : useragent} dev_url_list.append(dev_url_dict) return dev_url_list def send_request (dev_url_list): for dev_url_dict in dev_url_list: headers = { 'User-Agent' : dev_url_dict[ "ua" ] } dev_response = requests.get(dev_url_dict[ "dev_url" ], headers=headers) if dev_response.status_code >= 500 : logger.error(f 'development url is {dev_url_dict["dev_url"]}' ) logger.error(f 'development response status is {str(dev_response.status_code)}' ) ######################################### def start (): from_unixtime, to_unixtime = calc_query_time() le_query_id = le_query(from_unixtime, to_unixtime) json_data = le_longtime_query(le_query_id) dev_url_list = get_request_url(json_data) send_request(dev_url_list) if __name__ == "__main__" : start() 1つずつ解説していきたいと思います 流れとしては Logentries API で検索用のクエリIDを取得&実際のリクエストを取得 実際のリクエストのホストを開発環境用のホストに書き換える リクエストを投げる&500エラーをログ出力 2.3は難しいことはしていないので、説明は省略します。 Logentries API で検索用のクエリIDを取得&実際のリクエストを取得 Logentries API で検索用のクエリIDを取得 下記で、検索用のクエリIDを取得しています。 このクエリIDをベースにして、実際のリクエストを取得します。 必要なパラメータは logs(どこのログか?ここではアクセスログ) statement(どんな条件か?正規表現でpathを記述するのが一般的?) from,to(unixtimeなので、事前にfrom,toをunixtimeにする必要がある) です。 詳細はこちらをご覧ください。 docs.logentries.com def le_query (from_unixtime, to_unixtime): headers = { 'x-api-key' : settings[ 'logentries' ][ 'apikey' ]} body = { "logs" : settings[ 'logentries' ][ 'logs' ], "leql" : { "during" : { "from" : from_unixtime, "to" : to_unixtime}, "statement" : settings[ 'logentries' ][ 'query_statement' ]} } res = requests.post(f '{settings["logentries"]["endpoint"]}{settings["logentries"]["logs_query"]}' , headers=headers, json=body) return res.json()[ "id" ] 実際のリクエストを取得 先ほど取得したクエリIDをベースから、実際のリクエストを取得しています。 詳細はこちらをご覧ください。 docs.logentries.com def le_longtime_query ( id ): headers = { 'x-api-key' : settings[ 'logentries' ][ 'apikey' ]} res = requests.get(f '{settings["logentries"]["endpoint"]}{settings["logentries"]["query_api"]}/{id}' , headers=headers) return res.json() 実際のリクエストを開発環境に投げることでエラーを検知というところまでできました。ここから、試験と修正を繰り返せば、品質は上がり、安心してリリースできることになると思います。 もう一歩進めて 開発環境が本番相当であると上記の試験はさらに効果が発揮されるかなと思います。 一休では本番DBから個人情報はマスクしたものを開発環境用のDBとして使っています。 本番リクエストを本番相当DBにリクエストを投げることで、より精度の高い試験ができるのかなと思います。 さらにもう一歩進めて これを本番リリース前のStaging環境でこの仕組みを定期的に実行し、エラーになったら、Slackで通知し、リリース事故を抑止できるようになれば、さらにいいなと思います。 しかし、まだ一休ではここまでできておらず、近いうちにやりたいなと思っているところです。 イメージとしては NginxのMirror moduleやGoReplayといったCanaryReleaseに近いかなと思います。 まとめ 以上、本番リクエストを開発環境に投げて、エラーを検知し、修正するというサイクルで開発をすると品質が上がっていくというのを最近実感しました という話でした。 明日は id:rotom さんによる『一休における「情シス」の取り組み』です。 普段あまり語られることはない一休の情シス事情について詳しい話が聞けると思いますので、お楽しみに。 参考 CanaryRelease GoReplay Nginx Mirror module Running beta in production
この記事は一休.comアドベントカレンダー2018の22日目です。 qiita.com データベースに対するDDLの適用、みなさんはどのように運用していますか。 一休では長らく担当者が手動適用をしていました。が、開発者全員の依頼をまとめて、定期的にDDL適用を行うのはかなりの作業負荷です。 そこで、アプリケーションのソースコードと同じようにGitHubとCI/CDのパイプラインを構築して、適用したい開発者が自分で適用できる仕組みを構築しました。 この記事では、その概略を紹介したいと思います。 ※当社はMicrosoft SQL Serverを使っているので、その前提の記事になります。 デプロイフロー CIには Appveyor を、CDにはAWS CodeDeployを利用 CodeDeployは後述の通り、外部アクセス用のプロキシサーバも利用 GitHubにDDLを管理するリポジトリを作成 開発者はこのリポジトリで新規に適用したいDDLのPull Requestを作成 PRをmasterブランチに適用するとAppveyor経由でCodeDeployが起動される。 CodeDeploy Agentが動いているddl-controllerマシンでDDLを実行し、SQL Server に適用 適用履歴もSQL Serverのテーブルで管理して同じDDLが2重適用されないように制御 適用結果(実行ログ)はAmazon S3にアップロード Lambdaでログの中身をチェックして、適用結果をSlackに通知 工夫点、注意点 tsqllint でDDLの構文チェック データベースの定義変更なので、注意深く実施する必要があります。また、実際に定義を適用してみたらエラーになった、となると手戻りが発生して面倒です。 そこで、linterを導入しようと考えました。調べてみるとSQL Serverの T-SQL (Transact-SQL) の構文がチェックできる tsqllint というツールがありました。 動作確認してみたところ、きちんと動作するし、 lintのルールのカスタマイズもできる のでこれを採用しました。 そして、これを利用した簡単なNode.jsのスクリプトを書き、Appveyor上で動かして、構文チェックを実行するようにしました。チェックが通らなければ、Pull Requestをマージできないようにします。 ※ちなみに、tsqllintは VS Codeの拡張もあるようです 。SQL Serverの管理などでT-SQLに触れる機会が多い方には便利かもしれません。 linterでチェックできない点はレビューでチェック ファイルグループの指定が正しいか、などlinterのチェックだけでは検出できない重要な点があります。 このような点については Pull Requestのテンプレート にチェック項目を書き出し、Pull Requestの作成者とレビューワーの双方がチェックする、という運用ルールにしました。もちろん、レビューワーのApproveがなければ、Pull Requestはマージできません。 機械的なチェックではないので、多少の不安はありますが、今のところうまくいっています。 インターネットに出れないEC2インスタンスでCodeDeploy Agentを動かすにはプロキシが必要 これは、注意点なのですが、データベースが置かれているネットワークは、インターネットには接続できないようになっているのが一般的です。 一方でCodeDeploy Agentが正常に動作するためには、外向きHTTPSの通信ができることが必須です。 CodeDeploy Agentが外部のAPIを定期的にコールしてデプロイを実行する必要があるかどうか確認しているからです。 この場合、CodeDeploy Agentの構成ファイルに外向き通信をプロキシするためのプロキシサーバのURLを設定する必要があります。 当社では、外向きの通信ができるネットワークにApacheでプロキシサーバを立て、そのURLをCodeDeploy Agentの構成ファイルに設定しました。 Apacheの設定は以下の通りです。 Listen *:8088 <VirtualHost *:8088 > ProxyRequests on AddDefaultCharset off AllowCONNECT 443 <Proxy codedeploy-commands.ap-northeast-1.amazonaws.com > <LimitExcept CONNECT > Order deny , allow Deny from all # ↓ CodeDeploy Agent が動いているマシンのIP Allow from xx.xx.xx.xx </LimitExcept> </Proxy> </VirtualHost> CodeDeploy側は、conf.ymlを次のように修正します。 Windowsの場合、 C:\ProgramData\Amazon\CodeDeploy\conf.yml にあります。 --- :log_dir : 'Amazon/CodeDeploy/log' :root_dir : 'Amazon/CodeDeploy' :verbose : true :wait_between_runs : 1 :wait_after_error : 1 :bundle_name : 'artifact_bundle.tar' :proxy_uri : http://xx.xx.xx.xx:8088 # ここにプロキシサーバのIPを記述 これで、CodeDeploy Agentを再起動すれば正常に動くようになります。 ※ 環境によってはセキュリティグループなどネットワークレイヤの調整も必要です。 終わりに 長年、手動でやっていたものを自動化して、果たしてうまく運用が回るか、心配はありましたが、しっかりと運用に乗りました。 データベースをクラウドに移行したという前提条件とアプリケーションのCI/CDの構築経験の応用が実現のキーポイントだったと思います。 ※データベースのクラウド移行については、当ブログに詳細な記事がありますので、ご覧ください。 user-first.ikyu.co.jp user-first.ikyu.co.jp この記事の筆者について システム本部CTO室所属の 徳武 です。 サービスの技術基盤の開発運用、宿泊サービスの開発支援を行なっています。
この記事は一休.com アドベントカレンダーの20日目の記事です。 qiita.com はじめまして、宿泊サービスのUIデザインを担当しています河村です。 一休のデザイナーは部署ごとに在籍チームが異なります。私は長い間、営業企画部デザイナーとして働いていましたが、今年4月よりプロダクト開発部UIデザイナーとして働いています。(本ブログでは、前者をWebデザイナーとします) WebデザイナーとUIデザイナーをやってみて、多くのことが「違った」ので、どんな違いなのかをお話させていただきます。 目次 1) 仕事内容の違い 2) 必要なスキルの違い 3) 仕事の進め方の違い 4) まとめ 仕事内容の違い ビジュアル表現に特化 一休のWebデザイナーの主な業務内容は、トップページなどの更新作業、施設紹介ページ、特集・企画販促ページ、メールマガジン等の作成です。ページ全体のビジュアルを管理しているので、一つ一つの画像の選定などサイト全体の雰囲気作りに欠かせない役割です。 私は、施設の魅力をユーザーに届けることをゴールにして、施設の世界感を感じさせるページ作りを意識して行っていました。 操作性の向上に特化 一方、UIデザイナーの主な業務内容は、ユーザーが「使いやすい」と思うデザインを実現することに特化しています。宿泊プランがすっきり整理された構図、見やすいカラーリング、最適な文字の大きさ、使いやすい検索機能などを意識して、ユーザーにとって使い勝手のいいサイトを目指しています。 基本的には予約のコンバージョン率向上を目的として改善を繰り返しますが、予約には関係ない部分でも使いやすさや見やすさを良くする為の改善もあります。具体例としましては、宿泊料金表示を即時ポイント利用での割引後の料金表示に切り替えるられる機能を追加したり、フォトギャラリーのサムネイル表示の追加などを行いました。 必要なスキルの違い 意匠力 Webデザイナーの必要なスキルは「意匠力」です。施設の魅力をユーザーに届けるページ作り、時としてインパクトを残せるページ作りなどを行いますが、オシャレだったり、かっこいいといった見た目の印象、装飾的考案が大切です。 設計力 一方、UIデザイナーの必要なスキルは「設計力」です。ユーザーの目線に立ってユーザーが抱える問題の本質を考え、それらを解決するための設計をし、表現や形を作っていくことが重要です。 Webデザイナーは、多くの場合、施設側や営業担当から指示書があるので、何を見せたいかの意図をくみ取りデザインで表現する流れですが、UIデザイナーは、今何が問題で、こうやったら使いやすくなるのではと仮説を立てるなど、デザインをするまでの過程がとても重要になります。 仕事の進め方の違い ビジュアル的なクオリティの追求を自分だけで行える Webデザイナーは営業担当や施設との調整はありますが、作業の進行自体は一人で行います。納期内であればビジュアル的なクオリティを突き詰めることに時間を割くことができます。 共同作業ならではのルールがある 一方、UIデザイナーはマーケティングやエンジニアとの共同作業なので、作業の進行は自分だけの判断では進められません。また、エンジニアと認識合わせなどを文章に残すのも重要で、やりたいこと、なぜやるかなどを案件ごとに言語化します。 個人プレーからチームプレーとなったことで、自分のタイミングでリリースできないことにストレスを感じたり、言語化する作業が煩わしく感じたりすることもありました。 しかし、リリース前に必ずレビューが入ることでミスを防げたり、なぜやるかというのを文章で残すことで、自分自身も思考の整理ができたり、過去のリリースを振り返る際にも役立ってます。 まとめ 一休においての各デザイナーの違いについて、簡単にまとめてみました。あくまでも私が個人的に感じたことなので、一般的な「WebデザイナーとUIデザイナーの違い」とは異なるかもしれません。 目的別チームで各々作業している一休のデザイナーですが、「一休らしい綺麗なサイト」や「高級感を感じるデザイン」を作りたい、というのは共通して目指していることです。そのために私たちデザイナーは、デザインの力(意匠力、設計力)を駆使して、「美しく機能的なサイトで宿泊先を選んでいる」という、ユーザーの心地よい体験を叶えるべく、改善を繰り返します。 一休では、ともに良いサービスをつくっていく仲間(デザイナー/エンジニア/マーケティング)を 積極募集中 です。応募前にカジュアルに面談をすることも可能ですので、お気軽にご連絡ください。 明日は @hayatoise さんによる「読書合宿のお話」です。お楽しみに!
この記事は一休.comアドベントカレンダー2018の19日目です。 qiita.com ある程度の規模のウェブアプリケーションであれば、応答性能を損なうことなく複雑な業務処理を完遂させたい場面が出てきます。 このような場合、処理をある程度の粒度で切り出して、応答を返すプロセスとは別のプロセスで処理する、という方法が考えられます。 例えば、予約完了処理の中でメール送信部分だけを別のプロセスで処理する、といった具合です。 ASP.NETでこのようなバックグランドジョブ処理を実現するには、どのような方法があり得るでしょうか。 この記事では、.NET環境で利用できるバックグラウンドジョブのライブラリであるHangfireについて、当社での利用事例を簡単に紹介します。 Hangfireを導入した経緯 当社のサービスにも当然、上述したようなバックグラウンドジョブのニーズがありました。 ASP.NETでバックグラウンドジョブの処理を実現する場合、公式に提供されている QueueBackgroundWorkItem を使うという手段があります。 これなら導入は非常に簡便です。しかし、大事な処理をバックグラウンドジョブで実行することを考えると、ジョブのステータス(キューイング、処理中、処理正常完了、処理失敗)やジョブの実行履歴の管理も行いたい、と考えたました。 この場合、QueueBackgroundWorkItemを採用するなら、その部分は自前で作らなければなりません。 また、クラウドが提供するキュー処理のサービスを使う方法もあり得ますが、この場合も、ジョブのステータスやジョブの実行履歴の管理は自前で作らなければなりません。 Hangfireは、ジョブストレージとジョブの管理機構が組み込まれており、かつ、既存の.NETのコードを大きく変えることなく利用できるという利点があります。 作者によれば Hangfire is a .NET Framework alternative to Resque, Sidekiq, delayed_job, Celery. だそうです。 以上の点を総合的に加味して、Hangfireを活用することに決めました。 構成 Hangfireを使う場合、アプリケーションの構成は大きく以下のふたつがあり得ます。 バックグラウンドジョブ処理を行いたいウェブアプリケーションの内部にバックグラウンドジョブサーバを動作させる。 バックグラウンドジョブ処理を行いたいウェブアプリケーションとバックグラウンドジョブサーバを完全に分離する。 当社では、完全に分離する構成にしました。内部にバックグラウンドジョブサーバを動作させる場合、ウェブアプリのプロセスの再起動とジョブの処理の状態を気にする必要があるため、完全に分離したほうが運用が簡単だと判断しました。 上記の図の通り、ジョブキューのストレージにはElasticache Redisを使っています。Hangfireは標準ストレージとしてSQL Serverを使いますが、 Redisのほうがより高性能であるというベンチマーク結果 を考慮し、Redisにしました。 また、ジョブのステータスや各種管理ができるDashboardは、Hangfireを使うウェブアプリともバックグラウンドジョブサーバとも別のサーバに構築しました。 実装例 実装自体はとても簡単です。 namespace Sample.Service.JobQueue { /// < summary > /// バックグラウンドジョブ導入のためのサンプルクラス /// </ summary > public class JobQueueSampleService { public void DoSomething( string param1) { JobQueueClient.Enqueue(() => DoAsBackgroundJob(param1)); } /// < summary > /// このメソッドをキューに詰める。 /// </ summary > /// < param name = "param1" > /// Hangfireは、パラメータをJsonでシリアライズしてストレージに詰める。 /// </ param > [Hangfire.AutomaticRetry(Attempts = 3 )] // <== リトライ回数をキューに詰めるメソッドの属性で回数を指定する。指定しないと10回リトライ。リトライしないなら0回にしておく。 public void DoAsBackgroundJob( string param1) { // --- do something } } public class JobQueueClient { private static readonly Lazy<IBackgroundJobClient> _cachedClient = new Lazy<IBackgroundJobClient>(() => new BackgroundJobClient()); public static string Enqueue(Expression<Action> methodCall) { var client = _cachedClient.Value; return client.Create(methodCall, new EnqueuedState(QueueName)); } } } Hangfireは、メソッドをジョブキューにエンキューする、という仕組みになっています。↑のコードでは、 JobQueueClient.Enqueue(() => DoAsBackgroundJob(param1)); で、 DoAsBackgroundJobメソッドでジョブとしてジョブストレージに保存しています。 エンキューすることで、メソッドのシグネチャやパラメータ、そのメソッドが属するアセンブリ名などがジョブストレージに保存されます。バックグラウンドジョブサーバは、この情報をデキューして、リフレクションを使って、保存されたメソッドを呼び出します。 リフレクションを使うので、Hangfireのクライアントとなるウェブアプリとバックグラウンドジョブサーバは同じアセンブリを参照する必要があります。 ※Hangfireには、 BackgroundJob.Enqueue というエンキュー処理のためのわかりやすいインターフェースがあるのですが、これは後述する理由により、使いませんでした。 工夫点 前述した通り、Hangfireのクライアントとなるウェブアプリとバックグラウンドジョブサーバは同じアセンブリを参照する必要があります。 実運用でこれを実現しようとすると、次の2点を考える必要があります。 毎日複数回デプロイされるウェブアプリケーションとバックグラウンドジョブサーバのアセンブリを一致させる必要がある。 ウェブアプリケーションのデプロイサイクルよりもLong Runningなジョブでもきちんと処理を完遂させる必要がある。 それも、 エンキューしたウェブアプリケーションと同じアセンブリを参照しているバックグラウンドジョブサーバで処理を完遂させる必要がある。 このふたつを実現する実現するために、以下のふたつの手段を採用しました。 Hangfireの名前付きキューを利用する。 バックグラウンドジョブサーバはWindows Serviceとして稼働させ、同じアセンブリを参照しているウェブアプリケーションが古いバージョンになっても動かし続ける。 Hangfireの名前付きキューを利用する。 Hangfireは、キューに名前を付けることができます。ある名前のキューに詰められたジョブはその名前のキューを処理するように指定されたバックグラウンドジョブサーバでしか処理されません。キュー名は通常、次のようにキューに詰めるメソッドの属性で指定します。 [Queue( "testqueue" )] public void SomeMethod() { } そして、バックグラウンドジョブサーバ側ではサーバを初期化するときに、キューの名前を指定します。以下のように。 var options = new BackgroundJobServerOptions { Queues = new [] { "testqueue" } }; app.UseHangfireServer(options); // or using ( new BackgroundJobServer(options)) { /* ... */ } これで、 testqueue というキューに詰めたメソッドは、↑のサーバでしか処理されなくなります。 さて、 あるバージョンのアセンブリを参照しているウェブサーバがキューに詰めたメソッドは同じバージョンのアセンブリを参照しているバックグラウンドジョブサーバで処理させたい という要件は、以下を実現すれば満たせそうです。 同じバージョンのアセンブリを参照しているウェブサーバとバックグラウンドジョブサーバは同じキュー名を利用する。そのキュー名はビルド単位で生成され完全にユニークである。 ビルド単位で生成され完全にユニークな値として、ビルドバージョンの番号がありました。そこでこの番号をキュー名にすることでこの要件を満たすことができました。 ただし、Hangfireのサンプルなどで一番よく見かける BackgroundJob.Enqueue では、キュー名をメソッドの属性としてしか指定できません。つまり、動的に設定できないのです。調べてみると、 BackgroundJobClient クラスのCreateメソッドを直接使うことでキュー名を動的に指定できることがわかりましたので、以下のような BackgroundJobClient をラップしたクラスを作りEnqueueメソッドを実装しました。 public class JobQueueClient { private static readonly Lazy<IBackgroundJobClient> _cachedClient = new Lazy<IBackgroundJobClient>(() => new BackgroundJobClient()); public static string Enqueue(Expression<Action> methodCall) { var client = _cachedClient.Value; return client.Create(methodCall, new EnqueuedState(QueueName)); } } } バックグラウンドジョブサーバはWindows Serviceとして稼働させ動かし続ける。 新しいウェブアプリがデプロイされても古いバックグラウンドジョブサーバが動作していれば、デプロイサイクルよりもLong Runningなジョブもきちんと処理できます。 これは、シンプルに バックグラウンドジョブサーバはWindows Serviceとして実装し、新しいバージョンのバックグラウンドジョブサーバをデプロイをしても、古いバージョンのバックグラウンドジョブサーバは停止しないようにしました。 バックグラウンドジョブサーバをWindows Service として動かす方法は、 公式サイトにも開設されています 。しかし、このやり方には従わずに、 Topshelf を使ってServiceにしました。Windows Serviceのインストール、アンインストール、開始、停止が簡単に制御できるためです。 バックグラウンドジョブサーバのデプロイはAWS Codedeployを使います。 Codedeployのafter installのhookで、バックグラウンドジョブサーバをWindows Serviceとしてインストールし、開始をしています。 このとき、配置するフォルダをデプロイバージョンごとに変えることで、既存のServiceを動かし続けながら、新しいバージョンのServiceを稼働させることができます。 appspec.ymlは↓のようにします。 version : 0.0 os : windows files : - source : / destination : d:/latest hooks : AfterInstall : - location : /after-install.bat after-install.batは、以下の通り。 rem $T ARGET_FOLDER を ビルド時にビルドバージョンに書き換える。 xcopy D:\latest D:\ $T ARGET_FOLDER\ /E /Y /I /Q D:\ $T ARGET_FOLDER\bin\BackgroundJob.ProcessingServer.exe install D:\ $T ARGET_FOLDER\bin\BackgroundJob.ProcessingServer.exe start ただし、このままだと永遠にWindows Serviceが増え続けてしまいます。対策として、日次で1日以上古くなったバージョンのバックグラウンドジョブサーバを停止するようにしました。 まとめ 当社ではHangfireをプロダクション環境の業務処理で活用し始めてまだ日が浅いです。しかし、現時点では、問題なく動作しています。 今後は、短い間隔で動かしているバッチ処理をウェブアプリのバックグラウンドジョブに切り替えていく、という使い方も想定しています。 この記事の筆者について システム本部CTO室所属の 徳武 です。 サービスの技術基盤の開発運用、宿泊サービスの開発支援を行なっています。
この記事は一休.com アドベントカレンダーの18日目の記事です。 qiita.com こんにちは。 社内情報システム部の下村です。 一休ではOfficeITに関する全ての業務、改善を担当しています。いわゆる情シスです。 本日は、一休の情シスが行ってきた活動のうち、開発者ブログらしく社内向けのSlackツールを開発(?)したことについて記載したいと思います。 どんなツールを作ったのか? 一休ではコミュニケーションツールとして、非エンジニアであってもSlackが活用されています。 そのSlackを使って、 「ちょっとしたことを検索できるbotがあれば便利なんじゃないか?」 と思い 社内向けに提供しました。 具体的には下記のようなことがSlack上で検索できるようにしています。 内線番号や、メールアドレスなどの社員情報を検索。 「座席表」や「無線LANのキー(パスワード)」などのURLを検索。 会議室の空き状況を確認。(予定が空いていればbot上から予約も可能にしています。) デモ 「百聞は一見にしかず」ということで実際の動作デモです。 雰囲気が伝われば幸いです。 ちなみに「ぽ」とはbotの起動トリガーです。 ぽ<なんちゃら>と書くとbotが反応するようにしています。 余談:なんで「ぽ」なの? 起動トリガーは下記の条件を満たす必要がありました。 下記条件を全て満たすのが「ぽ」でした。 覚えやすい。 「ぽ」ってなんかインパクトありますよね。 打ちやすい。 ご自身のキーボード配列見てみてください。「p」と「o」が近くて打ちやすくないですか。 かぶらない。 かぶりやすいキーワードを起動トリガーにするとbotが誤検知しちゃうので。 「ぽ」で始まるキーワードってなかなかないですよね。 構成 このツールの構成は下記のようになっています。 ツールが検索/回答する順序として、 投稿されたキーワードが「会議室状況確認」を含む場合、会議室予約情報を回答。 上記キーワードではない場合「Google SpreadSheet」に記載があるかを確認。あればSpreadSheetの内容を回答。 SpreadSheetにもキーワードが無い場合、ADに登録されている社員情報かどうか確認し回答。 という順序で検索/回答をかけるようにしています。 (botの起動トリガーを無理やり一つで完結するようにしたので若干無理があります。。) ■詳細 社員が特定のチャンネルに投稿する。 SlackのOutgoing Webhookで投稿された内容を検知し、GAS(Google Apps Script)に投稿内容をPostする。 ※1 投稿内容が「会議室状況確認」なのか確認。 会議室予約の場合:会議室空き情報を返して終了。 ※2 そうではない場合:次のステップへ。 Google SpreadSheetの内容をGASにて確認。 記載がある場合:検索結果を返して終了。 ※3 記載がない場合:次のステップへ。 検索内容をhubot用のSlackチャンネルに投稿。ADやSlackのユーザ一覧に検索情報が含まれているか確認し、結果を返す。 ADからは電話番号や内線番号、部署名などの情報を抽出。 ※4 SlackAPIの「users.list」を使って、EmailAddressをキーにSlackUserIDを抽出。 ※5 ※1 参考URL ※2 参考URL 地味にハマったのが対象のリソース(会議室)を閲覧状態にしないと予定が取得できないので、 おまじないとして、 CalendarApp.subscribeToCalendar(\<resourceIDを入力>); と事前に定義しておくと良いです。 ※3 こちらのURL を参考に行番号を取得して、 こちら を参考に対象のセルを取得してます。 ※4 WindowsServerでhubotを起動しています。 「Get-AdUser -Filter 'enabled -eq $true' -Properties *」 して、AD登録情報を内線番号など必要なものを引っ張ってます。 ※5 参考URL 課題 メンテナンス性を考えずに適当に作ってしまったので構成がとても複雑になってしまいました。。 ADについてはいずれ、AWS Directory Serviceに移行予定なので、AD情報などはhubotを使わずに Lambdaで参照するようにしたら多少はシンプルな構成になるのではないかと考えています。 botをリリースしてから起きたこと 内線番号とか、座席表って参照する機会はあるものの「いざ」という時に 「どこに書いてあったっけ状態」に陥りますよね。 お陰様で「ぽ」は社内でよく利用されていますので、 結果として、「どこに書いてあったっけ状態」を解消でき、 社員が「何かを探す」時間を少しは減らせたのかなと思います。 余談ですが、人が「探す時間」に時間を費やすのは 年間150時間 もあるそうですよ。 その時間を少しでも減らせる手助けができたかなと思います。 最後に これからも、一休情シスは今回紹介したツールのようなものなどを提供して、 社員が 「本来やるべき業務に集中して時間を割けるよう」 サポートを続けていきます。 そして、この記事が同じ志を持つ誰かの参考になれば嬉しいです。 id:undersooon でした。 明日は@s-tokutake の 「Hangfire [導入編]」 です。お楽しみに!
この記事は一休.com アドベントカレンダーの17日目の記事です。 qiita.com 宿泊事業部のいがにんこと山口です。 UIUXチームでフロントエンド、バックエンドのアプリケーション開発を担当しています。 一休では宿泊事業とレストラン事業があります。 私が所属する宿泊事業では開発言語にC#とVB.NETを使用しています。 その背景から開発にはWindowsを使っています。 普段MacやLinuxでWeb開発しているWebエンジニアにとってはWindows、C#を使ったWeb開発はあまり馴染みがないかもしれません。 そこでそんな方向けにWindows、C#を使ったWeb開発についてお話したいと思います。 C#について まずはC#について。 C#に触れたことのない方はどんなイメージを持っていますかね? Microsoft製?Javaみたい?使っている企業は? 色々なイメージがあるかと思います。 ここではC#を企業、開発環境、記述といった点から説明します。 使っている企業 C#を使っている企業ってどんな業界なんでしょう? 主にゲーム系、金融系で使われているイメージです。 ゲーム系はコンシューマー、ソシャゲに限らないですが、Unityを使っている企業はその流れでサーバーサイドもC#という企業が多いようです。 金融系はMicrosoftのサポートを期待しての導入でしょうか。 サポート期間の長さも特徴の一つで、それも採用の理由の一つかもしれません。 https://support.microsoft.com/ja-jp/lifecycle/search?alpha=Microsoft%20.NET%20Framework%203.5 を見ると2008年に出た.Net Framework 3.5(C#の実行環境)が2028年までサポートされることになっているので、20年ものサポート期間があることになります。 自社のWebサービス、アプリ(ゲームを除く)をやっている会社ではまだまだ採用している企業が多いとは言えません。 余談ですが C#erに特化した会社 なんてのもできましたがゲーム向けですね。 開発環境 弊社では Visual Studio というIDEを使用しています。 Visual Studioと聞いて思い浮かべるのは Visual Studio Code でしょうか。 Visual StudioとVisual Studio Codeは全くの別物です。 Visual Studio Codeはクロスプラットフォームで動作するのに対して、Visual StudioはWindowsでのみ動作します。 C#の開発に使用するIDEとしてはVisual Studioが一強です。 他には Visual Studio for Mac やJetBrainsから出ている Rider というIDEもありますが一休では使用していません。 一休は.Net FrameworkというWindowsでのみ動作するC#の実行環境をメインに使用しています。 そのためWindowsにロックインされていますが、クロスプラットフォームである.Net Coreの登場に加え、上記のIDEもあるためMac、Linuxでの開発も可能です。 まだ.Net Coreの採用事例は少ないですがC#はWindowsだけのものではなくなってきています。 Windows内部でのWebサーバー ローカル開発環境はIISというものを使用します。 いわゆるApacheやnginxのようなWebサーバーです。 画像のようにGUIからの操作が可能であり、もちろんCUIからの操作も可能です。 一休では開発環境の初期構築をしてくれるバッチファイルがあり、それを実行するとこのIISのサイトを構築、設定してくれるようになっています。 Docker 実はWindowsもDockerを動かすことができます。 Macで使われているDocker for MacのWindows版、Docker for Windowsがあります。 内部ではHyper-VでMobyLinuxを立ててその上にDockerコンテナがホスティングされるようになっています。 それだけでなく今やVirualboxなどの仮想環境構築ソフトに頼らずUbuntuも使うこともできるWSL(Windows Subsystem for Linux)というものもあります。 VagrantやDockerとは使い勝手が違うので躓くこともありますが、使用用途を限定すれば今のところ問題ありません。 www.atmarkit.co.jp Webフレームワーク 一休ではC#でWebを開発するときにASP.NET Web FormsとASP.NET MVC、ASP.NET Web APIを使用しています。 ASP.NET Web FormsはイベントドリブンモデルでWindowsアプリケーションの知識をWebでも活用できるように作られたフレームワークです。 単体テストを行いにくかったり、固有の概念やビューとなるHTMLの制御が難しいなど、今のWeb開発には合っておらず新規で採用するメリットはありません。 そのためこれから新規プロジェクトを作る場合はMVCかWeb APIで作成するかと思います。 ASP.NET MVCについては特筆する点はありませんが、一般的なMVCフレームワークの機能が備わっており、薄すぎず厚すぎずでちょうどいい塩梅のフレームワークとなっています。 ASP.NET MVCはMVCという名前はついていますが実はモデルに相当する機能はありません。 そこでロックインされていないのでモデルの作成は柔軟に行えるようになっているところもメリットです。 記述型式 ここからはC#の記述を紹介します。 C#は強い静的型付けの言語です。 そしてオブジェクト指向言語でもあります。 実際のコードを見てみましょう。 public class Child : Parent { private readonly string str; private readonly int num; public Child( string str, int num) { this .str = str; this .num = num; } public string GetContent() { return $ "str = {str}, num = {num}" ; } } var child = new Child( "test" , 1 ); child.GetContent(); // str = test, num = 1 オブジェクト指向言語をやってきた方なら細かい差異はあれど理解しやすいのではないでしょうか。 このコードだけ見るとかなりJavaに近いですね。 名前空間、パッケージによるアクセシビリティレベルが違ったりEnumにメソッドが生やせないといった違いはあります。 加えて型推論、getter setter、async awaitなど便利な機能に加えてLINQという便利なコレクションライブラリもあります。 型推論 // int型に var num = 1 ; // List<string>型に var strList = new List< string >() { }; getter, setter, メンバ public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get => $ "{FirstName}{LastName}" ; } } var Person = new Person(); Person.FirstName = "Taro" ; Person.LastName = "Tanaka" ; Console.WriteLine(Person.FullName); プロパティごとにアクセス修飾子を変えることもできます。 public int Age { get; private set; } readonlyというものも。 public class Person { private readonly DateTime birthday; public Person(DateTime birthday) { this .birthday = birthday; } } インスタンスフィールドであればコンストラクタ内でのみ割り当て可能という制限をつけることができます。 初期化 オブジェクト初期化子 public class Person { public string FirstName { get; set; } public string LastName { get; set; } public Person() { } } var Person = new Person() { FirstName = "Taro" , LastName = "Tanaka" }; 名前付き引数 public class Person { public Person( int age, DateTime birthday) { // 処理 } } var Person = new Person(age: 5 , birthday: DateTime.Now); async await 単数のタスク public async Task< string > GetSingleAsync() { var result = await GetStringAsync(); return $ "結果={result}" ; } private Task< string > GetStringAsync() { var task = Task< string >.Run(() => { // 何か重い処理 return "async!" ; }); return task; } var single = new SampleAsync().GetSingleAsync().Result; 複数のタスク public async Task< string > GetMultiAsync() { var task1 = GetStringAsync(); var task2 = GetStringAsync(); var task3 = GetStringAsync(); var task4 = GetIntAsync(); var results = await Task.WhenAll(task1, task2, task3); return string .Join( "," , results); } var multi = new SampleAsync().GetMultiAsync().Result; LINQ コレクションを便利に扱える拡張メソッドです。 これがとても強力でこれがあるからこそC#は良いと言えるほどです。 var arr = new string [] { "a" , "bb" , "ccc" }; // 含んでいるか arr.Any(s => s == "a" ); // True // 絞り込み arr.Where(s => s.Length >= 2 ); // IEnumerable<string> { "bb", "ccc" } // 射影 arr.Select(s => s + s); // IEnumerable<string> { "aa", "bbbb", "cccccc" } // 他にも色々 IEnumerableな値(簡単に言うとforeachでループ可能)が返されるためメソッドチェーンで書くことが可能です。 arr.Where(s => s.Length >= 2 ) .Select(s => s + s) .ToArray(); 他にも 拡張メソッド ジェネリクス 属性定義、Attribute null条件演算子 例外フィルター タプル、分解 といったものがあります。 記述だけ見るとモダンな言語に劣らず便利なものがそろっていますよね。 この記事で少しでもWindows、C#でのWeb開発に興味を持ってもらえたら幸いです。 明日は id:undersooon 「ちょっとしたことを検索できる」Slack botを作った、です。
本記事は、一休.com Advent Calendar 2018の16日目の記事です。 qiita.com デジタルマーケティング部で主に宿泊サイトを担当している田中( id:yakisoba6318 )です。 今回は今年2月に導入したAMPについて導入時と今について紹介したいと思います。 内容に関しては主に宿泊サイトの話となります。 AMPとは? AMPとは、Accelerated Mobile Pages (アクセラレイティッド・モバイル・ページ)と言い、主にGoogleが提唱しているモバイルウェブ高速化を目的としたプロジェクトです。 ここではそのAMPプロジェクトのオープンソースフレームワーク(AMP HTML)を指してAMPと言います。 www.ampproject.org 一休.comにおけるAMP導入動機 ランディングページのスピード改善 速度改善による流入の増加 ユーザ体験向上 などいろいろありますが 簡潔に言うと ランディングページの最適化 です。 Choose a structured data feature | Search | Google Developers 一休.comのAMPページの紹介 現在一休で公開しているAMP対応ページとポイントを紹介します。 左からレストラン店舗ページ、宿泊施設ページ、宿泊観光ページ、宿泊特集ページ レストラン店舗ページAMP リッチカード x AMPで流入増 amp-list でプラン情報、割引率を動的表示 ランチ - ザ・ロビー - ザ・ペニンシュラ東京/コンチネンタルダイニング [一休.comレストラン] *スマホからのみ確認できます 宿泊施設ページAMP amp-list プラン情報・クチコミ表示 amp-access でクーポン表示 ザ・プリンス パークタワー東京 - 宿泊予約は[一休.com] *スマホからのみ確認できます 宿泊観光ページAMP レスポンシブ(Canonical / Responsive AMP ) amp-toolbox-optimizerを使ったページの最適化 まだまだ試験的なページで今は京都、金沢、箱根や一部温泉地のみですがこれからエリアを拡大予定 京都観光で行きたい名所!京都旅行におすすめ人気スポットランキング 19選【2018年】 - [一休.com] *レスポンシブ対応のためPC・スマホどちらでも確認できます 宿泊特集ページ amp-story実験的に公開中 高級宿の素材をリッチに表示できるページ ただ、日本では検索でAMPViewとしての表示はまだ出来てない 今後の展開を期待 ジ・ウザテラス ビーチクラブヴィラズ 新規開業[一休.com] *amp-storyがPC表示に対応しているためPC・スマホどちらでも確認できます AMPコンポーネントの活用例 AMPページを作るにあたって欠かせないのが公開されてるコンポーネントです。 日々いろんなコンポートの開発が進んでいてAMPで出来ることが増えてきています。 今回はその一部を使った利用例を紹介します。 www.ampproject.org 動的なカルーセルを出したい < amp-list src = "フォトギャラリーAPI" height = ”254” layout= ”fixed-height” single-item> < template type = "amp-mustache" > < amp-carousel controls loop height = "254" layout= "fixed-height" type = "slides" > {{#photos}} < div > < amp- img src = "{{ImageUrl}}" layout= "fill" alt = "{{ImageAlt}}" ></ amp- img > </ div > {{/photos}} </ amp-carousel > </ template > </ amp-list > amp-listはamp上でxhrできるのでAPIからリスト取得、受け取ったリストをmustache形式でループさせて画像を動的に生成することができます。 https://www.ampproject.org/docs/reference/components/amp-list https://www.ampproject.org/docs/reference/components/amp-mustache カルーセルの画像枚数を表示させたい < amp-state id = "GuideTopPhotoUrl" src = "フォトギャラリーAPI" ></ amp-state > < amp-carousel controls loop height = "254" layout= "fixed-height" type = "slides" on= "slideChange:AMP.setState({ GuideState: { header: { selectedSlide:event.index, Is_slide:true } } })" > <!-- 略 --> </ amp-carousel > < div > < span [ text ]= "!GuideState.header.is_slide ? '' : (GuideState.header.selectedSlide + 1) + '/' + (GuideTopPhotoUrl.items.photos.length + 1) " > </ span > </ div > on属性にslideChangeをトリガーを設定してsetStateを更新、更新した情報をspanにbindすることで動的に枚数を更新することができます。 https://www.ampproject.org/docs/reference/components/amp-carousel https://github.com/ampproject/amphtml/blob/14153fe212a80aeb6f1e7a7f14e4849cc228eba9/spec/amp-actions-and-events.md#L177 結局AMPはどうなの? 紹介したように既に多くのページをAMP化してきましたが効果どうだったのか、 いろいろ工夫が必要な点もあったのでそれを踏まえて紹介したいと思います。 サイトパフォーマンス Before: AMP Visually Complete 4G 上から順に通常のスマホページ、AMPページ、Googleキャッシュ上のAMPページ こちらは少し古いレポートですが、始めはAMPコンポーネントの使い過ぎやamp-stateの不要な更新が多かったため AMPの要件は満たしていたがユーザ体験が悪化していました。 なので対応として Googleキャッシュからの配信の恩恵を受けるためリアルタイム性の不要なコンテンツはxhrで取得しない amp-stateの更新をbindするのに時間がかかるためUI上での不要なstate管理を辞める 具体的な例として フォトギャラリーのamp-listを辞める 開閉のUIをamp-state + hidden属性からamp-accordionに変更 結果として After: AMP Visually Complete 4G 上から順に通常のスマホページ、AMPページ、Googleキャッシュ上のAMPページ AMPがoriginのスマホページよりVisually Completeの面では早い状態まで持っていくことができました。 ちなみにTTFBはoriginもほぼ同じでしたがDOMContentLoadで大きく遅れを取った結果このような数字になっていました(web page test) ユーザ体験 amp-listで施設のプランを取得していますが、導入当初は読み込み後に展開するようにしていました。 ただ、これだとページ下部のコンテンツ(アクセスや施設情報など)閲覧中にリフローによって画面がずれ込み体験が悪くなっていました。 そこであらかじめスケルトンスクリーンを用意して高さを確保することでユーザ体験を損なわないように対応しました。(AMP開発者の方からのアドバイス) これで表示が遅れてもユーザ体験に影響が少ないページになりました。 今後の方針 一休.comでの今後のAMP展開については以下の検討をしています。 amp-storyの活用事例を増やす AMP上でjsが動くamp-scriptを使ったコンテンツのリッチ化 ITP2.0によるトラッキング対策(Safari対応)としてamppackagerやamp-toolbox-optimizerなどの使用による対応 digitalidentity.co.jp まとめ AMPのコンポーネントは容量用法を意識して使うのが良さそうだと感じました。 AMPページとしてのサイトの役割を考えつつ良質なユーザ体験と流入を獲得できるサービスをこれからも作っていきたいと思います。 AMPと聞くとスマホだけの印象のありますが、 PCページとしてのAMPやコンポーネントを個別にshadowDOMとしてページに取り入れることができたりと活用の幅はとても広いです。 今後の進化からも目が離せないですね! 明日は id:IganinTea さんの「 C#とWindows」です!
この記事は一休.com アドベントカレンダーの14日目の記事です。 qiita.com こんにちは。 id:kentana20 です。一休で宿泊サービスの開発をしています。 今日は一昨日の夜に実施したイベント「Ikyu Frontend Meetup」の様子をレポートしたいと思います。イベントページはこちら。 ikyu.connpass.com 年末の忙しい時期にもかかわらず、多くの方にご応募・ご参加いただきました。 今回のイベントのきっかけ 過去に2度、一休ではテック系イベントをやっていましたが、「ぼちぼちまたイベントやりたいな〜」と思っていたときに、 id:supercalifragilisticexpiali が書いた user-first.ikyu.co.jp を見た他社のエンジニアの方から「情報交換しましょう」と複数問い合わせや依頼があったので、イベントにしてしまおう、と思って今回のMeetup開催に至りました。 セッションの内容 今回のイベントでは「一休.com / 一休レストランでのフロントエンド開発」をテーマに3本のセッションを行いました。 セッション1: 「JavaScript/Vue.js アプリケーションのパフォーマンスチューニング」 1本目のセッションは宿泊サービスのエンジニアである id:ryo-utsunomiya がJavaScript/Vue.jsのパフォーマンスチューニングについてお話しました。 ryo-utsunomiya によるJavaScript/Vue.jsのパフォーマンスチューニングの事例 一休.comモバイルWebのホテルページを高速化した事例をもとに Lighthouse, Calibreを使ったパフォーマンスの計測 同業他社のページを参考にした目標設定 JavaScriptのチューニングのポイント などをお話しました。 セッション2: 「imgix導入で画像最適化とサイトスピード改善」 2本目のセッションでは、画像の最適化によるチューニングの事例を id:akasakas がお話しました。 akasakasによる画像最適化事例のセッション もともと自社(Image Magick)で画像のリサイズや切り抜きなどの加工処理をしていたところを、画像最適化/配信のSaaSであるimgixを導入して最適化したお話を 導入前の課題整理 技術選定の観点とimgixに意思決定したポイント imgixの優れている点 導入後の効果 などの流れでお話しました。課題 → 解決のための手法 → 解決後の成果が順を追って語られていて、とてもわかりやすいセッションでした。セッションの中でも語られていましたが、一休が提供するサービスにおいて画像はとても重要で、ユーザ体験に大きな影響を与える部分なので、最適化によってユーザ体験を向上できたことは本当に良い成果につながったと思っています。 セッション3: 「一休.comレストランのスマートフォン検索ページがSPAになりました」 3本目のセッションでは、イベントのきっかけになったブログエントリをもとに id:supercalifragilisticexpiali が一休レストランモバイルWebのレストラン検索ページをSPAにした事例ををお話しました。 「もっと使いやすく」「サクサク動くように」というプロダクトのニーズ SEOを落とさないようにという集客面のニーズ Atomic Designを意識したコンポーネント指向設計を取り入れて生産性を上げたいという技術面でのニーズ といった多角的な観点から Vue.js, Nuxt.js, ITCSS を導入した事例を細部まで丁寧にお話しました。 内容が濃く、参加者のみなさんも真剣に聞いてくださっていて、とても良いセッションでした。 パネルディスカッション / 懇親会 3本のセッション終了後はビール片手にパネルディスカッションと懇親会を行いました。 事前に参加者の方からいただいていた質問をベースにスピーカーと話したり、参加者から当日出た質問に答えるQ&A形式で進行しました。 編集後記 過去2回は他社と合同で実施したMeetupイベントでしたが、今回は一休単独での開催だったので、参加者が集まるか、イベントに満足いただけるか、など不安な点はありましたが、まずまず満足いただけたようで、開催してよかったです。 おもしろかった。継続的に改善を続けていける体制ができているの羨ましい。 #ikyu_dev — Yoshiaki Itakura (@ita_3y) 2018年12月12日 面白かったです #ikyu_dev — ぎゃらす、@生の速度 (@gyagyagyaxtu) 2018年12月12日 色々有益な話が聞けて良かったです!ありがとうございました! #ikyu_dev — たーか (@Rw_taaaka) 2018年12月12日 一休.com / 一休レストランともに、まだまだフロントエンド開発でやりたいことはたくさんあるので、継続的にコツコツ改善を続けてユーザにとって使いやすいサービスを提供していきたいと思います。 明日はアドベントカレンダーはお休み、明後日16日は id:yakisoba6318 による「 一休.comにおけるAMP導入について」です。お楽しみに!
この記事は一休.comアドベントカレンダー2018の13日目の記事です。 qiita.com こんにちは。 今年の7月に入社したレストラン事業部の渥美です。 一休.com レストランにてフロントエンドとバックエンドの開発を行なっております。 この記事の概要 店舗ページをSPA化した背景 店舗ページリニューアル プラン詳細ページのSPA化 Vue.js によるモーダルの実装方針 事前ロード モーダルの開閉 URLを動的に生成する Fastly での段階的リリース Fastly について VCL の設定 Fastly Fiddle によるテスト 今後の展望 店舗ページをSPA化した背景 店舗ページリニューアル 私が一休に入社する1ヶ月前、店舗の情報は複数のページにまたがっていました。 具体的には、店舗のページはプラン一覧や店舗情報、アクセスマップなどの情報が別々のタブに分かれており、ページ遷移が必要でした。 しかし、restaurant2 *1 へのリニューアルと共に、SPAとして生まれ変わったのでした。 Before: 旧店舗ページ After: 新店舗ページ プラン詳細ページのSPA化 その後入社した私はユーザ体験をさらに向上させるために、 独立していたプランの詳細ページのモーダル化を担当しました。 これにより、店舗ページ内で完結できる範囲が広がりより予約がしやすいUIになりました。 ※現在も下記のプラン詳細ページは動いていますが、後述するようにモーダルへ移行予定です。 Before: プラン詳細ページ After: プラン詳細モーダル 今回はこのモーダル化の実装について簡単にお話するとともに、 段階的リリースの取り組みについてご紹介します。 Vue.js によるモーダルの実装方針 一休レストランのフロントエンド開発では Vue.js が使われています。 モーダルの実装もVue.jsで制御しており、今回はその基本的な実装方針を紹介します。 大まかな処理は以下のとおりです。 // 店舗ページの Vue ファイル <template> <!-- 略 --> <object-plan-item @mouseenter= "preloadPlanDetail" @click:plan= "openPlanDetail" /> <composition-plan-detail-modal :activated= "showPlanDetail" @update:activated= "onClose" /> </template> export default { // 略 data() { return { showPlanDetail: false } ; } , methods: { openPlanDetail(plan) { // ①モーダルの開閉処理 this .showPlanDetail = true ; // ②URL の動的生成 global.history.pushState( null , '' , this .planDetailUrl(plan)); } , onClose() { // ①モーダルの開閉処理 this .showPlanDetail = false ; // ②URL の動的生成 global.history.pushState( null , '' , `/$ {this .restaurant.id } /`); } , preloadPlanDetail(plan) { // ③プラン詳細情報の先読み } , planDetailUrl(plan) { // URL を生成し返却する } , } } <object-plan-item> が各プラン項目のコンポーネント、 <composition-plan-detail-modal> がモーダルのコンポーネントです。 ①モーダルの開閉処理 モーダルの表示状態は showPlanDetail を定義して、この boolean 値で管理しています。 単純ですが、プランの「詳細・予約」ボタンがクリックされたときに showPlanDetail = true , モーダルの背景がクリックされたときに showPlanDetail = false となり、表示が切り替わります。 ②URL の動的生成 モーダルを開いたときに URL を動的に生成しています。 History API を利用して、 global.history.pushState(null, '', this.planDetailUrl({ plan, time })); とすることでモーダルを開いたときに URL が更新され、履歴にも追加するようにしています。 モーダルを閉じるときは、 global.history.pushState(null, '', `/${this.restaurant.id}/`); として店舗ページの URL に戻します。 ③プランの詳細情報を先読み プランの詳細情報を先に読み込む処理を書いています。 実際には <object-plan-item> から mouseenter のイベントが emit され、 preloadPlanDetail が発火されます。 ユーザからすると、プラン一覧の項目をマウスオーバーすると同時にプリロードが走るので モーダルを開いたときにはシームレスにプラン詳細が表示される体験を提供できます。 (※ローディングが完了していない場合はローディング画像が表示されます) 勿論他にも処理はありますが、以上がモーダル化の大まかな実装となります。 Vue.js を使うことでモーダルの開閉状態や、先読みしたプランの情報をモーダルに受け渡せるのは便利な点でした。 Fastly での段階的リリース 今回モーダル化したページは予約導線に直結しているということもあり、 リリース当初は特定の店舗のみに限定公開しました。 この限定公開に Fastly を使いました。 Fastly について 一休ではリバースプロキシとして Fastly を利用しています。 Fastly は設定言語の VCL を書くことでリダイレクトの処理や、レスポンスの制御を簡単に行うことができます。 今回は、特定の店舗ページにアクセスしたときのみ、 ?modal_enabled=1 というクエリパラメータを付与するように VCLの設定を行いました。 そして、このパラメータが付与されている場合はモーダルを開くようにアプリケーション側でハンドリングしました。 流れとしては下記のようなイメージです。 特定店舗のURLにアクセスする https://restaurant.ikyu.com/( 店舗ID) Fastly で ?modal_enabled=1 が付与されたURLにリダイレクトさせる https://restaurant.ikyu.com/( 店舗ID)?modal_enabled=1 ?modal_enabled=1 が存在するときのみアプリケーション側でモーダルを開く メリット Fastly での段階的リリースのメリットとしては下記のようなものがあります。 限定リリースができる 今回の主目的である、URLに応じた限定リリースが可能です。 Fastly のデプロイが高速 VCL の本番環境への反映が、CIのテストを含めて2分ほどで完了します。 切り戻しが容易 上記のデプロイが高速であることと関係するのですが、なにかトラブルがあった場合の切り戻しが容易です。 アプリケーションのリリースは20分程かかるのでこれはありがたいポイントです。 それまで他の手法での段階的リリースは行われていたものの、 Fastly を使った段階的リリースは初めての試みでした。 VCL の設定 さて、実際に VCL で設定した内容をご紹介します。(必要に応じて変数名等を変えています) table test_ids { "100000": "true", } sub vcl_recv { #FASTLY recv if (req.url.qs !~ "modal_enabled=" && req.url.path ~ "^\/(\d{6})" && table.lookup(test_ids, re.group.1) ) { error 700; # 内部的にerror statusを飛ばすことでリダイレクトを行う } } sub vcl_error { #FASTLY error if (obj.status == 700) { set obj.http.Location = "https://" req.http.host req.url.path "?modal_enabled=1" if(req.url.qs == "", "", "&" req.url.qs); set obj.status = 307; set obj.response = "Temporary Redirect"; return (deliver); } } table の項目で特定店舗のIDを指定しています。 ここで気になった方もいるかも知れませんが、 VCL の設定では内部的に error status を飛ばすことでリダイレクトを行います。 Fastly Fiddle によるテスト この VCL の設定を行うにあたり、 Fastly Fiddle というサービスを利用しました。これは VCL のコードをオンラインで簡単にテストできるサービスです。 Fastly Labsが実験的に運用している ようです。 Fastly Fiddle の画面 Fastly Fiddle は実行したコードを他のユーザと共有することもできます。 上記のコードを サンプル として用意しました。 ページにアクセスしたら、右上の[RUN]ボタンを押してみてください。 table test_ids に含まれるIDに対応するURL(/100000)に対してリクエストを送ると、リダイレクトが発生することがわかります。 リダイレクト結果 反対に、 test_ids に含まれないIDに対応するURL(例えば /200000)にリクエストを送ってもリダイレクトされません。 是非 Send a request の項目を /100000 から変更して遊んでみてください。 このようにして、Fastly による特定店舗のみの段階的リリースができるようになりました。 今後の展望 今回のモーダル実装で店舗のページのSPA化が進みました。 しかし、モーダルのURLに直接アクセスしたときは、未だ以前のプラン詳細ページに遷移するようになっています。 今後はこのような導線にもモーダルが開くように対応していきます。 また、今回 Fastly での段階的リリースの仕組みも整ったので、今後の機能追加でどんどん活用していきたいと思います。 明日は id:kentana20 さんの 12/12 Ikyu Frontend Meetup 開催レポート です! お楽しみに! *1 : 新しいアーキテクチャによる一休.com レストランのWebアプリケーションを指す。
この記事は一休.comアドベントカレンダー2018の11日目です。 qiita.com はじめに デザイナーと聞いて、皆さんはどのような人を想像しますか? 「見た目を美しくかっこよく作れる人」、「ビジュアルデザインの専門家」というイメージを持たれている方も多いのではないでしょうか? デザイナーのアウトプットだけを見ればその通りですが、アウトプットに至るまでのデザインの考え方 や取り組み方、役割がここ数年で変わってきています。 以前は、情報を整理して色や形を使いこなすビジュアルデザインがデザイナーの中心的な業務だと考えられていました。 しかし、Webデザインの標準化が進み、類似サービスの乱立も増え、ユーザーから好まれるサービスかどうかが重視されるようになりました。 より効果的なデザインを実現するために、事業戦略やサービスの現状、マーケットニーズに目を向け、デザインに活かそうとする動きが活発になりました。 とはいえ、理屈は理解できても具体的に何をすれば良いのかがわかりにくいと感じている方も多いはず。そこで今回は、一例として私が一休のデザイナーとして取り組んでいることをご紹介したいと思います。 Index 1) トーンマナ―などのルールは最低限にして都度考える 2) 自分たちが目指すデザインの方針を明文化する 3) デザイナーが自由に発散し、少し先の将来について考える場を設ける 4) デザインに取り掛かる前に考える トーンマナ―などのルールは最低限にして都度考える 一休のレストラン事業のトーンマナーで定義しているのは、複数のページで登場する基本的なUIパーツとタイポグラフィの基準のみです。 あまり詳細まで定義するとルールに縛られて考える機会が失われる可能性がありますし、ドキュメントの整備に手間をかけるのも得策ではありません。 敢えてルールを決めすぎず、状況に応じてデザイナーが自分の頭で考え、最良と思うものを画面に反映させて効果を見てみる。 工夫できる余地を残すことでサービス改良の提案がしやすくなり、変化の促進につながると考えました。 ただし、都度デザインすることがユーザーのためにもサービスのためにもならない要素についてはVueコンポーネントに定義して、デザイン、コーディングの効率化とサービスの使いやすさの両方の実現を目指すことにしました。 目指したいデザインの方針を明文化して共有する デザインをしていてしばしば頭を悩ませるのは正解が複数あることです。 デザインの善し悪しは判断軸によって変わりますし、人によって異なる感想を持ちます。 チームでデザインを考える場合、個々に全く異なるコンセプトで考えてしまい、サービス全体での一貫性が損なわれることもあります。 かといって、トーンマナ―を充実させれば良いかと言うと前述の通り、そうではないと思っています。 そこで、サービスデザインコンセプトというものを明文化することにしました。 ルールではなくコンセプト、つまり方向性です。 具体的にあれをする、これをするを書くわけではなく、実現したい景色を3つ~5つの項目に落とし込み、自分たちが作っていくプロダクトの目標として据えました。 そして、この目標にマッチしていない箇所について、「こうしたらどうだろう?」「こういう方法もあるかも」「どうやって進めようか?」といったことを、次項で紹介する「UI/UXデザイン語り場」で話し合っています。 一休レストラン事業のサービスデザインコンセプト(抜粋) 楽しい、予定がなくても見たくなるサービスであること 最短で目的を達成できるサービスであること 直感的に操作ができ、考える必要がないサービス システマチックになりすぎず人間味があるサービス デザイナーが自由に発散し、少し先の将来について考える場を設ける 目前のタスク消化であっという間に数か月が過ぎてしまった、という経験はありませんか? また、自分の考えが正しいかわからず提案しても良いものかと躊躇してしまう、ということはありませんか? これらの課題をチームで解消して、小さなことでも良いので実行を積み重ねていきたい、という想いから「UI/UXデザイン語り場」というミーティングを実施することにしました。 週に一時間、今抱えているタスクから離れてこれからやっていきたいこと、やってみたいことについて考え、自由に話すための時間です。 提案した内容をなんでもやれば良いわけではありませんが、答えのないことを行動前にあれこれ考えすぎても意味がありません。 そして最も避けるべきは「何もしないこと」です。 何もしなければ業績は低下していきます。 よほど的外れなことでない限り、まずやってみる、やってみた結果を踏まえて次の打ち手を考える。 この繰り返しがサービス改善には不可欠です。 デザインに取り掛かる前に考える 「こういう機能を付けたいから画面デザインをください」と言われた時に、いきなりデザインツールに向かってしまうのは適切ではありません。 その前に、まずは企画者の話に耳を傾け、実現したいこと、手段、現時点での仮説、リスクなどを書き出し、前提情報を整理します。 そして、それらの前提を元にターゲットの深掘りをしていきます。 データからわかることと、データからはわからない不確かなことに分けてまとめると良いでしょう。 ヒアリングやデータ解析、他社分析の作業は必ずしもデザイナーが担当する必要はありません。 自分でできるなら自分でやれば良いですし、できない場合には得意な人の協力を得ても良いと思います。 重要なことは事実と可能性をしっかりと把握することです。 一通りまとめ終えたら、いくつかのターゲットグループのユーザー像を想像できるようにしてから、いざデザインに取り組みます。 このプロセスを踏んだ時と踏んでいない時とで、デザインを提案する際の説得力も自信も、提案内容も変わるはずです。 多少手間でもこのプロセスを踏んで予測し、実施後にどうなったのかをノウハウとして蓄積すると、アイデアの引き出しが増やせると思います。 おわりに いかがだったでしょうか? 「そりゃそうでしょ!」と思う内容が多かったと思いますが、13年間ECサイトのデザインに携わってきて気付いたのは、難しく考える必要はないということでした。 シンプルに考える、当たり前のことを当たり前にちゃんとやってみた結果、見えてくることも多々あります。 少しでも参考になれば幸いです。 最後までお読みいただき、ありがとうございました。 次回は@atsumim の 「プラン詳細ページのモーダル化を Fastly で段階的リリースした話」です。お楽しみに!
この記事は一休.comアドベントカレンダー2018の10日目です。 qiita.com こんにちは。レストラン事業部の所澤です。 WEBアプリケーションエンジニアとしてフロント/サーバー問わず機能開発を行っています。 今回は一休.com レストランの旧アプリケーションのフロントエンド開発環境改善についてお話します。 ※ この記事の執筆時点では以下の内容は master に取り込まれていません。同僚のフロントエンドエンジニア(ガチ勢)から何か指摘があったら追記します。 この記事の概要 一休.com レストランの旧WEBアプリケーション(以下 restaurant1 )はなぜかフロントエンドビルドが超遅い。 Storybook のようなアプリと切り離された、高速でビルドできる環境があればもっと快適に開発できるのではないか? Storybook だと vue-devtools が使えないので Storybook (の最低限の機能を持つ)小さいアプリケーションを作ってみた。 一休.com レストランの開発環境 新アーキテクチャへの移行状況 何度かこのブログでも取り上げていますが、現在(2018年12月)、一休.com レストランは新旧ふたつのWEBアプリケーションが並行する形で運用されています。 古くから稼働している VBScript で書かれたアプリケーションは restaurant1、リニューアル後の Python で書かれた新しいアプリケーションは restaurant2 と呼ばれています。 着々と restaurant2 への移行は順調に進んでいますが、依然として restaurant1 の上に乗っている部分も多く残っています。 たとえばスマートフォン版の店舗トップ画面は機能追加の機会が多いページですが、まだ restaurant2 への移行が完了していません。 新アーキテクチャだけを触ればOK、という状態まではまだ少しかかりそうだというのが現状です。 restaurant1 のフロントエンドについて さて、"レガシー"などと言ってしまいましたが、実はフロントエンドに限って言えば旧アプリケーションもそこまで古くはありません。 jQueryでゴリゴリ書かれたページもありますが、主要ページに関しては ES2015+ と Vue.js で開発できる環境が整っています。 restaurant2 のピカピカのコードに比べると若干見劣りはしますが十分モダンだと言っていいでしょう。 問題は、フロントエンドビルドが とてつもなく遅い ことです。 フルビルドに時間がかかることに関して良いとしても watch しているときの差分ビルドも 1分以上 かかります。(Core i7 の開発機で) 遅い原因は特定できていないのですが、 アセットの肥大化 そもそも Windows だとビルドが遅い( restaurant1 は ASP で書かれているので Windows 必須です) セキュリティのために入れているファイル監視ソフトの相性の問題 など、いろいろな可能性が考えられます。 さて本来であれば根本原因を特定して解決するのが筋ですが、どうにも問題の切り分けがうまくいかないので別の解決方法を考えてみます。 restaurant1 に Storybook を導入してみる いままではユニットテストを書いてブラウザを使った動作確認の回数を減らし、なるべくこの問題を意識しなくて済むように気をつけていました。 しかしやはり新規のコンポーネントを0から作るときやデザインの微調整をする際はどうしてもビルドの遅さが気になります。 restaurant2 や宿泊のサイトでは 既に Storybook を導入済みだったこともあり、"開発用の Playground として" restaurant1 にも Storybook を入れてみようと試してみました。 ※ restaurant2 では"デザイナーとの協働をスムーズにする" という目的で Storybook が活用されています。詳細はまたいつか。 Storybook で十分、か? さて冒頭でも書きましたが結局 Storybook を導入することは見送りました。理由は vue-devtools が使えないからです。 今回はデザインシステムとしてではなく、開発用の Playground として Storybook を使いたいので開発ツールがうまく動かない点は致命的な問題です。 (最初に気付けよ、という感じですが私自身はそれまであまりちゃんと Storybook を使ったことなかったので...) Github の issue を見るとワークアラウンドがありそうですが...。すでに導入でだいぶ消耗していて、これ以上の yak shaving をする気は起きなかったので別の方法を検討することにしました。 (追記: 無理やりですが iframe を別タブで開くと vue-devtools が使えます) Storybook 相当のアプリケーションを作ってみる よく考えれば今回の用途に限って言えば Storybook の全機能が使える必要はありません。やりたいことは至ってシンプルです。 要求・仕様 アプリケーションと独立した環境で動作確認しながらコンポーネントを開発できる vue-devtools が使える LiveReload Mac でも開発できる コードのコピペなどせずに restaurant1 のコードがそのまま確認できる Storybook 風にストーリーが書ける この仕様を満たす小さなアプリケーションを書いてみることにしました。 ※ あくまで Playground として使い、最終確認はアプリケーションに組み込んでやる前提。 まずは使い方をご紹介 stories.js にStorybook 風のAPIでストーリーを追加していく storiesOf( 'sample' ) .add( 'hello' , h => h( 'h3' , [ 'hello. this is my story.' ] , {} )); storiesOf( 'DatePicker' ) .add( 'select' , h => h(CustomDatePicker, { props: { value: '2018-12-01' } } )); restaurant1 内で yarn play を実行してサーバーを起動 UIがダサいのはご容赦ください...。 これだけですが、「アプリケーションと切り離された環境でコンポーネントを開発する」ということは実現できています。 こだわりポイント ここからは蛇足な気がしますが、せっかくなのでこだわりポイントをご紹介します。 とにかくシンプルに! 新しいライブラリを追加しない Storybook like な API 以上の3点を心がけて実装しました。 このツールを使う人やツールの機能を拡張しようとしてコードを読む人の負担が最小限になるように気をつけています。 まずは何よりコードが小さく、シンプルになるように心がけました。また不用意に新しいライブラリを追加すると、でコードを読んだ人に負担もかけてしまうので restaurant1 に追加済みのライブラリのみを使用することにしました。 webpack-dev-server でホスト 今回はアプリケーションの中に playground ディレクトリを作りそこに関連ファイルを格納し、 webpack-dev-server でホストしています。 今回のモチベーションが「ビルドの速度改善」なので 本アプリの webpack の設定を使い回すことはせずに新しく playground 以下に数十行のシンプルな設定ファイル( playground/webpack.config.js )を追加しました。 // package.json { // 略 "scripts" : { // 略 "play" : "webpack-dev-server --config playground/webpack.config.js" } } // playground/webpack.config.js { // 略 devServer: { contentBase: path.resolve(__dirname, 'dist' ), port: 9000, } , } 9000番ポートで dist ディレクトリの内容をホストします。 storiesOf 関数 storiesOf 関数の実装はこれだけです。 storiesOf と add でオブジェクトにコンポーネントを登録していきます。 // playground.js const stories = {} ; // Component を格納 const tableOfContents = {} ; // ナビゲーション用 // TODO : HMR function storiesOf(title) { tableOfContents [ title ] = tableOfContents [ title ] || {} ; return { add(scenario, value) { const key = `$ { title } :$ { scenario } `; tableOfContents [ title ][ scenario ] = key; playground [ key ] = { render: value } ; return this ; } , } ; } const getStories = () => stories; const getTableOfContents = () => tableOfContents; export { storiesOf, getStories, getTableOfContents, } ; ※ vue-play の実装を参考にさせていただきました。 github.com プレビュー機能 import { getStories } from './playground' ; export default { name: 'PlaygroundPreview' , data() { return { scenario: '' , stories: {} , } ; } , methods: { setScenario() { const hash = decodeURI( window . location .hash); this .scenario = hash.replace( '#' , '' ); } , } , computed: { current() { return this .stories [this .scenario ] ; } , } , created() { this .stories = getStories(); this .setScenario(); window .addEventListener( 'hashchange' , this .setScenario); } , render(h) { return h( this .current, [] , {} ); } , } ; ストーリーとして登録したコンポーネントのプレビュー部分です。 URL のハッシュ部分にコンポーネントのキーを入れるようにし、ハッシュの変更によってプレビューされるコンポーネントが切り替わるようにしています。 http://localhost:9000/#{ストーリーのタイトル}:{ストーリーの小見出し} 今後の展望 webpack の設定ファイルを含め、250行程度のコードでここまでの内容が実現できました。 シンプルな実装で最低限やりたいことはできた、と思っています。 WEBフォントの読み込みができていない Vuex と連携するコンポーネントの動作確認ができない DefinePlugin の対応 async/await を使っているコードでエラーが出るので webpack の設定を見直し UIがイケてない などすでにいくつか課題は見つかっているのですが、プレゼンテーションだけに責任を持つシンプルなコンポーネントの開発であれば十分に活用できるかと思います。 実はデモ用にいくつか実際に使われているコンポーネントを追加しようと思ったのですが、ほとんどの主要コンポーネントが Vuex に依存していてうまく追加できませでした。 よく言われていることですが、あらためて Presentation Component と Container Component の分離が重要ですね。 もう少しブラッシュアップして、良さそうであれば master に取り込もうと思います。
この記事は一休.comアドベントカレンダー2018の9日目です。 qiita.com 導入編 に続き、運用編です。 ここ2年間 Rundeckを運用してきて発生したトラブルとその対処について書きます。 ※この記事で言及するRundeckはバージョン2.6.9です。 トラブルはふたつありました。 データベースが高負荷になり動作が不安定になった なぜかジョブが起動しない データベースが高負荷になり動作が不安定になった 原因は複数ありました。 データベース(AWS RDS)のインスタンスタイプが小さすぎた 完全にサイジングのミスでした。動作確認で複数のジョブを大量に動かしたときでも、t2.smallのインスタンスで十分に動作したので、t2.smallで大丈夫だろうと、そのまま本番導入したのですが、運用開始して2ヶ月くらいで、高負荷になりました。速やかにt2.mediumにスペックアップしました。 コネクションプールの設定が漏れていた。 Rundeckは、rundeck-config.propertiesというファイルにデータベースの接続情報を記述します。 デフォルトでは、H2 Databaseを使用する前提の接続情報になっています。 これをRDS(MySQL)を使うように修正したのですが、その際、コネクションプールの設定が漏れていました。そのため、接続が一切プールされない、という状態になっていました。 以下の記述をrundeck-config.propertiesに追加することで適切にプールをするように修正しました。 dataSource.pooled=true dataSource.properties.removeAbandoned=true dataSource.properties.removeAbandonedTimeout=60 データベースのインデックス不足 このissue で議論されていますが、RundeckのデータベースにはGUIの性能を大きく改善できるインデックスがいくつかあります。これらのインデックスはデフォルトでは付与されていないようです。運用開始後、GitHubやGoogle Groupに投稿されている情報を調査し、 このissue で紹介されているインデックスや #1547 で紹介されているインデックスを付与することで性能を改善できました。 実行ログがたまり続ける これは運用を開始する前からわかっていた課題だったので、データベースの高負荷の原因にはなりませんでしたが、対処が必要な課題ではありました。 Rundeckはデータベース上の実行ログを削除しません。長期間運用して実行履歴が大量に溜まった段階で実行履歴の検索を行なった場合、データベースの負荷が高まる可能性があるので、定期的に削除する仕組みが必要だと判断しました。 削除する実行ログの条件は以下の通りです。 ジョブは最新の1万件の実行ログを保持する。1万件を超えたら古い順に削除される。 月次1回だけ実行されるジョブもあれば、1時間以内に複数回実行されるジョブもあります。また、調査のために古い履歴を調べることがあるかもしれません。このような前提を考慮して、上記のようなルールにしました。 そして、Rundeckのデータベースの構造を理解してどのテーブルのデータを削除すればよいのかを見つけ、定期的に削除するプログラムを作成しました。 調査したところ以下のdelete文の実行すれば良さそうです。 delete from log_file_storage_request where execution_id in ( @jobhistoryids ) delete from execution where id in ( @jobhistoryids ) delete from base_report where jc_exec_id in (@jobhistoryids ) @jobhistoryidsは、base_reportテーブルのjc_exec_id列の値です。 あとは、上記の条件に合致するジョブのIdと削除件数を特定する必要があります。 それは次のSQLで取得できました。 select inntable.jc_job_id as JobId, inntable.counts - 10000 as DeleteCount from ( SELECT jc_job_id, count(jc_exec_id) counts FROM base_report group by jc_job_id ) inntable inner join scheduled_execution se on se.id = inntable.jc_job_id where se.execution_enabled = 1 and inntable.counts > 10000 order by inntable.counts desc このselect文で、実行回数が1万回を超えているジョブのIdと超過回数がわかります。 ※例えば10300回実行されたいたら300回が超過回数になります。 そして、次のSQLで削除対象のjc_exec_idを特定します。 SELECT jc_exec_id FROM base_report where jc_job_id = @jobId -- 上のselect文で見つかった JobId order by date_completed asc -- 完了日時で昇順でソートすることで1万件を超過した実行ログのIdを特定できる limit @deleteCount -- 上のselect文で計算した削除対象件数 DeleteCount あとは、このselect文で取得できたjc_exec_idをパラメータにして上述した3つのdelete文を実行すれば、削除完了です。 なぜかジョブが起動しない データベースのトラブルが治った後はしばらく順調に動作していました。しかし、指定した時間なのにバッチに起動しない、という現象が発生するようになりました。 詳しく状況を見てみると、バッチ実行が遅延しているようでした。 いろいろと調べてみると、Rundeckが内部で使っているジョブスケジューラライブラリのQuartzのパラメータが原因でした。 Rundeckの公式ドキュメント によれば、 The maximum number of threads used by Rundeck for concurrent jobs by default is set to 10 と書いてある通り、デフォルトでは最大で10本のジョブの同時実行が可能です。 一方、一休では利用が促進された結果、タイミングによっては10以上のジョブが同時に実行されるような状況になっており、その結果、実行が遅延するようになっていました。この設定を変えるには、以下の記述をrundeck-config.propertiesに追加します。 quartz.props.threadPool.threadCount=30 この記述によって最大30まで同時実行できるようになり、問題が起きなくなりました。 終わりに 今回は実際に運用してきて発生したトラブルとその対処について紹介しました。参考になれば幸いです。 一休ではWindowsのタスクスケジューラからRundeckへ移行しました。Rundeckは未知のツールだったので苦労する点もありましたが、起こった問題は調査すれば解決策が見つかるものばかりだったので、移行は十分成功したと感じています。 おまけ GUIの日本語化 管理画面が英語だとわかりにくいので、一休では ガイドライン にしたがって、主要な部分だけですが日本語にしています。 部分的なローカライゼーションではありますが、ないよりマシ、なレベルではあるので、本家の方にも導入できるように PR を送っています。次のバージョンで取り込まれるかもしれません^ - ^ この記事の筆者について システム本部CTO室所属の 徳武 です。 サービスの技術基盤の開発運用、宿泊サービスの開発支援を行なっています。
この記事は 一休.comアドベントカレンダー2018 の7日目です。 こんにちは。スパ事業部 デザイナーの東根です。 約1年かけて10月25日にローンチした 一休.com スパ の 即時予約サービス をご紹介したいと思います。 SPAとは? 一休 .com スパの特徴・UIUXのポイント UIUXのポイント① 施設の魅力を伝える UIUXのポイント② プランの魅力を伝える UIUXのポイント③ 空き時間・予約時間をわかりやすく UIUXのポイント④ 予約の前に利用条件をしっかり説明 おすすめのホテルスパ15選 東京のホテルスパ 1. ザ・ペニンシュラ スパ 2. ザ スパ アット マンダリン オリエンタル東京 3. アマン・スパ/アマン東京 4. AO スパ&クラブ/アンダーズ 東京 5. スイス・パーフェクション スパ キオイ/ザ・プリンスギャラリー 東京紀尾井町 6. スパ&ウェルネス ジュール/ハイアット リージェンシー 東京 7. フォルトゥーナ/ホテルニューオータニ 8. SPA THE SAKURA/ザ・プリンス さくらタワー東京 9. 庵スパ TOKYO/ヒルトン東京お台場 東京から2時間以内で行ける旅館・リゾート 10. 赤沢スパ/赤沢迎賓館 11. GINYU SPA(ギンユウスパ)/箱根吟遊 12. 庵スパ KARUIZAWA/軽井沢マリオットホテル 大阪・京都のホテルスパ 13. CONRAD SPA/コンラッド大阪 14. MEGURI SPA & WELLNESS/インターコンチネンタルホテル大阪 15. ザ スパ アット フォーシーズンズホテル京都 おわりに SPAとは? エンジニアのみなさまは「SPA」と聞いてまず 「Single Page Application(シングルページアプリケーション)」 を思い浮かべたかもしれませんが、罠でした。すみません。 ここでご紹介するのは、 日帰り温浴やサウナのほかリラクゼーションマッサージの施術が受けられる 「 デイスパ (Day spa)」や「 ホテルスパ 」のことです。 最近では「サ道」「サウナー」も増えているそうですが、 温浴施設で身体を温めてからアロマトリートメントなどの施術を受けると、 血行やリンパの流れが良くなりより効果が高まります。 「Day spa」のwiki英語版によると、 A day spa is a business that provides a variety of services for the purpose of improving health, beauty and relaxation through personal care treatments such as hair, massages and facials. A day spa is different from a beauty salon in that it contains facilities such as a sauna, pool, steam room, or whirlpool that guests may use in addition to their treatment. ... 一休 .com スパの特徴・UIUXのポイント つまり、一休 .com スパが厳選するスパは、 街中にあるリラクサロンやエステサロンとは違い バス ・ サウナ などの温浴施設 プール や フィットネスジム などの運動施設 バスローブのまま寛げる リラクゼーションラウンジ などの施術前後に使える付帯施設があったり、 一般には流通しないこだわりの 化粧品ブランド を使っていたり、といった魅力をアピールしていく必要があります。 UIUXのポイント① 施設の魅力を伝える ・・・ 施設の魅力といえば、何と言っても 写真 です。 一休.comの他のサービスにも共通しますが、高級感のある美しい写真をできるだけ大きく見れるとユーザーにその施設を訪れたときのイメージを持ってもらえます。 なので、登録するのに写真は必須。また、お部屋のスペック(広さやスパスイートかどうか、完全個室であるかetc)や温浴施設の内容(ホットバス、コールドバス、ドライサウナ、スチームサウナ、温泉、岩盤浴があるか)、プールやフィットネスジムは水着やトレーニングウェアをレンタルできるかどうかをシンプルなテキストアイコンで表現しました。 UIUXのポイント② プランの魅力を伝える ここからはスマホ版のキャプチャで説明します。 プラン一覧およびプラン概要では、タイトル・利用できる付帯施設・滞在時間目安・料金がわかるようになっています。ちなみに、 タイトルにある分数 は 純粋なトリートメントの時間 で、カウンセリングや施術前後に使える付帯施設の利用時間を含んでいません。そのかわりに前後の付帯施設利用時間を含めた 全体の滞在目安の時間を別に表示 することで、どのくらい時間に余裕があればこのプランをしっかり体験できるかがわかります。 プラン内容ではトリートメントの詳細とともにどんなお部屋で施術受けるのか、付帯施設はそれぞれいつ(施術前か後か)どのくらいの時間、どんな施設を使えるのかわかります。 UIUXのポイント③ 空き時間・予約時間をわかりやすく プランが決まったら「予約時間を確認する」ボタンから空き時間を探してみます。 まずカレンダーで「日付」を選択、次に「 施術スタート時間 」を選びます。 時間はレストランの予約とは異なり 来店時間ではありません 。*印で「付帯施設は、施術スタートの○分前から利用できます」と表示していますので、それを参考に15分刻みの時間ボタンを選んで予約フォームに進みます。 UIUXのポイント④ 予約の前に利用条件をしっかり説明 注意文言に入れて読んでるハズとしてしまうのではなく 女性限定 、 マタニティ不可 のプランなどで利用できない条件を選ぶと 予約完了できないようになっています。 その他、施設スタッフから事前にユーザーへ質問・確認ができるようになっています。 おすすめのホテルスパ15選 ということで、 一休 .com スパの中から以下のポイントでおすすめの施設をセレクトして15選ご紹介します。 ホテルスパ、旅館・リゾートスパである 温浴施設利用付きプランがある 男性も女性も利用できる 東京のホテルスパ 1. ザ・ペニンシュラ スパ 東京・日比谷/ザ・ペニンシュラ東京 6階 ★こちら実際に体験してきました!私の中で満足度1位★ お風呂はなくサウナのみですが、しっかり暑いドライサウナと ほどよい暖かさのスチームサウナ、アロマの香りがするシャワーのほか アイスファウンテンがあるのでサウナーの方も満足いただけるはず。 アロマテラピーの施術がとっても気持ちが良いのはもちろん、 この写真のリラクゼーションルームではよく冷えたグレープフルーツジュースと マンゴージュースがいただけます。 またこのチェアではヘッドホンで音楽を聞くことができたり、読書灯で 雑誌を読むこともできるので、ネット断食にはぴったり。ゆったり寛げます。 2. ザ スパ アット マンダリン オリエンタル東京 東京・三越前駅直結/マンダリン オリエンタル 東京 37階 おそらく一休.com スパで一番お高いプランのあるホテルスパ。 でも、ローンチしてすぐにペアでXmas近くに予約が入りました。すごい。 とても人気なのでいつか行ってみたい! 完全個室の贅を極めたスパスイートのほか、お風呂やサウナからも眺望が楽しめます。 3. アマン・スパ/アマン東京 東京・大手町駅直結/アマン東京 34階 見てください、このプール!左端には富士山が見えます。 迷わずトップページのメインビジュアルに採用してしまいました。 プールのほかフィットネスジム、大浴場、トリートメントルーム毎の リラクゼーションエリアからも眺望が素晴らしいです。 4. AO スパ&クラブ/アンダーズ 東京 東京・虎ノ門ヒルズ/アンダーズ 東京 37階 白を貴重とした洗練された空間。木のぬくもり溢れるトリートメントルームからも高層階ならではの青空や皇居を望む素晴らしい眺望が楽しめます。 全トリートメントルームにはプライベートロッカー、シャワールームがあり他のゲストの目が気になりません。 ロッカーエリア併設の温浴エリアではお風呂、シャワーの他、男性はドライサウナと水風呂、女性はスチームサウナと360°シャワーを完備されています。 5. スイス・パーフェクション スパ キオイ/ザ・プリンスギャラリー 東京紀尾井町 東京・赤坂見附・永田町/ザ・プリンスギャラリー 東京紀尾井町 30階 世界中のセレブから愛されるスイス製植物性セルラー化粧品「スイス・パーフェクション」を使用する国内初の直営サロンで、エイジングケアの先端技術を結集したトリートメントを堪能できます。 写真はスパスイート・ペアルーム。角部屋のパノラマの眺望が素敵ですね。 スパスイートのプランではこのお部屋でアフターティーをいただくことができます。 6. スパ&ウェルネス ジュール/ハイアット リージェンシー 東京 東京・新宿西口/ハイアット リージェンシー 東京 28階 著名デザイナーが手掛けたスタイリッシュな空間は、木などの天然素材の温もりを感じさせながらコンテンポラリーな雰囲気が漂います。 トリートメント前にはプールやフィットネスジムも利用可能。写真のとおり、プールにはジャグジーやウォームルームを備え、プールサイドは居心地のよいデッキチェアとテーブルを配したウッドデッキとなっています。 7. フォルトゥーナ/ホテルニューオータニ 東京・赤坂見附/ホテルニューオータニ ガーデンタワー 3階 100℃前後の乾燥した高温で発汗を促すドライサウナ、50℃前後の温度で肌や体に負担がかかりにくいスチームサウナの2種類をラインナップ。 バイブラとジェットの機能を持つ浴槽や、体をゆったりと休められるラウンジも併設します。 プール・フィットネスジム付きのプランなら水着やトレーニングウェアを無料でレンタルできるので手ぶらで利用できます。 8. SPA THE SAKURA/ザ・プリンス さくらタワー東京 東京・品川/ザ・プリンス さくらタワー東京 B1階 都会のなかにたたずむ静寂と広い空間のなかで、日本古来の伝統的な香りに包まれるトリートメントルームです。 トリートメント後、アフターティーをいただけるラウンジは、竹林にたたずむような静けさに包まれながら、ゆっくりと過ごすことができる空間になっています。 9. 庵スパ TOKYO/ヒルトン東京お台場 東京・台場/ヒルトン東京お台場 5階 レインボーブリッジ、東京湾ビューが楽しめる大浴場は、プールを併設する水着着用エリアにあります。(有料レンタルあり) 海を眺めるテラスがついた開放的なフィットネスセンター。 各種マシーンを備え、ご宿泊者は無料で24時間利用できます。 東京から2時間以内で行ける旅館・リゾート 10. 赤沢スパ/赤沢迎賓館 静岡・伊東市/赤沢温泉郷 壮大な緑に囲まれた赤沢温泉郷内に、美と健康、リラクゼーションの場所として誕生。 フランス発祥の海洋療法「タラソテラピー」の発想をもとに生まれた海洋深層水のプールには、赤沢沖の深海800mから汲み上げた新鮮な海水を温めて使用されています。 エステエリアには、特徴の異なる3つのドーム(サウナ)と温・冷の足湯などを完備。 海洋深層水のプールで代謝をアップさせた後には、完全個室のアロマの香りに包まれたお部屋でトリートメントを受けられます。またご希望のお客様には、プラス料金で生花のバラを100輪浮べたバラ風呂をご用意することも可能です。 11. GINYU SPA(ギンユウスパ)/箱根吟遊 神奈川・箱根・宮ノ下 日本一予約の取れない宿として噂の旅館「箱根吟遊」その静寂な杜の空気に包まれた「Ginyu Spa」 トリートメント前後に ウォーターガーデンの向こうに望む雄大な自然を眺めていただきき源泉から湧き出る大地の力で、五感を解き放つことができます。 セラピストのテクニックにより、体の疲れをとることだけでなく、自分本来のバランスを整え健康や美しさへと導きます。 12. 庵スパ KARUIZAWA/軽井沢マリオットホテル 長野・軽井沢/軽井沢マリオットホテル B1階 こちらのお風呂は「小瀬温泉」泉質はナトリウム-炭酸水素塩泉。 “美肌の湯”とも呼ばれ、肌の不要な角質や毛穴の汚れを取ってくれる女性に嬉しい効能がたくさん。湯上がりがさっぱりするので、スポーツやアクティビティで汗を流した後などにもおすすめです。 和のエッセンスを随所に取り入れたヒーリング空間「庵スパ KARUIZAWA」。 日本人ならではの繊細で丁寧な施術と、日本由来の贅沢な粧材を使用し、心と身体を解きほぐしていきます。 大阪・京都のホテルスパ 13. CONRAD SPA/コンラッド大阪 大阪・中之島・梅田/コンラッド大阪 38階 淀川側を望むトリートメント全室からは、地上200mから望むスカイラインが刻一刻と表情を変える景色を眺めながら、安らぎの時間をお過ごしいただけます。シャワーブース、トイレ、パウダーコーナーが完備された完全プライベートな空間です。 温浴施設はサウナ、ジェットバス完備。 男性:ホットバス / コールドバス / ドライサウナ 女性:ホットバス / スチームサウナ ロッカールーム内にはダイナミックな景色を眺めながら、ゆっくりとした時間をお過ごしいただけるラウンジも併設。ドリンクコーナーには、季節のフルーツウォーター、温かいお茶が用意されています。 14. MEGURI SPA & WELLNESS/インターコンチネンタルホテル大阪 大阪・梅田・グランフロント大阪/インターコンチネンタルホテル大阪 4階 全室がクローゼット、シャワー、トイレ、ドレッサーを備え、お着替えからトリートメント後のお支度まで、全て個室内で行えるスパスイート。 温浴施設はまるで高級旅館の温泉を彷彿させる日本式浴場。 大都会の中心で味わう、想像を越えた極上のリフレッシュ&リラクゼーションジャーニーを楽しめます。 15. ザ スパ アット フォーシーズンズホテル京都 京都・東山周辺/フォーシーズンズホテル京都 インドアプールは、京都屈指のゆったりとした広さを確保しております。20メートルの広さに加え、2つのジャグジーも完備。リゾート感溢れる雰囲気のなか、贅沢なひとときをお楽しみください。 お風呂(温浴・冷浴)・サウナをお楽しみいただけます。 男性:ドライサウナ 女性:スチームサウナ 入念に選び抜かれた自然の力とラグジュアリーが融合したスキンケアブランドと、深いヒーリング効果をもたらす京都ならではの素材、それらを使用したトリートメントは、本物のくつろぎと新鮮な安らぎをもたらします。 おわりに みなさん毎日PC仕事やネットサーフィンで目を酷使しているので 肩こり・腰痛持ちの方も多いのではないでしょうか? (わたしは慢性的な肩こりの解消にアロマテラピーにはまりました。) たまには自分の身体もメンテナンスしないと高いパフォーマンスを出せません。 一休.com スパではただいまXmasまでのウィンターセールを開催中です。 この機会にぜひ心身を癒やす贅沢な体験をしてみませんか?