TECH PLAY

株式会社一休

株式会社一休 の技術ブログ

161

この記事は 一休.com Advent Calendar 2024 の1日目の記事です。 kymmt です。 当ブログ「一休.com Developers Blog」は、以前からはてなブログで運用しています。そして、今年からは執筆環境を少し改善しました。具体的には、GitHubを用いて記事の作成や公開ができるようにしました。 この記事では、当ブログの執筆環境をどのように改善し、ふだん運用しているかについて紹介します。 HatenaBlog Workflows Boilerplateの導入 従来は、執筆者が記事をローカルやブログ管理画面のエディタ上で書き、なんらかの方法でレビューを受け、公開するというフローでした。このフローで一番ネックになりやすいのはレビューで、Slack上でレビューが展開されがちになり、議論を追いづらいという問題がありました。 そこで、執筆環境の改善のために、はてなさんがβ版として公開しているHatenaBlog Workflows Boilerplateを利用して、記事執筆用のリポジトリを整備しました。 github.com これは、GitHub Actionsのはてなブログ用reusable workflow集である hatenablog-workflows を用いた、はてなブログ記事執筆用のリポジトリテンプレートです。 リポジトリを整備した結果、GitHub上ではてなブログの記事をMarkdownファイルとして管理したり、GitHub Actionsで原稿の同期や公開などの操作を実行できるようになりました。 現在は、記事執筆のフローは次のようになっています。 下書き用pull request (PR)作成actionを実行 手元にブランチを持ってきて執筆 ときどきコミットをpushしてはてなブログに同期し、プレビュー画面で確認 PR上でレビュー 公開できる状態にしてPRをmainにマージし、自動で記事公開 普段開発に携わっているメンバーはGitHubに慣れています。そのようなメンバーがPR上でブログ記事執筆やレビューができるようにすることでの執筆体験向上を図りました。とくに、レビューをPRで実施することで、あるコメントがどの文章に対するものなのか分かりやすくなる点は便利だと感じています。社内のメンバーからも、 ブログが GitHub で管理できるようになったのは、レビューの観点でホントありがたい という感想をもらっています。 記事のネタ管理 記事のネタはGitHubのissueとして管理しています。どの執筆PRでネタが記事になったのかや、ネタに関する細かいメモも記録しています。情報の一元化の観点では、スプレッドシートなどで別管理するよりは好ましいと思います。 校正 校正は textlint を利用しています。 pull_request トリガーでtextlintによる校正を実行するGitHub Actionsのワークフローを設定しています。ワークフローは.github/workflows/textlint.yamlに次のようなものを置いています。 name : textlint on : pull_request : paths : - "draft_entries/*.md" - "entries/*.md" jobs : run : runs-on : ubuntu-latest steps : - uses : actions/checkout@v4 with : fetch-depth : 0 - uses : actions/setup-node@v4 - run : npm ci - name : Add textlint problem matcher run : echo "::add-matcher::.github/textlint-unix.json" - name : Lint files added/modified in the PR run : | git diff --diff-filter=AM origin/main...HEAD --name-only -- '*.md' | xargs npx textlint --format unix .github/textlint-unix.jsonは次のとおりです。 { " problemMatcher ": [ { " owner ": " textlint-unix ", " pattern ": [ { " regexp ": " ^(.+):( \\ d+):( \\ d+): \\ s(.*)$ ", " file ": 1 , " line ": 2 , " column ": 3 , " message ": 4 } ] } ] } これでPR画面上にtextlintによる校正結果が表示されるようになります。 textlintで現在使っているルールは次のとおり最低限のものです。技術記事もテクニカルライティングの一種であるとは思いますが、Web上で気軽に読んでもらう類のものでもあるため、文体にある程度個性が出るのは問題ないと考え、そこまで厳しいルールにはしていません。 ja-no-abusage: 日本語の誤用チェック ja-unnatural-alphabet: 不自然なアルファベット検知 preset-japanese: 日本語文書向けのプリセット 読点の数、てにをは、文の長さなど レビュアー 記事ファイルが配置されるディレクトリのコードオーナーに技術広報担当者を設定して、担当者のレビューが通れば記事を公開してOK、というフローにしました。現在は2名の担当者で回しています。 HatenaBlog Workflows Boilerplateを利用しているという前提で、.github/CODEOWNERSを次のように設定しています。ここで @org にはGitHub orgの名前が、 team には技術広報担当者からなるteamの名前が入ります。 /draft_entries/ @org/team /entries/ @org/team この設定に加えて、リポジトリのbranch protection rulesとしてmainブランチでコードオーナーのレビューを必須にすることで、必ず技術広報担当者が目を通せる仕組みとしています。 とはいえ、各記事の内容については執筆者のチームメンバーのほうが深く理解していることも多いです。ですので、内容の正確性という観点ではチームメンバーどうしでレビューしてもらい、技術広報担当者は会社の名前で社外に公開する記事としてふさわしいかという観点でのチェックをすることが多いです。 余談: HatenaBlog Workflows Boilerplateへのフィーチャーリクエスト 上記のBoilerplateを用いてリポジトリを運用しているうちに、ほしい機能が1つ生まれたので、該当リポジトリのissueを通じて機能をリクエストしました。 github.com 具体的には、下書きPRを作成したとき、そのPRはdraft状態になっていてほしいというものです。GitHubの仕様上、PRがdraftからready for reviewになるとき、コードオーナーへレビュー依頼の通知が送られるようになっています。ですので、記事を書き始めるときのPRとしてはdraftとするのが自然と考えた、というものです。 結果、機能として無事取り込んでいただけました。ありがとうございました 1 。 staff.hatenablog.com おわりに この記事では2024年の当ブログの執筆環境について紹介しました。GitHubを起点とする執筆環境や自動化を取り入れることで、執筆体験を向上できました。この記事も紹介した仕組みで書きました。引き続き、このブログで一休での開発における興味深いトピックをお届けしていければと思います! 機能リリース後に、draft機能がGitHubの有料プランの機能であることに伴うフォローアップもしていただきました。ありがとうございました ↩
アバター
kymmt です。 11/30に開催されるRust.Tokyo 2024に一休はゴールドスポンサーとして協賛します。 rust.tokyo 一休でのRustの活用 一休では一休.comレストランにおいてRustの活用を進めています。昨年に当ブログで活用の様子を紹介した際は、当時開発が進んでいたレストラン予約サービスWeb UIのバックエンドにおけるユースケースだけに触れていました。 user-first.ikyu.co.jp それから1年弱経過した現在では、Rust活用の場はさらに広がっており、Rustを書いているメンバーも増えてきています。 Rust.Tokyoのゴールドスポンサー 2024年はRustでWebサービスを開発するのがますます現実的な選択肢として挙がるようになった年だったのではないかと思います。たとえば、RustでWebアプリケーションを開発するための本が複数発売されるなど 1 、学習リソースは着実に揃ってきています。このような環境のなかで、一休もRust採用企業として活用事例の共有などを通じてコミュニティを活性化させることが重要だと考えています。 以上のような状況に基づいて、一休はこのたびRust.Tokyo 2024にゴールドスポンサーとして協賛させていただくことになりました。当日は会場で スポンサーセッション登壇 ブースの出展 をやります。 スポンサーセッションでは、トラックAで15:25から 「総会員数1,500万人のレストランWeb予約サービスにおけるRustの活用」 と題してkymmtが発表します。2024年現在の一休.comレストランにおけるRust活用の様子のスナップショットとして、システムの設計や技術的なトピックについて紹介する予定です。 また、会場ではブースを出展する予定です。技術広報チームを中心に、一休.comレストランのエンジニアとしてkymmtが、ほかにも宿泊予約サービスの一休.comのエンジニアとEMがブースに参加する予定です。一休のサービス開発の様子について興味があるかたはぜひお越しください。 おわりに 11/30に一休はRust.Tokyo 2024にゴールドスポンサーとして協賛します。参加者のかたは当日会場でお会いしましょう! https://bookclub.kodansha.co.jp/product?item=0000398182 や https://www.shoeisha.co.jp/book/detail/9784798186016 など ↩
アバター
kymmt です。 今月は 11月16日に京都で開催される TSKaigi Kansai 2024 11月23日に東京で開催される JSConf JP 2024 と、JavaScript/TypeScriptに関するカンファレンスが2つ開催されます。今年は、一休.comレストランのフロントエンドアーキテクトを務めるエンジニア恩田 ( @takashi_onda )がこれらのカンファレンス両方に登壇します。 1週違いで開催されるそれぞれのカンファレンスでは、一休.comレストランのフロントエンド開発をきっかけとする内容のトークテーマを携えて登壇します。この記事では、現在絶賛発表準備中の本人からのコメントも交えつつ、発表内容について紹介します! TSKaigi Kansai 2024: 構造的型付けとserialize境界 kansai.tskaigi.org TSKaigi Kansai 2024は、TypeScriptをテーマとして2024年5月に東京で開催されたTSKaigi 2024から派生した地域型カンファレンスです。 本カンファレンスでは、16:00-16:30に「カミナシ堂」会場で「構造的型付けとserialize境界」というタイトルで恩田が発表します。 以下、恩田からのコメントです。 京都在住なので、地元でお話しできるのが楽しみです。一休では僕のように関西やその他の地域に在住しているエンジニアも複数います。発表を聞いていただき興味を持たれた方は、お気軽にお声がけください。 JSConf JP 2024: Reactへの依存を最小にするフロントエンドの設計 jsconf.jp JSConf JP 2024は、Japan Node.js Association様により運営されている、JavaScriptをテーマとする大規模カンファレンスです。 本カンファレンスでは、11:10-11:40にトラックBで「Reactへの依存を最小にするフロントエンドの設計」というタイトルで発表します。この発表は、本ブログで以前公開した記事「React / Remixへの依存を最小にするフロントエンド設計」をもとに、今回あらためてJSConfでお話しするものです。 user-first.ikyu.co.jp 以下、恩田からのコメントです。 一休レストランのフロントエンド開発では、疎結合な設計を心がけていて、ほとんどのロジックは Vanilla JS だけで完結しています。そのような書き方を可能とする、バックエンド開発で培われた知見をフロントエンドに適用する手法についてお話ししたいと思います。 おわりに この記事では、TSKaigi Kansai 2024とJSConf JP 2024でのエンジニア恩田 (@takashi_onda)の発表内容について紹介しました。 参加者の方は、それぞれの会場でぜひ発表を聞きに来ていただければと思います!
アバター
CTO室プラットフォーム開発チームのいがにんこと山口( @igayamaguchi )です。 先日、Vue Fes Japan 2024が開催され、一休は登壇とスポンサーをしました。その紹介をします。 Vue Fes Japan 2024が開催 10月19日(土)に日本最大級の Vue.js カンファレンス、 Vue Fes Japan 2024 が開催されました。一休は当カンファレンスでプロポーザル採択による発表と、ランチスポンサーとしてランチセッションでの発表を行いました。この記事ではその発表の概要を紹介します。 一休で行った発表は2つです。 Vue.js、Nuxtの機能を使い、 大量のコピペコードをリファクタリングする Nuxtベースの「WXT」でChrome拡張を作成する Vue.js、Nuxtの機能を使い、 大量のコピペコードをリファクタリングする 1つ目の発表は自分がプロポーザルを送り採択された発表です。一休.com、Yahoo!トラベルで発生したコピペ問題の原因と解決策の話です。 speakerdeck.com 一休.com、Yahoo!トラベルでは大量のコピペコードが発生していました。それにより何かを変更するときに作業がN倍になってしまい、開発スピードが落ちていました。 今回の発表ではこの問題の原因を紐解き解決していった話をしました。 コピペ問題といっても原因は様々です。ワークフローに問題があったり、コピペが発生せざるを得ない技術的背景があったり。それぞれに対応しています。 内容をざっくり抜き出すと、以下のようになります。 開発ワークフローに問題がありUIのコピペが横行していたので、ワークフローの見直し、UIコンポーネントの作成 デザイン差分を小さくしてコードの統一 Option APIでロジックが共通化できていなかったので、Composition APIを導入して共通化、さらには責務わけのためのコンポーネント分割 詳細は発表資料をご覧ください。 Nuxtベースの「WXT」でChrome拡張を作成する 2つ目は新規プロダクト開発部の星野によるランチセッションでの発表です。「WXT」というOSSを使用し、Chrome拡張を作成してみた話です。 speakerdeck.com 何かを便利にしようとChrome拡張を使おうと思った時、自分の求めるChrome拡張が存在しなかったり、信用できない開発者のChrome拡張を使用したくないということがあります。この問題を解決するために「WXT」というOSSを使用してChrome拡張を開発した話をしました。 WXTはNuxtベースで開発できる拡張機能開発フレームワークです。Chrome拡張を開発するにはいくつか設定ファイルを作成したりChrome拡張特有の作業が必要でとても面倒なのですが、WXTを使用することでこの作業がとても簡単になります。しかもNuxtを使用している人には分かりやすいインターフェースになっており、Vue.js/Nuxtを触っている開発者にはとても開発しやすいものになっています。さらにはVue.jsだけでなくReactなど他のUIフレームワークを使用して開発することもできます。 自分でChrome拡張を作りたいという方はこの発表を参考にWXTを試してみてください。 おわりに Vue Fes Japan 2024での一休の発表を紹介させていただきました。 Vue Fesですが、発表者、かつ一視聴者として参加してみて、すごく熱があるカンファレンスでした。参加者も多く、Vue.jsだけでなくViteの話も聞けて、さらにRolldownやoxcの話を開発者から聞けるという場は国内ではなかなかないと思います。そのような場で発表できたこと、スポンサーをできたことはとてもありがたかったです。 2025年も開催されるとのことなのでまた楽しみです。 一休では、ともに良いサービスをつくっていく仲間を募集中です。 hrmos.co カジュアル面談も実施しているので、お気軽にご応募ください。 www.ikyu.co.jp
アバター
はじめに kymmt です。 先日2024年9月18日に、「事業会社のサービスを支える基盤開発トーク」と題してイオンスマートテクノロジー(以下AST)さんと合同で技術イベントを実施しました。 ikyu.connpass.com イベントでは、各会社の事業を支える基盤プロダクトの開発や運用における苦労や工夫について登壇者の方々にお話しいただきました。 この記事では、このイベントの様子や発表の内容を紹介します。なお、X(旧Twitter)でも #ikyu_aeon というハッシュタグで当日の様子がご覧になれます。 会場 会場は、一休のオフィスも入っている東京ガーデンテラス紀尾井町内の LODGE でした。 当日は、一休/ASTのメンバーも合わせて計30人程度の方にご来場いただきました。また、イベント開始前に、ASTのもりはやさんからトップバリュ製品やイオンで販売中のお菓子を来場者向けに提供していただきました。 #ikyu_aeon わいわい!!トップバリューとイオンで買ってきたお菓子をデプロイしました!! pic.twitter.com/p3qZmhdD2K — もりはや (@morihaya55) September 18, 2024 発表 『歴史あるプロダクトにマイクロサービスを導入するプロセス』 1つ目の発表は、一休の菊地さんによる『歴史あるプロダクトにマイクロサービスを導入するプロセス』でした。 一休のプロダクトを横断して利用されるマイクロサービス群(社内では一休プラットフォームと呼んでいます)を開発するチームでは、個別のプロダクトへのマイクロサービス導入も進めています。長い歴史を持ちコードベースが複雑なプロダクトにマイクロサービスを導入するには、技術課題の解決にとどまらず、積み重なった仕様を解きほぐして関係者と調整を進める必要もあります。 この発表では、そのような課題に直面した一休.comレストランへのポイント管理マイクロサービス導入の事例を紹介いただきました。発表のなかで、デッドコード削除や仕様の調整のような地道な取り組みから、プロダクトとマイクロサービスを疎結合に保ちつつ連携する仕組みを解説していただきました。 『1000万DLを超えたiAEONアプリ:完全停止を防ぐ耐障害性と信頼性の設計』 2つ目の発表は、ASTの范さんによる『1000万DLを超えたiAEONアプリ:完全停止を防ぐ耐障害性と信頼性の設計』でした。 iAEONアプリは実店舗のレジ前で会員バーコードを表示するようなユースケースを想定しており、障害が発生して利用不可になることを可能な限り避けたいという要件があります。一方で、複雑なバックエンド側の一部で発生した障害がアプリに伝播することで、アプリが利用不可になることも過去あったとのことでした。 この発表では、上述したような特性を持つiAEONアプリの耐障害性を高めるための取り組みについてお話しいただきました。とくに、デバイス上のキャッシュなどを活用してフロントエンドであるiAEONアプリ上のエラーをできるだけ局所化する手法について詳しく解説いただきました。App StoreやGoogle Play StoreにおけるiAEONアプリの評価が結果的に向上したとのことで素晴らしいと感じました。 『宿泊予約サイトにおける検索と料金計算の両立』 3つ目の発表は、一休の鍛治さんから『宿泊予約サイトにおける検索と料金計算の両立』でした。 宿泊予約サイトである一休.comやYahoo!トラベルでの料金に関する業務ルールは複雑です。また、ユーザーが宿泊したい部屋をさまざまな条件に基づいて検索するときに、その複雑な業務ルールで算出される料金の検索サーバであるSolrで計算する必要があります。 この発表では、そのような一休.comの検索要件の複雑さの解説や、要件を満たすためにSolrのプラグイン機構を活用していることについて解説いただきました。SolrのプラグインはJavaで開発することができ、Javaの機能を活用できるので、Solrクエリをメンテナブルに保ちつつ、複雑な料金計算を検索時に実行できているとのことでした。 『SRE改善サイクルはチームを超えて - ダッシュボードを眺める会の取り組み』 4つ目の発表は、ASTのもりはやさんから『SRE改善サイクルはチームを超えて - ダッシュボードを眺める会の取り組み』でした。 New Relicなどを活用したシステムメトリクスを概観できるダッシュボードは、非常時に障害の原因を探るためのものとして非常に便利です。一方で、定点観測することで、日々のシステムの状態や、障害になり得る変化を見つけることができます。 この発表では、ASTのSREチームを中心にダッシュボードを定期的に眺める会を設けることで、メトリクスの変化や改善ポイントを発見して深掘りする機会を意図的に作り出す取り組みについてお話しいただきました。システムの信頼性を高める機会を作り出すのはもちろん、会を通じて「ザイオンス効果(単純接触効果)」でチームビルディングにも寄与している点が素晴らしいと感じました。 おわりに 「事業会社のサービスを支える基盤開発トーク」の様子について紹介しました。 各社のサービスの基盤プロダクト開発について社内外のエンジニアの工夫や知見が聞ける、とてもおもしろいイベントになりました。ご来場いただいたみなさま、ありがとうございました! 一休では、ともに良いサービスをつくっていく仲間を募集中です。 hrmos.co カジュアル面談も実施しているので、お気軽にご応募ください。 www.ikyu.co.jp
アバター
コーポレート本部 社内情報システム部 兼 CISO室 id:rotom です。 10/12(土) にハイブリッド形式で情シス向けのテックカンファレンス 「Business Technology Conference Japan 2024(BTCONJP 2024)」 が開催されます。 btcon.jp btajp.connpass.com 昨年オンライン開催された BTCONJP 2023 では私が登壇者として 本社を東京ガーデンテラス紀尾井町へ移転し、オフィスファシリティ・コーポレートIT を刷新した話 というテーマで発表させていただきましたが、今年は core staff として運営に参画しており、所属企業である一休はブロンズスポンサーとして協賛しております。 Identity, DX & AI, Device Management, Zero Trust, Cyber Security, Business Technology の6つのテーマに沿った12のセッションや、イオン / イオンスマートテクノロジー CTO の 山﨑 賢氏による基調講演を用意しています。 詳細は以下のプレスリリースをご覧ください。 prtimes.jp オフライン会場は一休 本社も入居する、東京ガーデンテラス紀尾井町 紀尾井タワーのLINEヤフー株式会社 本社です。 map.yahoo.co.jp LODGE での懇親会も予定しておりますので、ぜひ現地でご参加ください!
アバター
はじめに こんにちは。宿泊プロダクト開発部の宮崎です。 みなさん、生成 AI 使ってますか? 近年、AI の進歩はめざましく、文章生成や画像生成はもちろん、動画生成も実用的なレベルで出来るようになっています。 ChatGPT が話題になったのが 2022 年の 11 月なので、たった 2 年足らずでここまで来ているという事実に少し恐ろしくもありますね。AGI(汎用人工知能)の実現もそう遠くないのかもしれません。 一休でも AI 技術は注目していて今年の 6 月に、まさに生成 AI を使ってホテル検索システムの改善を行いました。 この記事では、その時に学んだ プロンプトエンジニアリングの重要性 について書いていこうと思います。 生成 AI を使ったホテル検索システム 今回我々が実装したのはフリーワード・文章でもホテルを検索できるシステムです。 以下のようなユーザーの自由な入力に対して、適切なホテル・旅館を返したいというのが目的でした。 福井でおいしいご飯が食べられる宿 沖縄 子供と楽しめる宿 静岡で犬と泊まれる宿 今までは、このような入力の場合、形態素解析をして検索を行っていました。しかしその場合、「子供と楽しめる」だと「子供」「楽しめる」に分割して検索するので、本来の意味「子供と楽しめる」とは少し結果が違っていました。 よって文章の入力に対しては意味を考慮して検索ワードを切り出す必要があります。 私たちが行ったのは、AI にユーザーの入力を解析させ意味毎に区切り、検索条件 area, condition に分解させるというものです。以下に具体的な例を示します。 例: ユーザーの入力:「銀座の夜景が楽しめる」 ↓ これを AI で解析して分解する。 求める出力: { area:銀座, condition:[夜景が人気] } 従来は「銀座」「夜景」「楽しめる」で分解されていたのが、「銀座」「夜景が人気」に分解する。 このようにして json 形式で返してくれれば、既存のシステムで検索できます。この分解を生成 AI にやってもらおうと考えました。 実際の検索結果 具体的にどんな検索になったのか、実際にリリースしたものを使ってみましょう。 まず、行きたい施設について文章を入力し、検索ボタンを押す。 今回は「 東京近郊でサウナがあって禁煙 」と入力してみました。 以下の結果が表示されます。 東京近郊の都市で、サウナが人気、そして禁煙で検索されていますね。 このように、ユーザーの入力ワードをシステムで検索できるよう AI で分解するというのが今回行った改善です。 実装における課題 ただ、この仕組みを実装する上で、1 つ大きな課題を抱えていました。 それは単純な話で、コストの問題です。 当時、私たちはこの分解に GPT-4 を使っていました。 理由は他の AI と比較して GPT-4 が一番結果が良かったからです。(このプロジェクトは去年の秋ごろに始まったため、選択肢があまりなかったというのもあります。) しかし、GPT-4 だとコストが高く、1 回の検索に 10 円以上かかることが分かりました。 どうしてそれほどコストがかかるのかというと、プロンプトが長いからです。 一休では、例えば近畿というエリアは「近畿(大阪以外)」と「大阪」という風に分けられています。 このような一休独自のエリアの分け方や、用語を AI が解釈できるようにプロンプトに詰め込んでいるので、どうしてもその分プロンプトが長くなってしまうのです。 プロンプト例: 扱うエリア名は以下です。 - 北海道 - 札幌 - 函館・湯の川・大沼 - 旭川・富良野・稚内 - ... - ... <<以下、地名がずらりと後に続く…>> 解決案 GPT-4 ではコストがかかる。プロンプトを短くするのも難しい。 そこで考えたのは、GPT-3.5 Turbo を使うことでした。 これを使えば GPT-4 の 1/60 の値段になるので、コストの問題は簡単に解決できます。 ただし、これが最初からできていれば苦労はしません。 例えば「大阪でサウナと温泉が楽しめる」なら、GPT-4 は { area:大阪, condition:[サウナ,温泉] } と出力されるのに対して、GPT-3.5 Turbo は以下の様に間違うことが多かったです。 いくつかのキーワードが欠けている 例: {area: 大阪,condition: [サウナ] } 入力がそのまま入ってしまっている 例: {area: 大阪,condition: [サウナと温泉が楽しめる] } こういった間違いに対して、最初はファインチューニングで精度を上げようとしました。 ファインチューニングは既存のモデルに追加でデータを学習させて、微調整するというものです。 参考: https://platform.openai.com/docs/guides/fine-tuning しかし、これも上手くいきませんでした。 例えば condition の解釈精度をチューニングすると area 解釈精度が悪化したり、逆もまた然りであちらをたてればこちらがたたず状態に…。 結果的にプロンプトを 2 つに分けて area 用と condition 用、のようにしてみましたが、精度の割にはコストが 2 倍に増えてしまって断念しました。 ※補足:精度の検証には新しくデータセットを作成しました。入力キーワードと、どのように分解して欲しいかの回答のセットです。これらを用いて生成された回答の一致率を測定しています。 プロンプトエンジニアリングをしよう 頼みの綱のファインチューニングもうまくいかず、頭を悩ませていた時のことです。 私は OpenAI 社のファインチューニングのドキュメントを読んでいました。チューニング精度を上げるための秘訣が書かれていればと思ったのです。 するとその時、以下の一文が目に留まりました。 Fine-tuning OpenAI text generation models can make them better for specific applications, but it requires a careful investment of time and effort. We recommend first attempting to get good results with prompt engineering, prompt chaining (breaking complex tasks into multiple prompts), and function calling, 引用:Fine-tuning ( https://platform.openai.com/docs/guides/fine-tuning/fine-tuning ) 簡単に言うと、ファインチューニングの前にまずはプロンプトエンジニアリングやプロンプトチェイニング(タスクを複数に分割する)をしましょう、ということです。 私はこの時まで、プロンプトエンジニアリングを軽視していました。プロンプトの修正で出力が改善されれば苦労しないだろうと。 ですが、このドキュメントを見ながら自分の今のプロンプトを見返すと、推奨されている書き方をほとんどしていないことが分かりました。 よって、半信半疑でありながらも、とりあえずドキュメントに従ってプロンプトエンジニアリングを行いました。 すると驚くべきことに、GPT-3.5 Turbo でも GPT-4 と同等の精度を出すことができたのです。 難しいことは何もしていません。ただ、プロンプトを修正しただけです。 それだけでコストが 1/60 になり、今年の 6 月にこの機能をリリースすることができました。 まず最初にやるべきはプロンプトエンジニアリングだったというわけです。 ※補足:現在は GPT-3.5 Turbo ではなく、 GPT-4o mini を使っています。 プロンプトエンジニアリングについて ここからは具体的にどういったプロンプトをどう修正したのかについて書きます。 OpenAI のプロンプトエンジニアリングのガイドラインを参考に以下の修正を行いました。 参考: https://platform.openai.com/docs/guides/prompt-engineering はっきりと簡潔な指示を出す 簡潔な言葉で何をしてほしいかを指示しました。 ★ 修正前のプロンプト 現在、日本のホテルを検索するシステムを開発しています。システムは以下の機能を持っています。 <<システムの説明 ※ここでは省略。実際は 50 行くらい使ってホテルや宿泊条件など、機能の詳細な説明をしていた。>> ここで、以下のユーザーのホテル検索クエリに対して、上記の機能を使って絞り込むのが適切だと判断したのなら、その条件を教えてください。 ★ 修正後のプロンプト あなたは優秀なクエリ分析システムです。 ユーザーのホテル検索クエリが与えられますので、それを分析して、どんな条件で検索すればいいのかをjson形式で回答してください。回答には、三重引用符で提供されたデータのみを使って答えてください。 このヒントは以下に書かれていました。 https://platform.openai.com/docs/guides/prompt-engineering/tactic-include-details-in-your-query-to-get-more-relevant-answers ステップバイステップで考えさせる 以前は 1 つの指示+複数のルールで指示していましたが、これをステップバイステップの指示に切り替えました。 ★ 修正前のプロンプト ここで、以下のユーザーのホテル検索クエリに対して、上記の機能を使って絞り込むのが適切だと判断したのなら、その条件を教えてください。 その際、以下のルールを守ってください。 1. JSON形式で教えてください。 2. たとえば、ユーザーのホテル検索クエリ「ペットと泊まれる 新潟」の場合、 {area: ["新潟"], condition: ["ペット"]} というJSONを期待します。 3. ... 4. ... ★ 修正後のプロンプト 以下の思考フローを用いて、ステップバイステップで絞り込んでいくことを想定してください。 ステップ1:ユーザーのクエリを確認し、エリアに該当するものを抽出してください。 ステップ2:ステップ1で抽出したワードを出力のareaのキーに追加してください。 ステップ3:... ステップ4:... このヒントは以下に書かれていました。 https://platform.openai.com/docs/guides/prompt-engineering/tactic-specify-the-steps-required-to-complete-a-task セクションを区別する エリア名称や、用語一覧を渡しているのですが、ここを引用符などで区切りました。 ★ 修正前のプロンプト 以下にシステムで扱える用語一覧を示します。 * サウナ * 禁煙 * 喫煙 * 室内プール * ... * ... ★ 修正後のプロンプト 以下にシステムで扱える用語一覧を示します。 """ * サウナ * 禁煙 * 喫煙 * 室内プール * ... * ... """ このヒントは以下に書かれていました。 https://platform.openai.com/docs/guides/prompt-engineering/tactic-use-delimiters-to-clearly-indicate-distinct-parts-of-the-input 例を付ける 出力の例を付けました。上述したように、以前はルールを使って回答を制御していて、1 つか 2 つしか例を付けていませんでした。 それを以下の様に例を増やしました。 ★ 修正後のプロンプト 以下は、出力のjsonの例です。 例1:「絶景と温泉」というユーザーのクエリの場合、以下の条件を出力してください。 { condition : ["絶景","温泉"] } 例2:「浜松でうなぎ料理を楽しめる旅館」というユーザーのクエリの場合、以下の条件を出力してください。 { area:["浜松"] condition: ["うなぎ料理"] } 例3:... 例4:... このヒントは以下に書かれていました。 https://platform.openai.com/docs/guides/prompt-engineering/tactic-provide-examples 以上がメインとなるプロンプトの修正です。これらは本当にガイドラインに従って足りていないものを書き換えただけです。 それだけで約 35% の精度改善となったので、同じような悩みを抱えている方はぜひ試していただきたいです。 おわりに 今回、プロンプトエンジニアリングの効果を十分に理解することができました。 ただ、考えてみれば、人が人に仕事を任せる時は当然相手の立場やスキルを考えて指示を出します。 これをどうして AI でやらないのか。 AI も AI の理解しやすい命令の構造があり、それをプロンプトエンジニアリングで最適化する。 人の気持ちを考えるように AI の気持ちを考える。 いい仕事をするためには人も、 AI も関係なく、相手を思い遣る心が大切なのだと思いました。
アバター
CTO室プラットフォーム開発チームの山口( @igayamaguchi )です。 この度、一休とイオンスマートテクノロジー合同で技術勉強会「事業会社のサービスを支える基盤開発トーク」を開催します! ikyu.connpass.com このイベントでは 一休.com 、 Yahoo!トラベル 、 一休.comレストラン を運営する 一休 iAEONアプリ 等を開発・運営する イオンスマートテクノロジー 両社のシステムを支える基盤の開発についてお話しします。 様々なお話をしていただく予定ですが、一休から1つ「歴史あるプロダクトにマイクロサービスを導入するプロセス」について紹介します。 この発表では一休.comレストランにマイクロサービスを導入したときの取り組みについてお話しします。一休.comレストランはWebサービスとして18年もの歴史があるサービスです。歴史の長いサービスになると全てが新しい技術でできているわけではなくレガシーなアーキテクチャのものもあります。それらを考慮しながらマイクロサービスを導入していった泥臭い取り組みについてお話しする予定です。 他にも以下のようなトークを予定しています。 宿泊予約サイトにおける料金計算の格闘 1000万DLを超えたiAEONアプリ:完全停止を防ぐ耐障害性と信頼性の設計 SRE改善サイクルはチームを超えて - ダッシュボードを眺める会の取り組み それぞれの開発で取り組んでいる課題についてリアルな話をお送りします。どのトークもとても面白い内容になると思います! そして今回はオフラインで開催します。場所は一休本社も入居する東京ガーデンテラス紀尾井町17階にあるLODGEです。 https://www.z-lodge.com/ www.z-lodge.com 懇親会もありますので、ご来場いただいた方はぜひ登壇者や他の参加者の方と交流していただければと思います。 ぜひお越しください!
アバター
CTO 室の恩田( @takashi_onda )です。 一休レストランのフロントエンドアーキテクトを担当しています。 Intro 一休レストランでは、以前ご紹介したようにフロントエンドで React / Remix を利用しています。 user-first.ikyu.co.jp 一方、設計方針としては、React / Remix への依存が最小になるように心掛けています。 今日は、そんな一見矛盾するような設計方針について、ご紹介したいと思います。 この記事を読んでいただき Remix に興味をもたれたら、明後日 2024/8/7(水) 19:00〜 のオンラインイベント offers-jp.connpass.com にもご参加いただけると嬉しいです。 この記事でご紹介している疎結合なフロントエンドアーキテクチャを実現する Remix の魅力についてお話します。 なぜ依存を最小にするのか? React / Remix を使っていて依存しないってどういうこと?と疑問を持たれる方も多いでしょう。 まずはその動機からご説明します。 フレームワークやライブラリより寿命が長いプロダクトは珍しくありません。 栄枯盛衰の激しいフロントエンドでは、そういったサービスの方がむしろ多いのではないか、とも感じます。 一休レストランのサービス開始は2006年です。 同じ2006年に jQuery 1.0 がリリースされました。 まだ、フロントエンドという言葉が生まれる前の時代です。 一休レストランは、そんな時代から二度のリニューアルを経ながら(今は二度目の真っ最中)継続しているサービスです。 これからも広く使っていただけるよう日々、開発を進めています。 したがって、将来、エコシステムが大きく変わったとしても、その変化に少ない労力で追随し続けられることが重要となります。 フロントエンド領域も成熟してきているので、今後は、エコシステムが激しく入れ替わるような状況はもう訪れないかもしれません。 それでも、フレームワークやライブラリレベルでみると、メジャーバージョンアップは数年単位で見れば避けられず 1 、その中には破壊的変更を伴うものもあるでしょう。 バックエンド設計に倣う バックエンド設計では、フレームワークへの依存を最小化する、という考え方が古くからあります。 すなわち、バックエンドの世界では、指針となる原則や、依存を最小にして疎結合にする技術が確立しているのです。 偉大な先人達の肩の上に乗るため、その知見はどういったものなのかを確認したいと思います。 依存性逆転の原則 (Dependency Inversion Principle) ボブおじさんこと Robert C. Martin が提唱した SOLID 原則をご存知の方は多いと思います。その SOLID 原則の D 、依存性逆転の原則がフレームワークへの依存を最小化するための根幹の考え方になります。 (少々長くなるので、ご存知の方は読み飛ばしてください) 簡潔に説明すると、 高レベルモジュールであるドメイン層が自身の必要とする抽象 (interface) を定義し 低レベルモジュールにあたるインフラ層がその抽象を実現する詳細 (class) を実装する ように設計すべし、という原則です。 高レベルモジュールが低レベルモジュールに直接依存するのではなく、低レベルモジュールが、高レベルモジュールの要求しているインターフェースの実装を提供する。 言い換えると、低レベルモジュールが高レベルモジュールの抽象に依存することから、依存性逆転の原則と呼ばれます。 少し雑に言えば、高レベルモジュールにあたるのが我々の開発するプロダクトです。 低レベルモジュールがフレームワークやライブラリと、それらを使ったインフラ層に相当します。 よく用いられる Repository の例で説明します。 プロダクトが知っているのは自身で定義した Repository の interface のみです。 プロダクトが Repository の実装を構成するライブラリやフレームワークの API を直接呼ぶことはありません。 Repository の interface もフレームワークが要求する interface から独立しています。 フレームワークへの依存が疎結合になりました。 腐敗防止層 (Anti Corruption Layer) 他にもバックエンドで培われた依存を最小化するテクニックとして、腐敗防止層は欠かせません。 DIP に比べると、シンプルでわかりやすいと思います。 ライブラリや外部のサービスを利用するときに wrapper を挟み、変更の影響をその wrapper に閉じこめるという手法です。 インターフェースや実装が安定しなかったり、将来交換する可能性が考えられるような場合に、特に有効です。 フロントエンドにどう適用するか? 「余計なことをしない」素直なフレームワークが大前提 現代のフロントエンド開発では、(メタ)フレームワークを利用することが一般的です。 まず、フレームワークに求められる要件を考えたいと思います。 「余計なことをしない」とは? 逆を考えるとわかりやすいかもしれません。 全部面倒を見てくれる、手厚いけれど、複雑な規約があり、その裏側がどうなっているかわからないフレームワークを思い浮かべましょう。フロントエンド・バックエンドは問いません。過去を振り返れば、だいたいどの言語にも一つはありそうです。 このような全部入りフレームワークは、 そのフレームワークが想定しているユースケースの範疇でコードを書けていて、 運用面でも安定しているのであれば、 とても優れた開発効率や開発体験が得られるでしょう。 ですが、フレームワークが用意してくれているレールから外れないと実現が難しい要件は往々にして存在します。 そんな要件に遭遇してしまうと、フレームワークそのものに手を入れる以外の回避策しか見つからないことが多く、詰みます。 2 また、そんな要件に出会わなかったとしても、そのフレームワークに密結合状態で依存しているだけで弊害は存在します。 たとえばメジャーバージョンアップで破壊的な変更が加えられたとき、その変更に追随するための苦労は過去にそのようなフレームワークを使ったことがある方なら想像に難くないと思います。 Remix の採用 さて、逆の状況を踏まえた上で、あらためてフロントエンドの世界で「余計なことをしない」フレームワークの条件を考えてみましょう。 標準 API の尊重、ブラックボックスがない、なだらかなアップデートパス、実装がシンプル、といった条件が挙げられます。 現時点において React ベースでは Remix が条件を最も満たしていると判断し、以前ご紹介したように、 一休レストランでは Remix に乗り換える ことを決断しました。 その上でバックエンド設計の原則に従い Remix API への依存は最小限になるよう努めています。 疎結合なアーキテクチャを実現するためには、逆説的ではありますが、フレームワークがそれを可能とする作りになっていることが重要なのです。 Remix だと、なぜそれが可能になるのかは、最初にご紹介した オンラインイベント でもお話する予定です。 React 非依存の Vanilla JS だけで使えるライブラリを選ぶ 一休レストランでは 以前ご紹介した XState に加えて Jotai と TanStack Query を利用しています。 いずれも React に依存していないという共通点を持ちます。 TanStack シリーズは Vanilla JS のコアと各フレームワークへのアダプタで構成されています。 Jotai は TanStack シリーズのようにフレームワーク独立を謳っているわけではなく、React で使うことを前提とした状態管理ライブラリですが、公式サイトにも Now with a store interface that can be used outside of React. とあるように v2 からは、Vanilla JS で利用できる状態管理ライブラリとしての側面も持つようになりました。 コアの Vanilla JS 部分と React アダプタが別れているため、他のフレームワークと組み合わせて使うことが可能です。 たとえば Vue や Svelte , SolidJS のアダプタを作っている方もいるようです。 React / Remix での DIP 具体例として、一休レストランの soft navigation 機能をご紹介します。 一休レストランでの soft navigation では Remix の useLocation や useNavigate を直接使っていません。 アプリ側で、 useLocation に対応する useNavigationContext useNavigate に対応する useEventNavigate というカスタムフックをそれぞれ定義しています。 DIP の観点から見ると、 useNavigationContext , useEventNavigate の signature が、高レベルモジュールの定義する抽象に相当します。 具体的に見てみましょう。 export function useNavigationContext (): NavigationContext { // implementation } 関数が first-class citizen なので signature (interface) さえ変わらなければ、 type UseNavigationContext = () => NavigationContext export const useNavigationContext: UseNavigationContext = createUseNavigationContext() 極端な例ですが、呼び出し元のコードを変えずに、後から実装を動的に差し替える形にすることも可能です。 3 さて、これらのフックは Remix の useLocation や useNavigate に加え Jotai, XState を利用して実装しています。 このフックの実装自体が、DIP における低レベルモジュールが実装する詳細にあたります。 もちろん、アプリで定義しているこれらのフックは、単に DIP を実現するためだけに導入しているわけではありません。 あくまで、サービス固有のユースケースを実現するための機能を付加した抽象層になっています。 4 ユースケースを具体的に見てみましょう。 ユーザーが一休レストランで予約をするとき、人数日時などの予約条件や空席状況に応じて、次に表示するステップを切り替えたい場面があります。 このステップ、すなわち遷移先の切り替えは、固定的なナビゲーションではなかなか実現が難しい機能です。 また、レストランの空席状況は刻々と変化するので、その時点の状況に応じた動的な画面遷移が必要となります。 このようなユースケースに対応するため、ナビゲーションロジックは XState を使ったステートマシンで定義しています。 そして、ステートマシンが次の操作に相当するイベントをもとに次の画面(状態遷移)を決定する仕組みを取りました。 useNavigationContext はステートマシンの現在のステートとそのコンテキストを返し、 useEventNavigate はステートマシンにイベントを送信する関数を返しています。 コンポーネント設計 最後に、フロントエンドのアーキテクチャにおいて、本丸となるコンポーネントの設計について説明します。 コンポーネント、カスタムフック、Vanilla JS ロジックの三層で構成しています。 コンポーネント コンポーネントは表示だけの責務を担う、テンプレートエンジン的な位置付けです。 function CourseFilter () { const facets = useAvailableFacets() return ( < div > { facets. map (( facet ) => ( < FacetButton key = { facet. key } facet = { facet } /> )) } </ div > ) } function FacetButton ( {facet} : {facet : Facet} ) { const toggle = useToggleFacet() const onClick = useCallback(( _ : MouseEvent < HTMLButtonElement >) => { toggle(facet) } , [ toggle, facet ] ) return ( < button type = "button" aria-pressed = { facet. selected } onClick = { onClick } > { facet. label } </ button > ) } 基本的に、フックで取得した値を表示したり、イベントハンドラにバインドしている以上の仕事はしていません。 カスタムフック カスタムフックは Vanilla JS ロジックとコンポーネントを繋ぐアダプター層です。 function useAvailableFacets () { return useAtomValue(availableFacetsAtom) } function useToggleFacet () { const set = useSetAtom(toggleFacetAtom) return useCallback(( facet : Facet ) => { set(facet) } , [ set ] ) } Jotai の atom と接続するだけの薄いアダプターになります。 5 Vanilla JS ロジック Jotai の atom から先が、Vanilla JS (TypeScript) で書かれたロジックです。 ただの TypeScript コードなので、可搬性が保証されます。 自動テスト 自動テストは、単体テストと e2e テストのみでカバーしています。 単体テストでは React Testing Library を一切使用していません。 このことからも、React への依存が最小限になっていることが伝わるかと思います。 Vanilla JS ロジックは、そのほとんどを純粋関数として実装しているので、複雑な fixture のセットアップも不要で vitest で高速にテストが可能です。 依存の最小化で得られる利点 確実に発生するシナリオとして、フレームワークのメジャーバージョンアップに伴う破壊的な変更の影響を受けにくくなる、という点が挙げられます。 React 19 や React Router v7 (fka Remix v3) でどう変わる? 現時点で判明している範囲からの判断ではありますが、影響はほぼゼロになるだろうと見ています。 直接 Remix API を呼んでいる箇所は、これまで見てきたように DIP の低レベルモジュールにあたるフックの実装内に閉じています。 また、その構造上、Remix API の呼び出し箇所も最小限に抑えられているため、仮に変更が必要になっても、手を入れないといけない箇所は必然的に最小限に留められます。 加えて Remix 自体もその設計哲学で、メジャーバージョンアップで導入される機能や変更は future flag として段階的に適用できる 形で提供されます。 破壊的な変更は伴うとはいえ、バージョンアップに追随する負担が抑えられたフレームワークと言えるでしょう。 例えば Server Component もオプトイン的に必要な箇所にだけ適用できるような 仕様が検討 されています。 React 19 や React Router v7 についても 前述したオンラインイベント でお話しする予定ですので、興味のある方はぜひご参加ください。 極端な例だが、仮に他のフレームワークに置き換えないといけなくなったら? コンポーネント設計でお伝えしたように、薄いコンポーネント層を移植するだけで対応できます。 TypeScript で書かれたロジックは、Vanilla JS というその性質上、変更することなくそのまま使えることには説明の必要もないでしょう。 Outro 依存ライブラリやフレームワークのメジャーバージョンアップ程度では、ほとんど揺らがないアーキテクチャができました。 あらためて前提を振り返ってみると、長い歴史を持ち、継続可能性が極めて高いサービスだからこその選択であるとも言えます。 スタートアップで、MVP でとにかく試行錯誤を高速に繰り返したい、といった状況下では、また別の選択肢があると思います。 かなり長くなってしまいましたが、ここまで読んでいただきありがとうございました。 この記事が、フロントエンドのアーキテクチャを考える上での一助となれば幸いです。 一休では、本記事でお伝えしたような課題をともに解決するフロントエンドエンジニアを募集しています。 www.ikyu.co.jp まずはカジュアル面談からお気軽にご応募ください! hrmos.co React 19 や React Router v7 のリリースが近づいています。 ↩ Remix への移行を決めた理由の一つです ↩ 実際には素直に関数として実装しています。事前に定義しなくても関数の signature が interface として機能することを示すための例です。 ↩ 実を言うと、最初から現在の設計に辿りつけていたわけではありません。当初は Remix の nested routes を駆使する案を検討していました。その過程でチームメンバーから示唆をもらい、ステートマシンを使ったアプローチに切り替えました。もし、当初案のままであれば DIP と正反対の状態になっていたと思います。 ↩ Jotai のようなライブラリを利用しない場合は useSyncExternalStore を使って、ロジックを React と疎結合にできます。 ↩
アバター
レストランプロダクト開発部の矢澤です。 一休では「 RESZAIKO 」というプロダクトの開発を行っています。 この開発を進めるにあたり、UI/UX に関するいくつかの課題があり、エンジニア主導でデザインシステムを構築することにしました。 本記事では、エンジニア主導でデザインシステムを構築することになった背景や、実際に取り組んだ内容について赤裸々にお話しします。 デザインシステムの導入を検討しているものの、最初の一歩を踏み出せずにいる・あるいは何から始めればよいかわからないチームにとって参考になれば幸いです。 そもそも RESZAIKO とは RESZAIKO は飲食店の予約管理を DX する SaaS 事業で、現在3つのプロダクトを提供しています。 複数予約サイトの在庫を一括管理する「サイトコントローラー」 予約や顧客情報を管理する「予約台帳」 店舗独自の予約ページを提供する「Web予約」 現在は3つのプロダクトがありますが、元々はサイトコントローラーだけを提供しており、予約台帳とWeb予約は後発のプロダクトとしてリリースしました。 デザインシステム作成の背景 後発プロダクトの開発を進めていく中での課題 私たちはサイトコントローラーがある状態で、「予約台帳」と「Web予約」の開発に着手しました。 同時進行で開発することになったこの2つの後発プロダクトは、既存プロダクトであるサイトコントローラーの UI/UX を参考にして設計・開発を進めていましたが、さまざまな課題に直面しました。 課題① スピード感を持ってデザインを固めていくことが難しい RESZAIKO にはサイトコントローラーの開発時から社内に専任のデザイナーがいなかったため、社外のデザイナーと週一回のミーティングでデザインを進めています。 限られた時間で多くの画面のデザイン調整を行うことは難しく、簡単な画面はエンジニアがデザインすることもありました。しかし、デザイナー以外デザインルールの把握ができていない状況であったため都度認識を合わせる必要があり、スピード感を持って進めていくことができませんでした。 課題② プロダクト間で UI/UX の統一感を生み出しづらい RESZAIKO は飲食店のスタッフが3つのプロダクトを横断して使用することを想定しています。横断して使用しても違和感のない操作を提供するために UI/UX を合わせる必要がありましたが、デザインや操作感に関する知見を開発チーム間でうまく共有できず、差異が生じてしまうことがありました。 これを防ぐために、社外のデザイナーに Figma 上でコンポーネントの置き場を作成してもらいました。 しかし、各プロダクトで使用するための仕組み化ができず、操作感の統一も Figma 上で十分に表現されていなかったため、うまく運用することができていませんでした。 課題③ デザインのクオリティの担保が難しい 同一プロダクト内で同じコンポーネントを使用していても、余白の取り方やコンポーネントの組み合わせ方など細かい部分まで統一することは難しいです。実際に、既存箇所を参考にしてできた画面は、大枠は同じでも完璧には揃えられておらず、画面ごとに少しずつ違いが出てしまいました。 また、デザインに関するチェック基準がないため、チーム内でのレビューもある程度までしか確認できず、デザイナーのレビューも毎回細部までチェックすることができない状態でした。 このような課題を感じながら当初は開発を進めていましたが、リリースが近づくにつれ、デザインのスピード感がボトルネックになり始めました。 そこで、エンジニアの中から比較的 UI/UX にこだわりをもつ3人が集まり、RESZAIKO デザインシステムを作成することになりました。 デザインシステム構築の流れ このようにしてデザインシステムを作ろうという話になったものの、メンバーがエンジニアだけだったため、デザインシステムに関する知見が不足していました。 そこで、知見を得るために「ちいさくはじめるデザインシステム」という本を読むことにしました。 ちいさくはじめるデザインシステム bnn.co.jp この本は、SmartHR 社のデザインシステムの取り組みを例にデザインシステムの構築・運用の方法について書かれています。 私たちはこの本を参考に以下の流れで進めていきました。 コンセプト設定 デザインシステムは、流行っているからや、なんとなくデザインの課題が解決しそうという理由で作成しがちです。 しかし、「ちいさくはじめるデザインシステム」には前提として目的が必要であると記載されていたため、まずデザインシステムを作成する目的を改めて定めるところから始めることにしました。 デザインシステムには正解がなく、「デザイン」という何らかの目的を機能させるための「システム」であり、システムとして成立させるために何らかの目的が必要 集まったエンジニア同士で現在感じている課題点を挙げ、それらの解決には何が必要なのか、どのような状態が理想なのかを話し合いました。 その結果、以下のようなデザインシステムのコンセプトとして明文化しました。 デザインシステム コンセプト デザインと開発を効率化し、課題解決に集中できる環境を作り、リリースや改善サイクルを早くする デザインに一貫性をもたせ、ユーザービリティとアクセシビリティを向上させる(サービス単体) 3つのプロダクトを違和感のないユーザー体験を提供する 非デザイナーとデザイナーとのコミュニケーションとして使用し、共通認識を作る手段とする 非デザイナーが自走して簡易的なデザインを行えるようにする(デザインのよりどころにする) 構成決め コンセプトを決めたところで、次にデザインシステムをどのような構成で実際に作成していくのかを検討しました。 構成を決めるにあたって、まずは他社のデザインシステムではどのような項目を採用しているのか調べました。 「ちいさくはじめるデザインシステム」にあるとおり、全てを網羅する必要はないため、調べたすべての項目を採用するのではなく、必要なものを取捨選択したり独自で項目を追加したりしました。 スタイルガイドには様々な種類がありますが、すべてを網羅している必要はありません。必要や目的・組織などに応じて柔軟に選ぶことができます。 私たちが特に参考にしたデザインシステムは、「 SmartHR Design System 」「 Spindle 」「 デジタル庁 デザインシステム 」の3つです。 「SmartHR Design System」はトークンやコンポーネントなど基本要素が網羅されているため、アウトライン作成の参考にしました。 「Spindle」は定義したコンポーネントの使用ルールがわかりやすくまとめられていたため、デザインルール作成の参考にしました。 また、今回作成するデザインシステムは元々コンポーネント置き場として使用していた Figma に定義したいと考えていたため、「デジタル庁 デザインシステム」のまとめ方を参考にして作成することにしました。 レビュー デザインシステムのコンセプトと構成が決まったところで、他のプロダクトのデザインシステムを作成している方にレビューを依頼しようということになりました。 一休.com ではすでにデザインシステムが構築されているため、その作成に携わったデザイナーやエンジニアにレビューを依頼しました。 user-first.ikyu.co.jp レビュー会では、定義するコンポーネントに対してどのように使うのかルールを記載した方が良いというアドバイスをいただきました。 これを受け、もともとルールは「デザインパターン」という名目でレイアウトに関することを定義する予定でしたが、これを「デザインルール」という名称に変更し、コンポーネントの使い方まで定義することにしました。 また当初は Figma 上でガイドラインの定義のみを行う予定でしたが、せっかくエンジニアが作成しているのだからライブラリまで作成したらどうかと提案をいただき、ライブラリの作成も試みることにしました。 そして、最終的に決定した構成がこちらです。 利用の手引き: デザインシステム構築の目的・利用方法 デザインフィロソフィー: RESZAIKOプロダクトが大切にしていること デザイントークン: デザインシステムにおける最小単位のスタイル定義 コンポーネント: UIを構成するための最小単位のパーツやアイコン デザインルール: デザイントークンやコンポーネントを使用する際のルール ライティングガイド: です/ます調や句読点の打ち方 チェックリスト: デザインシステムに則っているか判断するための確認項目 この中でも、まずは最小限でリリースしてみようということになり、主要部分となる「デザイントークン」「コンポーネント」「デザインルール」の策定を初回リリースの目標として進めていくことにしました。 作成 レビューのフィードバックを受けたところで、デザインシステムの作成に着手しました。 本業の実装も並行で行っていて、あまり時間を確保できない中、Figma に各自がトークンやコンポーネントを追加し、週に1回3人で集まってお互いが作成したものにフィードバックを行うという流れで進めました。 また、3人の中で合意が取れたものは、社外のデザイナーに確認してもらい、エンジニアとデザイナーの双方が問題なく使用できるように整えていきました。 実際にできたデザインシステムの一部をお見せします。 デザイントークン コンポーネント デザインルール β版リリース! 初回リリースとして掲げていた「デザイントークン」「コンポーネント」「デザインルール」の作成がある程度完了したところで、β版のデザインシステムとしてチームに展開しました。 現在は、実際に社外のデザイナーや開発メンバーに利用してもらい、元々の定義で不十分だった内容を拡充したり、新たなUIについては都度定義を追加したりしています。 デザインシステムを通じて、デザイナーとのコミュニケーションが円滑になっただけでなく、エンジニア同士でもデザインに関する会話ができるようになり、コミュニケーションツールとしても機能しはじめています。 まとめ 本記事では、RESZAIKO デザインシステムの導入背景からβ版リリースまでの流れをご紹介してきました。 エンジニア主導でデザインシステムを構築してみると、前提知識が不足していたことから調査しなければならない事柄が多く、最初の段階ではリリースまでたどり着けるのか不安に感じることもありました。 しかし、実装で必要になるコンポーネントやデザインルールが明らかなので、Figma への定義のフェーズに移ってからは特に迷うことなく進めることができました。 自分たちが実装時に必要としているものをダイレクトに反映することができることは、エンジニア主導で作成するメリットだと思います。 今回デザインシステムを導入したことにより、当初感じていた3つの課題は解決できたのかという点についてですが、解決できた部分もあればもう少し手を加える必要がある部分もあると感じています。 課題① スピード感を持ってデザインを固めていくことが難しい デザイナーとエンジニアの共通言語ができたことにより、デザインに対する質問の精度も上がり、コミュニケーションが取りやすくなったため、これまでよりスピード感を持って開発が進められるようになってきました。 今後は、さらにデザインルールなどをアップデートし、デザインについて考えやすい環境を整えていければと思います。 課題② プロダクト間で UI/UX の統一感を生み出しづらい 各プロダクト間でコンポーネントを検討しデザインに反映することがなくなったため、導入前よりも統一感を出すことができるようになりました。 しかし、実装レベルでの統一がまだ十分ではありません。これを解決するために、UI ライブラリとしてパッケージ化し、RESZAIKO の各サービスに反映していければ、さらに課題の解決につながると考えています。 課題③ デザインのクオリティの担保が難しい ルールが定まったことで、誰が作成しても差異が出ないようにする基盤は整えられましたが、チェック時の効率はまだ改善の余地があります。 より効率的に確認できるようにするため、チェックリストの作成など、デザインシステムの拡張も進めていきたいと考えています。 運用を始めたばかりの現段階では、まだ全体的に改善の余地が多く残っています。 これからさらにデザインシステムをアップデートし、利便性を高めていきたいと考えています。 おわりに 全く知見がない中で始めたデザインシステムの作成でしたが、ありがたいことにデザインシステムをオープンに運用している企業が多く、参考にできるものが豊富にありました。そのおかげで自分たちなりにアレンジしてなんとか形にし、プロトタイプまで持っていくことができました。 知見がない中で、またエンジニア主導でデザインシステムを導入しようとしている方々にとって、この経験が一つの手がかりになれば嬉しいです。 RESZAIKO デザインシステムはこれからもっとアップデートさせていくので、今後もブログで発信していきます! 一休ではともに良いサービスをつくっていける仲間を募集していますので、興味を持っていただけたら、ぜひ一休の 採用サイト をご覧ください。
アバター
Go Conference 2024にスポンサーしました CTO室プラットフォーム開発チームの山口( @igayamaguchi )です。 先日6/8(土)に一休でGo Conference 2024にスポンサーをさせていただき、スポンサーブースを出展しました。 gocon.jp 来ていただいた方はありがとうございます! 来ていただいた方と話していく中で、一休がGoを使っていることを知らない方がたくさんいることに気づきました。逆に、最近使い始めたばかりのRustの事例についてご存知の方のほうが多かったのです。これは、次のRustについての記事が多くの方に読まれたことによる影響だと思います。 user-first.ikyu.co.jp 実際には一休はGoを使っているサービスがたくさんあります。その点をアピールするため、この記事では一休のどのサービスでGoが活用されているかを紹介します。 一休がどのサービスでGoを使っているか まず、一休ではいくつものサービスを提供しています。 会社紹介資料 より抜粋 この中でGoが使われているサービスは以下の赤枠で囲われたサービスです。 ご覧の通り、Goが使われているサービスは多いです。 また、ユーザー向けのサービスとは別の社内プラットフォームでもGoが使われており、実際は上の図で表されている箇所以上にGoが活用されています。 ここからは、各サービスでのGo利用事例を個別に紹介していきます。 国内宿泊予約サービスでの活用 まず、一休の中で最も大きなサービスである国内宿泊予約においてGoは活用されています。 一休では国内宿泊予約サービスとして一休.comとYahoo!トラベル(LINEヤフー株式会社から運営を委託)を運営しています。 https://www.ikyu.com https://travel.yahoo.co.jp 宿泊施設を探すための検索を実行するバックエンドがGoで書かれています。バックエンドはgqlgenを用いたGraphQLサーバーになっており、ホテルやプランの検索、料金、在庫検索といったロジックが実装されています。他にも、全文検索エンジンであるSolrのインデクシングや、施設管理画面の一部APIなどもGoで書かれています。 ホテルの予約を行う処理、予約情報の閲覧ページはVB.NETですが、こちらも後々Goに置き換えていく予定です。 ふるさと納税、海外宿泊予約 国内宿泊予約サービス以外にもいくつかのサービスでGoは活用されています。 例えば宿泊予約時に割引クーポンを受け取れるふるさと納税サービスや、海外宿泊予約です。 https://furusato.ikyu.com/ https://www.ikyu.com/global/ これらのサービスは、社内では新しめということもあり、バックエンドは検索から予約まですべてGoで実装されています。 一休プラットフォーム ユーザー向けサービス以外に、社内向けプラットフォームでもGoは使われています。一休ではいくつものサービスを運営しており、サービス間で共通のアカウントを利用し、貯めたポイントをサービス横断で使用したり、同じ決済の仕組みを使ったりできます。 そういった機能を各サービスで再実装することなく提供するために、一休プラットフォームとして複数のマイクロサービスを実装し、運用しています。具体的なサービスとして、現在は会員サービス、ポイントサービス、決済サービスがあり、これらはすべてGoで実装しています。 現在移行中の一休プラットフォームの図 一休プラットフォーム開発の実例については、2023年のイベントでの資料も参照してください。 speakerdeck.com Goを選定してよかったこと 実際に国内宿泊予約や一休プラットフォームの開発に携わっているメンバーから、Goを選んでみてよかったことを聞いてみました。 並行処理が言語組み込みで入っている。しかもそれが使いやすい 言語仕様がシンプルで、入門から使えるようになるまでの時間が短い 業務ロジックが大事なので、シンプルかつ堅く書けるのがよい 一括処理や会計管理で大きめのデータを扱うときは非同期処理も書ける 総じて、Goは「シンプル、かつすばやく、それでいて堅牢に作れる」ことを重視する一休の技術選定方針に合致すると感じています。 おわりに この記事では、一休がGo Conference 2024にスポンサーさせていただいたこと、一休では幅広くGoが使われていることを紹介しました。一休では、これからも生産的かつ高効率にサービスを開発/運用できるGoを活用して、サービスを成長させていきます! 一休では、ともに良いサービスをつくっていく仲間を募集中です。 hrmos.co カジュアル面談も実施しているので、お気軽にご応募ください。 www.ikyu.co.jp
アバター
こんにちは。宿泊プラットフォーム開発チームの菊地です。 一休では月に一度、社内エンジニア向けにIkyu Tech Talkを開催しています。2022年から始まり、ありがたいことに2024年3月で丸2年を迎えることができました。 この記事では、 Ikyu Tech Talkの2年間のふりかえり をしていきます。 また、私は社内イベントの主催が初挑戦だったので、どうやったらイベントを盛り上げられるのかと悩んだときもありました。 そこで、同じように 自分の会社でTech Talkを開催してみたい人に向けてイベント運営の知見 もお伝えしたいと思います。 開催のきっかけ もともと定期的なプロジェクトの成果報告会はあるものの、業務で得たエンジニアリングの知見の共有をする場は設けられていませんでした。 あるとき「技術についてざっくばらんに話す場が定期的にあると楽しそう。一緒にやらない?」と声をかけてもらい、面白そうだったのでやってみることにしました。 Ikyu Tech Talkとは? 「技術のことならなんでもOK」と題して社内エンジニアに発表者をやってもらう60分の社内イベントです。 月に1回ペースでZoom開催しています。 カテゴリ別に過去の発表を抜粋してご紹介します。 自己学習の発表 個々人の技術研鑽の発表回です。業務では知ることができない興味関心分野を知ることができました。 GitHub Copilotで 次世代のコーディング体験 正式リリース直後の2022年6月にGitHub Copilotについて発表してもらいました。これをきっかけにCopilotの業務利用を行うことになりました! TypeScript による型レベルプログラミングに入門した話 型定義の表現力の高さを活用して、tscにアルゴリズムを実行させるデモが鮮烈でした プロジェクトのふりかえり 案件が終わったタイミングで、チームの皆さんに振り返りもかねて発表をしてもらいました。新しいフレームワーク・ツールを積極的に採用するスタンスなこともあり、初挑戦の技術のフィードバックが多かった印象です。チャット欄もおおいに盛り上がりました! 宿特化型SNS YADOLINKでのアーキテクチャ選定 一休で初めてReactを用いた事例でした。当時よくGraphQLクライアントとして選定されていたApollo Clientに対しての適不適の考察も興味深い内容でした。Apollo Clientについては以下の記事もご覧ください https://user-first.ikyu.co.jp/entry/2022/07/01/121325 宿泊予約サイトの検索処理チューニング 国内宿泊サイトの検索処理には複数のシステムが関わっています。それらを複合的にパフォーマンスチューニングしてレイテンシを半減させた実践的なテクニック紹介でした レストラン予約サイトフロントエンドの今とこれから YADOLINKに続きレストラン予約サイトもReact/Next.jsで構築していました。Next.jsによる状態管理の落し穴や今後の展望についての発表でした。詳細は他の技術ブログで詳しく書いているため、ご興味のある方は以下の記事もご覧ください https://user-first.ikyu.co.jp/entry/2023/12/15/093427 https://user-first.ikyu.co.jp/entry/2023/12/22/190342 専門性の高い部署の知見を広める 一休には、データサイエンス部・アーキテクトチーム・SEO対策チームといった専門性の高い部署があります。なかには「もっと早く知りたかった」「入社時の資料にしてほしい」という声をいただく発表もありました。 猫でもわかる一休のデータ分析基盤(参加型) 一休のデータサイエンス部は、各プロダクトのデータをもとに分析基盤を提供しています。分析基盤の全体像をキャッチアップできただけではなく、データ基盤を安定させるための実践的なテクニックが非常に面白い発表でした 一休のサービスを支える インフラのはなし プロダクトのネットワーク構成やデプロイフローについて、SREチームが解説しました。特に入社したての人にとっては垂涎の資料でした Tech Talkの成果 Ikyu Tech Talkは完全任意参加のイベントとして運営してきましたが、開発組織メンバーの半数以上が参加し続けてくれています! ここまで続けられてきたのは「エンジニアリングの話をするのが楽しいから」というのに尽きると思います。その一方、会社としてTech Talkを開催することで以下のような成果が得られました。 チームを超えてナレッジを共有できる これまでは、成果報告会などのビジネス的な成果を知る場はあったものの、互いのナレッジを知る機会はなかなかありませんでした。Tech Talkはエンジニアリングの話を聞く場として貴重な機会になりました。 実際にSlackを探してみたところ、Tech Talkの発表を受けて他のチームのソリューションを取り込んでいるやりとりもありました! 発表時のZoomのチャット欄では、「ウチでは○○を使ってます」というように参加者からの知見も多く寄せられ、双方向での知見交流が生まれたのも成果だと思います。 発表の機会があることで、個々人の知識がよりブラッシュアップされる プロジェクトに没頭している間は、知識が表面的なままになっていることがあります。Tech Talkを目標に、知識の精査や最新情報の確認をすることで、それらを自分の知見として昇華するきっかけにできます。 たとえば、あるプロダクトの新規リリースを行ったチームに発表をお願いしたところ、初期開発時の技術選定の是非を振り返った発表をしてくれました。 選定したソリューションの選定基準だけではなく、不採用にした他の案の理由や今振り返るとその選択は妥当だったのかの洞察も述べていて、今後の技術選定にとって価値のある資料になったと思います。 カジュアルに自己発信の経験を積む場を提供できる 自己発信の機会は貴重ですが、いざ外部の勉強会で発表しようとすると初心者には足が重いこともあります。 Tech Talkでは顔見知りが参加者なので、カジュアルに発表の経験を積むことができます。 採用活動をしている会社にとって社外で発表してくれるエンジニアは貴重ですが、Tech Talkを練習場として提供することができます。 社内イベントの運営をしてわかったこと この記事を読んでいる方のなかには、以下のような悩みを持った人もいるかと思います。 自分の会社でも社内イベントを開催してみたいけど、どうやったら盛り上がるだろうか、どう始めたらいいだろうか? 社内イベントを開催してるけど人がなかなか集まらない、集まっても盛り上がらない 登壇をお願いしても断られる、つらい ここからは、社内イベントを開催したい方に向けて、Ikyu Tech Talkで得た運営のノウハウをお伝えします。 イベントがコンスタントに続けられる仕組みにする Tech Talkの運営方針として、 エネルギーが必要すぎて続けられなくなるよりもかけるエネルギー少なく長く運営できるイベントにする ことを決めていました。 Zoom開催としたのも、イベント設営と集客に疲弊したくなかったからです。開催頻度も月1回くらいで「たまにやればいい」という気持ちで始めました。 発表を依頼したりイベント告知等の作業など、イベントの運営はただでさえ負担が大きいです。そのため、開催コストをできるだけ下げるのは非常に有用だったと感じました。 また一休ではリモートワークが導入されておりオフラインイベントにすると参加側の敷居も高くなってしまうため、双方にとってオンライン開催が最適でした。 発表者に対するリターンを設定する 発表準備や当日の精神的な負担が大きいので、モチベーションを高めるためにリターンを設けました。 具体的には「Tech Talk賞の開催」と「発表ごとに感想・メッセージの受付」を行っています。   Tech Talk賞とは、半期に一度、最も面白い発表をしてくれた人を投票で決め表彰するイベントです。一休各サービスで使用できるポイントを贈っています。 また、毎度の発表後には、参加者に発表の感想・メッセージを書いてもらってそれをまとめてお渡ししています。発表中はどうしても参加者のリアクションがわからなかったり、面白い発表だったかなど不安を感じる人も多いです。実際に発表者の方からも、発表のフィードバックがもらえてよかった、という声をいただきました。 一方で、メッセージの回収率が20%程度にとどまっているのが今後の課題です。対策としてイベント中にメッセージの記入時間を設けたらどうかと検討中です。 Zoomのコメント機能を活用して積極的な参加を促す 多くの参加者にリアクションしてもらいイベントを盛り上げるため、Zoomのコメント機能を活用しました。 質問や感想をその場で話すには緊張してしまう人もいるため、テキストベースでコメント欄に書き込んでもらう形式にしました。書かれた質問は、発表の区切りの良いタイミングで司会が拾いその場で発表者に回答してもらいました。 また、司会以外の運営はちょっとした感想も意識的に書き込むようにし、コメント欄を盛り上げることを心がけました。今ではコメント欄がフランクな感想を言える場所として定着したため、とてもよい試みだったと思います。 まとめ 以上が社内イベント運営のノウハウです。 社内イベントでは、 運営・発表者・参加者それぞれが継続できる仕組みを作る ことが最も重要だと感じました! さいごに ここまで読んでくださりありがとうございます! 今回は社内イベント Ikyu Tech Talkの紹介と、社内イベントの運営をしてみて得た学びをまとめました。本記事が自社のエンジニア組織を盛り上げたい方の力になれたら幸いです。 また、いつもIkyu Tech Talkに参加&登壇してくださっている一休のエンジニアの皆さんへ。 この場を借りて感謝の気持ちを伝えさせてください。皆さんがポジティブに参加してくれるおかげで、Ikyu Tech Talkが楽しいイベントとして継続できています。いつも参加いただき本当にありがとうございます。 さいごになりますが、一休では社内イベントに積極的に参加してくれる、アウトプットが得意なエンジニアを募集しています。 興味がわいた方は、以下のリンクから面接応募及びカジュアル面談へのご参加をぜひぜひお願いいたします!!! https://www.ikyu.co.jp/recruit/engineer/ https://hrmos.co/pages/ikyu/jobs/1745000651779629061
アバター
kymmt です。 一休では、技術力の底上げを目的として、さまざまな社内勉強会を開催しています。この記事では、今年2024年に入って社内で実施していた勉強会について紹介し、一休での勉強会の雰囲気を伝えられればと思います。 一休の社内勉強会 2024年にこれまで実施した勉強会 『A Philosophy of Software Design』輪読会 『Webフロントエンド ハイパフォーマンス チューニング』輪読会 フロントエンドワークショップ: Reactハンズオン 2024年の今後の勉強会 一休の社内勉強会 社内勉強会は輪読会の形式で実施することが多いです。参加意欲の高い人ができるだけ多く参加できる曜日と時間を決めて、週次で開催する形式をとっています。 読む本の決め方については、ボトムアップで決めることが多いです。先日は、次に読みたい本をSlack上でPollyを使った投票形式で募り、『なっとく!関数型プログラミング』を読むことになったりしました。 進め方については前日までの準備と当日で分かれています。 前日まで: 章ごとに担当を決める場合は担当者が、決めない場合は参加者全員がその回で読む範囲に基づいて概要や疑問点、コメントをConfluenceのページとしてまとめる 当日: 担当者/ファシリテータが会を進行しつつ、資料に基づいて参加者が議論しつつ書籍を読み進める Confluenceに「勉強会」というスペースがあり、そこに勉強会の資料が集積されています。 また、のちほど説明するとおり、輪読会以外にハンズオン形式でのイベントを開催することもあります。 2024年にこれまで実施した勉強会 『A Philosophy of Software Design』輪読会 ソフトウェアの複雑性に着目し、複雑性をいかに制御するかという観点でソフトウェア設計や実装の方法論が議論されている書籍です。 A Philosophy of Software Design, 2nd Edition: Ousterhout, John: 9781732102217: Amazon.com: Books この本はこれまでの通説とは異なる著者独自の観点からの主張が含まれる、という特徴があります。この主張を適切に咀嚼しつつ(いい意味で)批判的に読み進める態度が求められ、おもしろい輪読会になったと思います。 また、基本的には原著しか選択肢がない本なので、洋書を読む練習にもなりました。英語自体は平易で、書評記事などがWeb上に多く存在することから、調べながらなら難しい本ではありません。とはいえ、平易な文であっても意味を取り違えてしまうケースもあるので、そのあたりも注意して読んでいくのは多くの参加者にとって勉強になりました。 『Webフロントエンド ハイパフォーマンス チューニング』輪読会 タイトルから受ける印象とは異なり、実際はブラウザの仕組みについて詳しく書かれている書籍を読む会です。 gihyo.jp フロントエンドをこれから学習していきたいメンバーに、まずブラウザの仕組みを知ってもらうのがいいのではということで開催されました。 この勉強会については、別の記事で詳しく紹介する予定です。 フロントエンドワークショップ: Reactハンズオン 社内エンジニアのフロントエンドに関する技術力を底上げするための「フロントエンド技術力向上委員会」メンバーが主催しているのがフロントエンドワークショップです。第1回のワークショップでは、フロントエンドの経験が薄い人や、これまでReactを触ったことがない人向けに、Reactで簡単なToDoアプリを作るハンズオンを実施しました。 ハンズオンでは https://github.com/tak-onda/frontend-workshop-react/ のリポジトリを利用しました。 輪読会は基本的にオンラインで実施していますが、このハンズオンは社内のラウンジと呼ばれるスペースを使って、みんなでワイワイ取り組むためにオフライン開催しました。 ふだんサーバサイドやプラットフォーム中心に開発しているエンジニアが最近のフロントエンドについてキャッチアップできるイベントになりました。 2024年の今後の勉強会 2024年5月からは投票多数で選ばれた『なっとく!関数型プログラミング』の輪読会を開催しています。 www.shoeisha.co.jp 近年は、関数型プログラミングのエッセンスを含んだ言語やライブラリが広く使われるようになっています。既存のコードベースが関数型ではないとしても、堅牢なソフトウェアを作るためには、関数型の考えに親しむことで得られるものも多いだろうと考えています。 『なっとく!関数型プログラミング』を読み終えたら、次は『Domain Modeling Made Functional』を読むのがいいのではないかという話もすでに出ています。 pragprog.com また、フロントエンドワークショップの第2弾として、Reactで作ったToDoアプリにJotaiで状態管理を、Vitestでテストを組み込むワークショップも開催予定です。 一休では今後も継続的に勉強会を開催していく予定です! 一休では、ともに良いサービスをつくっていく仲間を募集中です。 hrmos.co カジュアル面談も実施しているので、お気軽にご応募ください。 www.ikyu.co.jp
アバター
CTO 室の恩田です。 今回は GitHub Copilot Enterprise を評価してみて、現時点ではまだ採用しないことを決めた、というお話をご紹介したいと思います。 きっかけ とあるエンジニアが Slack で自身の times チャネルに時雨堂さんの GitHub Copilot Enterprise のススメ という記事を投稿したことが発端でした。特に感想はなく URL に 👀 だけが添えられていたので、後で見るぐらいのメモだったんだと思います。 それを見かけた別のエンジニアが技術雑談チャネルにその投稿を共有して、これは凄そうと話題を向けたところ、CTO の「評価してみる?」の一言で、有志が集って評価プロジェクトが始まりました。 雑談チャネルできっかけとなる投稿が共有されてから、30分足らずの出来事でした(笑)。 この話題が出たのは金曜日でしたが、週明け早々に稟議を終え、火曜日の朝にアップグレードが完了しました。 GitHub Team から GitHub Enterprise Cloud に、Copilot Business から Copilot Enterprise への変更です。 そうして評価プロジェクトが動きはじめました。 評価にあたって Copilot Enterprise が有効になったことを確認したあと、集った有志がどう評価を進めようか話しはじめたところで、CTO からレビューしてちゃんと意思決定しようね、との補足をもらいました。 その要旨は次の二点です。 会社で支払うライセンス管理は、ほとんど使ってないのにとりあえずもらっておくなど、なあなあになりがち 評価は定性的でよい、インパクトのあるユースケースがどれだけ見つかるかが重要 個人的には、コストに見合う価値をどう定量化するかという観点でばかり考えていたので、後者の指摘には新鮮な視点をもらえたように思います。 定量化を前提にすると評価プロセスが重たく固定的になってしまい、様々な視点からの素早い意思決定には繋がらなかったことでしょう。 結果、プロジェクトに集まったメンバーが各自の興味のある観点で分担して、どういうユースケースが実現できると開発体験にインパクトを与えられるか、で評価することになりました。 いくつか抜粋すると、 ドキュメントを集約する場として knowledge bases は Confluence からの移行に値するか? レガシーコードの理解にあたり認知負荷をどれぐらい軽減してくれるか? PR サマリーの自動生成で開発プロセスがどの程度改善されるか? といった観点になります。 評価 冒頭でお伝えしている通り、2024年4月現在、一休のコードベースやドキュメントを学習させた限りにおいては、GitHub Copilot Enterprise は時期尚早という結論になりました。 ここでは評価していく中で、具体的にどんなことがわかったのかをご紹介したいと思います。 knowledge bases は使えるか? もともとプラットフォームエンジニアリングの文脈で、開発者の認知負荷を軽減させるために、ドキュメントをどうしていくかという議論が少し前からありました。 knowledge bases は2024年4月現在 GitHub リポジトリ内の markdown ファイルのみを学習します。 Copilot Enterprise を導入することになると、ドキュメントを今後は GitHub リポジトリで管理していく必要があります。 一休では現在、多くのチームが Confluence を使ってドキュメントを管理しています。 非エンジニアにとっても扱いやすく、階層的に情報を整理することができ、世界的に広く利用されているナレッジマネジメントサービスです。 特にドキュメントを同時編集したり、リアルタイムでインラインコメントを入れる機能は、一休でもミーティングの場で活用されています。 そういった現在享受している Confluence の良い点を失ってでも、なお余りある価値を Copilot Enterprise がもたらしてくれるのかが焦点でした。 上述の通り、knowledge bases にインデックスさせるデータは markdown ファイルで構成された GitHub リポジトリとして用意する必要があります。そこで、スペースを一括して markdown に出力する Confluence プラグインを導入し、それを使って knowledge bases 用のリポジトリをいくつか作成しました。 その上で、様々な質問で評価してみましたが、概念の学習がまだまだ限定的であるように感じられました。 ひとつ具体例を紹介したいと思います。 一休レストランは現在3バージョン存在します。 オリジナルの一休レストランは restaurant というリポジトリで作られました。 リニューアル時に restaurant2 が作られ、それ以後、オリジナルは restaurant1 や略して res1 と呼ばれるようになりました。 今回 knowledge bases に取り込んだドキュメントにも restaurant1 や res1 という記述が多数あります。 にも関わらず、res1 などのキーワードを含めた検索では、オリジナルのリポジトリである restaurant に関する回答が返されることはほぼありませんでした。 数字の有無が影響しているのか、restaurant2 に関する情報ばかりが要約されて返ってくることが多かったです。 他にも LLM でよく言われているように、knowledge bases においても、日本語で学習させたにも関わらず、英語で質問した方がより優れた回答になる傾向が見られました。 レガシーコードの理解にあたり認知負荷をどれぐらい軽減してくれるか? 一休は20年以上の歴史を持つサービスです。 継続的にモダナイゼーションを進めてはいるものの、まだまだレガシーコードが残っています。 そのようなレガシーコードを読んで理解することは、現状の振舞いや仕組み、そこに至った経緯を把握するために、避けて通れない作業です。 レガシーコード上でわからないことを GitHub Copilot Chat が適切に要約して回答してくれると、あちこち行ったり来たりすることなく、着目すべきコードに集中して読むことが可能になります。 ひいては開発生産性の向上にも寄与してくれるのではないか、と期待していました。 新しく入社した開発者がレガシーなリポジトリを見るとき、どこを読めばいいかを示してくれるか 営業スタッフやカスタマーサービスから問い合わせがあったとき、現状や経緯についてのピンポイントな質問に答えられるか リポジトリの全体感がどうだ、とかそういう質問に答えられるか もっと踏み込んで、検索にとどまらず改善策など示唆にあたる情報を提示してくれるか 具体的には上記のようなユースケースです。 このようなシナリオを評価するために、評価者が十分に理解しているような内容について、適切なまとめを返せるか、というテストを行いました。 このあたりは業務に深く関わってくる内容なので、具体例を紹介することは難しいのですが、たとえば、 複数のリポジトリを横断して内容をまとめる必要があるのですが、リポジトリ間でコードやコメントの質に差が大きく、より質の高いリポジトリに回答の内容が引っ張られてしまっている(ように見えた) 無関係の情報が回答に含まれないように、プロンプトの書き方や、knowledge bases にインデックスさせる情報を工夫する必要がある XXX の API を呼びだしているところを探して、という質問で、関係のないプレゼンテーション層のコードを返してきたり、リポジトリのコードをあまり学習しているように感じられなかった といった意見があり、期待した結果を得るにはハードルが高いなという印象でした。 もちろん、命名やコメントを含めてレガシーコード自体の品質に問題があることは否定できません。 ですが、そのようなコードベースであっても適切に情報を抽出できなければ、レガシーコードを扱う上での助けにはならないのが実情なのです。 PR メッセージ自動生成 Pull Requests のメッセージを自動で生成する機能は現状英語しかサポートされていません。 ソフトウェアエンジニアとして英語ドキュメントに触れる機会は多いといっても、社内コミュニケーションはもちろんのこと日本語です。 人に読んでもらうための PR メッセージが母語でなければ、当然、その効率は著しく下がってしまいます。 ノンバーバルな情報が得られない文章によるコミュニケーションにおいて、重要となる細かなニュアンスを伝えることも難しくなります。 また、英語であることを差し引いたとしても、生成される内容が現状ではそこまで有用とは言えませんでした。 たとえば、どのようなファイルにどのような変更をおこなったかという what の情報はうまく要約してくれます。 しかし、その PR の変更が必要となった背景や変更の意図といった why の情報は期待したほどには盛り込まれません。 レビューにあたって what は差分を見ればわかります。 ですが、その変更が適切かどうかを判断するために欲しい情報は why なのです。 もちろん why をコードのみから読み取るのは人間でも難しいので、コメントの形で補足する必要があります。 しかし、コメントを書いたとしても、コードの変更箇所に関する限定的な内容となってしまいがちで、そもそもの背景や目的を網羅するのは現実的に難しいところがあります。 結果、PR の説明として期待するほどの内容にはなりませんでした。 将来的には Issue や git の履歴を利用して、背景情報を補ってくれるようになることを期待しています。 総じて PR メッセージ自動生成は機能自体が発展途上であり、現時点で導入したとしてもそこまで大きな恩恵を受けられるわけではなさそうだという結論に至っています。 学習の対象が限定的 他にも評価の過程で以下のような声が挙がりました。 DB 定義書を開くのが手間なので聞けたら便利だと思ったが、Excel ファイルを読み取ることはできなかった。 回避策として Excel の DB 定義ファイルから markdown に変換して knowledge bases リポジトリに登録してみました。 自動生成された markdown という制限付きではあるものの、テーブル間の関係を学習できていないように思えます。 たとえば、ある機能に関連するテーブル定義の全体像を説明して、といった質問には適切な回答が得られませんでした。 ADRのリポジトリがあるので、これをインデックスして仕様を聞けたら便利だとおもったが、issueは対象外だったのでうまくいかず。。。 GitHub に蓄えられた情報は git リポジトリ以外にも Issues や Pull Requests, Wiki が存在します。 LLM にとって、もっとも学習しやすい対象であるテキスト情報の上、過去の経緯を追う上でも重要な情報が含まれています。 にも関わらず、Copilot の学習対象外であるため、これまで蓄積してきた情報にもとづく知見を抽出することはできませんでした。 近い将来の導入に向けて 上述した通り、残念ながら、現時点ではすぐに効果が得られるようなユースケースは見つかりませんでした。 ですが、日進月歩を文字通り体現している LLM の発展を見る限り GitHub Copilot Enterprise を導入する未来は近いとは考えています。 したがって、いざ導入するとなったとき、すぐに有効活用できるよう準備は進めておくのがよさそうです。 今回は採用を見送ったものの、評価内容を踏まえてコードとドキュメントの二つの観点で、どういった準備をしておくべきかの認識を共有して評価プロジェクトを終了しました。 といっても頑張って準備する類の活動ではなく、頭の片隅においておこう、という程度の対策です。 最後にその対策をご紹介して本記事を終えようと思います。 コードに意図が伝わるコメントを残す 自動生成された Pull Requests のメッセージは修正した内容の要約という what であって、その修正がどういう意図でなされたか、レビューにあたって特に重要な why は含まれていません。 もちろん、それはコードに意図や理由にあたる情報がないためであって、Copilot が why を説明するメッセージを生成できないのも当然です。 GitHub の Blog でも、Copilot を使う上でのベストプラクティスとして LLM に context を提供することの重要性を説いている記事 が公開されています。 ということで、ごく当たり前の結論ではありますが、コード自体に why や why not がわかるコメントをしっかり残すように意識していこう、となりました。 奇しくも同時期に行っていた A Philosophy of Software Design の輪読会でコメントの重要性についての議論をしていたのも功を奏しました。 コードに意図が伝わるコメントを記述する、というプラクティスが各チームに浸透してきており、今後 LLM に与えられる context が増えていくと見込んでいます。 また、副次的な効果として、フロー情報になってしまいがちな PR と異なり、ストック情報と言えるコード上のコメントは認知負荷を軽減してくれています。 普段から触れるコードだけで意図が伝わる状態と比べたとき、なにかしら問題が起きてから git の履歴や関連する PR を追う作業は、不要な課題外在性負荷でしかありません。 なお、前段で PR メッセージ生成の評価の中でコメントを追加しても why を含んだ内容は生成されなかった、という評価結果をご紹介しました。 これは、あくまで現時点で未成熟なだけであって、将来に向けての布石としては、コメントを充実させていくことには意味があると捉えています。 今後はコメントに加えて、どうすれば LLM フレンドリーなコードになるか、という観点での新しいコードの書き方も確立していくでしょう。引き続き動向を追いながら、新しい種類のコードの品質向上に努めていきたいと思います。 ただ、このような新しいプラクティスが浸透するのには時間がかかります。 Copilot の今後の進化で、ブランチと紐付けた Issue や PR に代表される、コードに関わる既存の context をうまく利用してくれるのでは、と個人的には期待しています。 ドキュメントの準備はあえて何もしない 近い将来の導入に向けて、今からドキュメントを GitHub に移行していくことも検討しました。 しかし、現時点では、あえて何もしないことを選択しました。 GitHub Copilot Enterprise がナレッジマネジメントの分野においても、LLM 時代のスタンダードになるかどうかはまだ判断しきれなかったからです。 もちろん、コードそのものに加えて Issue や PR の情報を持っているという強みがあるので、非常に有力な候補であることには疑う余地はないでしょう。 ですが、今はどの会社も LLM を自社製品にどう組み込むか最優先で試行錯誤しているのは間違いなく、どの製品が最終的に勝者となるかは未知数です。 Google が後発の検索エンジンであったことを忘れてはなりません。 一休では、前述したように多くのチームでドキュメンテーションに Confluence を利用しています。 Atlassian でも Atlassian Intelligence という AI 拡張機能が提供されはじめています。 Confluence には AI を利用した 要約 や 検索 、 社内用語やプロジェクト用語の自動定義 などの魅力的な機能が近日中に提供されるようです。 GitHub もまた、knowledge bases を単にリポジトリ中の文書を学習するだけに留めず、Copilot の中核となる機能として発展させる施策を進めているのではないかと予想しています。 たとえば Issues や Discussions, Pull Requests など GitHub に蓄えられた他の情報との統合は容易に想像できるところです。 加えて、忘れてはいけない観点として、ナレッジマネジメントサービスにとって、既存機能の重要性には変化がないことには触れておきたいと思います。 LLM による新しい検索体験は非常に強力で魅力的なフィーチャーであることは確かです。 しかし、情報の構造化やチームでの同時編集のしやすさ、他サービスとの連携といった、もともとの価値を見失うことがないよう留意していくつもりです。 将来、他のナレッジマネジメントサービスを採用することになったとしても、knowledge bases リポジトリの準備でご紹介したようにデータ移行はさほど難しくはありません。加えて、この分野で高いシェアを持つ Confluence からの移行機能が提供されることも期待できます。 引き続き動向を注視しながら、あらためて判断することになりそうです。 おわりに 一休では、よりよい価値を素早くユーザーに提供できるよう、開発生産性の向上にもチャレンジしていただける仲間を募集しています。 興味を持っていただけたら、ぜひ一休の 採用サイト をご覧ください。
アバター
こんにちは、 一休.comスパ (以下、「スパ」)の開発を担当しているshibataiと申します🙏 今回はスパのデータベースの在庫の持ち方で試行錯誤した話をさせていただきます。 背景 2024-03-29追記: 一休.comスパにおける在庫の特徴について 一休.comスパが扱う「在庫」は、「ある日付の特定の時間に対する空き枠」です。以降の説明では、スパ施設ごと、日付ごと、また時間ごとに増えていく「在庫」をいかに効率よく扱うかについて説明しています。 詳細については次のスレッドも参照してください! https://t.co/Y0SPmDE4yZ この記事のコメントみてると、少し我々のシステムの要件が伝わってないというかそこの説明が記事に不足しているように思った。ので以下その補足 — naoya (@naoya_ito) March 29, 2024 現在の実装 スパは予約を受け付けるために在庫の管理をしてます🎁 データベースで在庫テーブルを持っていますが、ベタな管理をしています。 特定の施設・日・在庫の数を00:00をt0000とみなして15分おきにt0000・t0015..t2345まで格納してます🤔 在庫テーブルのイメージは以下です。 shop_id inventory_id inventory_date t0000 t0015 (省略) t1300 t1315 (省略) t2345 1 1 2024-01-01 0 0 ... 1 0 ... 0 1 2 2024-01-01 0 0 ... 0 1 ... 0 この設計は在庫の調査時に在庫数を確認しやすいのですが、レコード挿入時にtxxxの形にしたり、描画時にtxxxをtimeに変換する必要があったりと、実際に在庫を含めた描画を行う処理に難ありでした😞 チーム内で相談した結果、検索で描画する際は時間の配列(例: ['10:00', '11:15', '12:45'] )を圧縮したビットを使うようにしました。 shop_id inventory_id inventory_date timeBits1 timeBits2 1 1 2024-01-01 1 0 1 2 2024-01-01 64 2 具体的な実装は後述しますが、カラムをビットで管理する場合のメリット・デメリットは以下です。 【メリット】 あるスパンごとのカラムを大量に持たずにビットの表現で圧縮できるのでデータ容量を抑えることができる 動的にカラムを決めるために一般的にオーバーヘッドの大きいと言われるリフレクションを使わなくていいため、ビット値を用いると比較的高速に検索可能 施設単位やプラン単位などで在庫有無をサマライズしたい時、ANDやOR検索で柔軟な条件指定が可能 【デメリット】 テーブルをSELECTで検索するだけでは状態がわからない(値を変換しなければならない)ため、デバッグやクエリ構築の難易度が上がる ビット値と時間の配列の間を相互変換するライブラリの用意が必要 ビット値はBIGINT型でも桁溢れする場合があるので、Bit1とBit2といったようにある部分で分割する検討が必要 以下からはビット演算の仕組みと、実際にどういうイメージで検索するかを説明します👀 ビット演算とは? データをビット列(0 or 1で構成される)とみなして演算します。 メリットは、値に対してANDやOR検索ができることです。 例えば1/2/3をビット列で表した場合、 00000001 / 00000010 / 00000011 です。 1と2でビットOR演算を行うと、 00000001 OR 00000010 ------------- 00000011 各ビットを縦に見て、少なくとも一方に1がある場合、結果のそのビット位置は1になるので、演算結果は10進数の3です。 実際にSQLServerで検索する際にAND演算を使う例を出すと、 CREATE TABLE Example ( Bits INT ); INSERT INTO Example(Bits) VALUES ( 3 ); SELECT * FROM Example  WHERE Bits & 1 = 1 ; // Bits列の値と 1 のビットANDが 1 に等しい行を選択するのでヒットする SELECT * FROM Example  WHERE Bits & 2 = 2 ; // Bits列の値と 2 のビットANDが 2 に等しい行を選択するのでヒットする SELECT * FROM Example  WHERE Bits & 4 = 4 ; // 3 ( 00000011 )と 4 ( 00000100 )はそれぞれに 1 が立っている位置が違うのでヒットしない Pythonの代表的なORMであるSQLAlchemyを使う場合は以下のように書けます。 query.filter(Example.Bits.op( "&" )(bits1) == bits1) 実装例 ビット演算で在庫管理するには、たとえば次のように実装します。 INSERT INTO Example(Bits) VALUES (n); の nに相当する値を在庫がある時間帯からビットへ変換して格納 検索時に時間を query.filter(Example.Bits.op("&")(bits1) == bits1) として検索し、取得できたBitsカラムを時間帯に変換 なので、デメリットでもお伝えしましたとおり、ビット値と時間の配列の間を相互変換するライブラリの用意が必要です。 今回は先人達が実装してくれていたライブラリが社内にあったため、ありがたく使わせていただきました。 変換の考え方 例えば00:00-23:45で15分スパンとしたとき、1日は96区切りです。 10:00 ~ 19:00に在庫が存在する を表現すると以下のようになり、96bitsで時間が有効であれば1が立つと考えることができます👼 要件によっては00:00で終わりではなく、24時以降の表現をしたい場合もあるので、1日の区切り数やスパンをどうするかはプロジェクトの定義によって決めて下さい。 |0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | || | | | | | | | | | | | | | | | | | | | | | | | | <000000000000000000000000000000000000000011111111111111111111111111111111111100000000000000000000> 1に関して、96bits(12bytes)のままではバイトオーダーの都合上扱いづらいので16bytesに変換すると、 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xf0\x00\x00' で、先頭~8bytesまでと9~16bytesまでの値を取得できます。これをbits1とbits2カラムとして格納します。 変換の一部をPythonでの実装してみると以下です。 実際の社内では複数のユースケースに対応できるように、より複雑なことをしてますが、社内のソースコードをそのまま載せられないのでサンプルコードのみです🙏 bits = '000000000000000000000000000000000000000011111111111111111111111111111111111100000000000000000000' bytes_array = int (bits, 2 ).to_bytes( 16 , byteorder= 'big' ) bits_int1 = int .from_bytes(bytes_array[ 0 : 8 ], byteorder= "big" , signed= True ) bits_int2 = int .from_bytes(bytes_array[ 8 : 16 ], byteorder= "big" , signed= True ) print (bits_int1) # 0 print (bits_int2) # 72057594036879360 2.に関しても逆の処理を行えば良く、検索したい時間をビットに変換し、データベースから時間帯をAND演算で取得。取得できたbits1/bitsをbytesに変換しつなげて、96bitsを復元します。 あとは0と1の状態によって、00:00から15分おきに繰り返しで判定することで時間帯を復元できます🍿 変換の一部をPythonでの実装してみると以下です。 bits_pair = ( 0 , 72057594036879360 ) bytes_int1 = bits_pair[ 0 ].to_bytes( 8 , byteorder= "big" , signed= True ) bytes_int2 = bits_pair[ 1 ].to_bytes( 8 , byteorder= "big" , signed= True ) reconstructed_bits = format ( int .from_bytes(bytes_int1 + bytes_int2, byteorder= "big" ), '096b' ) print (reconstructed_bits) # 000000000000000000000000000000000000000011111111111111111111111111111111111100000000000000000000が復元される 以上が相互変換するイメージでございます。 最後に 時間をビットで持つ実装の他にもチューニングしたため、単体での評価はできていませんが、今回の取り組みを通してスパの検索画面の描画は従来から1/3~1/5程度時間短縮することができました。 よって、ビットでの管理は今回スパの課題の解決手段としてはとても有効だったと考えます。 前述の通りデメリットもありますが、課題の解決手段の一つとして参考になれば幸いです! 一休では、ともに試行錯誤しながらよいサービスを作ってくれる仲間を募集しています! hrmos.co カジュアル面談も実施していますので、ぜひお気軽にご連絡ください! www.ikyu.co.jp
アバター
一休.comレストランのエンジニアの kymmt です。 2023年度の下半期、一休.comレストランの開発チームでは開発プロセス改善に取り組みました。改善は小さい単位で徐々に進め、バックログの作りかたやカンバンの運用方法を改善することで、フロー効率の向上、開発ペースの把握、チーム内外からの進捗の見える化ができるようになりました。 この記事では、このようなインクリメンタルな開発プロセス改善の取り組みについて紹介します。 従来の開発プロセス 主に2023年度前半の開発プロセスは次のような形でした 1 。 プロダクトのリリースに必要なタスクが長いバックログとして存在し、ひたすらタスクを消化 その状況に課題を感じ、区切りを入れるために2週間のスプリントを導入 この時点では、スプリントは2週間ごとに状況を確認するためのもので、目標に対するふりかえりや、次のスプリントの計画を作るためのものとしては活用していませんでした。 この開発プロセスに起因して、チームメンバーは次のような課題を感じていました。 どの機能に紐づくかが一見してわかりにくい技術的タスクや、やることが曖昧な項目がバックログにある タスクは進んでいるが、ひとまとまりの機能ができるのに時間がかかる 開発ペースを見通しにくく、今後の予定についてチーム内外に説明責任を果たしにくい スプリントを導入したものの、スプリント終了時の残項目が完了しなかった理由など、開発のボトルネックを深掘りできていない 改善の方針 先述した課題を受けて、開発プロセスをできるだけ早く改善したいという機運が生まれました。しかし、スクラムなど大きめの方法論をチームに導入するのはこれまで例がなく、ある種の理想的な開発プロセスには近づけますが、効果が出るまでに時間がかかりそうでした。また、著者(kymmt)は入社直後だったので、技術的なキャッチアップと並行してプロセス改善をサポートしたいという状況でした。 そこで、アジャイル開発のプラクティスをインクリメンタルに導入してプロセスを改善することにしました。 ここで、それらのプラクティスの生まれた理由や避けるべき罠は理解したうえで、課題の解決に必要なものを選択的に導入するという点に気を配りました。最近出た本だと 『アジャイルプラクティスガイドブック』 は参考になりました。 2023年度後半からの開発プロセス 上記の方針に基づいて、2023年度下半期からは、チームで次のような改善活動に取り組みました。 顧客価値に直結する開発はユーザーストーリーとして項目を整理し、その下で技術的タスクを分解/整理する カンバン上でユーザーストーリーを左から右に流すようにして、顧客価値がどの程度生み出せているか、ボトルネックはどこかを見える化する ユーザーストーリーに対する規模の見積もりとベロシティの計測を繰り返し、開発の見通しを立てられるようにする これらの活動はある小規模なプロジェクトから始めて、次にもう1つの中規模なプロジェクトに横展開することで、徐々にチーム全体に活動範囲を広げました。 導入の様子 小規模の開発プロジェクトへの導入 すでに述べたとおり、2週間ごとに期間を区切るという枠組みだけ導入されていました。今回はそれを足がかりに、まずは小さい規模の開発プロジェクト(強いていうならエピック)に対してプラクティスを導入していきました。 まず、事前にユーザーストーリーとして開発項目を改めて明らかにしつつ整理し直しました。そして、それらに優先度をつけてバックログ上で並び替えました。あくまでも例ですが、次のようなイメージです。 名前 優先度 ユーザーが関連するレストランの一覧を閲覧できる 高 ユーザーが人気のレストランの一覧を閲覧できる 中 ユーザーが近隣のスポットに基づくレストランの一覧を閲覧できる 低 (ここでは一休.comレストランの利用者のことを「ユーザー」と呼んでいます) そのうえで、項目の規模を相対見積もりしました。ストーリーに必要な技術的タスクについて認識を合わせながら、それぞれの項目の相対的な規模を比較します。現在に至るまで、フィボナッチ数列に基づくストーリーポイント(1, 2, 3, 5, 8)を使っています。ここでは、プロジェクトに携わる3人ほどで、規模の感覚を揃えて見積もりをしました。古典ですが 『アジャイルな見積りと計画づくり』 もあらためて参考にしました。 これらの項目を左から右に「To Do」、「In Progress」、「In Review」、「Done」のレーンを持つカンバンで管理します。これまでベロシティを計測したことがなかったので、見積もり実施後の初回スプリントでは、優先度に基づいてバックログの項目を「To Do」に並べ、優先度が高いものから取り組みました。また、できるだけ複数ストーリーを取らない(マルチタスクにならない)ように進めました 2 。 この時点でバックログの項目が整理された状態でカンバン上に現れ、関係者から見て進捗がわかりやすくなりました。また、スプリントを繰り返すなかで、カンバン上にあるストーリーを左から右に流すために複数人で手分けするような動きもできるようになりました。この点が効いて、目標期日をきつめにとっていましたがプロジェクトの作業を完了できました。 一方で、一部の開発プロジェクトだけに改善を適用していたので、チーム全体の開発ペースの計測ができていませんでした。これについては、次の中規模の開発プロジェクトであらためて進めました。 ツールの適切な運用 カンバン導入と前後して、コードベースとプロジェクト管理の距離が近いほうがチームの好みに合っていたので、従来Jiraを使っていたところをGitHub Projectsに移行し、これまで述べた運用に沿うようにカンバンや項目のメタデータを整備しました。また、チームで合意した運用方法はドキュメントとして明文化しました。 GitHub Projectsの効果的な利用方法については、以前このブログでitinaoが紹介しているのでぜひご覧ください。 user-first.ikyu.co.jp できるだけ業務に支障がないように、Jiraにあったデータも移行しました。こういう移行はやり切るのが大事なので、GitHub APIを利用して必要なデータを極力自動でGitHub側にインポートしました。 一休.comレストラン開発チームのカンバン 項目間の依存関係を示しづらいなどの課題感もありますが、現在はおおむね現状を把握しやすいカンバンを運用できています。 中規模の開発プロジェクトへの導入 前述のとおり、ある程度プラクティスの導入による効果が出てきたので、著者(kymmt)が直接担当しているわけではない別の中規模プロジェクトについても導入してみました。 このフェイズでは、メンバー全員がプラクティスを実践できるように、プロジェクトを進めるメンバーと一緒にストーリーの単位で項目を整理し直し、方法のコツなどを共有しました。さらに、それらの相対規模の見積もりも一緒にやることで、規模に対する感覚をチーム全体で揃えていきました。 もとは「状態管理追加」、「UI実装」のような技術的タスクの単位で項目が並べられていましたが、項目間の依存関係やまとまりを顧客価値として整理することで、何が実現できるか明確になりました。また、カンバン上でユーザーストーリーの粒度で左から右に1つずつ開発項目を流せるようになりました。チームメンバーからも作業が進めやすくなり、1つ1つのユーザーストーリーのリードタイムが向上したという声をもらいました。 加えて、見積もりされたバックログ項目に取り組む中で、チーム全体のベロシティも安定して見えるようになってきたので、今後の開発の見通しを立てやすくなりました。 一休.comレストラン開発チームのベロシティ スプリント開始時にチームで計画づくり 以前は前のスプリントの残項目をそのまま次スプリントに移す 3 というプロセスでしたが、現在はビジネスの状況やすべきことの優先度、またチームのベロシティも都度確認して、目標を決めてバックログを作っています。 結果的に前スプリントで残った分も次のスプリントでやりましょうになることはあるのですが、なにも考えずに移すのではなく議論をしたうえで必要なら移すというプロセスを経るようにしています。 結果 2023年度下半期に次のような開発プロセス改善活動をおこないました。 顧客価値に直結する開発をユーザーストーリーとして項目を整理 カンバン上で顧客価値につながる開発の進捗やボトルネックを見える化 ユーザーストーリーに対する規模の見積もりとベロシティの計測で開発ペースを見える化 スプリントの計画づくりで目標を定め、そのために必要なバックログを作る もともと技術的にしっかりしたチームだったので、これらの改善活動の結果でフロー効率をよくすることで、以前よりリードタイムの向上や安定が見られるようになりました。 また、ストーリーに基づいた開発項目の見える化によって進捗がチーム内外からわかりやすくなり、デモやレポーティングなど組織運営に必要な業務も進めやすくなりました。先の計画を立てやすく、予定変更にも柔軟に対応できるようになってきています。 他には、計画づくりに意識的に取り組むようになったので、ずるずると開発してしまうことが減りました。ビジネスの推進に必要なことがなにかを都度確認しながら開発を進められています。 これから すでに始めている取り組みとして、継続的に各チームメンバーがプロセス改善できるように、開発プロセスに関する知識をインプットする読書会を週次で開催しています。先日 『カンバン仕事術』 を読み終えたところです。 課題としては、技術的に専門性のあるメンバーに下周りの整備のようなタスクが集中したり、緊急の差し込みタスクをシステムに詳しいメンバーが多めに取りがちだったりと、メンバー間のスキルの差によってWIPが多くなったりすることもあります。こういうときにタスクを取捨選択したり、メンバー間で知識を共有していく方法については、既存のプラクティスも参照しながら継続的にチームで考えていくつもりです。 一休では、ともに良いサービスをつくっていく仲間を募集中です。 hrmos.co カジュアル面談も実施しているので、お気軽にご応募ください。 www.ikyu.co.jp 著者(kymmt)は入社前〜入社直後なので聞いた話も含みます ↩ WIP制限に基づく方針ですが、このとき数値はとくに指定していませんでした ↩ Jiraの機能でそうなっていたというのもあります ↩
アバター
この記事は 一休.com Advent Calendar 2023 25日目の記事です。 一休レストランでは、よりスムーズな予約体験の提供を目的とするシステムのリニューアルを進めています。その一環として、2023年10月から、レストラン個別ページの表示から予約までのスマートフォンビューにおいて、バックエンドのサーバをRustで書かれたものに置き換えました。 一休レストランの Rust バックエンドが正式リリースされました。 https://t.co/7N4VGv5ej9 このページのスマートフォンビューはバックエンドが Rust で書かれた GraphQL になってます — naoya (@naoya_ito) October 4, 2023 本番運用が始まって3か月近く経ちましたが、これまで安定して継続的な開発と運用ができています。これはRustだからと構えることなく、「ふつう」のバックエンド開発を心がけてきたからだと考えています。 Advent Calendar 2023最終日は、一休レストランの開発チーム一同から、一休レストランのRustバックエンド開発の様子をお届けします。 Rustを選定した理由 現在のバックエンドのユースケース レストラン情報の取得 予約の確保 現在のアーキテクチャ 各モジュールの紹介 ドメインモデル データアクセス層 GraphQLとHTTPサーバ ライブラリ Rustによる開発のふりかえり よかったこと Rustはビジネスロジックを書くのにも便利 アプリケーションの各層で型安全にデータを変換 Cargo workspaceを活用した開発 パフォーマンスの向上 もっとよくなると嬉しいこと エコシステムのさらなる成熟 まとめ Rustを選定した理由 一休レストランのリニューアル計画が始まったころ、一休では宿泊予約サービスや社内の基盤サービスを中心としてGoが標準的なバックエンドの技術スタックでした。 一休レストランの開発でも、宿泊予約サービスでの経験があるメンバーのスキルセットに基づいてGoを使うこともできました。その一方で、この方針だと社内の技術ポートフォリオがGoに偏ってしまうという懸念もありました。 一休では、社内で蓄積する技術的知見に多様性を持たせ、結果として状況に応じて最適な技術選定ができるように、複数のプログラミング言語を使うことを意図的に選択しています。 株式会社一休 会社紹介資料 / introduce-ikyu - Speaker Deck より一休の技術選定の方針について そこで、チームメンバーの中にRustに詳しいエンジニアがいたことも助けになり、Rustをバックエンドの言語として採用するかどうかを検討しました。 Rustの採用による狙いは次のとおりです。 まず置き換えたい参照系処理のCPU利用効率を上げて、高速なバックエンドサーバとする 今後のさらなる開発を見据え、メモリ安全、型安全な開発体験を実現する 技術的知見の多様性という点で、関数型のメンタルモデルでプログラミングできるエンジニアを増やす 同時に、Rustの採用に対する次のような懸念も上がりました。 初めて使うエンジニアにとっては学習に時間がかかる ライブラリの自作が必要となるケースもありそう Rustは 公式ドキュメント や docs.rs のリファレンスなどでドキュメントが充実しているので、学習曲線は急ではあるものの、学習自体は進めやすいと判断しました。 ライブラリについては、Rustから一休の基幹DBであるSQL Serverにどうやって接続するかという技術的な検証が必要でした。最終的には、Prismaが公開している Tiberius というSQL Server用のDBドライバをベースとして、ある程度アプリケーションから使いやすいインタフェースのライブラリを整備することで開発が進められると判断できました。 これらの議論や調査に基づいて、一休レストランのバックエンドでRustを採用することになりました。 現在、一休レストランのバックエンドを開発するエンジニアは3人います。そのうち2人は、一休レストランの開発をきっかけに、はじめてRustを本格的に利用し始めました。豊富な学習リソースやRustに詳しいメンバーのヘルプを通じて、プロジェクト開始前の学習では String と &str の違いを理解するところから始めたメンバーも、プロジェクト開始後はスムーズに開発できるようになりました。 現在のバックエンドのユースケース ここからはRustでバックエンドを「ふつう」に開発するための、設計や実装における面白いポイントを紹介していきます。 現在は主に次のユースケースでバックエンドを利用しています。 レストラン情報の取得 店舗情報や予約可能時間など、レストランの情報をお客様に提供するための情報を取得します。機能はGraphQLのクエリとして提供しています。 今回はレストラン個別のページの表示から予約までのフローの置き換えを開発スコープとしたので、現在はこのユースケースが大半を占めています。後述のとおりコードベース上もデータの読み出しに関するコードが多いです。 予約の確保 お客様から入力いただいた情報をもとに予約を確保するエンドポイントをGraphQLのミューテーションとして提供しています。また、実際の予約処理は、予約処理モジュールを持つ既存の社内別サービスに委譲しています。 現在のアーキテクチャ 現在、アプリケーションのアーキテクチャとしてコマンドクエリ責務分離(CQRS)に基づいた構造を採用しています。つまり、データを読み出すだけのクエリと、データの作成や更新をするコマンドで、利用するモデルを分離する方式をとっています。 また、たとえばクエリの場合、DBとSolrそれぞれについてデータアクセス層を設け、GraphQLのデータローダーのようなシステムの界面に近い層からは、データアクセス層を通じてクエリモデルの形式でデータを取得します。 モジュールとその依存関係 これらのモジュールはCargo workspaceを用いて管理しています。この点についてはあとで詳しく説明します。 各モジュールの紹介 上述した図における各層を構成するモジュールについて紹介します。 ドメインモデル CQRSにおけるクエリとコマンドで利用するモデルを実装している層です。ドメインモデルは他のどのモジュールにも依存しません。また、クエリとコマンドは別モジュールとするためにcrateを分けています。 クエリモデルの例としては、レストラン詳細画面で表示する店舗情報があります。これらのデータは実際は複数のテーブルに存在しますが、クエリモデルはそのような実装詳細には依存せず、クエリの結果としてほしい構造を定義しています。実際には、SQL ServerもしくはSolrから得たデータをクエリモデルに変換して利用します。 #[derive( Debug , Clone )] pub struct Restaurant { pub id: RestaurantId, pub name: String , pub description: Option < String > , // ... } コマンドモデルの例としてはお気に入り店舗登録用のコマンドモデルなどが存在します。こちらはまだ数が少ないので割愛します。 データアクセス層 実際のデータを取得するためのロジックを実装している層です。現在は、一休の基幹DBであるSQL Serverや、検索サーバであるSolrからデータを取得しています。このデータアクセス層の利用者に対して、取得したデータをもとにモデルのインスタンスを返します。つまり、ドメインモデルに依存します。 クエリを実行するときは、 Serde や serde_with を利用して、データストアから取得した生データをDTOにデシリアライズします。 mod dto { // ... #[serde_with::serde_as] #[derive( Debug , serde::Deserialize)] pub struct Restaurant { #[serde(rename = "restaurant_id" )] #[serde_as(as = "serde_with::TryFromInto<i32>" )] id: RestaurantId, #[serde(rename = "restaurant_name" )] name: String , // ... } } さらに、このDTOからクエリモデルに変換するために std::convert の From トレイトや TryFrom トレイトを活用しています。詳しくは後述します。 GraphQLとHTTPサーバ バックエンドはGraphQLを通じてフロントエンドにクエリとミューテーションを提供しています。このGraphQL APIの実装にはasync-graphqlを利用しています。async-graphqlはコードファーストでGraphQLスキーマを定義できるcrateです。 github.com // Restaurant { // name // } // のようなスキーマをコードで定義 pub struct Restaurant ( pub query_model :: Restaurant); #[async_graphql::Object] impl Restaurant { async fn name ( & self ) -> &str { & self . 0 .name } // ... } また、HTTPサーバとしてはAxumを利用しています。 github.com これまではGraphQLなのでエンドポイント1つで済んでいましたが、最近は社内の他サービスと通信するためにインターナルなREST APIを作る機会も増えてきています。 ライブラリ アプリケーションを構成するモジュールとは別に、独立したロジックをまとめたライブラリとしてのcrateもいくつか作成してworkspaceに含めています。これらのライブラリは他モジュールから利用されます。 たとえば、先述したTiberiusをベースにしたDBドライバや社内サービスのクライアント、他にはログなどの横断的関心事を扱うライブラリが存在します。 Rustによる開発のふりかえり よかったこと Rustはビジネスロジックを書くのにも便利 Rustの言語機能として、所有権やライフタイムのようにメモリ安全性を意識したものがよく注目されます。さらに、Webアプリケーションバックエンドを書くうえでは、 Option や Result に代表される関数型言語のエッセンスを取り込んだ機能や、データ変換にまつわる機能も非常に便利だとあらためて感じました。 一休レストランは15年以上の歴史があるサービスです。このようなサービスは、しばしば歴史的事情からなるデータ構造やコードを多く持っています。たとえば有効な値とnullの両方が存在しうるカラムを扱うこともあります。このときに Option を利用することで、ビジネスロジック上でnullにまつわるバグを避け、match式やif let式によって値がないケースをつねに考慮できます。 また、Webアプリケーションは無効な値を入力されたり外部のサービスとの通信に失敗するなど、つねにロジックが失敗する可能性があります。そのようなロジックでは返り値として Result 1 を使うことで、確実にエラーをハンドリングできます。また、 ? 演算子を利用することで、コードを簡潔に保ちつつエラーハンドリングできるのも便利な点です。 他には、一休レストランだと予約可能な時間や食事コースの検索結果などでコレクションを操作する場面が数多くあります。このようなときに、イテレータと map や filter のようなイテレータアダプタを利用することで、コレクションにまつわるビジネスロジックを簡潔に書けるのもよい点だと感じています。 アプリケーションの各層で型安全にデータを変換 先述したように、このアプリケーションでは複数のモジュールで責務を分けています。よって、そのままではデータアクセス層でデータストアから取得した生のデータをDTOを経由してクエリモデルに変換するロジックを書く必要が出てきます。 ここで、 From トレイトや TryFrom トレイトを用いて型安全なデータの変換を実装することで、層の間で安全にデータを受け渡しできます。たとえばDTOをクエリモデルに変換するために From トレイトや TryFrom トレイトをDTOに対して実装し、適切にモデルへ変換できるようにしています。 impl From < dto :: Restaurant > for query_model :: Restaurant { fn from (d: dto :: Restaurant) -> Self { query_model :: Restaurant { id: d.id, name: d.name, // ... } } } このようにモデルに対して変換のためのトレイトを実装しておけば、あとは from / try_from や into / try_into を使うだけで層の間の型安全なデータ変換が可能になります。 Cargo workspaceを活用した開発 Cargo workspaceを活用してモジュール間の依存関係を制御しながら開発できているのもよい点です。 リポジトリのルートディレクトリにあるCargo.tomlでは、workspaceのmembersとしてアプリケーション内の各モジュールを指定しています。そして、それらのモジュールをcrateとして実装し、各crateのCargo.tomlではアーキテクチャを意識して他のcrateへの依存関係を設定することで、意図しない依存はコンパイラによってエラーにできる構造にしています。 # ルートディレクトリのCargo.toml [workspace] resolver = "2" members = [ "backend/*", ] # データアクセス層のCargo.toml [package] name = "backend-data-access" version.workspace = true authors.workspace = true edition.workspace = true publish.workspace = true [dependencies] backend-query-model = { workspace = true } また、モジュールをcrateに分離したことで、コードを変更したときに、変更のあったcrateとそのcrateに依存するcrateだけを再ビルドすればよくなりました。結果として、毎回アプリケーション全体をビルドせずに済み、開発時のビルド時間の短縮にも貢献しています。 パフォーマンスの向上 もちろんパフォーマンスの向上も当初の狙いどおり達成できた点であり、よかったことの1つです。 バックエンドはGoogle Cloud Runで運用しています。現在は年末年始でレストラン予約が非常に増える時期ですが、ピーク時でも3台程度のインスタンスでリクエストを受けることができています。 また、一休レストランのバックエンドの一部をRustに移行したことで、従来のPythonのバックエンドにおけるKubernetes DeploymentのReplicaSet数を次のように60程度から40程度に減らすことができました。 Wed 4以降はPythonバックエンドの負荷をオフロードできた 他には、バックエンドの高速化にともなってサービス全体の構成を最適化することで、一休レストラン全体のパフォーマンスが向上しました。こちらについてはチームメンバーのkozaiyが次の記事に詳しく書いたのでご覧ください。 user-first.ikyu.co.jp もっとよくなると嬉しいこと エコシステムのさらなる成熟 Webアプリケーションバックエンドを開発するうえで、さらにプラットフォームのRust対応が拡充されると開発が楽になりそうです。 たとえば、現在はCloud Runを使っているので、APMとしてCloud Traceを利用することにしました。しかし、公式にはRustのSDKが提供されていないことから、独自のライブラリを開発することで対応しています。 まとめ この記事では、一休レストランにおいてRustを採用した理由と、Rustによる「ふつう」のWebアプリケーションバックエンド開発の様子について紹介しました。 Rustを採用したことで、期待どおり性能面で大きなメリットを得ることができました。また、RustやCargoの機能を適切に活用することで、生産性を保ちつつ今後の継続性も考慮した設計で開発を進めることができています。 新たにRustを利用し始めたチームメンバーからは、Rustに対する感想として 自分自身にプログラミングを教えてくれる言語だなと思いました プログラミングする上で、気にすべきポイントを気にさせてくれる言語 という声もあがっています。 今後のバックエンドの展望としては、よりよい予約体験の提供やレガシーシステムの改善を目的として、 高速なレスポンスが求められるレストラン検索 レストラン予約のロジックなどのレガシーかつコアドメインであるモジュール についてもRustで置き換えていく予定です。このような箇所では、高いパフォーマンスや型に守られた開発体験を提供してくれるRustを活かすことができるだろうと考えています。 このような技術的なチャレンジができる一休レストランのバックエンド開発に興味があるかたは、ぜひカジュアル面談応募ページや求人ページからご連絡ください。 hrmos.co www.ikyu.co.jp 一休レストランでは anyhow::Result を利用しています ↩
アバター
この記事は 一休.com Advent Calendar 2023 24日目の記事です。 宿泊プロダクト開発で開発ディレクターをしています、橋本と申します。 ついにクリスマスイブ。残すところこの記事を含めて2つとなりました。 本日の記事では開発ディレクター1年目の奮闘劇を皆さんに紹介したいと思います。 同じディレクターの方はもちろん、何か新しいことに挑戦している皆さんに届くと嬉しいです。 簡単に経歴紹介 新卒でNWインフラの会社に入社し、 エンジニアとして法人顧客のサービス導入をサポートをしてきました。 AWSの運用、セキュリティ商材の導入、NW機器の導入運用、スマホ管理サービス導入など様々な分野を担当し、直近では技術営業として提案メインでの活動に従事していました。 5年目になったころ、サービスの導入ではなく、サービスを作ることに興味を持ち、プロダクト開発という新しい分野にチャレンジすることを決めました。 そこから社内の制度を活用し、現在は一休にお世話になっています。 奮闘劇 インプット多量死をなんとか免れた序盤 入社前に開発ディレクターとはなんぞやということですごく簡単な資格だけ取りました。 Webディレクション | Web検定(ウェブケン) 一般的なWeb業界の用語がメインで、実際に何をする役割なのかはふわっとだけ学びました。ただ、実際に入社してみると、言葉通り「右も左もわからない」状況でした。 社内で使われているツールで触ったことがあったのがGmailとSlackだけで、進め方以前に使い方がわからない。。 業務フローについて説明を受けるも、表面的なところだけ分かった気になってしまう。。 SQLも書けないのでデータ抽出を頼まれても時間がかかる。。 操作方法について聞くも1回では理解できないので、録画をして後で自分でコンフルにまとめる日々。。 なによりアウトプットが何もできない状態でした。 そこで自分が意識したのは、 わからないことはわからないままにしない 一度教えてもらったことは、次回からは一人でできるようになる とにかく周りに迷惑をかけないように、渡されたタスクはミスなくこなせるようにすることを日々考えていました。 まずは仕事に慣れること、一人分の仕事ができるようになることを目指してがむしゃらに取り組む日々でした。 とにかくインプット量が多くて整理しきれなくなりそうになるのをなんとか踏ん張った2カ月。 2カ月目で起きたのが常に追いかけまわしていたディレクターの先輩が産休に入られるという出来事。。。 必死に犬掻きをする中盤 産休に入られた先輩から複数プロジェクトのディレクションを引き継ぎました。 正直、やってやる!という気持ちと、自分が主体になることでプロジェクトが失敗するのではないかという不安で、精神的には余裕のない状態でした。 実際に業務に取り掛かると、 引き継いだ業務をうまく進めようと意気込むが、頑張りどころと向かう先がイマイチ合っておらず、日々犬掻き状態。。 チームからはディレクションとしての役割を求められるが調整業務にも何日も時間をかけてしまう状態。。 今振り返るとこんな状態でした。 進め方や要件、仕様について各所と調整をしているつもりが、状況や要望を聞いてきて持って帰るだけの伝書鳩になっていた ユースケースを複数考慮できず出戻りが発生することが多かった 自分がやるべき最低限タスクができていないのに、改善や新規の提案など背伸びをして何か価値を出そうと空回りしていた 振り返るとなかなか恥ずかしいですね、、 この状況を打破すべく意識したことは 取り組む前に進もうとしている方向の認識合わせを行う 悩むポイントはこまめに壁打ちを行う チームマネージャーに週1回、プロジェクトの進め方やチームのコンディションについて会話をする時間をいただきました。 これがとても大きかった…! この時に必ず自分の考えをもって臨み、ギャップを埋めていくことに努めた結果、 敷いてもらったレール上を進めることはできるようになってきました。 では次は自ら動けるようにならねば。。。 自分の役割が何となくわかってきた今 複数のプロジェクトを経験することで、プロジェクトの初期、中盤、リリース前、リリース後のぞれぞれのタイミングでディレクションがやるべきことがわかってきました。 「あのプロジェクトと同じように、こう進めていきます」といえるようになったのは大きい。 今後の動きを予測して動けるようになったこともあり、チームメンバーや他部署から依頼をされることも増えてきました。 さらに成長を感じたところとしては、【考えるタスク】を少しずつこなせるようになったこと。 調整業務やチームの開発を前に進めることだけではなく、本来のディレクション(方向を示す)という意味での【考えるタスク】を担当し、チームがその方向に進んでいくという体験が少しずつできるようになってきています。 最近ではこのようなことに悩んでいます。 開発目線になりすぎてビジネス観点(価値あるもの適切なタイミングで世に出すためにはどうすべきか)が漏れてしまうことがある これは同じ悩みをお持ちの方もいらっしゃるのではないでしょうか。 開発ディレクターは開発メンバーと過ごす時間が多いこともあり陥りがちな思考だと思います。 安全にミスなく進めるためにはとても重要ですが、忘れてはいけないのは、 リリースをすることがゴールではなく、【価値のあるプロダクトを生み出すことがゴールである】ということです。 例えばA案が良いと思って進めていたけれど、リリース直前になってB案の方が顧客の満足度も高く、売上にもつながるとわかったケースがあるとします。 開発チームとしては、直前で変更を加えなくてはいけない、リリース日の延長はなるべく避けたい、という状況はストレスにつながると思います。 しかし、私たちが進むべきゴールは【価値あるプロダクトを生み出すこと】です。 開発チームには負荷がかかりますがディレクターとしてはサンクコストではなく、プロダクトの価値を見るべきです。 私は同様の経験を通じで、ディレクターは開発目線とプロダクトオーナーのどちらの目線も持つことがとても重要だと身をもって学びました。 ディレクターとして働き始めた当初は、ディレクターって正直いなくても開発は進むよな…と自分の価値を見つけられずにいました。 今では、プロダクトの価値を最大化すること、さらに開発チームとプロダクトオーナーの両者が最も進めやすい方法を模索することがディレクションの価値だと思っています。 今後に向けて 今の私が意識し、目標にしていることを宣言させてください。 私は、エンジニアより技術力はない。 私は、マーケターよりも市場の把握や予測に強くない。 私は、営業よりも現場の考え方が理解できていない。 けれど、チームの推進力を高め、開発によって生み出されるプロダクトを価値あるものにする力は誰よりも持てるようになりたい。 そのために2つのことを意識していきたいと考えています。 チームマネジメントについて学び、チームに合った進め方でさらに推進力を上げていく ビジネス目線を常に意識し、開発で生まれるサービスが価値あるものになるようにディレクションを行う 一人のディレクターとしてチームや会社にとってなくてはならない存在になることを目指してきます。 最後に一言 勇気を出して、別業界かつ別職種にチャレンジしたことを本当によかったと思っています。 辛い時もありますが、日々自己成長できていると実感することができています。 この場を借りてチームメンバー、同じエンジニアメンバー、一休の皆さんに感謝の気持を伝えたいです。いつも温かいアドバイス、ありがとうございます。 これからも明るさと元気を取柄に頑張ります!
アバター
このエントリーは 一休.comのカレンダー | Advent Calendar 2023 - Qiita の22日目の記事です。 レストランプロダクトUI開発チームの鍛治です。 一休レストランのフロントエンドを担当しています。 一休レストランでは Next.js App Router Remix を採用しています。 user-first.ikyu.co.jp 昨年の終わり頃から始まった一休レストランのリニューアルですが、フロントエンドは Nuxt v2 (Vue 2) から Next.js App Router (React) に、という大きな切り替えで、不慣れだった我々は React 初心者がひっかかる落とし穴を全部踏み抜いてきました。 例えば、チュートリアルに従って useState で変化する状態を定義して、最初はそれで全てがうまくいっていました。機能追加していく過程でいつの間にか一つ増え二つ増え、あとはズルズルと。 ふと我に返ると一つのコンポーネントに10個もの useState が生えてしまっていました。 その結果、 && , || , ?? のオンパレードと三項演算子だらけの JSX だけが残りました。何度も何度も読み返してるのに、コンポーネントが今どんな状態にあるのか、さっぱり把握できない… 他にも、 バケツリレー コールバック useEffect 問題 といった落とし穴を踏み抜いてきました。 フロントエンドの状態管理って本当に難しいですよね。 あらためて本日は React 状態管理改善の第一弾として useState 濫用からどう抜け出したのかについてお話しします。 コールバックや useEffect 問題は来月以降の記事でご紹介する予定です。 useState の難しさ まずは一番初歩的なところから考えてみましょう。 複数のuseStateフックを使用する場合、予期しない状態の組み合わせが発生する可能性があります。 function Sample () { const [ show , setShow ] = useState ( false ); const [ disabled , setDisabled ] = useState ( false ); const toggle = useCallback (() => { setShow (( prev ) => ! prev ); } , [] ); const toggleDisabled = useCallback (() => { setDisabled (( prev ) => ! prev ); } , [] ); return ( <> < button onClick = { toggle } disabled = { disabled } > show < /button > < button onClick = { toggleDisabled } > disable < /button > < SampleModal show = { show } / > < / > ); } このシンプルな例では、show(モーダル表示用)と disabled(ボタン無効化用)の二つの状態を管理しています。 しかし、たった二つしかないのに show === true && disabled === true のように、ボタンが無効化されているにも関わらずモーダルが表示されている、という矛盾した状態を表現できてしまいます。useState で管理する状態が増えれば増えるほど、矛盾した状態を生んでしまう可能性は高くなります。 この問題を解決するためには、コンポーネントの粒度を小さくし、useState には primitive 値を入れず構造化されたデータを用いて、ありえない状態を生まないようにするのが自然な発想でしょう。 type State = Initial | Disabled | Modal type Initial = { type : 'Initial' disabled: false show: boolean } type Disabled = { type : 'Disabled' disabled: true show: false } type Modal = { type : 'Modal' disabled: false show: true modalData: ModalData } function Sample () { const [ state , setState ] = useState < State >( { type : 'Initial' , disabled: false , show: false } ) const open = useCallback (() => { setState ( { type : 'Modal' , modalData: 'data' , disabled: false , show: true } ) } , [ setState ] ) const toggleDisabled = useCallback (() => { if( state.disabled ) { setState ( { type : 'Disabled' , disabled: true , show: false } ) } else { setState ( { type : 'Initial' , disabled: false , show: false } ) } } , [ setState ] ) return ( <> < button onClick = { open } disabled = { state.disabled } > show < /button > < button onClick = { toggleDisabled } > disable < /button > < SampleModal show = { state.show } / > < / > ) useState + union 型では足りなかった 上述した実装のように、union 型によって不正な状態が作られなくなりました。 遷移はイベントハンドラ内で暗黙的に記述されます。上記のモーダルでは状態が2つしかなく、シンプルな実装なので遷移の全体像を把握できていますが、状態の数が増え遷移が複雑になると遷移の全体を把握するのが困難になり、人為的に遷移先を決定するロジックをテストする必要があります。結果、誤って不正な遷移が紛れ込む場合があります。 例えば、一休レストランでは空席確認カレンダーという機能があります。 空席確認カレンダー 上記空席確認カレンダーの状態遷移図は以下のようになります。黒色で囲われているのが状態で、灰色で囲われているのが遷移イベントです。 カレンダーの状態遷移図 状態が7個、遷移イベントが20個あり、イベントハンドラ内での遷移先を決めるロジックが複雑になってしまい不正な遷移を起こしてしまう可能性がありました。 このような不正な遷移を人為的ではなく機械的に防ぐために、state machine を導入します。 state machine とは? state machine は複数の「状態」と「状態間の遷移」で構成されます。 上述した web 画面のシナリオを例にすると「フラットな状態」(通常の状態)から「モーダルが開いた状態」への遷移は「 show ボタンをクリックする」というイベントによって行われます。 「モーダルが開いた状態」では再度 「show クリック」イベントが発生しても、そのイベントに対応する状態遷移は定義されていないので、それ以上何も起きません。 また「フラットな状態」から最初に disabled ボタンが押されて (disable イベントが発火して)「ボタンが無効化された状態」になると、そこで仮に show イベントが発火しても、同様に show イベントに対応する状態遷移が定義されていないので、「ボタンが無効なのにモーダルが開いてしまう」という矛盾した状態が生じません。 モーダルの状態遷移図 state machine では、あらかじめ定義した状態とその状態間の遷移しか存在しないので、予期しない状態に陥ることがありません。state machine を導入すると、アプリケーションロジックを明確かつ宣言的に定義できるのが非常に魅力的なポイントです。 XState (state machine) の導入 state mcahine を導入するために、 XState を使った状態管理方法を導入することを決定しました。 もちろん他の解決策もあったと思います。 例えば、弊社 CTO が以前ご紹介した TypeScript の discriminated union (タグ付きユニオン型)で状態を、関数で遷移を表現する手法はその一つであり、弊社プロダクトで実績あるソリューションであることは間違いありません。 techplay.jp ただ、現在の自分達では、制約のない状況下でうまく型を定義して、状態を完全にコントロールできるという自信は持てませんでした。state machine もどきの不完全な物を生み出してしまわないか不安があったのです。 XState であれば state machine を正しく定義することを強制されます。技術としてのフレームワークに留まらず、思考のフレームワークとしてガイドレールを提示してくれる点を評価しました。 XState とは? stately.ai state machineを作成することができる非常に高機能なライブラリです。 例えば、フロントエンドのサンプルとしてよく用いられる TODO リストを XState で実装 *1 すると以下のようになります。 type TodoList = { items: { id: number name: string completed: boolean }[] } type TodoEvent = Add | Toggle | Disable | Enable type Add = { type : 'ADD' item: { id: number name: string completed: boolean } } type Toggle = { type : 'TOGGLE' id: number } type Disable = { type : 'DISABLE' } type Enable = { type : 'ENABLE' } type TodoState = { value: 'ACTIVE' ; context: TodoList } | { value: 'INACTIVE' ; context: TodoList } export const machine = createMachine < TodoList , TodoEvent , TodoState >( { initial: 'ACTIVE' , states: { ACTIVE: { on: { ADD: { target: 'ACTIVE' , actions: assign (( ctx , event ) => ( { items: [ ...ctx.items , event.item ] } )), } , TOGGLE: { target: 'ACTIVE' , actions: assign (( ctx , event ) => ( { items: ctx.items.map (( item ) => item.id === event.id ? { ...item , completed: ! item.completed } : item ), } )), } , DISABLE: 'INACTIVE' , } , } , INACTIVE: { on: { ENABLE: 'ACTIVE' , } , } , } , } ) まず state として TODO を追加したりトグルを変更が可能な状態の ACTIVE と、なにもできない状態の INACTIVE を定義します。 次に、各 state が各イベントを受け取った時にどの状態に遷移するか、すなわち状態遷移を on で定義し、その状態遷移時の副作用としてのデータ更新を actions で指定することで、state machine が完成します。 XStateでは、内部情報として context (詳しいことは後のセクションで説明します)を持ちます。 ADD イベントでは context である items に 新しい TODO を追加しています。 XState で定義した state mahine では、 INACTIVE の状態で ADD や TOGGLE のイベントに対する状態遷移を定義していないので、 ありえない状態に遷移しないことが保証されます。 。 context context とは、state machine が扱う状態の「詳細」や「変動する部分」を吸収して、複雑な状況に対応する仕組みです。 state machine 、厳密には有限状態機械(FSM: Finite State Machine)の「有限」は、あくまで数学的な「有限」です。 実際のアプリケーションでは、管理しなければならない状態に紐づくデータや条件が複雑で、有限状態機械を原理的に適用すると、たとえ「有限」であっても、人間の認知能力ではとうてい把握しきれない膨大なバリエーションを生み出してしまいます。 有限状態機械を現実的に利用するために 状態とその状態に関連するデータを分離して、context という形で保存・管理します。 例えば以下のように、ユーザーの入力やアプリケーションの現在の状態など、状態自体ではなく、状態の「内容」を表すデータのことです。 予約する人数日時 予約の際に選択する支払い方法 使用するクーポン情報 予約入力の状態遷移図 XState で管理すべきでない状態 XState で全ての状態を管理すべきと言ってるわけではありません。ボタンを押すとモーダルが表示される状態遷移は、XState で管理してしまうと却ってオーバーエンジニアリングになってしまいます。 また、以下の場合は状態として持つべきではありません。 状態遷移から独立しており、値が操作の過程で変化しないもの 例えば、API レスポンスは state machine の遷移に変化する値ではないので XState で管理すべきではなく、useState で管理すべきです。 XStateで管理すべき基準としては 1つのコンポーネントで useState が3つ以上定義されている 何かアクションを起こした時の遷移先が2つ以上ある 場合だと思ってます。(プロダクトによって基準は違うと思うのであくまで目安です) XStateを導入して良かったこと フロントエンドの改修が容易になった state machine によりありえない状態ができないことが担保されているので、フロントエンドの改修をする際に大きいバグが起きなくなりました。 実装前の仕様 / モデリングの議論ができるようになった state machine が画面のドメインモデルとなるので、画面や機能を作成する際にどのような state machine にするか議論することで、意図せずも画面や機能のモデリングの議論ができるようになりました。 所感 XState による state machine という考え方のガイドレールができたことで、条件文を最小限にする state mahine のメンタルモデルが形成されてきたように思います。 また、上述したように全て XState で管理すべきだとは思ってません。適材適所で XState をうまく活用していきたいです。 さいごに 一休では、より良いサービスを作ってくれる仲間を募集しています! www.ikyu.co.jp カジュアル面談も実施していますので、ぜひお気軽にご連絡ください! hrmos.co *1 : XState 4 ベースのコードです。XState 5には近日中に移行予定です
アバター
概要 この記事は 一休.com Advent Calendar 2023 16日目の記事です。 RESZAIKO開発チームの松村です。 一休では各サービス毎に、開発中のサービスの動作を社内で確認できる環境があります。 それぞれmain(master)ブランチと自動的に同期している環境と、特定のブランチを指定して利用できる環境の2種類があります。 今回、RESZAIKOの新規サービス(予約画面)に対してブランチを指定してデプロイできる環境を作成したので、その方針と反省点と今後について記述していきます。 現在運用中の予約画面 開発環境を作る理由 一休では長らく、EKS上に複数の環境を用意して、ブランチを指定すると開発環境にデプロイするシステムが利用されてきました。 一般的にこのような環境を構築するのは以下のような理由が挙げられます。 動作確認 マイクロサービスで、異なるブランチ同士の組み合わせで動作確認がしたい ローカルだと何故か再現しない デプロイがちゃんと動くか確認したい 他人と成果物の共有 リリースできるほど動作に自信は無いが、ステークホルダーと内容を共有したい 本サービスでは Prisma を利用してDBのスキーマをアプリのコードと同じリポジトリで管理しているため、 複数の新機能を平行して開発していく場合に開発環境が1つだと、DB定義が衝突したりして尚更大変です。 そこで、複数の開発環境を作成できるようにしました。 本サービスは基盤にGoogle Cloudの Cloud Run を使用しています。 Cloud Runは特に設定しなければアクセスがある時だけコンテナが起動するようになっているので、EKSを使用した場合よりスペックやコストをあまり気にせず環境を増やしていけます。 実現方法 サーバはCloud Runで動いていて、デプロイは Github Actionsで行っています。 そのため、開発環境用のGithub Actions Workflowを作成していきます。 デプロイを行うGithub Actions Workflowの作成 本記事の主旨から外れるので詳しく説明しませんが、 Google Cloudには Github Actionsと連携してデプロイを行うための機能 が各種用意されているので、参考にしてWorkflowのyamlファイルを作成します。 name : backend.demo.create on : workflow_dispatch : inputs : name : required : true type : string description : "Environment name to deploy" jobs : build : runs-on : ubuntu-latest steps : - name : "Checkout" uses : actions/checkout@v3 # SecretからGCPの認証用のjsonを読み出す - id : "auth" uses : "google-github-actions/auth@v0" with : credentials_json : "${{ secrets.gcp-dev-service-accont-key }}" - name : "Set up Cloud SDK" uses : "google-github-actions/setup-gcloud@v0" with : install_components : 'alpha,beta' # 以下ビルド・デプロイの記述 Workflowの呼び出し Workflowに workflow_dispatch を定義することで、 外部からREST APIでWorkflowを呼び出す ことができます。 開発環境用のアプリを作成して、そちらからREST APIで必要に応じてWorkflowを呼び出してあげます。 POST https://api.github.com/repos/test/test-repo/actions/workflows/backend.demo.create/dispatches Content-Type: application/json Accept: application/vnd.github+json Authorization: Bearer <TOKEN> X-GitHub-Api-Version: 2022-11-28 { "ref":"feature/branch-to-test", "inputs":{"name":"demo-1"} } 実装された運用 ブランチデプロイサービス画面 こんな感じのアプリを作成しました。 ブランチ名を入力して Deploy を押すと、デモ環境に該当のブランチがデプロイされます。 いつ、誰が、どのブランチをデプロイしたかを記録するようになっています。 削除機能はまだ実装していないので、使い終わったらmainブランチを手動で適用する運用になっています。 反省と将来 折角Cloud Runを使っているのに、既存の他サービスの仕様に引きずられた実装にしてしまいました。 特に以下の点が良くないです。 設定ファイルをコピペして増やしていたので、環境を増やす毎に同じような設定ファイルが増える 環境毎に社内用のドメイン( [env-name].dev.reszaiko.com のような)を作っていたので、環境を増やす度にDNSとSSLの設定が必要になる このため、気軽に環境を増減させる事が困難になっていて、既存の問題をそのまま引き継いでいます。 使わなくなった環境を戻し忘れてそのまま占有し続ける 空いている環境がない場合、他の環境を使っている人とコミュニケーションして融通してもらう必要がある このままデプロイ環境を作るなら ブランチデプロイ環境として、全てのブランチに対して自動的にデモ環境を作成、破棄するのが理想です。 コンテナのビルドやDBやサーバの用意、デプロイは既にGithub Actionsで行うようにしていますし、 開発環境へのアクセスはCloud Routerを利用して振り分けているため、 dev.reszaiko.com/[branch-name]/ のように環境毎のパスの追加もGithub Action上で構築できます。 また、特に開発環境を必要としない軽微な修正に対しても無制限に環境を作るのを防ぐために、以下の手段が考えられます。 dev-**** のように、特定のprefixを持つブランチに対して自動で環境を作る 既存のデプロイ用UIを拡張して、環境数を増やしたり減らしたりできるようにする 前者はブランチが消えれば自動で環境が消えるので、使わなくなった環境が残ってしまうというよくある問題が解消できます。 後者はUI上で存在する環境の把握やアプリへのリンク、DBのリセットなど機能を追加する事ができて便利です。 開発環境を作らないと駄目なのか そもそもブランチデプロイ環境が必要か、という問題もあります。 開発中のブランチを長期間利用していると本番環境との乖離が大きくなり、mainブランチにマージする際に入念なチェックが必要になります。 RESZAIKOの予約チームでは トランクベース開発 のように 頻繁にリリースする手法を導入するか議論していますが、 このような手法では開発中の機能はフィーチャーフラグを利用して出し分けるのが適しています。 RESZAIKOでは LaunchDarkly というフィーチャーフラグ機能を提供してくれるSaasを導入しているため、 コストをかけてブランチデプロイ環境を開発していくよりは、フィーチャーフラグを適切に利用する体制を整備し、開発環境はmainブランチと同期したものだけで運用していく方がいいかもしれません。 まとめ 使用している技術やサービスは日々新しい物が導入対象になるので、最適な開発手法というのはその時に合わせて検討する必要があります。 次に記事を書くときは「トランクベース開発に合わせたフィーチャーフラグの運用法」みたいなのが書けるように頑張ります。 一休では、共に働くエンジニアを募集しています。 www.ikyu.co.jp カジュアル面談も実施しているので、お気軽にご応募ください。 hrmos.co
アバター