LCL Engineers' Blog バス比較なび・格安移動・バスとりっぷを運営する LCLの開発者ブログ 2024-03-29T15:46:08+09:00 lcl-engineer Hatena::Blog hatenablog://blog/6653586347151479423 LCLにエンジニアとして入社して1年が経ちました(入社1年エントリ) hatenablog://entry/6801883189094455152 2024-03-29T15:46:08+09:00 2024-03-29T15:46:08+09:00 はじめに こんにちは! LCLエンジニアの地引です。 最近減量期に入ったので、筋トレ後に冷水シャワーを浴びることで、甘いものを食べたいという煩悩を禊いでいます🥶 さて、月日は早いもので、LCLに入社してからあっという間に1年以上が経過していたので、今回は入社1年で分かったLCLのことを中心に記事を書いていければと思います。 LCL入社ホヤホヤの頃のエントリ:https://www.lclco.com/blog/admini/work-environment/4922/ 入社1年で分かったLCLのこと3選 1. 何にでも挑戦できる エンジニアの枠を超えて業務や組織の課題解決にチャレンジすることが… <h2 id="はじめに">はじめに</h2> <p>こんにちは! LCLエンジニアの地引です。</p> <p>最近減量期に入ったので、筋トレ後に冷水シャワーを浴びることで、甘いものを食べたいという煩悩を禊いでいます🥶</p> <p>さて、月日は早いもので、LCLに入社してからあっという間に1年以上が経過していたので、今回は入社1年で分かったLCLのことを中心に記事を書いていければと思います。</p> <p>LCL入社ホヤホヤの頃のエントリ:<a href="https://www.lclco.com/blog/admini/work-environment/4922/">https://www.lclco.com/blog/admini/work-environment/4922/</a></p> <h2 id="入社1年で分かったLCLのこと3選">入社1年で分かったLCLのこと3選</h2> <h3 id="1-何にでも挑戦できる">1. 何にでも挑戦できる</h3> <p>エンジニアの枠を超えて業務や組織の課題解決にチャレンジすることができます。</p> <p>もちろん、やることが強制というわけではなく、<strong>何かに向かって挑戦するときに、それを後押ししてくれる環境が整っている</strong>という意味合いです。</p> <p>僕自身は組織の採用課題を感じていたので、同じ課題感を感じているメンバーを募り、採用課題を解決するワークショップを開催し、課題の解決策やネクストアクションに繋げることができました。</p> <p>その他にも、思考力・提案力を鍛えるワークショップやノベルティ作成ワークショップなど、様々なワークショップが任意で開催されています。</p> <p>採用ワークショップとても楽しかったです↓</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20240329/20240329152114.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="2-キャリアプランのロールモデルがいる">2. キャリアプランのロールモデルがいる</h3> <p>LCLには、エンジニア経験年数3〜5年・〜10年・20年以上などなど幅広いスキルを持ったエンジニアの皆さんが在籍しています。</p> <p>ジュニアエンジニアとして入社した僕からすると、3年後・5年後・10年後のロールモデルの方が既にいるので、<strong>どのようにステップアップしてキャリアを積めば目標に到達できるかを、実際のエンジニアの方々と相談しながら決められるのでとてもありがたいです。</strong></p> <p>また当然のことながら、LCLエンジニアの皆さんは穏やかな方が多いので安心して相談することができます。</p> <h3 id="3-超働きやすい環境">3. 超働きやすい環境</h3> <p><strong>LCLはリモートワーク・フレックスタイム制度が整っているので、自由な働き方を実現できます。</strong></p> <p>僕の場合、基本的には週1〜2出社して、その他の日は在宅で9〜18時くらいで働いています。</p> <p>コアタイムが10時〜14時なので、14時に終業して遊びに行く日があったり、プログラミングがノリに乗っている時は20時くらいまで勢いで続けてしまう日もあります。(残業があるというわけではありません。)</p> <p>気分転換にオフィスに出社したり、天気が悪い日はリモートにしたりと、エンジニアとして程よく出社とリモートを組み合わせて効率良く働けて最高です。</p> <h2 id="さいごに">さいごに</h2> <p>転職する前、「会社の雰囲気は入ってみてからじゃないと分からないし、入ってからわかるやばい部分もたくさんある。」と、どこかの記事で見ましたが、お世辞を抜きにしてLCLは入ってから発見する良いところや、メンバーの雰囲気がとっっっても良くて大当たりでした!</p> <p>これからも組織・サービスと共に成長していきたいと思います。</p> <h2 id="Join-Us-">Join Us !!!</h2> <p>LCLは、主要サービスの急成長期を終え、現サービスを安定稼働・改善させながら新しいビジネスに挑戦していくフェーズに入りました。</p> <p>直近では、メインサービスのリファクタリング(Next.jsへのリニューアル化)やChatGPT Pluginの開発、上記で挙げたコーポレートサイトのリニューアルプロジェクトなど、様々な取り組みを行っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Fblog%2Fadmini%2Fdailylife%2F5035%2F" title="新技術への挑戦で、もっと面白い組織へ:「格安移動」ChatGPTプラグイン開発者インタビュー | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/blog/admini/dailylife/5035/">www.lclco.com</a></cite></p> <p>一緒にチャレンジしてくれる仲間を募集しています!</p> <p>まずはカジュアル面談からでも、随時対応していますのでぜひお気軽にお声がけください。</p> lcl-jibiki 「バス比較なび」リファクタ始動:なびリプレイスプロジェクトの全体方針 hatenablog://entry/6801883189079969523 2024-02-08T14:00:05+09:00 2024-02-08T14:00:05+09:00 振り返り:私たちの歩み LCLでフロントエンドエンジニアをしている川辺です。 この記事ではバス比較なびという弊社の主力サイトをリプレイスしていった体験を紹介します。 ※内容的にはリプレイス作業なのですが、社内ではリファクタプロジェクトと呼んでいるので、今後もこの記事では「リファクタ」と表現します。 前回の記事ではリファクタリングの必要性や過去の失敗ケースを紹介しました。 techblog.lclco.com 簡単におさらいすると、リファクタをせずにいると以下のような問題が発生します。 サイトを改修する作業に時間がかかってしまう サイトに何かしらの変更を加えたときにバグを発生させる可能性が高まる… <h2 id="振り返り私たちの歩み">振り返り:私たちの歩み</h2> <p>LCLでフロントエンドエンジニアをしている川辺です。 この記事では<a href="https://www.bushikaku.net/">バス比較なび</a>という弊社の主力サイトをリプレイスしていった体験を紹介します。</p> <p>※内容的にはリプレイス作業なのですが、社内ではリファクタプロジェクトと呼んでいるので、今後もこの記事では「リファクタ」と表現します。</p> <p>前回の記事ではリファクタリングの必要性や過去の失敗ケースを紹介しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2023%2F09%2F29%2F133622" title="「バス比較なび」リファクタリング序章:「バス比較なび」の現状と課題 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2023/09/29/133622">techblog.lclco.com</a></cite></p> <p>簡単におさらいすると、リファクタをせずにいると以下のような問題が発生します。</p> <ul> <li>サイトを改修する作業に時間がかかってしまう</li> <li>サイトに何かしらの変更を加えたときにバグを発生させる可能性が高まる</li> <li>一部のエンジニアしかコードの意味を理解できず触ることができない</li> <li>古いコードやライブラリが使われ続けることで、セキュリティリスクが高まる</li> <li>世の中の進化についていけなくなることで競争力が下がる</li> <li><a href="https://www.profuture.co.jp/mk/column/37606">Application Service Provider (ASP)</a>を思うように導入できない</li> </ul> <p>そして、上記の問題を解決するために<strong>サイトに部分的にReactを導入する</strong>や<strong>新規で作成するページやコンテンツはReactで書くようにする</strong>などを試みていったのですがうまくいかなかった話をしました。</p> <p>リファクタリングをする目的は<strong>技術的負債を解消すること</strong>です。 技術的負債を解消することで、開発速度が向上し、その結果、より<strong>速くユーザーに価値を届けること</strong>につながります。</p> <p>我々の会社は長い間、技術的負債を解消するために様々な試みを実施してきましたが、これまでの取り組みは成功に至りませんでした。しかし、今回の技術選定は、これまでにないドラスティックな変更をもたらすものになりました。</p> <p>まずはどのような過程で技術選定をしたのかを見ていきたいと思います。</p> <h2 id="技術選定の物語私たちの決断">技術選定の物語:私たちの決断</h2> <p>リファクタ前の比較なびの現状はRails + jQuery(一部React)で実装していて、フロント部分はERBもしくは jQueryまたはReactで書かれています。 一部のページでは歴史的な経緯もありますが、ユーザービリティとSEO対策の両立を目指した結果、初回ロード時にはERBでレンダリングし、その後インタラクティブに応じて画面を更新するためにjQueryで再度レンダリングする、非常にメンテナンス性の悪い構造になっています。</p> <p>フロント側の課題としては以下のようなものがありました。</p> <ul> <li>ERBやjQueryやReactとページによって記法・ライブラリや設計概念が異なっていてわかりづらい</li> <li>そもそもjQueryがわかりづらい</li> <li>ERBとjQueryで同じような処理を書いていたりするのでダブルメンテになっている、また対応漏れも発生しやすい</li> <li>過去のリファクタプロジェクトの影響でディレクトリが散在している</li> <li>ERBを使う部分については、Rubyの知識が必要であり、現代の一般的なフロントエンドエンジニアの技術スタックから外れていて、市場とミスマッチが起きている</li> <li>歴史的にも長らく様々な方法でメンテナンスされていることで、コードを実行してみないとどんな動きをするのかわからない</li> </ul> <p>技術的負債やコードの複雑性が増大している現状では、リファクタリングには高いコストと難易度が伴います。加えて、たとえ大きな投資をしてリファクタリングを実施したとしても、技術スタックが現代のスタンダードから大きく乖離しているため、その費用対効果は低い可能性があります。このような状況を鑑みると、既存のコードベースのリファクタリングよりも、<strong>フレームワークを変更して一から作り直す</strong>方が最適な解決策じゃないかという結論に至りました。</p> <p>その時はNext.jsとNuxt.jsが広く使われていたのでなびでもNext.jsとNuxt.jsのどちらにするかの検討になりました。</p> <p>Nuxt.jsは数年前に別のサービスで実装実績もあって知見もそれなりに溜まっていました。 Next.jsはこのプロジェクトの少し前にリリースしたサービスをNext.jsで実装していたので、ある程度いけそうな感覚は持っている状態でした。</p> <p>最終的な結論としてはNext.jsを採用しました。</p> <p>採用した理由は以下の通りです。</p> <ul> <li>Nuxt.jsはTypeScriptとの相性があまり良くない</li> <li>Vue.jsよりもReactの書き方の方が好み</li> <li>Next.jsの方がパフォーマンス最適化が進んでいる印象</li> <li>Next.jsのGitHubのスター数やnpm ダウンロード数でもNext.jsの方が多い</li> <li>直近にリリースしたサービスをNext.jsで実装したのである程度の知見がある</li> </ul> <p>なので、最終的なゴールとしてはRailsはAPIとしてだけ利用して、フロント側はNext.jsで実装する構成を目指します。</p> <p>ちなみに、ディレクトリ構成やlinterの設定、playwrightでのテストやstorybookの運用などは直近にリリースしたサービスをベースにする方針でこのタイミングではあまり話しませんでした。やっていくうちに見えてくることもあると思っていたのと、何よりもスピード感を持ってプロジェクトを前に進めていかないと中途半端な状態で自然消滅すると考えていたからです。</p> <h2 id="プロジェクトの道のり私たちの計画">プロジェクトの道のり:私たちの計画</h2> <p>今までの経験上、エンジニアだけで完結するように進めようとするとどうしても途中で他の案件に時間が奪われていって自然消滅していってしまいました。 なので今回はディレクターやデザイナーと共にしながら共通の課題として進めたいと考えていました。 しかし、問題はリファクタに膨大な時間がかかりそう(この時点ではどのくらいかかるのかさえも見えていませんでした)なことでした。</p> <p>そこで一度で全てのリプレイスを完了させるのではなく、段階的にページ単位でなびの実装を置き換えていく方針を取りました。 つまり、一時的にRails環境とNext.js環境が共存することになります。 この方法だとリファクタプロジェクトが途中で止まってしまった場合、Rails環境とNext.js環境の二つが存在することになってしまうデメリットがありますが、一度で全てを置き換えるのは時間もかかる上、リスクも大きすぎるので、比較的ユーザーへの影響が小さいページから徐々に試していく方が良いだろうと考えました。 この方法だとユーザーに悪い影響が出てしまった時に容易に前の状態に戻すことができるのも大きなメリットです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kavis777/20240208/20240208094713.png" width="1200" height="607" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>プロジェクトの進め方の流れは以下の図の様なイメージです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kavis777/20240206/20240206175737.png" width="1200" height="670" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>また、コンテンツごとに難易度を設定して、「難易度:低」から始めて「難易度:中」「難易度:高」と開発の知見を溜めながら徐々に複雑なページを置き換えていく戦略を考えました。</p> <h2 id="開発体制の紹介私たちの組織">開発体制の紹介:私たちの組織</h2> <p>ここでは、なびリファクタプロジェクトにおける開発体制とそれぞれの役割を紹介しようと思います。</p> <h3 id="デザイナーの体制と役割">デザイナーの体制と役割</h3> <p>当時のデザイナーはLCL全体で2人しかおらず、比較なびだけでなく全てのサービスを担当しています。</p> <p>以前はユーザーエージェントでPCとスマホの表示を分けていましたが、現代のデバイスや利用シーンの多様化もあり、デザイナーと相談してレスポンシブ化した方が良いという結論になりました。</p> <p>また、それとは別になびのデザインは時代的にも古くなってきたこともあったのでこれを機にデザイン面も刷新していきたいとなびリファクタプロジェクトについてもかなり良い反応を示してくれました。</p> <h3 id="バックエンドエンジニアの体制と役割">バックエンドエンジニアの体制と役割</h3> <p>当時のバックエンドチームは3人で、それぞれアプリや他のサービスとの掛け持ちだったりして専任の担当者はいない状態でした。インフラの対応もしてくれています。</p> <p>なびリファクタプロジェクトにおいては、テスト環境や本番環境の作成だったりページ単位での出し分けの設定などをしてくれました。 また、なびリファクタの相談をした時にRails側のAPIも整理したいということになったので、リファクタ用にAPIを新規で作成するのが主な役割です。</p> <h3 id="フロントエンドエンジニアの体制と役割">フロントエンドエンジニアの体制と役割</h3> <p>フロントエンドエンジニアは3人いて、それぞれのサービスごとに専任の担当者が1人いる状態でした。</p> <p>基本的にはそれぞれのサービスをそれぞれの担当者が対応していたのですが、なびリファクタは規模が大きく、到底1人で対応できる量ではなかったので、必要なタイミングでは全員で一緒にやる方針でした。</p> <p>自分たちがリファクタプロジェクトを発案したこともあり、開発業務以外にも進捗管理やミーティングのファシリテーションなど様々なことをやっていました。</p> <h3 id="ディレクターの体制と役割">ディレクターの体制と役割</h3> <p>ディレクターは2人いて、1人は部長だったので基本的にはもう1人のディレクターの方がなびのメインの担当者でした。 主な役割としては、リファクタ対象ページのコンテンツの見直しやリリース後の効果検証です。 また、リファクタプロジェクト進行中は既存の案件の対応が少なくなってしまいますが、そのことをステークホルダーに説明してくれるのもディレクターです。 ステークホルダーからの理解が得られないとリファクタを進めることができないのでとても重要な役割です。</p> <p>幸いなことに現ディレクターも前ディレクターもリファクタの必要性を理解をしてくださったので、スムーズにリファクタプロジェクトを進めることができました。</p> <p>もしステークホルダーの理解を得るのが難しい方がいらっしゃれば、リファクタリングの必要性に関する<a href="https://techblog.lclco.com/entry/2023/09/29/133622">前回の記事</a>を参照していただくことをお勧めします。その部分では、プロジェクトの背景や取り組むべき理由が詳しく説明されており、プロジェクトの重要性と目的を理解するのに役立つはずです。</p> <h2 id="まとめ振り返りとこれからの展望">まとめ:振り返りとこれからの展望</h2> <p>この記事ではなびリファクタに際してNext.jsを採用するまでの経緯や、プロジェクトを進める方針を紹介しました。</p> <p>記事中では決定したことだけを書いていきましたが実際には</p> <p>リファクタ方針の草案を作る → フロントチーム内で相談してフィードバックをもらう → フィードバックを元にリファクタ方針をブラッシュアップ</p> <p>というプロセスを何度も繰り返したり、各部署とも何度も相談したりしながらリファクタプロジェクトの目線を合わせていきました。</p> <p>この記事を書いておきながら言うのもアレですが、<strong>大事なのは方針よりも関係者全員が同じ目的を共有すること</strong>だと感じました。</p> <p>ある程度の規模になってくると、リファクタを1人の力で完結させるのは難しいと思います。リファクタに対して課題感を持っている人はぜひ自分の周りの関係者と相談して自分の課題をみんなの課題にしていって欲しいと思います。</p> <p>次回の記事では実際になびリファクタへの取っ掛かりとなる最初の1ページ目をリファクタした時の話を書きたいと思います。 ここまで読んでいただきありがとうございました。</p> kavis777 在籍11年目のエンジニアが語る、LCLの魅力。 hatenablog://entry/6801883189079415993 2024-01-31T14:38:11+09:00 2024-01-31T14:38:11+09:00 はじめに LCLでバックエンド兼データマネジメントの業務を行っている高橋です。 最近、ハマったことは映画「ゴジラ-1.0」です。映画館では、迫力があるシーンや緊張感があるシーンで何度か叫んでしまいました! もしかしたら続編があるかも?と期待していたらモノクロ版「ゴジラ-1.0/C」も上映されることを知り、観に行こうと思っている今日この頃です。 ちなみに趣味は登山で、日本百名山を目指し、現在25座を制覇しました。 今年こそはテント泊での縦走にチャレンジしたいと思っております! 入社歴 2013年にLCLに入社し、現在は11年目となります。 入社した当時はマンションの一室で社長やエンジニア、ディレ… <h2 id="はじめに">はじめに</h2> <p>LCLでバックエンド兼データマネジメントの業務を行っている高橋です。</p> <p>最近、ハマったことは映画「ゴジラ-1.0」です。映画館では、迫力があるシーンや緊張感があるシーンで何度か叫んでしまいました!</p> <p>もしかしたら続編があるかも?と期待していたらモノクロ版「ゴジラ-1.0/C」も上映されることを知り、観に行こうと思っている今日この頃です。</p> <p>ちなみに趣味は登山で、日本百名山を目指し、現在25座を制覇しました。 今年こそはテント泊での縦走にチャレンジしたいと思っております!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toono_f/20240131/20240131131211.jpg" width="883" height="1165" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="入社歴">入社歴</h2> <p>2013年にLCLに入社し、現在は11年目となります。</p> <p>入社した当時はマンションの一室で社長やエンジニア、ディレクターなど7人で「夜行バス比較なび<a href="#f-3a6c72b8" id="fn-3a6c72b8" name="fn-3a6c72b8" title="現在のサービス名は「バス比較なび」に変わっており、全国の高速バス・夜行バスの価格情報を比較・検索できるサービスサイトになっています。">*1</a>」を運営していました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.bushikaku.net%2F" title="全国の高速バス・夜行バスの予約!格安・最安値情報【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.bushikaku.net/">www.bushikaku.net</a></cite></p> <p>あれから10年の歳月が流れ、「<a href="https://idou.me/">格安移動</a>」や「<a href="https://www.bushikaku.net/bustrip/">バスとりっぷ</a>」など運営するサービスも増えて、今では5倍以上の約40人が働いています。</p> <h2 id="業務内容">業務内容</h2> <p>主にバックエンド、特にOTA<a href="#f-9e717728" id="fn-9e717728" name="fn-9e717728" title="インターネット上だけで取引を行う旅行会社のことを指しています。">*2</a>様やバス会社様とのAPI連携関連の開発を行ってきました。</p> <p>入社当時は社長と共に先方へ営業なども行っていました。その当時は連携している会社は3〜4社でしたが、SEOが強かったことやAPI連携の数が増えるにつれ、バス会社様の方からAPI連携をしたいとの話がちょくちょく入ってきました。</p> <p>現在はAPI連携の開発もそうですが、バス便のデータを管理するマネジメントにも従事しています。</p> <h2 id="LCLの特徴や雰囲気どんな人が多い">LCLの特徴や雰囲気・どんな人が多い?</h2> <p>(今はそうではないですが)入社当初の数年は有線のBGMとキーボードの音しか聞こえないくらい静かなオフィスでした。</p> <p>カカクコムグループ加入とサービスの成長、サービスごとに企画営業・ディレクターからエンジニアまで所属するチームができたことなどがキッカケで、部署問わずメンバー間の連携が強くなり、サービスへの思いから他愛ないことまで気軽に話せるようになってきました。</p> <p>所属するエンジニアは負けん気の強いガツガツとした人は少なく、比較的穏やかな人が多いと思います。また、お互いに意見を言い合い、良いところをどんどん伸ばしていこうという考え方の人が多い印象です。相談したときも嫌な顔ひとつ見せず一緒に解決していこうとしてくれるメンバーが揃っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjobseek.ne.jp%2Fcorporate-data%2Flclco%2F" title="「バス比較なび」を運営する株式会社LCLの、コミュニケーションに注力したカルチャーが若手活躍を後押し |ミライのお仕事" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jobseek.ne.jp/corporate-data/lclco/">jobseek.ne.jp</a></cite></p> <h2 id="LCLで10年以上働き続けている理由とは">LCLで10年以上働き続けている理由とは?</h2> <p>入社直後はベンチャー企業でしたが、その当時から高速バスの比較サイトではNo.1になれると確信していました。</p> <p>LCL入社後は以下をモチベーションに今まで頑張れたと思っています。</p> <ul> <li>ベンチャー企業がどのように成長していくのかを仕事をしながら見れた・実感できたこと</li> <li>日本一の高速バス比較サイトを運営している楽しさと責任感</li> <li>ユーザーから「使いやすいし選べるバスが多い」と喜んでもらえたこと</li> <li>取引先から「バス比較なびに掲載したことで売上UPしました!」と喜んでもらえたこと</li> <li>心穏やかでお互いを尊重し合えるメンバーが揃っている</li> <li><a href="https://www.lclco.com/blog/admini/dailylife/4873/">コミ活</a>や<a href="https://www.lclco.com/blog/admini/dailylife/5131/">シャッフルランチ</a>という制度があり、メンバーと気軽にランチやイベント、趣味などを一緒に楽しめる文化がある</li> </ul> <p>などなど</p> <p>私以外でも勤続年数が長い人は多いですが、メンバー自身が携わった仕事でサイトが成長していくのを感じられることや、みんなが一丸となってサイトを成長させようという思いが強いからだと思っています。</p> <h2 id="Join-us">Join us!!!</h2> <p>LCLは、主要サービスの急成長期を終え、現サービスを安定稼働・改善させながら新しいビジネスに挑戦していくフェーズに入りました。</p> <p>直近では、メインサービスのリファクタリング(Next.jsへのリニューアル化)やChatGPT Pluginの開発、コーポレートサイトのリニューアルプロジェクトなど、様々な取り組みを行っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Fblog%2Fadmini%2Fdailylife%2F5035%2F" title="新技術への挑戦で、もっと面白い組織へ:「格安移動」ChatGPTプラグイン開発者インタビュー | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/blog/admini/dailylife/5035/">www.lclco.com</a></cite></p> <p>一緒にチャレンジしてくれる仲間を募集しています!</p> <p>まずはカジュアル面談からでも、随時対応していますのでぜひお気軽にお声がけください。</p> <div class="footnote"> <p class="footnote"><a href="#fn-3a6c72b8" id="f-3a6c72b8" name="f-3a6c72b8" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">現在のサービス名は「バス比較なび」に変わっており、全国の高速バス・夜行バスの価格情報を比較・検索できるサービスサイトになっています。</span></p> <p class="footnote"><a href="#fn-9e717728" id="f-9e717728" name="f-9e717728" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">インターネット上だけで取引を行う旅行会社のことを指しています。</span></p> </div> taro-takahashi エンジニアがバステクin首都圏2023に参加してみた! hatenablog://entry/6801883189063209647 2023-12-04T10:43:43+09:00 2023-12-04T10:43:43+09:00 バックエンドエンジニアの高良です。 前回に引き続き業界関連のイベントに参加してきました! techblog.lclco.com バステクin首都圏とは? バスに関わる最新技術が集結するイベントです。 EVバスの新型車両やAIカメラによる事故防止システムといったトレンド関連のものから、新型のタイヤやデジタルサイネージなど様々な技術が一同に会します。 ニッチな業界ではありますが、それだけに様々なレイヤーの技術を展示しているブースを一度に見ることができて、非常に見応えのあるイベントでした! いくつかお話を伺ったブースの技術を紹介していきます。 ⁠AIカメラによる車内転倒事故防止システム AIカメラを… <p>バックエンドエンジニアの高良です。 前回に引き続き業界関連のイベントに参加してきました! <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2023%2F10%2F30%2F093739" title="エンジニアがツーリズムEXPOジャパン2023に参加してみた! - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2023/10/30/093739">techblog.lclco.com</a></cite></p> <h2 id="バステクin首都圏とは">バステクin首都圏とは?</h2> <p>バスに関わる最新技術が集結するイベントです。</p> <p>EVバスの新型車両やAIカメラによる事故防止システムといったトレンド関連のものから、新型のタイヤやデジタルサイネージなど様々な技術が一同に会します。</p> <p>ニッチな業界ではありますが、それだけに様々なレイヤーの技術を展示しているブースを一度に見ることができて、非常に見応えのあるイベントでした!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takara7283/20231201/20231201185111.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>いくつかお話を伺ったブースの技術を紹介していきます。</p> <h3 id="AIカメラによる車内転倒事故防止システム">⁠AIカメラによる車内転倒事故防止システム</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takara7283/20231201/20231201185400.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>AIカメラを使って転倒のおそれがある姿勢の乗客を検知し、運転手に通知します。(中腰の姿勢が一番危ないそうです)<br/> 高速バスのSA出発時に乗客の乗車確認をするなど、応用の幅が広そうだと感じました。</p> <h3 id="電動バス試乗体験">⁠電動バス試乗体験</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takara7283/20231201/20231201185124.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>通常のバスよりもかなり快適な乗車体験でした。<br/> エンジン音が無いため車内が静かで、トルクも大きく加速がスムーズで揺れがだいぶ少なく感じます。<br/> バスは酔うのであんまり...という人も多そうですが、EV車であればこの課題もだいぶ軽減されるのでは?と思いました。<br/> EV車は構造的にマニュアルではなくギア操作が必要ないため、ドライバー確保のハードルも下がるかもしれませんね。</p> <h3 id="デジタルサイネージ時刻表">⁠デジタルサイネージ時刻表</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takara7283/20231201/20231201185136.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>現在時刻の箇所だけ枠を大きくして表示する、などのデジタルならではの表示方法が新鮮でした。 広告掲載による収益化も可能とのこと。動画も載せられるため収益も上がりそうですね。</p> <h2 id="参加してどうだったか">参加してどうだったか</h2> <p>一部の自治体にしか導入されていない最新技術や、普段目にする事のないtoB向けのシステムなど、様々な課題解決のテクノロジーに多く触れることができて新鮮でした。<br/> LCLで運用しているサービスは基本的にWeb上で完結します。しかしIoTやAIによる画像検出など、様々な技術が生まれてくる中でのイチ技術者として、一つのレイヤーに囚われずに課題解決を考えていけるような価値を提供していきたいと感じました!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takara7283/20231201/20231201185045.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="Join-us">Join us!</h2> <p>LCLではLife + more funというミッションのもとで、ユーザーや事業者の課題解決を一緒に進めていけるエンジニアを募集中です!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> <h2 id="おまけ">おまけ</h2> <p>デジタルな技術とは異なりますが、通常の座席から寝台席に変形可能なシートが一番驚きました。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takara7283/20231201/20231201185752.jpg" width="1200" height="1160" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> takara7283 新たなミッションとバリューの舞台裏:LCLの挑戦 hatenablog://entry/6801883189058509348 2023-11-14T14:14:30+09:00 2023-11-14T16:18:07+09:00 こんにちは、iOSアプリエンジニアの山下です。 気づいたら今年も11月、いつの間に前回の記事から1年が経ちそうで久方ぶりのエンジニアブログです。 さて、覚えている方は一人もいらっしゃらないと思いますが、前回の記事では我々設立12年目のLCLがMVV(Mission、Vision、Value)を考えるプロジェクトを始動した理由について紹介しました。 techblog.lclco.com この活動は無事に3月で終えて、LCLは4月から新しいミッションとバリューを掲げて動き出しています。 今回は、このプロジェクトを通じて我々がどのようにミッションとバリューを定義し、それを実行するためにどのように行動… <p>こんにちは、iOSアプリエンジニアの山下です。</p> <p>気づいたら今年も11月、いつの間に前回の記事から1年が経ちそうで久方ぶりのエンジニアブログです。</p> <p>さて、覚えている方は一人もいらっしゃらないと思いますが、前回の記事では我々設立12年目のLCLがMVV(Mission、Vision、Value)を考えるプロジェクトを始動した理由について紹介しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2022%2F12%2F20%2F130543" title="設立12年目のLCLが今、MVVプロジェクト始動した理由 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2022/12/20/130543">techblog.lclco.com</a></cite></p> <p>この活動は無事に3月で終えて、LCLは4月から新しいミッションとバリューを掲げて動き出しています。 今回は、このプロジェクトを通じて我々がどのようにミッションとバリューを定義し、それを実行するためにどのように行動しているかについてご紹介したいと思います。</p> <p>まず、ミッションとバリューは多くの場合、これらは経営層によって決定され、社員に通達される形式が一般的だと思いますが、LCLでは異なるアプローチを取りました。社員発信でMVVを考えるプロジェクトが立ち上げられ、そのメンバーによって形成され約半年間、社内全員を巻き込みながら決定に至りました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamshta/20231113/20231113192641.png" width="823" height="170" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>プロジェクトは業種を横断した7名のメンバーと、人や組織の創造性を耕すコンサルティング事業を営むMIMIGURIさん伴走のもと進行し、最初は各々が持つ漠然としたアイディアや想いが広がっていきましたが、MIMIGURIさんの素晴らしいファシリテートにより、徐々に焦点が絞られ、対話と共感が生まれる過程を経て、納得のいくアウトプットが形成されました。この過程は楽しさと難しさが共存するものであり、対話と悩みの連続でしたが最終的に全員が腹落ちするものを生み出す上でとても大切な時間でした。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamshta/20231113/20231113192853.png" width="779" height="178" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>プロジェクトでは、会社の歴史の深掘り、経営層へのヒアリング、全社員でのワークショップを通じて、ミッションとバリューを形成しました。全社員でのワークショップは合計3回実施され、それぞれ5時間に及ぶ長丁場でしたが、充実した時間となりました。この過程で、メンバーだけでなく社員一人一人が自分たちの想いや価値観を言葉にし、共感や理解を深めました。そして、最終的に4月に社内へ発表されたLCLのミッションとバリューが形成されました。</p> <p>そのミッションとバリューがこちらです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamshta/20231113/20231113191350.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>Our Mission</strong></p> <pre class="code" data-lang="" data-unlink>&#34;Life + more fun&#34;. 便利なモノで、楽しいコトを、世界中のヒトに 旅行・遊び・グルメ・スポーツ・学びなど、世界はさまざまな「楽しいコト」に満ちています。 わたしたちは、ITサービスを通じてあふれる情報の中から、一人ひとりにぴったりな「価値」を見つけるお手伝いをします。 いつでも、どこでも、だれでも「楽しいコト」を発見できる生活に。 見つける過程はシンプルに、体験はリッチなものに変え、心豊かな日々の提供を目指します。 </pre> <p><strong>Our Value</strong></p> <pre class="code" data-lang="" data-unlink>1. ユーザーの良き理解者になろう 2. 失敗もヒントに変える挑戦者になろう 3. 変化を楽しむ先駆者になろう 4. いつも感謝と敬意の表現者でいよう + 高速で価値を届けよう </pre> <p>これらのミッションとバリューは、我々の組織文化の中でさっそく重要な役割を果たしています。</p> <p>特に5つのバリューは日々業務の中で共通言語としてよく扱われていて、誰かがそれに沿ったアクションをした際にSlackのリアクションでそれを讃えたり、月に1回「ピックアップバリューアクション」略PVAとして月次の全社定例で発表されるシーンも新たに作られました。これらの行動は些細なものかもしれませんが会社として人と人とかポジティブに仕事をする上でとても良い取り組みになっています。以前までは特に部署間の会話が少ないLCLでしたが、これを機に誰がどんなことをしているか知ることが増え、会話する機会が増えた気がします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yamshta/20231114/20231114121129.png" width="1200" height="631" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>意思決定をする際もこの5つのバリューがいい意味で矛盾する関係である故にフェーズごとにどちら側にアクセルを踏むべきかを明確にしやすくなりました。変化の多い時代において、これらの原則を大切にし、実践することで、価値を提供し、成長し続ける未来を描けるようになったと思います。</p> <p>LCLでは、今回掲げたミッションとバリューを一人ひとりがしっかりと理解し、冒険的世界観を共有していきたいと思っています。</p> <h2 id="JOIN-US">JOIN US!!</h2> <p>LCLではミッションとバリューに共感し、共に成長し、楽しみながら働きたいと思う方々を歓迎します。一緒に未来を創りましょう。ご興味がある方はぜひ、採用ページからご応募お待ちしております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> yamshta エンジニアがツーリズムEXPOジャパン2023に参加してみた! hatenablog://entry/6801883189054542129 2023-10-30T09:37:39+09:00 2023-12-04T10:44:06+09:00 はじめに LCLでバックエンドエンジニアをしているjibikiです。 10/26~10/29にインテックス大阪で開催されたツーリズムEXPOジャパン2023に参加させていただいたので、エンジニア視点で参加してどのようなメリットがあったのかを記録します✏️ ツーリズムEXPOジャパンとは? 旅の総合イベントとして2014年にスタートした「ツーリズム EXPO ジャパン」。この日のために、世界中の国と地域、日本全国の観光地が集結する、年に一度の世界最大級の旅の祭典。 www.t-expo.jp 具体的にどんな催しがあるの? 観光・旅行、全国・世界各地のブース 観光関連のセミナー イベントステージ … <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20231030/20231030093432.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>LCLでバックエンドエンジニアをしているjibikiです。</p> <p>10/26~10/29にインテックス大阪で開催されたツーリズムEXPOジャパン2023に参加させていただいたので、エンジニア視点で参加してどのようなメリットがあったのかを記録します✏️</p> <p><br></p> <h2 id="ツーリズムEXPOジャパンとは">ツーリズムEXPOジャパンとは?</h2> <blockquote><p>旅の総合イベントとして2014年にスタートした「ツーリズム EXPO ジャパン」。この日のために、世界中の国と地域、日本全国の観光地が集結する、年に一度の世界最大級の旅の祭典。</p></blockquote> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.t-expo.jp%2F" title="ツーリズムEXPOジャパン" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.t-expo.jp/">www.t-expo.jp</a></cite></p> <p><br></p> <h2 id="具体的にどんな催しがあるの">具体的にどんな催しがあるの?</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20231028/20231028173829.png" width="1200" height="649" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>観光・旅行、全国・世界各地のブース</li> <li>観光関連のセミナー</li> <li>イベントステージ</li> <li>全国のグルメ</li> </ul> <p>などなど、多種多様な催しがあり、1日楽しめます🤩</p> <p><br></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20231028/20231028180609.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>実際の会場にはこのようなブースがずらりと並んでいて目移りしてしまいます。</p> <p><br></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20231028/20231028180558.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>岐阜県のブースは趣深くて個性的でした!</p> <p><br></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20231030/20231030090053.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>本格的なトークイベントなども開催されていました。</p> <p><br></p> <h2 id="エンジニアが参加してメリットはあるの">エンジニアが参加してメリットはあるの?</h2> <p><strong>結論:非常に多くのメリットがありました!!!</strong></p> <p>以下に詳しく記載していきます。</p> <h3 id="1-知見が広がる">1. 知見が広がる</h3> <p>日本だけではなく、世界各地の国もブースを出展しており、ブース規模や特色から、それぞれの地域で「食」や「文化」など、どのようなことを宣伝して、売り出していきたいのかがよく分かりました。</p> <p><strong>日本も世界も、地域ごとにどの分野に力を入れているのかを把握できたことで、観光業全体への知見が広がりました。</strong></p> <p><br></p> <h3 id="2-プロダクトに活かせるアイディアが浮かぶ">2. プロダクトに活かせるアイディアが浮かぶ</h3> <p>会場には観光(移動)× ITのブースも多くあり、API連携で自社プロダクトとコラボできそうなサービスが見つかったり、出展サービスから着想を得て自社プロダクトに活かせるようなアイディアだったり、ただブースを回って気になった場所でお話を聞くだけでも多くの着想が得られました。</p> <p><strong>エンジニア視点で、連携可能・実装可能などのアイディアが浮かぶのでとても有意義な時間でした。</strong></p> <p><br></p> <h3 id="3-純粋に楽しい">3. 純粋に楽しい</h3> <p>ブースでは、最新VRや民族コスプレ、足湯などなど、地域や会社ごとにキャッチーな催しがあるので、純粋に楽しめます。</p> <p>VR+可動式座席の体験中の写真です↓</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20231030/20231030090807.png" width="557" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span></p> <p><br></p> <h2 id="さいごに">さいごに</h2> <p>ツーリズムEXPOジャパン2023 in 大阪 最高でした!!</p> <p>エンジニアとして多くの収穫があったので、この記事をきっかけに普段の業務に還元していきたいと思います🔥</p> <p>来年は東京開催予定らしいので、今から楽しみです!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20231030/20231030091451.jpg" width="900" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></p> <p><br></p> <h2 id="Join-Us-">Join Us !!!</h2> <p>LCLは、主要サービスの急成長期を終え、現サービスを安定稼働・改善させながら新しいビジネスに挑戦していくフェーズに入りました。</p> <p>直近では、メインサービスのリファクタリング(Next.jsへのリニューアル化)やChatGPT Pluginの開発、上記で挙げたコーポレートサイトのリニューアルプロジェクトなど、様々な取り組みを行っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Fblog%2Fadmini%2Fdailylife%2F5035%2F" title="新技術への挑戦で、もっと面白い組織へ:「格安移動」ChatGPTプラグイン開発者インタビュー | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/blog/admini/dailylife/5035/">www.lclco.com</a></cite></p> <p>一緒にチャレンジしてくれる仲間を募集しています!</p> <p>まずはカジュアル面談からでも、随時対応していますのでぜひお気軽にお声がけください。</p> lcl-jibiki 「バス比較なび」リファクタリング序章:「バス比較なび」の現状と課題 hatenablog://entry/820878482970484206 2023-09-29T13:36:22+09:00 2024-02-08T14:24:44+09:00 はじめに LCLでフロントエンドエンジニアをしている川辺です。 早いものでLCLに入社して既に7年が経ちました。 僕が入社した時から今に至るまでずっと言われ続けてきたことに「なびフロントエンドの開発環境を何とかしたい」というのがあります。 弊社ではバス比較なびという高速バスの比較サイトを運営しています。 www.bushikaku.net サービスをリリースしたのが2006年なのでリリースしてから15年以上の歳月が経ちました。 そしてフロント側はリリース時からほとんどの実装をjQueryで書かれています。 なので僕が入社した2016年頃からずっとフロントチームとしてこのレガシーなコードに対して… <h2 id="はじめに">はじめに</h2> <p>LCLでフロントエンドエンジニアをしている川辺です。 早いものでLCLに入社して既に7年が経ちました。 僕が入社した時から今に至るまでずっと言われ続けてきたことに「なびフロントエンドの開発環境を何とかしたい」というのがあります。</p> <p>弊社では<a href="https://www.bushikaku.net/">バス比較なび</a>という高速バスの比較サイトを運営しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.bushikaku.net%2F" title="全国の高速バス・夜行バスの予約!格安・最安値情報【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.bushikaku.net/">www.bushikaku.net</a></cite></p> <p>サービスをリリースしたのが2006年なのでリリースしてから15年以上の歳月が経ちました。 そしてフロント側はリリース時からほとんどの実装をjQueryで書かれています。 なので僕が入社した2016年頃からずっとフロントチームとしてこのレガシーなコードに対して課題を持ちつつもなかなか根本的な解決をすることができずといった状態が続いていました。</p> <p>そんな中で遂に、なびリファクタリングの大きな一歩を踏み出すことができたのでいくつかのシリーズに分けて紹介したいと思います。</p> <h2 id="リファクタリングとは何か">リファクタリングとは何か?</h2> <p>そもそもリファクタリングとは何でしょうか?</p> <p>リファクタリングとは、「ソフトウェアの外部的振る舞いを保ちつつ、内部構造を効果的に改良すること」です。</p> <p>つまりユーザーから見た時の振る舞いを変えることなく、内部の構造を改良することです。</p> <p>ユーザーから見た時の振る舞いを変えない → ユーザーから見た時に変化がない ≒ 直接サイトの売り上げに貢献しない</p> <p>と捉えることもできるので多くの会社ではリファクタリングみたいな業務の時間を取ることが難しいのが現状だと思います。</p> <h2 id="リファクタリングの必要性">リファクタリングの必要性</h2> <p>ではなぜリファクタリングをする必要があるのかというと、リファクタリングをしないことによって問題が起こるからです。</p> <p>具体的にどのような問題が発生するかというと、</p> <ul> <li>サイトを改修する作業に時間がかかってしまう</li> <li>サイトに何かしらの変更を加えたときにバグを発生させる可能性が高まる</li> <li>一部のエンジニアしかコードの意味を理解できず触ることができない</li> <li>古いコードやライブラリが使われ続けることで、セキュリティリスクが高まる</li> <li><a href="https://www.profuture.co.jp/mk/column/37606">Application Service Provider (ASP)</a>を思うように導入できない</li> </ul> <p>などが考えられます。</p> <p>リファクタリングしないことによってすぐに大きな問題が発生するわけではありませんが、放置し続けると誰も触ることができず最終的にはサイトを改修することが困難になったり、最悪の場合はセキュリティインシデントにつながることになります。</p> <p>弊社はまだリファクタリング最中なところもありますが、リファクタリングによって以下のような効果を実感しています。</p> <ul> <li>開発作業の速度が向上した</li> <li>新しくジョインしたメンバーもスムーズに開発に加わることができた</li> <li>リファクタに伴いテストコードを実装するようになったので変更への心理的負担が下がった</li> <li>採用の場でモダンな開発環境を求めている人たちが応募してくれるようになった</li> <li>開発に携わるエンジニアのモチベーションが向上した</li> </ul> <p>リファクタリングによって、環境がモダンになったことにより生産性が上がるだけでなく、開発者のモチベーションも上がったり、採用面でもより良い人材にアプローチできるようになるという副次的な効果もありました。</p> <h2 id="過去の失敗ケース">過去の失敗ケース</h2> <p>ここまで話を聞くとリファクタリングをしたいと思ってくれる人も多いかと思いますが、弊社でもちゃんとしたリファクタリング体制が整うまでに様々な失敗がありました。 ここではどのようなチャレンジと失敗を繰り返してきたかを紹介しようと思います。</p> <h3 id="失敗ケース1サイトに部分的にReactの導入を試みる">失敗ケース1:サイトに部分的にReactの導入を試みる</h3> <p>2016年の僕が入社したばかりの頃、React熱が高まっていて弊社でも部分的にReactに置き換えていこうということになりました。</p> <p>ただ、当初はまだReactで生成したDOMをクローラーが正しくクローリングしてくれるか怪しい時でもあったので、クローリングされなかったとしてもSEO的に問題のないページを対象にして試してみました。</p> <p>その結果、いくつかのページにReactが導入されたものの、サイトの主要な部分としては依然としてjQueryで実装されたままの、ReactとjQueryが混在している状態になってしまいました。</p> <p>通常案件と並行してリファクタ作業をしていたこともあり、だんだんとリファクタの時間を確保できなくなってきて、最終的には中途半端な状態で残ってしまいました。</p> <p>ちなみにこの時のリファクタプロジェクトでfrontendディレクトリを作成して、Reactで書かれたコードはすべてfrontendディレクトリ配下に入れるようにしたので、既存のjQuery用のディレクトリとReact用のディレクトリが二つ存在するようになってしまいました。</p> <p>Reactに対する知見も不十分だったこともあり、frontendディレクトリ配下のコードもかなりごちゃごちゃとしたものになってしまいました。</p> <p>なので、開発工数改善には大きく繋がらず、結果的に保守対応でも考慮する点が増えてしまいました。</p> <h3 id="失敗ケース2新規で作成するページやコンテンツはReactで書くようにする">失敗ケース2:新規で作成するページやコンテンツはReactで書くようにする</h3> <p>今回は</p> <ul> <li>新しく作成する部分をReactで書いていく</li> <li>既存のコンテンツも改修のタイミングでReactに置き換えていく</li> </ul> <p>戦略で少しずつリポジトリ内のReact率を高めていくやり方でチャレンジしました。</p> <p>失敗ケース1の時に懸念していたクローリングの問題も、新規追加ページなら仮にクローリングがうまくいかなかったとしても影響を小さく済ませられるので試したいという考えもありました。</p> <p>この結果、一部のページやコンテンツでは良い感じに開発をすることができるようになりましたが、ReactでDOMを書き換える仕組みの都合上、どうしても初回レンダリング時に一瞬画面が真っ白になってしまう問題があり、ユーザー体験を損なってしまうのでなびのコアとなるコンテンツへの適用はできませんでした。</p> <p>また、急ぎの案件だったり部分的な修正の場合は速度を優先して既存のjQueryに変更を加えることも多く、なかなか思うようにリファクタを進めていくことができませんでした。</p> <p>Rails + Reactの組み合わせでリファクタ作業を進めることに限界も見えてきました。</p> <h2 id="失敗ケースの振り返り">失敗ケースの振り返り</h2> <p>今までの失敗ケース1と失敗ケース2についてそれぞれ振り返ってみたいと思います。</p> <ul> <li>失敗ケース1:サイトに部分的にReactの導入を試みる <ul> <li>Reactを導入すればコードがクリーンになると思っていたがそうではなかった</li> <li>通常案件と並行してリファクタ作業をしていたのでだんだんと作業時間を確保するのが難しくなっていった</li> </ul> </li> <li>失敗ケース2:新規で作成するページやコンテンツはReactで書くようにする <ul> <li>Rails + Reactの組み合わせだと<a href="https://gmotech.jp/semlabo/seo/blog/cwv_cls/">CLS(Cumulative Layout Shift)</a>の問題を避けられないので使用できる箇所が限定的になってしまうことがわかった</li> <li>忙しくなってくると案件を進めることを優先しがちになってファクタが後回しになってしまった</li> </ul> </li> </ul> <p>失敗ケース1の時には、</p> <p>jQueryをReactに置き換える → 宣言的にコードを書くことができる → コードをクリーンに保つことができる</p> <p>と考えていましたが実際にはそうではありませんでした。 Reactでクリーンなコードを書くためにはコンポーネント設計や規約による縛りも必要でしたがその観点が抜けていました。</p> <p>失敗ケース2では、少しずつでもなび開発環境を改善したい思いで進めていきましたが結果的に思うように進捗を出すことができませんでした。</p> <h2 id="まとめ">まとめ</h2> <p>過去のケースを振り返ってみると、</p> <p>ある程度時間的に余裕ができた時にリファクタリングに向けて動き出す → 徐々に開発業務が忙しくなってきてそちらを優先対応する → リファクタプロジェクトが自然消滅する</p> <p>ということを繰り返していました。 根本的な失敗の要因としては<strong>エンジニア内でリファクタの作業を完結させようとした</strong>ところが良くなかったと思います。</p> <p>リファクタリングは想像以上に根気と労力のかかる作業です。 <strong>プロダクトオーナー(PO)の理解と協力</strong>がなければ進めることは難しいです。</p> <p>幸いなことに弊社のPOはリファクタリングの必要性に対して理解を示してくれて、現在も協力してリファクタリングを進めていっています。ここについてはまた別記事にてお話しできればと思います。</p> <p><a href="https://techblog.lclco.com/entry/2024/02/08/140005">次回の記事</a>では過去の失敗を踏まえた上でどのような戦略でリファクタリングを計画をしていったかを話せればと思っています。</p> <h2 id="JOIN-US">JOIN US!!!</h2> <p>LCLでは、<a href="https://www.lclco.com/blog/admini/in-housesystem/4977/">失敗もヒントに変える挑戦者になろう</a> というValueを掲げ、常に新しい挑戦と学びに全力で取り組んでいます。</p> <p>失敗を恐れず、困難を乗り越えて成長できる仲間を募集しています。 まずはカジュアル面談からでも、随時対応していますのでぜひお気軽にお声がけください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> <p>次の記事はこちらです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2024%2F02%2F08%2F140005" title="「バス比較なび」リファクタ始動:なびリプレイスプロジェクトの全体方針 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2024/02/08/140005">techblog.lclco.com</a></cite></p> kavis777 Zennでエンジニアブログ、はじめました。 hatenablog://entry/820878482958762824 2023-08-22T09:39:08+09:00 2023-08-22T09:39:08+09:00 みなさん、こんにちは。 LCLでフロントエンドエンジニアとして働いている「おとの」です。 個人的な話になりますが、LCLで働き始めて1年が経とうとしています。 海外航空券料金比較サービスのUIUXアップデートや、バス比較なびのリファクタリング&レスポンシブ化など、さまざまなプロジェクトに携わることができ、充実した日々を過ごしています(詳しくは入社エントリで書きたい気持ち) さて、今回は、LCLエンジニアチームの新たな試みについて報告いたします。 Zennでエンジニアブログをスタートしました 記事のタイトル通り、Zennでエンジニアブログを開始しました!! zenn.dev ZennのPubli… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toono_f/20230816/20230816105953.png" width="941" height="625" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>みなさん、こんにちは。</p> <p>LCLでフロントエンドエンジニアとして働いている「おとの」です。</p> <p>個人的な話になりますが、LCLで働き始めて1年が経とうとしています。</p> <p>海外航空券料金比較サービスのUIUXアップデートや、バス比較なびのリファクタリング&レスポンシブ化など、さまざまなプロジェクトに携わることができ、充実した日々を過ごしています(詳しくは入社エントリで書きたい気持ち)</p> <p>さて、今回は、LCLエンジニアチームの新たな試みについて報告いたします。</p> <h2 id="Zennでエンジニアブログをスタートしました">Zennでエンジニアブログをスタートしました</h2> <p>記事のタイトル通り、Zennでエンジニアブログを開始しました!!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fzenn.dev%2Fp%2Flclco" title="LCL Engineers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://zenn.dev/p/lclco">zenn.dev</a></cite></p> <p>ZennのPublicationを機能を活用することで、LCLのエンジニアがZennで執筆した記事をLCLエンジニアブログとして掲載することができるようになりました。</p> <p>現時点ではまだ記事の数や執筆メンバーは限られていますが、今後は積極的に更新を行い、活発なブログを維持していく予定です。ぜひフォローしていただければ嬉しいです🙏🏻</p> <h3 id="Zennを選んだ理由">Zennを選んだ理由</h3> <h4 id="強力なSEO対策">強力なSEO対策</h4> <p>まず第一に、Zennは優れたSEO対策が施されているという点が挙げられます。そのため、Googleからの検索流入が多く、Zennのトレンドに掲載されることで、1日に1万以上のページビューを記録することもありました。</p> <h4 id="Publication機能の利便性">Publication機能の利便性</h4> <p>Publication機能を活用することで、執筆した記事は個人のデータに関連付けられ、永続的に保存されます。これにより、エンジニアが執筆のモチベーションを維持しやすくなるのも理由の1つです。</p> <h4 id="魅力的なUIUXとユーザー志向">魅力的なUIUXとユーザー志向</h4> <p>Zennは広告なしで無料利用ができ、記事だけでなくスクラップや本の形式でも執筆できます。さらに、エンジニアが読みやすいUIが提供されており、その点もZennの利点です。</p> <p>他のブログサービスと比べても、現役エンジニアの利用が多く、競合が少ないため、今後ますますユーザーが増えると予想されます。そのため、早い段階からZennでエンジニアブログを開始することを決めました。</p> <p>以上の理由から、私たちはZennを選びました。LCLエンジニアチームの記事更新頻度が高まり、多くのエンジニアに届くことで、採用活動にプラスになれば幸いです💫</p> <h2 id="ブログを活性化させたい理由">ブログを活性化させたい理由</h2> <p>もっとも重要な理由として、採用活動への貢献が挙げられます。</p> <p>採用活動を続けている中で、求める人材とのマッチングが難しいことに課題を感じています。そのため今後は外部への情報発信を増やし、LCLで働く魅力やエンジニアチームの雰囲気、技術志向など、面接では伝えきれない情報を積極的に提供できればと考えています。</p> <p>これにより、LCLに興味を持っていただける方や、他社よりも志望度を高く持ってくれる方、入社後に感じるギャップが少なく、LCLで働き続けたいと思ってくれる方が少しずつでも増えればと思っています。</p> <h3 id="このLCLエンジニアブログとの位置づけ">このLCLエンジニアブログとの位置づけ</h3> <p>Zennでは、個々のエンジニアの興味に基づいて執筆された記事を「LCLエンジニアブログ」として更新します。一方、このブログでは以下記事のようにエンジニアチームの活動やプロジェクトの進行状況、会社全体の取り組みなどを引き続き発信していく予定です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2022%2F12%2F25%2F202213" title="LCLエンジニアチームの紹介 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2022/12/25/202213">techblog.lclco.com</a></cite></p> <p>LCLのエンジニアたちが日々どのような活動をしているか、どのような雰囲気があるか、どのようなキャリアパスを歩んでいるかなど、採用応募者にわかりやすく伝えられるよう、運用を続けていきたいと考えています。</p> <p>ちなみに現在、LCLではコーポレートサイトのリニューアルプロジェクトが進行中です。このプロジェクトでは、Next.jsとmicroCMS、AWS Amplifyを使用してJamstackアーキテクチャを導入し、このブログの記事だけでなく、Zennで投稿された記事もコーポレートサイトで表示されるように開発を進める予定です🔥</p> <h2 id="Join-Us-">Join Us !!!</h2> <p>LCLは、主要サービスの急成長期を終え、現サービスを安定稼働・改善させながら新しいビジネスに挑戦していくフェーズに入りました。</p> <p>直近では、メインサービスのリファクタリング(Next.jsへのリニューアル化)やChatGPT Pluginの開発、上記で挙げたコーポレートサイトのリニューアルプロジェクトなど、様々な取り組みを行っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Fblog%2Fadmini%2Fdailylife%2F5035%2F" title="新技術への挑戦で、もっと面白い組織へ:「格安移動」ChatGPTプラグイン開発者インタビュー | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/blog/admini/dailylife/5035/">www.lclco.com</a></cite></p> <p>一緒にチャレンジしてくれる仲間を募集しています!</p> <p>まずはカジュアル面談からでも、随時対応していますのでぜひお気軽にお声がけください。</p> toono_f 移動手段比較をAIでサポート!格安移動ChatGPTプラグインの開発記録 hatenablog://entry/820878482951361045 2023-07-26T10:00:00+09:00 2023-07-26T17:28:17+09:00 格安移動ChatGPTプラグインで出発地・到着地・出発日を指定し、移動手段を比較。本記事でプラグインの概要と開発過程を紹介します! <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20230725/20230725144142.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>LCLエンジニアの地引です!</p> <p>7月19日に格安移動ChatGPTプラグインをリリースしました🎉</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fprtimes.jp%2Fmain%2Fhtml%2Frd%2Fp%2F000000081.000013379.html" title="「格安移動」、ChatGPTプラグインの提供を開始 ChatGPTを使って日本国内の高速バス・飛行機・新幹線の価格比較ができる" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://prtimes.jp/main/html/rd/p/000000081.000013379.html">prtimes.jp</a></cite></p> <p>本記事では格安移動ChatGPTプラグインの概要と開発過程をまとめて紹介していきます!</p> <p><br></p> <h2 id="格安移動ChatGPTプラグインとは">格安移動ChatGPTプラグインとは</h2> <p>こちらの画像のように、出発地、目的地、出発日を入力するだけで、バス、飛行機、新幹線・特急列車など、さまざまな交通手段の料金と所要時間を一覧で見ることができます。</p> <p>また、ChatGPTの検索結果から格安移動のサービスページに遷移して、詳細の確認から予約まで進むことができます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20230725/20230725175012.png" width="708" height="685" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><br></p> <h2 id="プラグイン開発">プラグイン開発</h2> <p>先日、いち早くChatGPTプラグインをリリースした食べログ(カカクコム社)の取り組みが大きな話題になったと思います。 「<a href="https://tech-blog.tabelog.com/entry/first-challenge-tabelog-chatgpt-plugin-devleopment">日本初の挑戦〜食べログによるChatGPTプラグイン開発の舞台裏</a>」</p> <p>今回LCL社でプラグインを開発するにあたり、カカクコムグループ会社の繋がりを通して食べログのエンジニアの方に直接知見を共有していただいたお陰で、スムーズに開発を進めることができました。</p> <p>この場を借りてお礼申し上げます。この度はご協力ありがとうございました!</p> <p>※LCLはカカクコムのグループ会社です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Fcompany%2F" title="LCLについて | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/company/">www.lclco.com</a></cite></p> <p>GPTプラグインについては、基本的には<a href="https://platform.openai.com/docs/plugins/introduction">OpenAIの公式ドキュメント</a>を参考に作成しました。</p> <p>基本的な仕様については以下の図にわかりやすくまとめました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20230724/20230724143411.png" width="894" height="621" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>図にある通り、プラグイン開発にあたって、新規に作成したものは、以下の3つになります。</p> <ul> <li><strong>格安移動API</strong></li> <li><strong>openapi.yaml(API定義書)</strong></li> <li><strong>ai-plugin.json(マニフェストファイル)</strong></li> </ul> <p>それぞれを詳しく解説します!</p> <p><br></p> <h3 id="格安移動API">格安移動API</h3> <p>本プラグインの肝となるAPIです。 基本的な仕様としては、出発地・到着地・出発日のリクエストを受け取り、それに基づき高速バス・飛行機・新幹線のデータをレスポンスします。</p> <p>既存の<a href="https://idou.me/">格安移動サイト</a>の検索ロジックをベースに、ChatGPT用に最適化したAPIをエンジニア2人で1週間少々かけて実装しました。</p> <p>具体的には、「移動手段の比較の概要」と「移動手段の詳細情報」の2つをレスポンスに持たせて、ChatGPTの方でそれらを出し分けるようにしています。</p> <pre class="code" data-lang="" data-unlink>例 &#34;search_summary&#34;: [ { &#34;type&#34;: &#34;express_bus&#34;, &#34;detail&#34;: { &#34;four_rows_of_seats&#34;: { &#34;lowest_price&#34;: &#34;2,000&#34;, &#34;detail_url&#34;: &#34;https://idou.me/search/bus/tokyo/osaka/20230725 }, &#34;three_rows_of_seats&#34;: null, &#34;total_time&#34;: &#34;8時間0分&#34; } }, . . . &#34;search_result&#34;: [ { &#34;type&#34;: &#34;express_bus&#34;, &#34;detail&#34;: { &#34;four_rows_of_seats&#34;: [ { &#34;departure_place&#34;: &#34;池袋サンシャインシティ文化会館&#34;, &#34;arrival_place&#34;: &#34;大阪梅田&#34;, &#34;departure_time&#34;: &#34;23:30&#34;, &#34;arrival_time&#34;: &#34;07:30&#34;, &#34;price&#34;: &#34;2,000&#34; }, . . .</pre> <p><br></p> <h3 id="openapiyamlAPI定義書">openapi.yaml(API定義書)</h3> <p>上記の格安移動APIのリクエスト・レスポンス形式等をこちらのファイルで定義します。</p> <p>具体的にはそれぞれ以下の通りです。</p> <p>リクエスト形式を定義する場合は、APIのリクエストで受け取りたい形式に変換する記述をこちらに記述することで、ユーザーのリクエスト内容を解析して、APIのリクエスト形式に合わせたパラメータをセットしてくれます。</p> <pre class="code" data-lang="" data-unlink>例 parameters: - name: from_prefecture description: Specify the prefecture of departure in Roman (lowercase) letters.</pre> <p><br> レスポンス形式を定義する場合は、レスポンスで想定される内容をなるべく分かりやすく、具体的に記述することで、ChatGPTがレスポンスを生成する際に、その意図を汲み取った回答をしてくれる確率が上がります。</p> <pre class="code" data-lang="" data-unlink>例 SearchSummaryDetail: type: object description: Detailed information about a specific type of transit. properties: type: type: string description: Type of transit (e.g., bullet train, airplane, express bus).</pre> <p><br></p> <h3 id="ai-pluginjsonマニフェストファイル">ai-plugin.json(マニフェストファイル)</h3> <p>上記のAPIとopenapi.yamlで制御できない項目については、こちらのai-plugin.jsonで制御します。</p> <p>主にAPIのレスポンスを受け取ったChatGPTに対して、そのレスポンスをどのように出力してほしいかを記述します。</p> <p>複雑な処理でない限りマニフェストファイルに記述した内容を逸脱する回答はしませんが、チューニングをしてみた感じ英文字で250字を超えると、予期しない回答やマニフェストファイルに反する回答などを生成する確率が上がるので注意が必要だと感じました。</p> <pre class="code" data-lang="" data-unlink>例 Answer in the language asked.</pre> <p><br></p> <h2 id="プラグイン開発を通して">プラグイン開発を通して</h2> <p>元々エンジニア内で、ChatGPTを使ったサービス開発ハッカソンなどを行なっており、プラグイン開発の機運が高まっていたタイミングで、格安移動ChatGPTプラグインプロジェクトが立ち上がりました。</p> <p>SEOに依存しない流入経路の確立や、新時代のサービス開発への挑戦など、これまで経験したことのなかった新しいチャレンジを行えて、エンジニアとして知見を広げることができました。</p> <p>格安移動プラグインをきっかけに、今後もLCLエンジニア全体で新たなチャレンジをしていきます!!!</p> <p><br></p> <h2 id="JOIN-US">JOIN US!!!</h2> <p>LCLは、主要サービスの急成長期を終え、現サービスを安定稼働・改善させながら新しいビジネスに挑戦していくフェーズに入りました。ChatGPTプラグインのような新しい時代の新しいサービス作りに、一緒にチャレンジしてくれる仲間を募集しています!</p> <p>まずはカジュアル面談からでも、随時対応していますのでぜひお気軽にお声がけください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> lcl-jibiki インフラ初心者のためのAWSソリューションアーキテクト取得ガイド hatenablog://entry/820878482941301693 2023-06-20T15:25:56+09:00 2023-06-20T15:46:48+09:00 実務経験1年未満&インフラ初心者でもSAAを取得した方法を記します! 前提事項 バックエンドエンジニア(実務10ヶ月くらい) インフラ業務の経験はゼロ ネットワーク知識は書籍1冊読んだくらい ソリューションアーキテクトアソシエイト(SAA)の学習前はEC2とS3を知ってるくらいで、その他のサービスについては全く知りませんでした🫠 合格認定証↓ 結論 Ping-tの最強WEB問題集だけやれば合格できます! Web上のSAA学習教材は主に以下の3つがあります。 Ping-t最強WEB問題集(無料) メリット:わかりやすい デメリット:特になし Udemy 【SAA-C03版】これだけでOK! AW… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-jibiki/20230620/20230620154635.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="実務経験1年未満インフラ初心者でもSAAを取得した方法を記します">実務経験1年未満&インフラ初心者でもSAAを取得した方法を記します!</h2> <p>前提事項</p> <ul> <li>バックエンドエンジニア(実務10ヶ月くらい)</li> <li>インフラ業務の経験はゼロ</li> <li>ネットワーク知識は書籍1冊読んだくらい</li> </ul> <p>ソリューションアーキテクトアソシエイト(SAA)の学習前はEC2とS3を知ってるくらいで、その他のサービスについては全く知りませんでした🫠</p> <p>合格認定証↓ <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274644/22647844-8ee0-98e0-485e-b0377b2c07d2.png" alt="image.png" /></p> <p><br></p> <h2 id="結論">結論</h2> <p><strong>Ping-tの<a href="https://mondai.ping-t.com/question_subjects/72">最強WEB問題集</a>だけやれば合格できます!</strong></p> <p>Web上のSAA学習教材は主に以下の3つがあります。</p> <ul> <li><a href="https://mondai.ping-t.com/question_subjects/72">Ping-t最強WEB問題集</a>(無料) <ul> <li>メリット:わかりやすい</li> <li>デメリット:特になし</li> </ul> </li> <li><a href="https://www.udemy.com/course/aws-associate/">Udemy 【SAA-C03版】これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座</a>(¥2,400) <ul> <li>メリット:動画教材</li> <li>デメリット:問題集が激ムズ</li> </ul> </li> <li><a href="https://d1.awsstatic.com/ja_JP/training-and-certification/docs-sa-assoc/AWS-Certified-Solutions-Architect-Associate_Sample-Questions.pdf">AWS公式問題集</a>(無料) <ul> <li>メリット:公式</li> <li>デメリット:とっつきにくい</li> </ul> </li> </ul> <p>私は最初はUdemyをやっていましたが、問題集が難しすぎてPing-tに乗り換えました🤯<br> この中で一番Ping-tがわかりやすく、本番と同レベルの問題を大量に解くことができます!<br> <strong>実際、ほぼ同じ問題が数問出題されました👏</strong></p> <p><br> <br></p> <h2 id="勉強方法合格レベル勉強期間">勉強方法・合格レベル・勉強期間</h2> <ol> <li><strong>Ping-tで各AWS分野の問題の説明文を熟読</strong>して、どのサービスにどんな機能があり、どのように他のサービスと連携できるのか、などをキャッチアップします✏️</li> <li>Ping-tの問題をひたすら解いて、<strong>2周くらい(コンボ数が500前後)行います!</strong></li> <li><strong>10問解いて10問 or 9問正解くらいのレベル感になったら、認定試験を受けても合格できるレベル感です💪</strong></li> </ol> <p>※ 動画教材がお好きな場合は、Udemyでインプット→Ping-tでアウトプットを行うことをオススメします!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274644/7154eec2-c021-cec7-ed20-5c1beb2cd339.png" alt="image.png" /></p> <p>私は4ヶ月間ほどダラダラと学習してました(そのうちPing-tは1ヶ月と少し)が、<strong>Ping-tだけで全集中するとしたら、インプットに半月+アウトプット(問題集)に1ヶ月で、合格レベルに達することはできると思います🔥</strong></p> <p><br> <br></p> <h2 id="試験の申し込み">試験の申し込み</h2> <p><strong><a href="https://www.pearsonvue.co.jp/Clients/Amazon-Web-Services.aspx">PearsonVUE</a>経由で申し込みました!</strong> (たまたま再受験無料キャンペーンをやってました)</p> <p>受験方法はテストセンターとオンライン受験を選べるので、オンラインで受験 予定がたまたま空いていたので、朝8時に申し込んで、その日の10時から受けられました!<br> <a href="https://nokonokonetwork.com/certificate/aws/aws_associate/how_to_apply_for_aws_exam.html">こちらのサイト</a>を参考にさせていただきました🙏</p> <p><br> <br></p> <h2 id="その他注意事項">その他注意事項</h2> <p>オンライン受験前のセットアップになんだかんだ時間を取られるので、早めに準備しておくことをオススメします!</p> <p><br> <br></p> <h2 id="LCLのバックアップ体制">LCLのバックアップ体制</h2> <p>今回のSAAはLCLのバックアップ体制ありきの合格でした😭<br> 【バックアップ一覧】</p> <ul> <li><a href="https://ufb.benesse.co.jp/"><strong>Udemy Business</strong></a> <ul> <li>今回はメインで使用しませんでしたが、ほとんどの技術講座をLCL負担(無料)で受けられます!</li> </ul> </li> <li><strong>資格補助</strong> <ul> <li>当然のようにあります!</li> </ul> </li> <li><strong>技術投資</strong> <ul> <li>業務時間の一部を自身のスキルアップに当てられる制度があります!</li> </ul> </li> <li><strong>気軽に質問できる環境</strong> <ul> <li>LCLはとても穏やかな人が多いので、インフラつよつよの方々にAWSサービスを気軽に聞けちゃいます!</li> </ul> </li> </ul> <p><br> <br></p> <h2 id="最後に">最後に</h2> <p>SAAの勉強をして、インフラ領域の幅広いキャッチアップと、自身が携わっているサービスのインフラにどんなAWSサービスが使われているかの理解に繋がったので、早い段階で取得しておいて本当に良かったと思います!<br> <strong>ある程度の勉強時間は必要ですが、インフラへの理解が深まる資格なので、頑張ってください🔥</strong> <br> <br></p> <p>LCLでは、一緒にサービスを成長させていけるエンジニアを募集中です。ご興味がある方はぜひ、採用ページからご応募お待ちしております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit">www.lclco.com</a></cite></p> lcl-jibiki LCLエンジニアチームの紹介 hatenablog://entry/4207112889947569650 2022-12-25T20:22:13+09:00 2022-12-26T11:16:00+09:00 この記事はLCL Advent Calendar 2022 - 25日目です。 qiita.com 本年度LCLの技術開発部の部長に就任しました、エンジニアのid:ytkr0813です。 最近、電気毛布を導入したらQOLが爆上がりしました。あと電熱ベストも買いましたが最高でした。ありがとうブラックフライデー。 気づいたら2022年も終わりですね。昨日のフロントエンドチーム振り返り記事でもありましたが、今年度のLCLはエンジニアチーム的にも、全社的にも大きな変化があった1年でした。 今年の4月に代表が交代し、組織体制の変更もあり、コロナ後の再加速に向けて着々と準備を進めてきました。 MVV(ミッ… <p>この記事はLCL Advent Calendar 2022 - 25日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2022%2Flcl" title="LCLのカレンダー | Advent Calendar 2022 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2022/lcl">qiita.com</a></cite></p> <p>本年度LCLの技術開発部の部長に就任しました、エンジニアの<a href="http://blog.hatena.ne.jp/ytkr0813/">id:ytkr0813</a>です。<br> 最近、電気毛布を導入したらQOLが爆上がりしました。あと電熱ベストも買いましたが最高でした。ありがとうブラックフライデー。</p> <p>気づいたら2022年も終わりですね。昨日の<a href="https://techblog.lclco.com/entry/2022/12/24/000000">フロントエンドチーム振り返り記事</a>でもありましたが、今年度のLCLはエンジニアチーム的にも、全社的にも大きな変化があった1年でした。<br> 今年の4月に代表が交代し、組織体制の変更もあり、コロナ後の再加速に向けて着々と準備を進めてきました。<br> MVV(ミッション・ビジョン・バリュー)プロジェクトもその一環で現在進行形で全社員が一丸となって進めています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2022%2F12%2F20%2F130543" title="設立12年目のLCLが今、MVVプロジェクト始動した理由 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2022/12/20/130543">techblog.lclco.com</a></cite></p> <p>私が在籍する技術開発部でもメンバーの入れ替わりがあったり、また自分の立場が変わったことなどもあり、特にこの下期はあっという間に過ぎた3ヶ月だったと思います。</p> <p>そんなこんなで出会いがあり別れもあり...色々とあった1年でしたが、前向きで非常に充実した日々を過ごせています。今回は2022年の締め括りということで、この場を借りて弊社LCLのエンジニアチームについて紹介したいと思います。</p> <h3 id="LCLってどんな会社">LCLってどんな会社</h3> <p>このブログをご覧いただている方はなんとなくご存知の方が多いかと思いますが... <br> LCLは、「ユーザファースト」を軸に全国の高速バス・夜行バス、バスツアーの料金比較サイト「<a href="https://www.bushikaku.net/">バス比較なび</a>」や、国内移動・海外航空券の最安値比較サイト「<a href="https://idou.me/">格安移動</a>」などユーザーさんの旅行・移動をサポートするメディアを運営しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.bushikaku.net%2F" title="全国の高速バス・夜行バスの予約!格安・最安値情報【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.bushikaku.net/">www.bushikaku.net</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fidou.me%2F" title="【格安移動】高速バス・飛行機・LCC・新幹線の最安値比較・予約" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://idou.me/">idou.me</a></cite></p> <h3 id="エンジニアチームについて">エンジニアチームについて</h3> <p>そんなサービスたちを運営しているLCLですが、実際にサービスの開発に携わっているエンジニアチーム(技術開発部)は、現在総勢16名のメンバーが在籍しています。 当然、年齢や経歴もバラバラの我々エンジニアチームですが、ズバリどんなチームなのか言語化すると下記のキーワードなんかが当てはまるんじゃないかと思っています。(※個人の感想です)</p> <ul> <li>フラットな関係性</li> <li>チャレンジしやすい環境</li> </ul> <p>なんか書いててありきたりなワードになってしまったような気がしますが...<br> いわゆる理想論的な話ではなく、日々そういう組織にするための環境作りを意識しています。</p> <h4 id="フラットな関係性">フラットな関係性</h4> <p>エンジニアメンバーですが、年代は20代後半〜30代前半(私もここ)が多いと思いますが、中には経験豊富な40代テックリードのエンジニアも在籍しています。 エンジニアメンバー全員が集まる週次の定例会議なども実施していますが、誰かが誰かに遠慮したり、忖度したりといった場面はほとんど無いです。 むしろベテランメンバーが積極的に若手メンバーに意見を求めたり、時には技術的なアドバイスを真摯にしてくれたりといった場面を多く目にします。もちろん、MTGの場だけでなく日々の業務の中でも自然とそういったコミュニケーションが活発にされています。</p> <p>現在は在宅勤務メンバーの方が多かったりする中でこういった関係性を築けているのはLCLエンジニアチームの強みなんじゃないかと感じています。<br> 今年度ジョインしてくれた若手メンバーの入社エントリに、実際に働いてみてどうだったか所感を綴ってくれています。ぜひご覧ください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2022%2F12%2F14%2F175659" title="LCLにフロントエンドエンジニアとして入社して約4ヶ月が経ちました(入社エントリ) - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2022/12/14/175659">techblog.lclco.com</a></cite></p> <h4 id="チャレンジしやすい環境">チャレンジしやすい環境</h4> <p>こちらもフラットな関係性を築けているからこそだと思いますが、個々のメンバーのやりたいことやチャレンジしたいことを、お互いサポートし合える環境づくりを目指しています。 今期は、メンバーの発案で長年の課題であったフロントエンド領域のリファクタリングに着手(&amp;一部リリース)できたり、UI/UXのリニューアルなどにも手を出すことが出来ました。 下記の記事でも取り上げているのでぜひご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2022%2F12%2F24%2F000000" title="フロントエンドチームの2022年振り返り - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2022/12/24/000000">techblog.lclco.com</a></cite></p> <p>既存サービスを構成しているインフラアーキテクチャの刷新(コンテナ化)なども随時進行中ですので、この辺りもまたどこかで記事にしたいと思います。<br></p> <p>また、下記記事でも紹介していますが今まであまり手をつけられていなかったデータ分析領域にも徐々に進出していっています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2022%2F12%2F22%2F171026" title="LCLでのデータ分析領域の活動と未来 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2022/12/22/171026">techblog.lclco.com</a></cite></p> <p>これらのチャレンジをエンジニア間でサポートし合うのももちろんですが、業務で関わり合うエンジニア以外の人たちへチャレンジの意義を説明し工数を使うことを納得してもらうといった活動も、担当エンジニア本人が実行するだけでなく、サポートするマネージャー層にも尽力してもらっています。<br> こういった文化醸成は、先人のLCLエンジニアたちが長年かけて築き上げてきたものでもありますが、今期は組織変更を起点によりその強みを加速できている実感もあります。<br></p> <p>今後も、これらの特徴・強みを最大限に活かしたチームづくりを部長という役割のもと、メンバーの皆さんに協力していただきながら徹底していきたいと思います。</p> <h3 id="Join-Us-">Join Us !!!</h3> <p>LCLは、主要サービスの急成長期を終え、現サービスを安定稼働・改善させながら新しいビジネスに挑戦していくフェーズに入りました。上記のキーワードに共感して、一緒にチャンレジしてくれる仲間を募集しています!<br> まずはカジュアル面談からでも、随時対応していますのでぜひお気軽にお声がけください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> ytkr0813 フロントエンドチームの2022年振り返り hatenablog://entry/4207112889947291342 2022-12-24T00:00:00+09:00 2022-12-24T00:00:02+09:00 この記事はLCL Advent Calendar 2022 - 24日目です。 qiita.com フロントエンドエンジニアの亀田です。メリークリスマス。 気が付けばあっという間に年末ですね。自分がLCLに入社してからは、約半年でコロナ禍になり、色々と失われた期間があるので、そんなに長く在籍している感覚はありませんが、今年の10月で丸3年経ったみたいです。 時の流れが早すぎて、時に焦りや残酷さすら覚えますが、今年は会社としてもチームとしても大きな変化があり、結果的にとても充実した1年でした。 毎年恒例ではありますが、「フロントエンドチームの2022年振り返り」と題して、今年の出来事を振り返って… <p>この記事はLCL Advent Calendar 2022 - 24日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2022%2Flcl" title="LCLのカレンダー | Advent Calendar 2022 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2022/lcl">qiita.com</a></cite></p> <p>フロントエンドエンジニアの亀田です。メリークリスマス。</p> <p>気が付けばあっという間に年末ですね。自分がLCLに入社してからは、約半年でコロナ禍になり、色々と失われた期間があるので、そんなに長く在籍している感覚はありませんが、今年の10月で丸3年経ったみたいです。</p> <p>時の流れが早すぎて、時に焦りや残酷さすら覚えますが、今年は会社としてもチームとしても大きな変化があり、結果的にとても充実した1年でした。</p> <p>毎年恒例ではありますが、「フロントエンドチームの2022年振り返り」と題して、今年の出来事を振り返っていきます。</p> <h2 id="会社の変化">会社の変化</h2> <p>上述の通り、2022年は全社としても、技術開発部としても、フロントエンドチームとしても、大きな変化があった1年となりました。</p> <p>4月に社長が変わり、10月には組織が変わり、内部的なフォーメーションも変わり、新たなメンバーが加わり、長年活躍してきたメンバーの退職もありと、ここ数年で最もメンバーが入れ替わった年になったと思います。</p> <p>もちろん退職された方々がいなくなってしまった寂しさはありますが、新たにパワフルなメンバーも加わり、会社として1つの時代が幕を閉じ、新たな時代が始まった印象で、個人的にはポジティブな印象を持っています。</p> <p>社会情勢としても、コロナ禍から明けたとは到底言えない状況は続いていますが、以前に比べて徐々に人の動きも活発になり、有り難いことに当社のサービスも多くのユーザーに利用していただいています。もちろん、同時に社員のモチベーションも大きく回復した1年だったと思います。</p> <h2 id="フロントエンドチームの現状">フロントエンドチームの現状</h2> <p>2020年春から、フロントエンドチームは3名体制で運営してきましたが、今年新たなメンバーが加わり、現在は4名体制になりました。</p> <p>昨年から、各サービス単位で職種をミックスしたサービスカット体制を敷いているため、フロントエンドエンジニアも担当サービスを持ち、普段は自分が担当しているサービスのアプリケーション開発を行っています。</p> <p>サービスで開発対象が分かれているため、フロントエンドのメンバー同士で共同開発を行うことは少ない状態にはなっていますが、週次のMTGで積極的にコミュニケーションを取ったり、普段から困ったことや相談したいことがあれば、気軽に相談し合える関係性があります。</p> <p>フロントエンドチームが所属している技術開発部としても、組織・体制として大きな変化がありましたが、フロントエンドチームがこれまで築き上げてきた関係性や雰囲気の良さは健在で、今後より良くなっていく期待感すら漂っている状態です。</p> <h2 id="2022年のフロントエンドチームの振り返り">2022年のフロントエンドチームの振り返り</h2> <h3 id="メンバーの加入退職">メンバーの加入・退職</h3> <p>今年の春、現在の4名体制になる前に、1名のメンバーがジョインし、その後退職してしまう出来事がありました。</p> <p>フロントエンドチームとしては、昨年から新メンバーの採用活動を継続的に行ってきて、チームにとって念願のメンバー加入でしたが、結果的に退職してしまったことは、とても残念なことであり、素直に悲しい出来事でした。</p> <p>あえてブログに書くことなのかは分かりませんが、自分達にとっては忘れてはならない出来事だと思っているので、この記事に刻んでおこうと思います。(退職されたメンバーの活躍を陰ながら願っています)</p> <h3 id="改めてメンバーの加入">(改めて)メンバーの加入</h3> <p>前述でのメンバーの退職で、正直なところ大きなショックを受けましたが、下ばかり向いているわけにはいかないと、メンバー同士で奮い立たせて前を向いて引続き採用活動に挑みました。</p> <p>ミスマッチが起きた部分はどこか、今後同じことにならないために自分達に何が出来るのかを考え、採用フロー、面接項目・内容、コミュニケーションの取り方、オンボーディングと見直しを行いました。この部分はかなりメンバー間で根気強く会話して、現時点で妥協のないものを考えていきました。</p> <p>その後、採用活動を再開して、新たなメンバーを迎え入れることが出来ました。</p> <p>新たに加入してくれたメンバーは、上手くチームや組織に馴染むことが出来て、今では毎日楽しそうにバリバリと開発しています。性格的にも明るいメンバーで、チームの雰囲気としても一段と良くなった効果までもたらしてくれていると感じています。</p> <h3 id="バス比較なび-リファクタリング--レスポンシブ対応">バス比較なび リファクタリング + レスポンシブ対応</h3> <p>ここからは各サービスでの、フロントエンド開発が絡む話題をピックアップします。</p> <p>当社の中で、最も多くのユーザー数を持つサービスであるバス比較なびですが、リリースから十数年経ちました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.bushikaku.net%2F" title="全国の高速バス・夜行バスの予約!格安・最安値情報【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.bushikaku.net/">www.bushikaku.net</a></cite></p> <p>十数年経つと、当然ですが技術的負債も多くなり、これまで適切に対策を行ってこなかったことや歴史的な経緯でかなり負債が肥大化した状態になっています。</p> <p>実はこの状況は、自分が入社した3年前から大きな課題となっており、自分の入社前から何度もリファクタの計画と実施を行ったものの、上手く突破口が見つけられていない状態にありました。</p> <p>昨年、当社の別のサービスである海外航空券の立ち上げで、LCLとしては初めてNext.jsを採用しました。</p> <p>Next.jsで実際に1つのサービスを開発する中で、開発体験の良さ、カスタマイズ性、現代のフロントエンドに必要な観点において、好感触が得られたこともあり、バス比較なびも小手先のリファクタではなく、思い切ってNext.jsを採用した新環境にリプレイスする判断を行いました。</p> <p>また、リプレイスという判断をしたことで、同時にこのタイミングでしか現実的に実施できないであろう、レスポンシブデザインの採用の提案を行い、結果的に「リファクタリング(リプレイス) + レスポンシブ対応」という名目でプロジェクトを計画しました。</p> <p>年内としては、第1フェーズとして、既存アプリケーションとの共存を実現するための、アーキテクチャやインフラの検討と実装、フロントエンド環境の整備を行い、画面としては1画面のリリースを目指して、ディレクター、デザイナー、バックエンド・インフラエンジニアとも協力しながらプロジェクトを進行し、先日どうにかリリースにこぎ着けました。</p> <p>画面の変更としては1ページの変更ですが、リプレイス環境の運用を開始出来たことや、同時にレスポンシブ対応も加えてユーザビリティ的にも改善が期待出来るプロジェクトの第1フェーズが完了したことは、とてもとても大きな一歩だと感じています。</p> <p>ちなみにリリースした1画面はこちらの「<a href="https://www.bushikaku.net/content/kiyaku/">ご利用規約</a>」です。ヘッダー・フッターのデザインもとてもすっきり見やすくなりつつ、簡単なテキストだけのページにはなりますが、レスポンシブ対応されてます。</p> <p>来年以降も、課題にぶつかりながら、エンジニア含めメンバーがわくわくしながら、より一層スピードアップして取り組んでいきたいと思います。</p> <h3 id="バスツアー-特集コンテンツ">バスツアー 特集コンテンツ</h3> <p>バスツアーもリリースしてから3年が過ぎましたが、どんどん機能がアップデートされているサービスです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftour.bushikaku.net%2F" title="日帰り・宿泊バスツアーの人気格安プラン検索【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tour.bushikaku.net/">tour.bushikaku.net</a></cite></p> <p>今年もいくつも機能やコンテンツを追加していますが、中でもインパクトの強いのは特集コンテンツの追加です。</p> <p>コンテンツの内容としては、その時期におすすめしたい、特定のテーマのバスツアーの情報が1つのページにまとめられているコンテンツです。このページを見れば、そのテーマのツアー情報、おすすめツアー、新着ツアーが確認できます。</p> <p>また、技術的にも新たな試みとして、<a href="https://microcms.io/">Micro CMS</a>を導入しており、季節や時期で訴求内容が変わる特集コンテンツを、ディレクター等企画職のメンバーが、簡単に管理画面から作成、カスタマイズできるようになっている等、運用面での工夫もされた開発案件でした。</p> <p>12月のこの時期は、寒い時期にぴったりの<a href="https://tour.bushikaku.net/featured/illumination">イルミネーションバスツアー特集</a>、年末詣や初詣をまとめた<a href="https://tour.bushikaku.net/featured/new-year-holidays">年末年始特集</a>、シンプルにただただ美味しそうな<a href="https://tour.bushikaku.net/featured/crab">カニ特集</a>等、ユニークな特集がいくつもあるので是非見てみてください。</p> <p>※特集コンテンツは時期によって特集が入れ替わるため、閲覧タイミングによってはリンクからページが見られないかもしれません。その場合は、<a href="https://tour.bushikaku.net/">バスツアー トップページ</a>から閲覧ください。</p> <h3 id="海外格安航空券-UIUXアップデート">海外格安航空券 UIUXアップデート</h3> <p>昨年、格安移動の姉妹サービスである海外航空券の立ち上げを行い、サービス全体として見ると、国内・海外の移動をカバーするサービスになりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fidou.me%2F" title="【格安移動】高速バス・飛行機・LCC・新幹線の最安値比較・予約" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://idou.me/">idou.me</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fair.idou.me%2F" title="海外格安航空券・LCC・飛行機の料金比較予約【格安移動】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://air.idou.me/">air.idou.me</a></cite></p> <p>格安移動全体としてやるべきことはたくさんありますが、今年はサービスとしてのブランディングの再整理、ユーザビリティの再検討に力を入れてきました。</p> <p>格安移動も、かれこれ10年弱の歴史があるサービスで、立ち上げ当時から大きなデザインのアップデートもなく、今では一昔前を彷彿とさせるレガシーなものになっています。</p> <p>昨年リリースした姉妹サービスの海外航空券も、当時は既存の格安移動のデザインに合わせるという方針で進められたため、同じくレガシーなデザインをベースに構築された経緯があります。</p> <p>デザインが作り出す世界観、時代に合ったUI、心地の良いUXは、プロダクトの価値そのものに繋がるとても重要な要素です。</p> <p>また、我々フロントエンジニアもデザインやUIUXに対しては特に責任を持つ領域です。エンジニア自身が仕様に対しての納得感があるかどうかは開発のモチベーションを大きく左右します。</p> <p>今後ユーザーにとってより良い体験や価値を提供するために、今年はUIUXをアップデートするプロジェクトを立ち上げ、継続的に進めてきました。</p> <p>まずは、手の加えやすい海外航空券から随時変更している状況で、既に共通ヘッダーや、検索フォーム等、以前と比べて間違いなくより使いやすくなってきている実感があります。</p> <p><figure class="figure-image figure-image-fotolife" title="海外格安航空券のトップページの変化"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kabrg/20221222/20221222171321.jpg" alt="&#x6D77;&#x5916;&#x683C;&#x5B89;&#x822A;&#x7A7A;&#x5238;&#x306E;&#x30C8;&#x30C3;&#x30D7;&#x30DA;&#x30FC;&#x30B8;&#x306E;&#x5909;&#x5316;" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>海外格安航空券のトップページの変化</figcaption></figure></p> <p>来年の3月にかけて、随時アップデートを続けていき、最終的に全ページの体裁が新デザインに統一される予定です。</p> <p>その後は、国内の格安移動のデザインやUIUXをアップデートして、サービス全体として更なる飛躍を目指していきます。</p> <h2 id="2023年に向けて">2023年に向けて</h2> <p>改めて振り返ってみても、今年は本当に色々なことや大きな変化があり、会社としての時代が変わった年だと感じています。</p> <p>会社、技術開発部、フロントエンドチームとしても、新たなフォーメーションで新たな挑戦が始まった印象です。</p> <p>社会情勢としても、会社全体としても、とても良い風が吹き始めていると感じるので、来年は各サービスも会社もより良く変えていければと思いますし、既に来年が楽しみです。</p> <p>今後のLCLに乞うご期待ください。それではまた来年。早いですが良いお年を。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> kabrg LCLでのデータ分析領域の活動と未来 hatenablog://entry/4207112889944478074 2022-12-22T17:10:26+09:00 2022-12-26T09:31:48+09:00 バックエンドエンジニアの高橋です。 弊社LCLでは高速バスや旅行分野における様々なデータを分析し利活用していこうという活動を徐々にではありますが進めています。 今後はデータの活用が進んでいる企業が生き残ると言われて久しいですが、そもそも「何のためにデータ分析しているんだっけ?」とふと振り返ってみました。一般論というよりは私個人の思いなので悪しからず。 データ活用の目的 ここではデータ活用の目的を対象とするユーザーで大きく3つに分類してみます。 ビジネスの創出 施策の意思決定の精度向上 業務の自動化 まず、「1. ビジネスの創出」 は主な対象が取引先企業や弊社サービスの利用ユーザーです。何かしら… <p>バックエンドエンジニアの高橋です。</p> <p>弊社LCLでは高速バスや旅行分野における様々なデータを分析し利活用していこうという活動を徐々にではありますが進めています。</p> <p>今後はデータの活用が進んでいる企業が生き残ると言われて久しいですが、そもそも「何のためにデータ分析しているんだっけ?」とふと振り返ってみました。一般論というよりは私個人の思いなので悪しからず。</p> <h2 id="データ活用の目的">データ活用の目的</h2> <p>ここではデータ活用の目的を対象とするユーザーで大きく3つに分類してみます。</p> <ol> <li>ビジネスの創出</li> <li>施策の意思決定の精度向上</li> <li>業務の自動化</li> </ol> <p>まず、「1. ビジネスの創出」 は主な対象が取引先企業や弊社サービスの利用ユーザーです。何かしらの分析や機械学習を行なった結果を提供し、お客様の業務や日常生活に役立ててもらうことが目的となります。 言い換えればデータそのものに価値を見出しその価値をお客様に提供するということでもあります。具体的にはデータ分析ダッシュボードの提供や、価格の最適化システムの提供などがこれに当てはまります。</p> <p>次に、「2. 施策の意思決定の精度向上」は対象が社内のプロダクト開発チームの方々です。プロダクトの改善ポイントやユーザーニーズをデータから読み解くことで、不確実性の高い施策をより成功確率が高い方向へ進められるように手助けすることが目的です。弊社ではディレクターがAdobeAnalyticsを利用してWebトラフィック分析を行いプロダクト改善の施策を検討したり効果検証を行っていますので、この目的に関しては既に部分的に達成できています。ですが、より一歩進んだ洞察を得るにはWebトラフィックだけではなく業務領域におけるさまざまなデータ(LCLの場合は例えば高速バスの便データ)を分析するのが理想ですし、分析の方法も統計学や機械学習などを使うなどしてより客観性の高い意思決定をすべきなど改善の余地があると考えています。</p> <p>最後の「3. 業務の自動化」は対象が営業数値やWebサービスのKPIといった数値レポートを日々作成している方々です。レポートを作成するために必要なデータを取得し集計するという人力作業を自動化することが目的です。これは必ずしもデータの分析が必要になるわけではなく、必要とされるスキル的にもデータサイエンスというよりもデータ基盤の整備スキルの方が重要になるので上記2つの目的とはちょっと毛色が異なりますが、LCLではこれもデータ分析プロジェクトの一環として同一メンバー(私)が行っています。</p> <h2 id="データ活用分析の成熟度">データ活用・分析の成熟度</h2> <p>データ活用・分析の理想とする姿までのロードマップをざっくりイメージしてみると、</p> <p>レベル1:最低限の分析をするためのデータが蓄積できている</p> <p>レベル2:データを「利用できるデータ」に加工・整備するデータパイプラインがある</p> <p>レベル3:データの可視化ができている</p> <p>レベル4:データを見て意思決定する社内文化が醸成できている</p> <p>レベル5:データを統計的に分析する知見が社内に蓄積されている</p> <p>レベル6:取引先やユーザーに分析したデータの価値を提供できている</p> <p>現状はレベル3とレベル4の間に位置しているかなといった感じです。</p> <p>レベル4以上は私個人だけでは達成できないのでとてもハードルが高く感じていますが、徐々にデータ分析の布教活動を進めていこうと思います。</p> <h2 id="最後に">最後に</h2> <p>あまり具体性の内容のない記事になってしまいましたが、また別の機会にデータ分析で実現した事例を書こうかと思います。</p> <p>道のりはまだまだ遠いですが、最終的には業界内で一番データ分析が進んでいる企業と言われるくらいにプレゼンスを高めていきたいです。</p> <p>LCLではデータ分析のように未開拓な業務領域がたくさんありますので、チャレンジ精神旺盛なエンジニアを募集中です。ご興味がある方はぜひ、採用ページからご応募お待ちしております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit">www.lclco.com</a></cite></p> ktx_ku 設立12年目のLCLが今、MVVプロジェクト始動した理由 hatenablog://entry/4207112889946475489 2022-12-20T13:05:43+09:00 2023-11-14T14:16:01+09:00 この記事はLCL Advent Calendar 2022 - 19日目です。 qiita.com こんにちは、iOSアプリエンジニアの山下です。 先日初子が産まれまして新米パパになりました。現在は育休に入ったところで気分転換も兼ねてこの記事を書いています。 ちなみにLCLは先輩パパが沢山いて非常に心強いです。育休に入る前、皆が口を揃えて「産後は今後の関係性に大きく影響する大切な時期だから全力で尽くした方がいい」と力強いアドバイスを頂きました。震えました。 さて、そんな育休中の私ですが唯一MVVプロジェクトには週に1時間ほど無理のない範囲で参加しています。 MVVとは「Mission(ミッショ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lcl-engineer/20221224/20221224111851.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この記事は<a href="https://qiita.com/advent-calendar/2022/lcl">LCL Advent Calendar 2022</a> - 19日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2022%2Flcl" title="LCLのカレンダー | Advent Calendar 2022 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2022/lcl">qiita.com</a></cite></p> <p>こんにちは、iOSアプリエンジニアの山下です。</p> <p>先日初子が産まれまして新米パパになりました。現在は育休に入ったところで気分転換も兼ねてこの記事を書いています。</p> <p>ちなみにLCLは先輩パパが沢山いて非常に心強いです。育休に入る前、皆が口を揃えて「産後は今後の関係性に大きく影響する大切な時期だから全力で尽くした方がいい」と力強いアドバイスを頂きました。震えました。</p> <p>さて、そんな育休中の私ですが唯一MVVプロジェクトには週に1時間ほど無理のない範囲で参加しています。</p> <p>MVVとは<strong>「Mission</strong>(ミッション)」、「<strong>Vision</strong>(ビジョン)」、「<strong>Value</strong>(バリュー)」の頭文字をとった言葉です。</p> <p>このMVVプロジェクトは現在進行形のもので私たちの中でもまだ模索している最中ではありますが、なぜこのプロジェクトが立ち上がったのか、どのようなプロセスで進めているのかを簡単にご紹介したいと思います。サクッと読める内容なので連日の技術記事のおつまみとして読んでいただけると嬉しいです。</p> <h3 id="そもそもMVVってなに必要なの">そもそもMVVってなに?必要なの?</h3> <p>MVVが何なのか、なぜ重要なのかは世の中に沢山の詳解記事があるので割愛させていただきますが、簡潔に言うと<strong>「働き方や仕事に対する考え方が多様化する中、組織と個人のパフォーマンスを最大化するために全員が価値観を共有し、同じ前提条件を持ち、視座を高めるために必要なもの」</strong>と私は解釈をしています。</p> <p>「これがないと仕事ができない!」というものではもちろんありませんが、組織や個人に対する価値観や期待値の前提条件に"ギャップ"が生まれることで、互いをリスペクトしたり、率先して責任を持つ(=リスクを負う)ことが難しくなり、組織の成長とは裏腹にに個人および組織のパフォーマンスは低下すると思っています。</p> <p>LCLは来月で設立12年目になります。バス比較なびに至ってはローンチから15年も経ちます。これほど長い期間、組織・サービスが運営されていると仕組みとしては成熟してくる一方で、これまで以上の価値を提供するためには大きな意思決定をして既存の壁を越えなければなりません。しかし、人の流れは入れ替わるもので、皆が過去を知り、同じ未来を描くのが難しくなってきます。と同時にあらゆる場面で”ギャップ”も生まれやすくなってきます。</p> <h3 id="MVVプロジェクト発足のきっかけは1on1">MVVプロジェクト発足のきっかけは1on1</h3> <p>LCLのHPには「旅人をふやす」と言うキーワードがあります。このキーワードこそがミッションとも受け取れるフレーズではありますが、実際は社内で発言されている場面が滅多にありません。寧ろ「移動手段の選択肢を提供することには特化しているものの、旅先の目的に対するコンテンツを持っていないため我々のミッションとしてあっているのか疑問」といった声もあります。つまり、ミッションとして組織に浸透しておらず、人によってはHPに書かれているただのキャッチコピーでしかなかったのです。</p> <p>反対に社内でよく使われている「ユーザーファースト」と言う言葉があります。これは現在は退任している創業者が度々発言していた言葉で、長く在籍しているメンバーには強く根付いている言葉です。MVVで言うとバリューにあたるキーワードですが、これもある時を境に以前ほどは発言される場面が減りました。但し、これについては体感的には形骸化したよりも前提条件になったと言う方が正しいかもしれません。しかし、全員が腹落ちしてるかは不明でした。</p> <p>と、このように組織として掲げているフレーズや個々人が持つ前提条件にギャップが生じているのが明らかでした。</p> <p>LCLでは本年度から四半期ごとに社長との1on1を行っています。その機会でこのことについて会話をしたところ、私以外にもこのギャップに違和感を感じているメンバーがいるということ、そして今後の組織の成長において優先して解決すべき課題だと社長も認識されていたということでMVVプロジェクトが発足されました。</p> <h3 id="どういったプロセスでプロジェクトが進行されているか">どういったプロセスでプロジェクトが進行されているか?</h3> <p>人材育成から、組織開発、組織デザイン、事業開発、経営戦略など総合的なコンサルティング事業を営む<a href="https://mimiguri.co.jp/">MIMIGURI</a>さん伴走のもと、私たちLCLの過去・現在・未来の深掘りから始めています。</p> <p>業種を横断した8人とMIMIGURIさんとで週1でミーティングを行い、各々が認識・大切にしている価値観を洗い出しを行い、それについて所感を交えながら解像度を上げている最中です。</p> <p>全社員を交えた<a href="https://miro.com/ja/">Miro</a>上でのワークショップも行い、社員一人一人のフィードバックも拾いながら最終的には全社員が共感して腹落ちして目標にできるMVVを作っていきます。</p> <p>具体的なプロセスはまたMVVが確定した際に振り返りとしてまとめたいと思いますが、MIMIGURIさんの素晴らしいファシリテートとプロジェクトメンバーのそれぞれが持つ異なった良い価値観が交わってLCLらしいMVVができることを予感しています。</p> <h3 id="20231114-追記">2023/11/14 追記</h3> <p>続編はこちら!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2023%2F11%2F14%2F141430" title="新たなミッションとバリューの舞台裏:LCLの挑戦 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2023/11/14/141430">techblog.lclco.com</a></cite></p> <h3 id="JOIN-US">JOIN US!!</h3> <p>今回は私たちが現在進行形で行なっているMVVプロジェクトについて少し紹介させていただきました。組織を運営している以上、サービスや技術以外にも多くの課題が発生しますが私たちは個々人がその課題に皆と共有し、改善していく組織でありたいと思っていますし、今回それが実際に行動に示せた良い例になっているかと思います。</p> <p>そんなLCLでは一緒に組織やサービスを成長させていけるエンジニアを募集中です。ご興味がある方はぜひ、採用ページからご応募お待ちしております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit">www.lclco.com</a></cite></p> lcl-engineer BigQueryでユニーク数を推測するHyperLogLog++について hatenablog://entry/4207112889942763400 2022-12-16T14:07:52+09:00 2022-12-22T17:29:43+09:00 バックエンドエンジニアの高橋です。 弊社ではデータ分析・可視化のためのデータ基盤としてBigQueryを用いていますが、その中でHyperLogLogアルゴリズムを活用したユニーク数の計測における工夫についてご紹介します。 なお、HyperLogLogアルゴリズム自体については詳細に理解していなくても実用上困ることは少ないのでこの記事では扱いません。詳細についてはネット上の記事を探ったりGoogleが公式に出している論文を当たるといいです。(私も説明できるほど詳細な理解はしてないです) ユニーク数の計測の面倒臭さ Webサイトのアクセス解析において重要な指標としてPV(ページビュー数)とUU(… <p>バックエンドエンジニアの高橋です。</p> <p>弊社ではデータ分析・可視化のためのデータ基盤としてBigQueryを用いていますが、その中でHyperLogLogアルゴリズムを活用したユニーク数の計測における工夫についてご紹介します。 なお、HyperLogLogアルゴリズム自体については詳細に理解していなくても実用上困ることは少ないのでこの記事では扱いません。詳細についてはネット上の記事を探ったり<a href="https://research.google.com/pubs/pub40671.html?hl=ja">Googleが公式に出している論文</a>を当たるといいです。(私も説明できるほど詳細な理解はしてないです)</p> <h2 id="ユニーク数の計測の面倒臭さ">ユニーク数の計測の面倒臭さ</h2> <p>Webサイトのアクセス解析において重要な指標としてPV(ページビュー数)とUU(ユニークユーザー数)がありますが、PV数は単純に足し合わせでカウントすればいいのに対し、UUについては重複を排除した上でカウントする必要があるので分析上の取り扱いが面倒です。</p> <p>簡単なお題で考えてみましょう。</p> <p>例題.「12月1日のユーザー数は10人、12月2日のユーザー数は20人でした。では、12月1日と12月2日の合計ユーザー数は何人でしょうか?」</p> <p>答えとしては「正確な人数は不明で最小20人、最大30人」です。「え?30人じゃないの?」と思った人はもう一度よく考えてみてください。^^;</p> <p>これが実務上どんな時に困るかというと、集計の粒度が異なる場合に集計処理の計算コストが膨れ上がるケースです。ここでいう計算コストとは費用的な意味とパフォーマンス的な意味の両方です。</p> <p>例えば、</p> <ul> <li>Aさん「月間のUUを知りたい」</li> <li>Bさん「日毎のUUを知りたい」</li> </ul> <p>といった場合、BigQueryで集計するとしたら</p> <ul> <li>Aさん用のSQL</li> </ul> <pre class="code SQL" data-lang="SQL" data-unlink>select date, count(distinct user_id) as uu from event_logs group by date</pre> <ul> <li>Bさん用のSQL</li> </ul> <pre class="code SQL" data-lang="SQL" data-unlink>select extract(month from date), count(distinct user_id) as uu from event_logs group by date</pre> <p>このような感じでAさん用とBさん用に異なる集計処理を行う必要が出てきます。他にも「訪問ページ別で集計したい」、「流入区分別に集計したい」などビジネスサイドからの要望はどんどん出てくることでしょう。</p> <p>この集計元となっているテーブルのサイズが小さければ計算コストを気にする必要はないですが、Webトラフィックなどのデータは基本的に大量のデータなので毎度集計するようだと大変な計算コストがかかってしまいます。</p> <p>仮に上記のような集計SQLを1回実行するたびに100GBが処理されるとして、必要とされる粒度によって集計を10通り行うとしたら1TBを処理することになります。</p> <p>パフォーマンス的な意味でも処理されるデータ量は低く抑えたいですし、BigQueryをオンデマンド料金プランで利用している場合は処理されるデータ量に課金額が比例するので気を使うところでしょう。</p> <p>この計算コストを抑えるテクニックとして、事前にある程度集計した中間テーブルのようなもの(事前集計テーブルやPre-Aggregationと呼ばれたりする)を作るケースがありますが、更にHyperLogLogアルゴリズムを組み合わせることでよりスマートになります。</p> <h2 id="HyperLogLogの使い方">HyperLogLog++の使い方</h2> <p>BigQueryでは<a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/hll_functions?hl=ja">HyperLogLog++関数</a>という名前のユニーク数を低コストでカウントするための関数を用意しています。</p> <p>使い方としてはユニークにカウントしたい値の集合からsketchと呼ばれるハッシュ値を生成し、後続の集計処理でそれを用いてユニーク数を推定する(※)という流れになります。</p> <p>※ HyperLogLogではアルゴリムの特性上正確なユニーク数を保証できません。よって数値が1でもずれるのが嫌な場合は適していませんのでご注意を。</p> <p>説明してもよくわからないと思うので、お題として以下の3名の要望をそれぞれHyperLogLog++を使って集計してみます。</p> <ul> <li>Aさん:「日別のUUを知りたい」</li> <li>Bさん:「日別で流入区分別のUUを知りたい」</li> <li>Cさん:「日別で訪問ページ別のUUを知りたい」</li> </ul> <p>まずはユニークにカウントしたいuser_idを <code>hll_count.init(user_id, 24)</code> としてsketchを作成します。引数の24はスケッチの精度でこの値が高いほどより正確なユニーク数の推定ができるようになります。(ただし計算コストとのトレードオフになります)</p> <pre class="code SQL" data-lang="SQL" data-unlink>CREATE TABLE preaggregated_event_logs as ( select date, inflow_segment, page_url, hll_count.init(user_id, 24) as sketch from event_logs group by 1, 2, 3 )</pre> <p>sketchを作成する場合は集計したい粒度でgroup by する必要があります。</p> <p>また、SQLの出力結果は後続の集計段階で使いまわすので物理テーブルとして保存しておくといいです。一旦 preaggregated_event_logs と命名したテーブルに保存しています。</p> <p>そして生成したsketchを以下のように <code>hll_count.merge(sketch)</code> とすればUU数の推定ができます。</p> <ul> <li>Aさん「日別のUUを知りたい」</li> </ul> <pre class="code SQL" data-lang="SQL" data-unlink> select date, hll_count.merge(sketch) as uu from preaggregated_event_logs group by 1</pre> <ul> <li>Bさん「日別で流入区分別のUUを知りたい」</li> </ul> <pre class="code SQL" data-lang="SQL" data-unlink> select date, inflow_segment, hll_count.merge(sketch, 24) as uu from preaggregated_event_logs group by 1, 2</pre> <ul> <li>Cさん 「日別で訪問ページ別のUUを知りたい」</li> </ul> <pre class="code" data-lang="" data-unlink> select date, page_url, hll_count.merge(sketch, 24) as uu from preaggregated_event_logs group by 1, 2</pre> <h2 id="HyperLogLogでの集計メリット">HyperLogLogでの集計メリット</h2> <p>使い方は上記の通りですがそもそものHyperLogLogを利用するメリットを挙げると以下のような点があります。</p> <ol> <li>集計処理が速い</li> <li>集計で処理されるデータサイズが低く抑えられる</li> </ol> <p>この内、2の処理データサイズが低く抑えられる点については事前集計テーブルを作ったことによる恩恵です。 ケースバイケースですが事前集計テーブルは元の集計前テーブルのサイズよりもかなり圧縮されます。</p> <p>仮に100GB→10GBに圧縮されたと仮定すると、事前集計テーブルなしで集計を行った場合は100GBのテーブルの集計を3回行うことになるので単純計算で300GBの処理データサイズになりますが、事前集計テーブルを作成した場合は130GBになります。(事前集計テーブルの作成1回と10GBのテーブルの集計を3回)</p> <p>事前集計テーブルの作成は1回でいいので、集計粒度が多ければ多いほどお得感が増していきますね。</p> <h2 id="事前集計テーブルの分割">事前集計テーブルの分割</h2> <p>事前集計テーブルは1回の作成でいいと今書いたばかりですが、集計粒度が細かくなりすぎると今度は事前集計テーブル自体が肥大化しすぎて困る場合があります。</p> <p>例えば、集計粒度として、</p> <ul> <li>「日付」... 過去3年分 = 1095日</li> <li>「訪問ページ」... 1000ページ</li> <li>「流入区分」 ... 10種類</li> <li>「リファラードメイン」... 1000ドメイン</li> <li>「デバイス種類」 ... 5種類</li> <li>「OS」... 5種類</li> <li>「ユーザー年齢層」... 10区分</li> </ul> <p>これらの粒度で事前集計テーブルを作ろうとすると、単純計算で1兆レコード数を超える事前集計テーブルができてしまいます。 これでは結局処理されるデータサイズが肥大化してしまい元も子もないので、一案としては事前集計テーブルを目的に合わせて分割する方法があります。</p> <p>例えば、「日付」「訪問ページ」の粒度で集計したテーブルA、「日付」「流入区分」「リファラードメイン」の粒度で集計したテーブルB、「日付」「デバイス種類」「OS」「ユーザー年齢層」の粒度で集計したテーブルC、といった具合です。分割の基準は厳密にはないですが頻繁にクロスして集計する粒度かどうかで考えています。</p> <p>また、事前集計テーブルを分割した場合でもテーブルを跨いで集計することが可能です。つまりは異なる集合の和集合を求めたいので高校数学でお馴染みの以下のような計算をすれば良いです。</p> <p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%7CA%20%5Ccap%20B%7C%20%3D%20%7CA%7C%20%2B%20%7CB%7C%20-%20A%20%5Ccup%20B%0A" alt=" \displaystyle |A \cap B| = |A| + |B| - A \cup B "/></p> <p>例として、日付 × 訪問ページで事前集計したテーブルを preaggregated_visitsとし、日付 × 流入区分 × リファラードメイン で事前集計したテーブルをpreaggregated_inflows として、日付 × 訪問ページ × リファラードメインでUUの推測値を出してみます。</p> <pre class="code SQL" data-lang="SQL" data-unlink>with page_sketch as ( select date, page_url, hll_count.merge_partial(sketch) as sketch from preaggregated_visits group by 1, 2 ), referrer_sketch as ( select date, referrer, hll_count.merge_partial(sketch) as sketch from preaggregated_inflows group by 1, 2 ), uu_sketches as ( select date, page_url, referrer, [by_page.sketch, by_referrer.sketch] as sketch_array from page_sketch left join referrer_sketch using (date) ) select date, page_url, referrer, hll_count.merge(sketch_array[OFFSET(0)]) + hll_count.merge(sketch_array[OFFSET(1)] - hll_count.merge(sketch)) as uu from uu_sketches, unnest(sketch_array) as sketch group by 1,2,3</pre> <p>ポイントとしては、preaggregated_visits と preaggregated_inflowsをそのままjoinしてしまうとレコード数が爆発的になり処理時間が長くなってしまう可能性があるので、CTEで必要な粒度に沿って事前に集計します。CTEの段階ではUU数を出したいわけではないので、hll_count.merge_partial によって既存のsketchを更に部分的に集計した結果のsketchを出しています。</p> <p>また、それぞれのテーブルのsketchは1つのカラムにする必要があるためjoinしてarrayにまとめ、最後のselectクエリのところで <code>hll_count.merge(sketch_array[OFFSET(0)]) + hll_count.merge(sketch_array[OFFSET(1)] - hll_count.merge(sketch))</code> として上記公式の計算をすれば最終的に求めたいUU数となります。</p> <h2 id="最後に">最後に</h2> <p>今回はHyperLogLogを使ったユニーク数推定の実用方法についてご紹介しました。</p> <p>冒頭でも述べたように、処理データサイズを低く抑えられるのでBigQueryをオンデマンド料金プランで利用している方にとっては重宝するテクニックかと思います。</p> <p>ちなみにGoogleAnalyticsやAdobeAnalyticsなどのWebアクセス解析サービスでは事前定義済みの指標としてユニークユーザー数の可視化ができるので、アクセス解析だけが目的であればそもそもBigQueryを使う場面は少ないかと思います。ただ今回紹介したテクニックはWebアクセス解析だけに留まらず色々な場面で使えるものなので知っておいて損はないかと思います。</p> <h2 id="採用情報">採用情報</h2> <p>LCLでは開発メンバーを募集しています。</p> <p>カジュアル面談も行っていますので、少しでも興味をお持ちでしたらご連絡下さい。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> ktx_ku LCLにフロントエンドエンジニアとして入社して約4ヶ月が経ちました(入社エントリ) hatenablog://entry/4207112889944939476 2022-12-14T17:56:59+09:00 2022-12-26T11:24:45+09:00 この記事はLCL Advent Calendar 2022 - 14日目です。 はじめに はじめまして、フロントエンドエンジニアの「おとの」です。 今年の夏にLCLに入社してから、約4ヶ月が経ちました。短くも長くも感じるような充実した時間が過ごせていると感じています。 今回は人生初の「入社エントリ」を書いていきたいと思います。生まれて初めて「入社エントリを書いてみたいな」と思えた会社がLCLです。そんなLCLの魅力が伝われば幸いです。 Web制作会社でコーダーとして働いていたわたしが、フロントエンドエンジニアとしてLCLに転職するまでの経緯は下記記事で語っています。よろしければご覧ください。 … <p>この記事は<a href="https://qiita.com/advent-calendar/2022/lcl">LCL Advent Calendar 2022</a> - 14日目です。</p> <h2 id="はじめに">はじめに</h2> <p>はじめまして、フロントエンドエンジニアの「おとの」です。</p> <p>今年の夏にLCLに入社してから、約4ヶ月が経ちました。短くも長くも感じるような充実した時間が過ごせていると感じています。</p> <p>今回は人生初の「入社エントリ」を書いていきたいと思います。生まれて初めて「<strong>入社エントリを書いてみたいな</strong>」と思えた会社がLCLです。そんなLCLの魅力が伝われば幸いです。</p> <p>Web制作会社でコーダーとして働いていたわたしが、フロントエンドエンジニアとしてLCLに転職するまでの経緯は下記記事で語っています。よろしければご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fzenn.dev%2Ftoono_f%2Farticles%2F6daca8fa1c6bc9" title="Web制作会社のコーダーが自社開発企業(事業会社)のフロントエンドエンジニアに転職するまで" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://zenn.dev/toono_f/articles/6daca8fa1c6bc9">zenn.dev</a></cite></p> <h2 id="わたしが感じたLCLの特徴">わたしが感じたLCLの特徴</h2> <p>本ブログに既に投稿済みのエントリ記事で紹介されている所感とは異なるところもあるかもしれません。今年からLCLに就任した松井社長と技術開発部の新部長・新マネージャーを中心に、組織内のアップデートが行われている最中である影響が確かにあると思います。</p> <p>そんな最中に入社した自分が感じたLCLの特徴について、素直に述べていきます。</p> <h3 id="とても働きやすい社内環境と雰囲気">とても働きやすい社内環境と雰囲気</h3> <p>入社後、まず感じたのはオフィスがとても<strong>良い感じ</strong>です。</p> <p>わたしの席から見た景色はこのとおり。(おっと今日は出社人数が少ないようだが・・・)普段はもう少し人がいます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toono_f/20221214/20221214175514.png" width="1200" height="890" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>社内は心地よい音楽が流れていて、静かすぎない程度に落ち着いた雰囲気です。1人1人の座席の間隔も近すぎず遠すぎず、働きやすい距離感が保たれていると感じています。</p> <p>エンジニアが所属する技術開発部の座席は他部署と若干遠いですが、部署関わらずコミュニケーションを取りやすい空気が流れています。もちろん、罵声を上げる人はいません。距離感の取り方が絶妙で、落ち着いた(かつユニークな)社員が多いイメージです。</p> <p>ちなみに、本日の技術開発部の出社人数は4/12人です。(本日の出社人数は)これでも多い方です。コロナ渦前から(当時はエンジニアのみ)在宅勤務が認められていたのもあり、オフライン・オンライン関わらず、仕事を進めやすい仕組みが成立しています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toono_f/20221214/20221214122538.png" width="1200" height="1032" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>わたしは週1〜4回程度出社しています(最近寒くなってきたので、リモート増加傾向)</p> <p>ただ、会社の椅子(Baron)の座り心地が最高なのと、デュアルモニタやリアルフォースのキーボードも希望次第で利用できる恵まれた環境なので、仕事はとても捗りやすいと感じています。ウォーターサーバーで美味しい水を、コーヒーメーカーで美味しいコーヒーを飲み放題なのもGoodです。</p> <p>また、社内には憩いの場も存在します。個人スペースやスタンディングデスクでの開発やミーティング、時には靴を脱いでヨギボーに寄りかかれるスペースも存在します(まだ自分はやったことない)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toono_f/20221214/20221214122353.png" width="1200" height="523" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>最近は運動不足なので、他の社員を誘って一緒にここでヨガをやってみたいと思っています。以前よりコロナが落ち着いてきているのもあり、出社メンバーとランチに行く回数も増えてきました。恵比寿には美味しい飲食店が多数存在しているので、ランチの選択肢が多いのも魅力の1つです。</p> <h3 id="心理的安全性がマジで高い技術開発部">心理的安全性がマジで高い技術開発部</h3> <p>LCLのエンジニアは全員、技術開発部に所属しています。半数以上がリモート勤務中心の働き方にも関わらず、コミュニケーションの取りやすさはピカイチです。Slackが浸透しており、個人の分報(times)もあり、気軽に発言しやすく、会話しやすいのもGoodです。</p> <p><figure class="figure-image figure-image-fotolife" title="この後、出社メンバーと社長と焼肉ランチに行きました。大満足です。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toono_f/20221214/20221214105344.png" width="988" height="879" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>この後、出社メンバーと社長と焼肉ランチに行きました。大満足です。</figcaption></figure></p> <p>入社間もない頃に「(文章では伝えづらいことだけど)<strong>こんなことまで聞いてもよいのだろうか?</strong>」と思ったときにも、気軽にオンラインミーティング(ハドル)のお誘いもしやすかった記憶があります。そもそも入社後に手厚いオリエンテーションしていただいたのもあって、入社して2週間が経つ頃には「自分は昔からLCLに居たのではないか?」と錯覚するまでありました(あくまで錯覚)</p> <p>また、週に1回、技術開発部全員参加のミーティング(オンライン参加可)があり、その次にフロントエンド・バックエンドに分かれたミーティングがあります。最近では、Figmaを使って各々好きなようにアイデアを出したり、各自の関心やスケジュールを共有するなどの情報共有も、より一層行いやすくなっています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/toono_f/20221214/20221214110724.png" width="1200" height="463" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>とってもフリーダムですね。</p> <p>LCLのエンジニアの年齢層は20代後半〜50代前半と幅広いですが、みんな「誠実」で「親しみやすい」という共通点があるように思えます。気軽に話しやすく、頼りになるメンバーが揃っています。</p> <h3 id="成長したいモチベーションが湧き出る">成長したいモチベーションが湧き出る</h3> <p>LCLではエンジニアの担当が3つのサービスに分かれています。わたしは「格安移動・海外航空券」のサービスを担当しており、現在は海外航空券のUIUXアップデート(デザインリニューアル)をメインに取り組んでいます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fair.idou.me" title="海外格安航空券・LCC・飛行機の料金比較予約【格安移動】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://air.idou.me">air.idou.me</a></cite></p> <p>自分の担当しているサービスがより使いやすく、より多くのユーザーに浸透するにはどうしたら良いか、ディレクターやデザイナーたちとコミュニケーションを取りながら、サービスの顔を担当するフロントエンドエンジニアとして責務を持って開発を進めています。</p> <p>前職のWeb制作会社では代理店経由の受託開発がメインだったために、1つのプロジェクトに主体的に取り組むことが難しかったのもあって、当時の自分が望んでいた形で開発に携われていることに喜びを感じると共に、LCLのフロントエンドエンジニアとして、もっともっと貢献できるよう励んでいきたいという気持ちが日に日に強くなっています。</p> <p>また、前職のWeb制作会社では60時間を下回ることがなかった残業時間も、LCLに入社してからは、ほぼゼロです。技術面のキャッチアップにも時間を確保できるのも良いですね。</p> <h2 id="おわりに">おわりに</h2> <p>ここまで入社して感じた思いを述べてきましたが、LCLにはまだまだ紹介しきれていない魅力も多いです。最後に伝えたいのは、今は本当に良いメンバーに恵まれているということと、このメンバーと一緒にサービスを大きくしていきたい思いが強いということです。</p> <p>そんなLCLでは、一緒にサービスを成長させていけるエンジニアを募集中です。ご興味がある方はぜひ、採用ページからご応募お待ちしております。</p> <p>JOIN US!!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit">www.lclco.com</a></cite></p> toono_f レガシーなRails製管理画面開発をimportmap-railsとdartsass-railsで最適化する hatenablog://entry/4207112889895971538 2022-07-08T09:00:00+09:00 2022-07-08T09:00:02+09:00 はじめに バックエンドエンジニアの星野です。 最近、節電を意識して自宅のUbuntuサーバーを停止しました。 夏真っ盛りですが、電気代の値上げで自宅サーバーにとっては冬の時代です。 LCLではレガシー脱却の取り組みの一環としてRuby on Railsで作成された社内向け管理画面のアップデートを実施しました。 その際にRails 7リリース前後で登場した、importmap-railsとdartsass-railsを利用してフロントエンドツールチェインを更新しました。 tl;dr サードパーティのJaveScriptライブラリをImport maps経由で読み込みように変更しました。 Sass… <h2>はじめに</h2> <p>バックエンドエンジニアの星野です。</p> <p>最近、節電を意識して自宅のUbuntuサーバーを停止しました。 夏真っ盛りですが、電気代の値上げで自宅サーバーにとっては冬の時代です。</p> <p>LCLではレガシー脱却の取り組みの一環としてRuby on Railsで作成された社内向け管理画面のアップデートを実施しました。 その際にRails 7リリース前後で登場した、importmap-railsとdartsass-railsを利用してフロントエンドツールチェインを更新しました。</p> <h2>tl;dr</h2> <ul> <li>サードパーティのJaveScriptライブラリをImport maps経由で読み込みように変更しました。</li> <li>SassのコンパイルをDart Sassに乗り換えました。</li> <li>importamap-railsとdartsass-railsはRails 6.0以上で利用できるので最新のRailsではなくても利用できます。</li> </ul> <h2>レガシーなRails製管理画面とは</h2> <p>この記事ではレガシーなRails製管理画面は以下の構成を指します。</p> <ul> <li>Ruby on Rails 6.0以上</li> <li>Bootstrap 3以上</li> <li>jQuery 2以上</li> </ul> <p>2022年の技術ブログとは思えないライブラリが並んでいますが、これをお読みの皆さんのチームにも年代物の管理画面が1つや2つあることでしょう。<a href="#f-3c737c08" name="fn-3c737c08" title="業務上必要ではあるけど改修の優先度は低いのでモダンなフロントエンドスタックで更新するほどではないところがポイントです。">*1</a></p> <p>当時は管理画面を作ろうとした場合にBootstrap + jQueryが鉄板の組み合わせで、LCLも漏れなく該当していました。 レガシーと呼ぶ割にはRailsのバージョンが少し高いような気がしますが、5.2以前は既にEOLのため6.0以上としています。(伏線)</p> <h2>サードパーティのJaveScriptライブラリの読み込みをimportmap-railsに乗り換える</h2> <p>現在、RailsでサードパーティのJaveScriptライブラリを読み込もうとした場合、主に次のパターンが考えられます。</p> <ol> <li><code>vendor/</code>以下にライブラリ本体を置いてアセットパイプラインで読み込む</li> <li><a href="https://github.com/rails/jquery-rails">jquery-rails</a>、<a href="https://github.com/twbs/bootstrap-rubygem">bootstrap</a>等Gemを使う</li> <li>npm、yarnで管理して<code>node_modules/</code>をアセットパイプラインで読み込む</li> <li>Webpacker、<a href="https://github.com/shakacode/shakapacker">Shakapacker</a>でバンドルする</li> <li><a href="https://github.com/rails/importmap-rails">importmap-rails</a>で管理する</li> <li><a href="https://github.com/rails/jsbundling-rails">jsbundling-rails</a>でバンドルする</li> </ol> <p>今回の更新では2から5に移行しました。</p> <p>importmap-railsはRails 7で紹介されている新しいJavaScriptの管理方法ですが、実はRails 6.0以上で利用できます。 npmを使わずにライブラリをバージョン管理できることが大きな特徴で、すでにWeb上に多くの解説記事がでているので詳細はそちらを参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Fimportmap-rails" title="GitHub - rails/importmap-rails: Use ESM with importmap to manage modern JavaScript in Rails without transpiling or bundling." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/importmap-rails">github.com</a></cite></p> <p>jQueryとBootstrapで必要なJavaScriptを読み込むだけであれば、それぞれGemが提供されているためimportmap-railsを使う必要性は低いですが、 jQueryプラグインやちょっとしたライブラリを足したい場合に管理方法が分散してしまうのを防ぐため採用しました。</p> <p>導入はREADMEの通りに行うだけで簡単です。Gemfileからjquery-railsやbootstrapやtherubyracerやmini_racerを削除して、必要に応じて<code>app/javascript/application.js</code>を調整します。</p> <pre class="code" data-lang="" data-unlink>bundle add importmap-rails bundle exec rails importmap:install bin/importmap pin boostrap@4 # jQuery3はBootstrapの依存で追加される</pre> <p>terserやuglifierを利用している場合も、execjsが必要になってしまうのでコメントアウトしましょう。 管理画面程度あればJavaScriptを圧縮する必要性はないと判断しています。</p> <pre class="code" data-lang="" data-unlink># config/environments/production.rb # 前略 # 以下をコメントアウトか行を削除 # config.assets.js_compressor = :terser # config.assets.js_compressor = :uglifier</pre> <h3>importmap-railsで気になるところ</h3> <p>お手軽に利用できるimportmap-railsですが、デメリットとしてDependabotなどの自動更新ツールに対応していない点が挙げられます。 outdatedやauditコマンドが存在するのでCIで定期実行してうまく調整することもできますが、rubygemsやnpmよりも手間がかかってしまいます。 <code>config/importmap.rb</code>で読み込むライブラリと内容を揃えた<code>package.json</code>を用意して更新の検知だけ行う方法もありますが一長一短です。<a href="#f-0c333322" name="fn-0c333322" title="社内からのみの利用であれば依存ライブラリのこまめな更新は無視したくなりますが、サイバー攻撃による脅威はどこからくるか分からない昨今の事情を鑑みると用心することに越したことはありません。">*2</a></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Fimportmap-rails%2Fpull%2F109" title="Add outdated and audit commands by cover · Pull Request #109 · rails/importmap-rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/importmap-rails/pull/109">github.com</a></cite></p> <h2>Sassのコンパイルをdartsass-railsに乗り換える</h2> <h3>Sassコンパイラの変遷</h3> <p>dartsass-railsの前にSassコンパイラについて大雑把におさらいします。</p> <p>CSSのスーパーセットであるSassはコンパイルしてCSSにする必要があります。 最初のSassコンパイラはRuby実装(<a href="https://github.com/sass/ruby-sass">Ruby Sass</a>)でしたが、パフォーマンスや互換性の改善されたC++実装(<a href="https://github.com/sass/libsass">LibSass</a>)に置き換わっていきました。 現在は、さらに改善を試みたDart実装の<a href="https://github.com/sass/dart-sass">Dart Sass</a>が登場して公式の推奨はこちらになっています。新機能の追加や非推奨な記法の廃止はDart Sassのみになるため今後の選択肢としてもDart Sass 1択です。</p> <p>そのような経緯によるかはわかりませんが、同じ名前のsassというパッケージがnpmとrubygemsで全く異なるのでややこしいことになっています。</p> <table> <thead> <tr> <th>実装</th> <th style="text-align:center;">npm</th> <th style="text-align:center;">rubygems</th> </tr> </thead> <tbody> <tr> <td>Ruby Sass</td> <td style="text-align:center;">-</td> <td style="text-align:center;"><a href="https://rubygems.org/gems/sass">sass</a></td> </tr> <tr> <td>LibSass</td> <td style="text-align:center;"><a href="https://www.npmjs.com/package/node-sass">node-sass</a></td> <td style="text-align:center;"><a href="https://rubygems.org/gems/sassc">sassc</a></td> </tr> <tr> <td>Dart Sass</td> <td style="text-align:center;"><a href="https://www.npmjs.com/package/sass">sass</a></td> <td style="text-align:center;">-</td> </tr> </tbody> </table> <h3>サードパーティのCSSライブラリの読み込みをdartsass-railsに乗り換える</h3> <p>RailsでSassをコンパイルする場合もDart Sassに置き換える必要があります。ここで問題になるのがDart Sassのインストールです。OSのパッケージマネージャーでインストールできるので導入は容易ですが、Railsの明示的な依存として宣言する場合はnpmでインストールする必要がありました。これではImport mapsでnpmを捨てることができたのに嬉しくありません。</p> <p>そこで、RailsチームはDart Sassをラップしてアセットパイプラインに組み込むdartsass-railsをリリースしたと予想しています。こちらもRails 7前後のフロントエンドツールチェイン刷新に伴っての登場ですが、依存はRails 6.0以上なので最新のRailsでなくても利用できます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Fdartsass-rails" title="GitHub - rails/dartsass-rails: Integrate Dart Sass with the asset pipeline in Rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/dartsass-rails">github.com</a></cite></p> <p>dartsass-railsの導入もimportmap-railsと同様に、READMEの通りに実行すれば難しいことはありませんでした。Gemfileからsass-railsとsasscの削除も忘れないようにしましょう。</p> <pre class="code" data-lang="" data-unlink>bundle add dartsass-rails bundle exec rails dartsass:install</pre> <p>importmap-railsと異なり、CSSライブラリのバージョン管理まで実施してくれないため必要なライブラリは<code>vendor/assets</code>以下にダウンロードする必要があります。Railsの設定によりますが、必要に応じて<code>assets.rb</code>でアセットパイプラインのパスに加えます。</p> <pre class="code" data-lang="" data-unlink># このディレクトリ構成の場合 % tree -d vendor/assets/ vendor/assets/ ├── bootstrap │   ├── mixins │   ├── utilities │   └── vendor └── font-awesome    ├── fonts    └── scss</pre> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># config/initializers/assets.rb</span> <span class="synComment"># 前略</span> <span class="synType">Rails</span>.application.config.assets.paths &lt;&lt; <span class="synType">Rails</span>.root.join(<span class="synSpecial">'</span><span class="synConstant">vendor</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">assets</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">bootstrap</span><span class="synSpecial">'</span>) <span class="synType">Rails</span>.application.config.assets.paths &lt;&lt; <span class="synType">Rails</span>.root.join(<span class="synSpecial">'</span><span class="synConstant">vendor</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">assets</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">font-awesome</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">scss</span><span class="synSpecial">'</span>) <span class="synType">Rails</span>.application.config.assets.paths &lt;&lt; <span class="synType">Rails</span>.root.join(<span class="synSpecial">'</span><span class="synConstant">vendor</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">assets</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">font-awesome</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">fonts</span><span class="synSpecial">'</span>) </pre> <p>Import mapsやnpmと異なりCSSライブラリを手作業で管理しなければいけないのはかなりイケてないのですが、DHHもそう答えているので頑なにnpmを避ける場合は妥協します。ここはレガシーな管理画面程度あれば問題にならないとして許容しました。<a href="#f-ee9227d3" name="fn-ee9227d3" title="Tailwind CSSのみTailwond CSSのCLIをラッパーしたtailwindcss-railsがあるので回避できます。bootstrap Gemのように既存のGemとして提供されているCSSライブラリはsassc依存なことが多くDart Sassと相性が悪いことが多いです。">*3</a></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Fimportmap-rails%2Fissues%2F80%23issuecomment-985563223" title="What is the recommended way to import css bundled in js libraries · Issue #80 · rails/importmap-rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/importmap-rails/issues/80#issuecomment-985563223">github.com</a></cite></p> <h3>Dockerでファイル変更を検知する</h3> <p>READMEでは<code>rails dartsass:install</code>すると以下のProcfile.devが作成されるのでforemanで起動するように記載されていますが、LCLでは開発環境のデフォルトはDockerなのでdocker composeでSassをコンパイルできるようにします。</p> <pre class="code" data-lang="" data-unlink># Procfile.dev web: bin/rails server -p 8080 css: bin/rails dartsass:watch</pre> <p>細部はプロジェクトごとに異なると思いますが、リポジトリルートをバインドマウントして<code>dartsass:watch</code>すればOKです。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synComment"># docker-compose.yml</span> <span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">'3.8'</span><span class="synComment"> # 現在はcompose specificationで記述しても良い</span> <span class="synIdentifier">services</span><span class="synSpecial">:</span> <span class="synComment"># 中略</span> <span class="synIdentifier">sass</span><span class="synSpecial">:</span> <span class="synIdentifier">volumes</span><span class="synSpecial">:</span> <span class="synStatement">- </span>.:/ <span class="synIdentifier">restart</span><span class="synSpecial">:</span> always <span class="synIdentifier">command</span><span class="synSpecial">:</span> bundle exec rails dartsass:watch </pre> <h3>Font Awesomeのvariablesを上書き</h3> <p>Bootstrapが依存するFont Awesomeも利用する場合、Sassの中で指定するフォントのパスをアセットパイプラインに含めるために<code>$fa-font-path</code>を変更する必要があります。こちらの対応はDart Sassから利用できる<code>@use...with</code>の記法で解決できました。</p> <pre class="code lang-sass" data-lang="sass" data-unlink># app/assets/stylesheets/application<span class="synSpecial">.</span><span class="synType">scss</span> # 前略 @use <span class="synConstant">&quot;font-awesome&quot;</span> with ( <span class="synIdentifier">$fa-font-path</span>: <span class="synConstant">&quot;.&quot;</span> ); </pre> <h2>Propshaftについて</h2> <p>importmap-railsとdartsass-railsを使うことでSprocketsの仕事はアセットパスの解決とダイジェストハッシュの付与のみになります。Rails 7からSprocketsの精神的後継と位置付けられているPropshaftはまさにそのために作られているため採用を検討しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Fpropshaft" title="GitHub - rails/propshaft: Deliver assets for Rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/propshaft">github.com</a></cite></p> <p>結論から言うとPropshaftは前述のGemとは異なりRails 7以上でないとインストールできないため利用できませんでした。Rails 6.1までは<code>require 'rails/all'</code>はsprockets-railsを含み、Rails 7からは依存から外れたのが理由と考えています。</p> <p><a href="https://github.com/rails/rails/pull/43261">Make Sprockets more optional, offer Propshaft as alternative by dhh &middot; Pull Request #43261 &middot; rails/rails &middot; GitHub</a></p> <p><a href="https://edgeguides.rubyonrails.org/7_0_release_notes.html#railties-notable-changes">Ruby on Rails 7.0 Release Notes &mdash; Ruby on Rails Guides</a></p> <h2>まとめ</h2> <p>BootstrapとjQueryで構築されたレガシーなRails管理画面のフロントエンドツールチェインを、Import mapsとDart Sassに移行することで、npmをなくすことができました。</p> <p>npmをなくすことで開発環境やデプロイの構築が簡略化され、たまに発生する保守作業の開発者体験の向上が期待できます。</p> <h2>参考資料</h2> <ul> <li><a href="https://sg.wantedly.com/companies/wantedly/post_articles/354873">Rails 7.0&#x3067;&#x30A2;&#x30BB;&#x30C3;&#x30C8;&#x30D1;&#x30A4;&#x30D7;&#x30E9;&#x30A4;&#x30F3;&#x306F;&#x3069;&#x3046;&#x5909;&#x308F;&#x308B;&#x304B; | Wantedly Engineer Blog</a></li> <li><a href="https://devlog.atlas.jp/2021/09/14/3974">さようならLibSass、こんにちはDart Sass。 | Atlas Developers Blog</a></li> <li><a href="https://techracho.bpsinc.jp/hachi8833/2021_10_07/112183">Rails 7: importmap-rails gem README&#xFF08;&#x7FFB;&#x8A33;&#xFF09;&#xFF5C;TechRacho by BPS&#x682A;&#x5F0F;&#x4F1A;&#x793E;</a></li> <li><a href="https://techracho.bpsinc.jp/hachi8833/2022_03_02/116014">Rails 7: dartsass-rails gem&#x306F;Node.js&#x306A;&#x3057;&#x3067;&#x4F7F;&#x3048;&#x308B;&#xFF5C;TechRacho by BPS&#x682A;&#x5F0F;&#x4F1A;&#x793E;</a></li> </ul> <div class="footnote"> <p class="footnote"><a href="#fn-3c737c08" name="f-3c737c08" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">業務上必要ではあるけど改修の優先度は低いのでモダンなフロントエンドスタックで更新するほどではないところがポイントです。</span></p> <p class="footnote"><a href="#fn-0c333322" name="f-0c333322" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">社内からのみの利用であれば依存ライブラリのこまめな更新は無視したくなりますが、サイバー攻撃による脅威はどこからくるか分からない昨今の事情を鑑みると用心することに越したことはありません。</span></p> <p class="footnote"><a href="#fn-ee9227d3" name="f-ee9227d3" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">Tailwind CSSのみTailwond CSSのCLIをラッパーした<a href="https://github.com/rails/tailwindcss-rails">tailwindcss-rails</a>があるので回避できます。bootstrap Gemのように既存のGemとして提供されているCSSライブラリはsassc依存なことが多くDart Sassと相性が悪いことが多いです。</span></p> </div> hosht Udemy Businessによるスキルアップ hatenablog://entry/4207112889892677736 2022-06-29T07:43:57+09:00 2022-07-07T07:13:52+09:00 イントロダクション LCLエンジニアチームの杉山です。この2年間は新型コロナの影響もあり、会社として粛々とサービスの基礎体力を強化する事に力を入れてきました。 やっと、社会状況も戻ってきて、これからLCLは更なる成長に向けて歩んで行く事になると思っています。🏃 同時に会社の成長に伴う既存サイトの複雑さという、多くのネット企業でも抱えているだろう技術的負債の解消や属人性の解消に向けての対応も引き続き必要になっています。 フロントエンド、バックエンド、データマネージメントチームそれぞれが、リファクターや自動化に取り組み少しずつ改善して来ている部分も多々ありますが、 今後、更に全体で生産性を上げ、ア… <h2>イントロダクション</h2> <p>LCLエンジニアチームの杉山です。この2年間は新型コロナの影響もあり、会社として粛々とサービスの基礎体力を強化する事に力を入れてきました。 やっと、社会状況も戻ってきて、これからLCLは更なる成長に向けて歩んで行く事になると思っています。🏃 同時に会社の成長に伴う既存サイトの複雑さという、多くのネット企業でも抱えているだろう技術的負債の解消や属人性の解消に向けての対応も引き続き必要になっています。 フロントエンド、バックエンド、データマネージメントチームそれぞれが、リファクターや自動化に取り組み少しずつ改善して来ている部分も多々ありますが、 今後、更に全体で生産性を上げ、アイデアを出し易くする為に、既存社員のスキルアップにも力を入れて行く必要があると考えています。 そんな課題に対する取り組みの一環として、今月から<a href="https://ufb.benesse.co.jp/">Udemy Business</a>を導入し、エンジニアがスキルアップ出来る環境を少しだけ改善する事が出来ました。 勿論、導入して終わりでは無いので、これから効率良く運用出来るかどうか試行錯誤する必要はあります。</p> <table> <th>LCLのこれまでの歩み</th> <tr> <td> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20220624/20220624074548.png" width="1019" height="612" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </td> </tr> </table> <h2>Udemy Businessについて</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fufb.benesse.co.jp%2Fplan%2F" title="サービスの特徴 | Udemy Business" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://ufb.benesse.co.jp/plan/">ufb.benesse.co.jp</a></cite></p> <p>Udemyに関しては、個人的にも数年前から利用していて、 ハンズオン含めて体系的に勉強出来るので<U>エンジニアとしての勉強</U>には便利だと考えていた事もあり、 全部で4サービス程比較したうえで、内容(レベル、実践性、ハンズオン、汎用性)、コスト、コンテンツ数、コスト、対応言語の観点から5段階評価して選択しました。 勿論、既に知見がある基本的な内容のコースもあったり、高レベルの英語版のコースがあったりと様々ですが、コースを受講する本人が確認しながら選択し受講する事も出来ますし、 学び放題のコースなので、コストも気にせず知見が既にあるコースだった場合はアーカイブしてしまう事も可能です。👌</p> <pre class="code ポイント" data-lang="ポイント" data-unlink>定額制学び放題サービス 世界中䛾18.3万を超える講座から厳選した6,000講座が年間学び放題(英語・日本語) 英語のコンテンツは英語の字幕が付いているものもあるのでOK コンテンツは毎月追加されるので陳腐化しない エンジニア向けのコンテンツが豊富(エンジニア以外には他のサービスが良いケースも有)</pre> <h2>導入目的と目標設定</h2> <p>リモートワークが日常的になっている社会状況下でも、柔軟に学習する事が可能なので継続的なスキルアップが可能。 年間契約なので、稟議が1回/年承認されれば、追加の申請無しでカジュアルに勉強する事が可能なのはエンジニア的には有難いと考えています。</p> <table> <thead> <tr> <th> 導入前提条件 </th> </tr> </thead> <tbody> <tr> <td> ❶ バックエンドはフロントエンドで利用されている Next.js等の基本的なスキルを身に付ける。</td> </tr> <tr> <td> ❷ フロントエンドはバックエンドで利用されている Rails等の基本的なスキルを身に付ける。</td> </tr> <tr> <td> ❸ インフラ,セキュリティ,SEO関連のコースを基本コースとして選択し全員に受講して貰う。</td> </tr> </tbody> </table> <table> <thead> <tr> <th style="text-align:center;"> LCLで利用している技術を、全員が理解して使える様になる</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20220624/20220624083606.png" width="705" height="200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> </tbody> </table> <p>福利厚生の一環として、会社全体でこの様なサービス導入を取り組む企業も多いと思いますが、 今回は技術開発部の「属人性の解消」を主目的として説明し承認をして頂きました。<br> 「 ≒ 導入して終わりでは無く、学習を通じ、属人性解消の足掛かりにしてサービスと個人の成長に紐付ける事が必要。」</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20220624/20220624083826.png" width="1199" height="568" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3>基本ルールとKPI</h3> <pre class="code" data-lang="" data-unlink>業務時間内で受講する場合䛿、~8時間/月程度に収まる様に調整する。 業務終了後、週末等にプライベートの時間に趣味で勉強する場合は自由(マーケや経営等も学べる)</pre> <table> <thead> <tr> <th> KPI </th> <th> 目標 </th> </tr> </thead> <tbody> <tr> <td>定性目標</td> <td> バックエンド↔フロントエンドがお互いに業務サポート<br>それぞれのチームが主担当領域以外のIssue対応を実施可能な状態</td> </tr> <tr> <td>定量目標 </td> <td> Issue消化率の改善 <br>(200 Issue/年であれば, 初年度5%のIssueは異なったチームが対応)</td> </tr> <tr> <td>レビュー</td> <td> 2023年5月の段階で効果測定<br>効果があれば継続し・効果が見えなければ契約更新は要検討</td> </tr> </tbody> </table> <table> <thead> <tr> <th>#</th> <th> その他のメリット</th> </tr> </thead> <tbody> <tr> <td>1</td> <td> エンジニアがSEO含む様々なスキルを体系的に整理出来る為、<br>対応時にエンジニアからの指摘や提案も増える。</td> </tr> <tr> <td>2</td> <td> UXやUI等にも知見が増える事によってサービスへの提案の幅が広がる。</td> </tr> <tr> <td>3</td> <td> セキュリティ等に対しての意識や理解が深まり、<br>結果としてLCLのサービス全体のセキュリティレベルが向上する。</td> </tr> <tr> <td>4</td> <td> 技術開発部全員がクラウドインフラに関する基本的な知識を習得し理解する。</td> </tr> <tr> <td>5</td> <td> 基本的なマーケティング等の知識を得る事で、<br>提案時に他部署と相互理解し易くなりコンセンサスが取りやすくなる。</td> </tr> </tbody> </table> <p>「LCLでは各サービスにエンジニアが専任として参加してくれているので、ビジネス・マーケの知識があるとより同じ目線でコミュニケーション取る事が出来、ディスカッションの活性化につながると思っています」👌</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20220624/20220624130540.png" width="1200" height="306" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2>現状</h2> <p>今月導入したばかりなので、まだ学習開始出来ていないメンバーもいますが、 自分は他部署のマネージメントメンバーとコミュニケーションを取る事が多いので、 マーケティング、SEO周りを軽く受けてみましたが、手を動かしながら座学が出来るので非常に重宝しています。 勿論既に知っている事も多いですが、細かな部分は「あ~そうか」と気付かされる事も多いです。 贅沢に、ラジオ代わりに仕事をしながら、聞き流して気になる所だけ聞き入る事も多いです。 「塵積って山となる」という事なので、少しずつでも100時間/年くらい勉強すれば、来年の今頃には知識も相当増えているかと思っています。💪</p> <p>インフラやセキュリティ周りに関しても軽く見てみましたが、普段使わないオプション等も知る事が出来ました。 良さそうなコースがあれば、「Slackで共有」機能を利用してお勧めして行きたいと思います。 UXまわりもユーザー向けにサービスを提供している会社としては、重要な知識なので少しずつ知見を溜めてチーム内でも共有してサービスに活かして行ければと思っています。</p> <p>現状は、学習目標を設定する為のお勧めコンテンツを、以下の様にUdemyの講座、外部の記事リンクなどを組み合わせて、 ラーニングパス内にお勧めコース(一部必須)のドラフトを検討段階ですが、上期中には、お勧めコースを取りまとめて、 下期には少しずつ実績を積み上げて行ければ良いかなと考えています。</p> <table> <thead> <tr> <th> ラーニングパス </th> </tr> </thead> <tbody> <tr> <td><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20220627/20220627172627.png" width="933" height="705" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> </tbody> </table> <table> <thead> <tr> <th> ラーニングパスには講座以外にも外部チュートリアルも登録する事が可能 </th> </tr> </thead> <tbody> <tr> <td><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20220628/20220628113829.png" width="687" height="87" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> <tr> <td><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20220627/20220627173405.png" width="735" height="96" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> </tbody> </table> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fufb.benesse.co.jp%2Fplan%2F" title="サービスの特徴 | Udemy Business" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://ufb.benesse.co.jp/plan/">ufb.benesse.co.jp</a></cite></p> <h2>サマリー</h2> <p>自律的な学びがあるエンジニアは、今後も自然と色々な場面で活躍して行く事がイメージ出来ます。 自分の市場価値を上げると同時に、会社のサービスも「学習」と「実務」で得た知見を活かして更に伸ばして行ければ、 会社にとっても、本人のキャリアにとっても建設的で健全な状況なのかなと思います。</p> <p>「自分が大学のVAX Systemに接続してCをコンパイルしていた、1990年代はインタネットが自分の住んでいたアーカンソー州でもダイヤルアップの時代でした。 それから早30年弱経ってしまいましたが、この30年でも大きく技術が変わっているので、今後も一定の周期で大きく変化して行くのだと思います。 エンジニアとして生きていくには、継続的に学んで変化し続ける必要がありますね。」面白いですが、大変なので1つでも自分の得意な技術を深堀するのも良いかもしれません。</p> <p><code>"Good Luck" and "Let's keep Studying !!"</code></p> <table> <thead> <tr> <th style="text-align:center;">初めてCを習った時はこんな感じ。懐かしい!!</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20220628/20220628122703.png" width="590" height="788" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> </tbody> </table> <h2>採用情報</h2> <p>LCLではエンジニアのアイデアを生かして、更にサービスを盛り上げて行こうと思ってます!! 引き続き、生産性向上に励む仲間を募集中です。</p> <p>2022年6月現在: データマネージメントエンジニア募集中</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> oss-rdbms バス便データチームの2021年振り返り hatenablog://entry/13574176438045730870 2021-12-25T00:00:00+09:00 2021-12-25T00:00:01+09:00 この記事はLCL Advent Calendar 2021 - 25日目です。 https://qiita.com/advent-calendar/2021/lcl バックエンド兼、バス便データを管理をしている高橋です。 2021年においてバス便データチームのこの1年を振り返ってみます。 バス便データチームについて バス便データチームについては以下のLCL Advent Calendar 2020にて説明しているので割愛させていただきます。 techblog.lclco.com この2021年を振り返って 今年も新型コロナウイルスの影響でバス便の運休や減便が多くその対応に追われた一年でした。 … <p>この記事はLCL Advent Calendar 2021 - 25日目です。</p> <p><a href="https://qiita.com/advent-calendar/2021/lcl">https://qiita.com/advent-calendar/2021/lcl</a></p> <p>バックエンド兼、バス便データを管理をしている高橋です。</p> <p>2021年においてバス便データチームのこの1年を振り返ってみます。</p> <h2>バス便データチームについて</h2> <p>バス便データチームについては以下の<code>LCL Advent Calendar 2020</code>にて説明しているので割愛させていただきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2020%2F12%2F13%2F000000" title="バス停の座標登録は地道な作業で対応してます! - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2020/12/13/000000">techblog.lclco.com</a></cite></p> <h2>この2021年を振り返って</h2> <p>今年も新型コロナウイルスの影響でバス便の運休や減便が多くその対応に追われた一年でした。 10月1日に緊急事態宣言が解除されてからは運休や減便していたバス便が運行開始され少しずつバス便の運行数を増えてきました。</p> <p>そんな中でもバス比較なびではユーザー様に使いやすいサービスを提供するためバス便データチームとして以下のような対応をしてまいりました。</p> <h3>WiFi・仕切りカーテンの対応</h3> <p>2020年は「2席利用可」の対応をしましたが今年は「Wi-Fi」と「仕切りカーテン」の絞り込み検索機能を追加しました。 データ連携情報に「Wi-Fi」と「仕切りカーテン」が含まれている場合はその情報を利用し、データ連携に含まれていない場合は販売会社様・バス会社様へ連絡を行い追加していただけるかを確認しました。 APIは各社ごとにありますので新たに追加していただいた連携先についてはAPIの改修を行いました。</p> <p><figure class="figure-image figure-image-fotolife" title="WiFi・仕切りカーテン"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taro-takahashi/20211224/20211224151706.png" alt="f:id:taro-takahashi:20211224151706p:plain" width="253" height="248" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>WiFi・仕切りカーテン</figcaption></figure></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.bushikaku.net%2Fsearch%2Ftokyo_osaka%2Fbus_option-curtain-wifi%2F" title="【Wi-Fi・仕切りカーテン】東京発 ~ 大阪行きの高速バス・夜行バス予約【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.bushikaku.net/search/tokyo_osaka/bus_option-curtain-wifi/">www.bushikaku.net</a></cite></p> <h3>JRバス様のバス便を掲載開始</h3> <p>長年JRバス様とのデータ連携が出来ていませんでしたが、<br /> 今年に入り販売会社様経由ではありますが東京⇔大阪間など多くのバス便をバス比較なびに掲載していただけるようになりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.bushikaku.net%2Fsearch%2Ftokyo_osaka%2Fbus_code-10031%2F" title="西日本JRバス 東京発 ~ 大阪行きの高速バス・夜行バス予約【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.bushikaku.net/search/tokyo_osaka/bus_code-10031/">www.bushikaku.net</a></cite></p> <h3>バス停ページ</h3> <p>停車地の緯度経度の登録を長年対応してきましたが、ある程度精査できましたのでバス停ページをリニューアルしました。 ユーザー様に使いやすいと思われる地図に半径500mの円を入れたり、そのバス停から運行されているバス便を掲載する機能を追加しました。 例)バスタ新宿</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.bushikaku.net%2Fbusstop%2F17165%2F" title="バスタ新宿のバス停地図・時刻表 | 高速バス・夜行バス予約【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.bushikaku.net/busstop/17165/">www.bushikaku.net</a></cite></p> <h3>新規データ連携</h3> <p>新型コロナウイルスの影響下でもありましたが新規にデータ連携して頂いたバス会社様が数社ありAPIの開発を行いました。</p> <h3>手入力便の自動登録</h3> <p>2020年に続き2021年も新型コロナウイルスで影響を受けたのは手入力便の登録と更新でした。 バス比較なびでは掲載しているバス便の約40%がデータ連携で自動で登録しているのに対し、約60%のバス便を手動対応しています。 昨年から検討していた手入力便の自動登録についてはヴァル研究所様が提供している「駅すぱあとWebサービス 」を導入しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.ekispert.com%2Fv1%2Fapi%2F" title="API概要" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://docs.ekispert.com/v1/api/">docs.ekispert.com</a></cite></p> <p>APIや管理画面の開発や駅すぱあとWebサービスと弊社で登録しているバス便の紐付け、またバス便の運行状況(例:平日のみ運行)などの 様々な課題がありますが少しずつ自動登録できるバス便を増やし手入力による工数削減を目指しています。</p> <h3>その他</h3> <p>新型コロナウイルスによる緊急事態宣言などによりバス便の運休や臨時便などダイヤ変更が多くありました。 また緊急事態宣言が明けてからはバス便の運行が増えてきました。 バス会社様でもバス便登録の変更が多くあり例年以上にデータ連携でのトラブル対応が多かった気がします。</p> <h2>2022年に取り組むこと</h2> <p>2022年もメインは手入力便を駅すぱあとWebサービスにて自動登録するプロジェクトにチャレンジしていきます。 特に路線によって特定日しか運行しないバス便や時間帯により停車地が変わったりするバス便など複雑なパターンの対応をクリアしていきたいと考えています。 <br /> また、バス便データチームの属人性の解消にも努めていこうと考えています。 <br /> なかなか一筋縄ではいきませんが多くの方々にご協力を頂きながら進めていきたいと思います。</p> taro-takahashi 機械学習分野で頻出のTeX記法の基本 hatenablog://entry/13574176438043003837 2021-12-23T09:00:00+09:00 2021-12-23T09:00:06+09:00 この記事はLCL Advent Calendar 2021 - 23日目です。 qiita.com Androidアプリエンジニアの高橋です。 LCLに入社してから早2年と9ヶ月になります。 入社当時はAndroidアプリに割と専念してましたが、今はバックエンド・データエンジニアリングが主な業務になってきています。 LCLでもデータ活用の機運が高まってきており、自分のキャリア含め将来を見越してデータ周りに強くなろうと機械学習・強化学習分野の勉強を少しずつしています。 機械学習の勉強を進めているとどうしても数式とじっくり向き合う必要性が出てくるのですが、テキストエディタにメモ書きするときに数式が… <p>この記事はLCL Advent Calendar 2021 - 23日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>Androidアプリエンジニアの高橋です。</p> <p>LCLに入社してから早2年と9ヶ月になります。 入社当時はAndroidアプリに割と専念してましたが、今はバックエンド・データエンジニアリングが主な業務になってきています。</p> <p>LCLでもデータ活用の機運が高まってきており、自分のキャリア含め将来を見越してデータ周りに強くなろうと機械学習・強化学習分野の勉強を少しずつしています。</p> <p>機械学習の勉強を進めているとどうしても数式とじっくり向き合う必要性が出てくるのですが、テキストエディタにメモ書きするときに数式が綺麗に書けなくていつも困っていたので、TeX記法のチートシートをブログにしたためようかと思います。(はてなブログだと見づらくない?というツッコミはなしで)</p> <p>以下、自分の偏見でよく使うと思ったものだけを選抜して載せています。つまりほぼ自分用です。(笑)</p> <p>とりあえず<strong>初歩的な記法を知りたいという人向け</strong>なので、網羅的なチートシートを求めている方はネットで探せば他にいくらでも出てくるかと思いますので、そちらを参照した方が良いかと思います。</p> <ul class="table-of-contents"> <li><a href="#前提">前提</a></li> <li><a href="#四則演算">四則演算</a></li> <li><a href="#等号不等号">等号・不等号</a></li> <li><a href="#分数">分数</a></li> <li><a href="#数式の縦揃え">数式の縦揃え</a></li> <li><a href="#括弧">括弧</a></li> <li><a href="#場合分けの式">場合分けの式</a></li> <li><a href="#冪乗">冪乗</a></li> <li><a href="#平方根">平方根</a></li> <li><a href="#ギリシャ文字">ギリシャ文字</a></li> <li><a href="#総和総乗">総和・総乗</a></li> <li><a href="#三角関数">三角関数</a></li> <li><a href="#指数関数">指数関数</a></li> <li><a href="#対数">対数</a></li> <li><a href="#確率">確率</a></li> <li><a href="#集合">集合</a></li> <li><a href="#極限微分">極限・微分</a></li> <li><a href="#線形代数行列">線形代数・行列</a></li> <li><a href="#最後に">最後に</a></li> <li><a href="#採用情報">採用情報</a></li> </ul> <h3 id="前提">前提</h3> <p>この記事自体をはてなブログで書いている関係上、はてな記法に従っています。</p> <p>具体的には数式を書くときは以下のようなブロックを使います。</p> <pre class="code lang-tex" data-lang="tex" data-unlink>&lt;div&gt; <span class="synSpecial">[</span>tex: <span class="synStatement">\displaystyle</span> 1行目 <span class="synSpecial">\\</span> 2行目 <span class="synSpecial">]</span> &lt;/div&gt; </pre> <p><code>[tex: ~]</code> の部分ははてなブログ独自の記法です。<code>\\</code> は改行ですが、はてなブログの場合<code>&lt;div&gt;&lt;/div&gt;</code> で囲まないとうまく改行できないケースがあるみたいです。</p> <p>この記事の焦点は数式の部分なので上記記法(<code>&lt;div&gt;&lt;/div&gt;</code>と<code>[tex: ~]</code> の部分)は省略して触れないことにします。</p> <h3 id="四則演算">四則演算</h3> <pre class="code lang-tex" data-lang="tex" data-unlink>1+1=2 <span class="synSpecial">\\</span> 10-1=9 <span class="synSpecial">\\</span> 2<span class="synStatement">\times</span>3=6 <span class="synSpecial">\\</span> 4<span class="synStatement">\div</span>2=2 </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A1%2B1%3D2%20%5C%5C%0A10-1%3D9%20%5C%5C%0A2%5Ctimes3%3D6%20%5C%5C%0A4%5Cdiv2%3D2%0A" alt=" \displaystyle 1+1=2 \\ 10-1=9 \\ 2\times3=6 \\ 4\div2=2 "/> </div> <h3 id="等号不等号">等号・不等号</h3> <pre class="code lang-tex" data-lang="tex" data-unlink>1=1 <span class="synSpecial">\\</span> 2&gt;1 <span class="synSpecial">\\</span> 1&lt;2 <span class="synSpecial">\\</span> a <span class="synStatement">\geq</span> b <span class="synSpecial">\\</span> b <span class="synStatement">\leq</span> a </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A1%3D1%20%5C%5C%0A2%3E1%20%5C%5C%0A1%3C2%20%5C%5C%0Aa%20%5Cgeq%20b%20%5C%5C%0Ab%20%5Cleq%20a%0A" alt=" \displaystyle 1=1 \\ 2&gt;1 \\ 1&lt;2 \\ a \geq b \\ b \leq a "/> </div> <p>プログラムの演算子みたいに <code>&gt;=</code> と書いても上記の様な記号にならないので注意です。</p> <h3 id="分数">分数</h3> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\dfrac</span><span class="synSpecial">{</span>1<span class="synSpecial">}{</span>2<span class="synSpecial">}</span>-<span class="synStatement">\dfrac</span><span class="synSpecial">{</span>1<span class="synSpecial">}{</span>3<span class="synSpecial">}</span>=<span class="synStatement">\dfrac</span><span class="synSpecial">{</span>1<span class="synSpecial">}{</span>6<span class="synSpecial">}</span> <span class="synSpecial">\\</span> <span class="synStatement">\dfrac</span><span class="synSpecial">{</span>a+b<span class="synSpecial">}{</span>2ab<span class="synSpecial">}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cdfrac%7B1%7D%7B2%7D-%5Cdfrac%7B1%7D%7B3%7D%3D%5Cdfrac%7B1%7D%7B6%7D%20%5C%5C%0A%5Cdfrac%7Ba%2Bb%7D%7B2ab%7D%0A" alt=" \displaystyle \dfrac{1}{2}-\dfrac{1}{3}=\dfrac{1}{6} \\ \dfrac{a+b}{2ab} "/> </div> <h3 id="数式の縦揃え">数式の縦揃え</h3> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\begin</span><span class="synSpecial">{align}</span> <span class="synSpecial">f(x)&amp;</span><span class="synStatement">=</span><span class="synSpecial">x</span><span class="synStatement">^2</span><span class="synSpecial">+3x+2 \\</span> <span class="synSpecial">&amp;</span><span class="synStatement">=</span><span class="synSpecial">(x+1)(x+2)</span> <span class="synStatement">\end</span><span class="synSpecial">{align}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cbegin%7Balign%7D%0Af%28x%29%26%3Dx%5E2%2B3x%2B2%20%5C%5C%0A%26%3D%28x%2B1%29%28x%2B2%29%0A%5Cend%7Balign%7D%0A" alt=" \displaystyle \begin{align} f(x)&amp;=x^2+3x+2 \\ &amp;=(x+1)(x+2) \end{align} "/> </div> <p>ポイントは、</p> <ul> <li>数式を <code>\begin{align} ~ \end{align}</code> で囲む</li> <li>イコール(=) の前に&amp;をつける</li> </ul> <h3 id="括弧">括弧</h3> <pre class="code lang-tex" data-lang="tex" data-unlink>a <span class="synStatement">\bigl</span><span class="synSpecial">\[ b(c+d) + e(f+d) </span><span class="synStatement">\bigr</span><span class="synSpecial">\]</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0Aa%20%5Cbigl%5B%20b%28c%2Bd%29%20%2B%20e%28f%2Bd%29%20%5Cbigr%5D%0A" alt=" \displaystyle a \bigl[ b(c+d) + e(f+d) \bigr] "/> </div> <p>ポイントは、</p> <ul> <li>\bigl(左括弧) や\bigr(右括弧) で直後の括弧を大きくする。</li> <li>はてな記法の場合、[]はバックスラッシュでエスケープする必要がある。</li> </ul> <h3 id="場合分けの式">場合分けの式</h3> <pre class="code lang-tex" data-lang="tex" data-unlink>f(x) = <span class="synStatement">\left</span><span class="synSpecial">\{</span> <span class="synStatement">\begin</span><span class="synSpecial">{</span><span class="synPreProc">array</span><span class="synSpecial">}{</span>ll<span class="synSpecial">}</span> <span class="synStatement">\frac</span><span class="synSpecial">{</span>1<span class="synSpecial">}{</span>x<span class="synSpecial">}</span> <span class="synSpecial">&amp;</span> (x <span class="synStatement">\geq</span> 0) <span class="synSpecial">\\</span> -x<span class="synError">^</span>2+1 <span class="synSpecial">&amp;</span> (x <span class="synStatement">\lt</span> 0) <span class="synError">\end{array}</span> <span class="synStatement">\right</span>. </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0Af%28x%29%20%3D%20%5Cleft%5C%7B%0A%5Cbegin%7Barray%7D%7Blll%7D%0A%5Cfrac%7B1%7D%7Bx%7D%20%20%20%26%20%20%20%28x%20%5Cgeq%200%29%20%20%5C%5C%0A-x%5E2%2B1%20%20%20%20%20%20%20%20%26%20%20%20%28x%20%5Clt%200%29%0A%5Cend%7Barray%7D%0A%5Cright.%0A" alt=" \displaystyle f(x) = \left\{ \begin{array}{lll} \frac{1}{x} &amp; (x \geq 0) \\ -x^2+1 &amp; (x \lt 0) \end{array} \right. "/> </div> <p>結構ややこしいですが、ポイントとしては、</p> <ul> <li>数式は <code>\begin{array}{列数文のl}</code> と <code>\end{array}</code> を使って囲む。</li> <li>{列数文のl} の「l」は左寄せを意味する。r(右寄せ)やc(中央)も指定可能。</li> <li><code>\begin</code> は <code>\left\{</code>と<code>\right.</code>を使って囲む</li> <li>数式の後に、<code>&amp; (条件式)</code> で条件式の表示が可能。</li> </ul> <h3 id="冪乗">冪乗</h3> <pre class="code lang-tex" data-lang="tex" data-unlink>a<span class="synError">^</span>2 <span class="synSpecial">\\</span> a<span class="synError">^</span><span class="synSpecial">{</span><span class="synStatement">\frac</span><span class="synSpecial">{</span>2<span class="synSpecial">}{</span>3<span class="synSpecial">}}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0Aa%5E2%20%5C%5C%0Aa%5E%7B%5Cfrac%7B2%7D%7B3%7D%7D%0A" alt=" \displaystyle a^2 \\ a^{\frac{2}{3}} "/> </div> <p>ポイントは、</p> <ul> <li>上付き文字に式が含まれている場合は {} で囲む。</li> <li>分数を入れたいときはdfracではなくfracを使うことで、上付き文字を小さくできる。</li> </ul> <h3 id="平方根">平方根</h3> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\sqrt</span><span class="synSpecial">{</span>a<span class="synError">^</span>2+b<span class="synSpecial">}</span> <span class="synSpecial">\\</span> <span class="synStatement">\sqrt</span><span class="synSpecial">\[5\]{</span>a<span class="synSpecial">}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Csqrt%7Ba%5E2%2Bb%7D%20%5C%5C%0A%5Csqrt%5B5%5D%7Ba%7D%0A" alt=" \displaystyle \sqrt{a^2+b} \\ \sqrt[5]{a} "/> </div> <p>ポイントは、</p> <ul> <li>冪根は[]で囲む。(はてな記法の場合はエスケープが必要)</li> </ul> <h3 id="ギリシャ文字">ギリシャ文字</h3> <p>機械学習・統計分野で個人的によく見かけると思ったものだけ書きます。</p> <table> <thead> <tr> <th style="text-align:left;">読み </th> <th style="text-align:left;">表記 </th> <th style="text-align:left;">TeX </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;">アルファ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Calpha" alt=" \alpha"/> </td> <td style="text-align:left;">\alpha </td> </tr> <tr> <td style="text-align:left;">ガンマ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cgamma" alt=" \gamma"/> </td> <td style="text-align:left;">\gamma </td> </tr> <tr> <td style="text-align:left;">デルタ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdelta" alt=" \delta"/> </td> <td style="text-align:left;">\delta </td> </tr> <tr> <td style="text-align:left;">デルタ(大文字) </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5CDelta" alt=" \Delta"/> </td> <td style="text-align:left;">\Delta </td> </tr> <tr> <td style="text-align:left;">エプシロン </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cvarepsilon" alt=" \varepsilon"/> </td> <td style="text-align:left;">\varepsilon </td> </tr> <tr> <td style="text-align:left;">シータ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Ctheta" alt=" \theta"/> </td> <td style="text-align:left;">\theta </td> </tr> <tr> <td style="text-align:left;">ラムダ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Clambda" alt=" \lambda"/> </td> <td style="text-align:left;">\lambda </td> </tr> <tr> <td style="text-align:left;">ミュー </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmu" alt=" \mu"/> </td> <td style="text-align:left;">\mu </td> </tr> <tr> <td style="text-align:left;">クシー </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cxi" alt=" \xi"/> </td> <td style="text-align:left;">\xi </td> </tr> <tr> <td style="text-align:left;">オミクロン</td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20o" alt=" o"/> </td> <td style="text-align:left;">o </td> </tr> <tr> <td style="text-align:left;">パイ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cpi" alt=" \pi"/> </td> <td style="text-align:left;">\pi </td> </tr> <tr> <td style="text-align:left;">パイ(大文字) </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cprod" alt=" \prod"/> </td> <td style="text-align:left;">\prod </td> </tr> <tr> <td style="text-align:left;">シグマ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Csigma" alt=" \sigma"/> </td> <td style="text-align:left;">\sigma </td> </tr> <tr> <td style="text-align:left;">シグマ(大文字) </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Csum" alt=" \sum"/> </td> <td style="text-align:left;">\sum </td> </tr> <tr> <td style="text-align:left;">ウプシロン </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cupsilon" alt=" \upsilon"/> </td> <td style="text-align:left;">\upsilon </td> </tr> <tr> <td style="text-align:left;">カイ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cchi" alt=" \chi"/> </td> <td style="text-align:left;">\chi </td> </tr> <tr> <td style="text-align:left;">オメガ </td> <td style="text-align:left;"><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Comega" alt=" \omega"/> </td> <td style="text-align:left;">\omega </td> </tr> </tbody> </table> <p>エプシロンには \epsilon (<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cepsilon" alt=" \epsilon"/>) の表記もありますが、\varepsilon (<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cvarepsilon" alt=" \varepsilon"/>) の方がよく見る気がします。</p> <p>シグマ(大文字)やパイ(大文字)にはそれぞれ、\Sigma (<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5CSigma" alt=" \Sigma"/>)、\Pi(<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5CPi" alt=" \Pi"/>)の表記もありますが、総和・総乗の式を書く場合には \sum (<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Csum" alt=" \sum"/>)、\prod (<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cprod" alt=" \prod"/>)が多いかと思います。</p> <h3 id="総和総乗">総和・総乗</h3> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\sum</span><span class="synError">_</span><span class="synSpecial">{</span>k=1<span class="synSpecial">}</span><span class="synError">^</span><span class="synSpecial">{</span>n<span class="synSpecial">}</span>k=1+2+3+<span class="synStatement">\cdots</span>+n=<span class="synStatement">\dfrac</span><span class="synSpecial">{</span>n(n+1)<span class="synSpecial">}{</span>2<span class="synSpecial">}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Csum_%7Bk%3D1%7D%5E%7Bn%7Dk%3D1%2B2%2B3%2B%5Ccdots%2Bn%3D%5Cdfrac%7Bn%28n%2B1%29%7D%7B2%7D%0A" alt=" \displaystyle \sum_{k=1}^{n}k=1+2+3+\cdots+n=\dfrac{n(n+1)}{2} "/> </div> <p>シグマの下付き文字は <code>_{下付き文字}</code> で表現できます。</p> <p>上付き文字は冪乗のところで説明したものと同じです。</p> <p>「・・・」は \cdotsで表現できます。</p> <p>同様に総乗は以下のようになります。</p> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\prod</span><span class="synError">_</span><span class="synSpecial">{</span>k=1<span class="synSpecial">}</span><span class="synError">^</span><span class="synSpecial">{</span>n<span class="synSpecial">}</span> a<span class="synError">_</span>k = a<span class="synError">_</span>1 <span class="synStatement">\times</span> a<span class="synError">_</span>2 <span class="synStatement">\times</span> a<span class="synError">_</span>3 <span class="synStatement">\times</span> <span class="synStatement">\cdots</span> <span class="synStatement">\times</span> a<span class="synError">_</span>n </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cprod_%7Bk%3D1%7D%5E%7Bn%7D%20a_k%20%3D%20a_1%20%5Ctimes%20a_2%20%5Ctimes%20a_3%20%5Ctimes%20%5Ccdots%20%5Ctimes%20a_n%0A" alt=" \displaystyle \prod_{k=1}^{n} a_k = a_1 \times a_2 \times a_3 \times \cdots \times a_n "/> </div> <h3 id="三角関数">三角関数</h3> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\sin\theta</span> <span class="synSpecial">\\</span> <span class="synStatement">\cos\theta</span> <span class="synSpecial">\\</span> <span class="synStatement">\tan\theta</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Csin%5Ctheta%20%5C%5C%0A%5Ccos%5Ctheta%20%5C%5C%0A%5Ctan%5Ctheta%0A" alt=" \displaystyle \sin\theta \\ \cos\theta \\ \tan\theta "/> </div> <h3 id="指数関数">指数関数</h3> <pre class="code lang-tex" data-lang="tex" data-unlink>e<span class="synError">^</span><span class="synStatement">\pi</span> <span class="synSpecial">\\</span> <span class="synStatement">\exp</span>(x) </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0Ae%5E%5Cpi%20%5C%5C%0A%5Cexp%28x%29%0A" alt=" \displaystyle e^\pi \\ \exp(x) "/> </div> <h3 id="対数">対数</h3> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\log</span><span class="synError">_</span>a <span class="synStatement">\frac</span><span class="synSpecial">{</span>x<span class="synSpecial">}{</span>y<span class="synSpecial">}</span> = <span class="synStatement">\log</span><span class="synError">_</span>a x - <span class="synStatement">\log</span><span class="synError">_</span>a y </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Clog_a%20%5Cfrac%7Bx%7D%7By%7D%20%3D%20%5Clog_a%20x%20-%20%5Clog_a%20y%0A" alt=" \displaystyle \log_a \frac{x}{y} = \log_a x - \log_a y "/> </div> <h3 id="確率">確率</h3> <p>特に専用の記法はないですが一応例を挙げておきます。</p> <pre class="code lang-tex" data-lang="tex" data-unlink>P(B|A)=<span class="synStatement">\dfrac</span><span class="synSpecial">{</span>P(A|B)P(B)<span class="synSpecial">}{</span>P(A)<span class="synSpecial">}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AP%28B%7CA%29%3D%5Cdfrac%7BP%28A%7CB%29P%28B%29%7D%7BP%28A%29%7D%0A" alt=" \displaystyle P(B|A)=\dfrac{P(A|B)P(B)}{P(A)} "/> </div> <pre class="code lang-tex" data-lang="tex" data-unlink>E<span class="synSpecial">\[X\]</span>=<span class="synStatement">\sum</span><span class="synError">_</span><span class="synSpecial">{</span>i<span class="synSpecial">}</span>p<span class="synError">_</span><span class="synSpecial">{</span>i<span class="synSpecial">}</span>x<span class="synError">_</span><span class="synSpecial">{</span>i<span class="synSpecial">}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AE%5BX%5D%3D%5Csum_%7Bi%7Dp_%7Bi%7Dx_%7Bi%7D%0A" alt=" \displaystyle E[X]=\sum_{i}p_{i}x_{i} "/> </div> <h3 id="集合">集合</h3> <pre class="code lang-tex" data-lang="tex" data-unlink>a <span class="synStatement">\in</span> A <span class="synSpecial">\\</span> A <span class="synStatement">\subset</span> B <span class="synSpecial">\\</span> A <span class="synStatement">\subseteq</span> B </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0Aa%20%5Cin%20A%20%5C%5C%0AA%20%5Csubset%20B%20%5C%5C%0AA%20%5Csubseteq%20B%0A" alt=" \displaystyle a \in A \\ A \subset B \\ A \subseteq B "/> </div> <h3 id="極限微分">極限・微分</h3> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\lim</span><span class="synError">_</span><span class="synSpecial">{</span>n <span class="synStatement">\to</span> <span class="synStatement">\infty</span><span class="synSpecial">}</span> a<span class="synError">_</span>n <span class="synSpecial">\\</span> <span class="synStatement">\dfrac</span><span class="synSpecial">{</span>d<span class="synStatement">\sin</span>(x)<span class="synSpecial">}{</span>dx<span class="synSpecial">}</span>=<span class="synStatement">\cos</span>(x) <span class="synSpecial">\\</span> <span class="synStatement">\frac</span><span class="synSpecial">{</span><span class="synStatement">\partial</span> f<span class="synSpecial">}{</span><span class="synStatement">\partial</span> x<span class="synSpecial">}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Clim_%7Bn%20%5Cto%20%5Cinfty%7D%20a_n%20%5C%5C%0A%5Cdfrac%7Bd%5Csin%28x%29%7D%7Bdx%7D%3D%5Ccos%28x%29%20%5C%5C%0A%5Cdfrac%7B%5Cpartial%20f%7D%7B%5Cpartial%20x%7D%0A" alt=" \displaystyle \lim_{n \to \infty} a_n \\ \dfrac{d\sin(x)}{dx}=\cos(x) \\ \dfrac{\partial f}{\partial x} "/> </div> <p>偏微分で使われる <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cpartial" alt=" \partial"/> は筆記体のdを様式化したものらしいです。</p> <h3 id="線形代数行列">線形代数・行列</h3> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\begin</span><span class="synSpecial">{</span><span class="synPreProc">pmatrix</span><span class="synSpecial">}</span> a <span class="synSpecial">&amp;</span> b <span class="synSpecial">\\</span> c <span class="synSpecial">&amp;</span> d <span class="synStatement">\end</span><span class="synSpecial">{</span><span class="synPreProc">pmatrix</span><span class="synSpecial">}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cbegin%7Bpmatrix%7D%0Aa%20%26%20b%20%5C%5C%0Ac%20%26%20d%20%0A%5Cend%7Bpmatrix%7D%0A" alt=" \displaystyle \begin{pmatrix} a &amp; b \\ c &amp; d \end{pmatrix} "/> </div> <p><strong>ベクトル</strong></p> <pre class="code tes" data-lang="tes" data-unlink>\boldsymbol{v} \\ \vec{v} \\ x = (x_1, x_2, \ldots, x_d) </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cboldsymbol%7Bv%7D%20%5C%5C%0A%5Cvec%7Bv%7D%20%5C%5C%0Ax%20%3D%20%28x_1%2C%20x_2%2C%20%5Cldots%2C%20x_d%29%20%0A" alt=" \displaystyle \boldsymbol{v} \\ \vec{v} \\ x = (x_1, x_2, \ldots, x_d) "/> </div> <p><strong>内積</strong></p> <pre class="code lang-tex" data-lang="tex" data-unlink><span class="synStatement">\boldsymbol</span><span class="synSpecial">{</span>A<span class="synSpecial">}</span> <span class="synStatement">\cdot</span> <span class="synStatement">\boldsymbol</span><span class="synSpecial">{</span>B<span class="synSpecial">}</span> </pre> <p>表示例.</p> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cboldsymbol%7BA%7D%20%5Ccdot%20%5Cboldsymbol%7BB%7D%0A" alt=" \displaystyle \boldsymbol{A} \cdot \boldsymbol{B} "/> </div> <h2 id="最後に">最後に</h2> <p>冒頭でも述べたようにTeX記法の初歩レベルの内容のみですが、上記を空で書けるようになればテキストエディターに最低限のメモ書きが取れるようになるかも?しれません。</p> <p>なお、個人で使っている<a href="https://github.com/inkdropapp/inkdrop-math">Inkdropのプラグイン</a>では上記の記法で綺麗に表示されていることは一応確認済みですが、他のエディタなどでは正常に描写されない可能性もあるのでご了承ください。</p> <h2 id="採用情報">採用情報</h2> <p>LCLでは開発メンバーを募集しています。(現在はフロントエンジニアを重点的に募集中)</p> <p>カジュアル面談も行っていますので、少しでも興味をお持ちでしたらご連絡下さい。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> ktx_ku 開発環境をM1 Mac対応した話 hatenablog://entry/13574176438044522444 2021-12-21T18:00:18+09:00 2021-12-21T18:00:18+09:00 この記事はLCL Advent Calendar 2021 - 21日目です。 qiita.com フロントエンドエンジニアの川辺です。 早いもので私がLCLに入社してから5年以上の月日が経ちました。 入社時に購入していただいた当時の最新のMac Book Proも今ではすっかり古くなってしまったので、9月に新しいM1 Macを購入してもらいました。 今回は弊社のメインのサイトであるバス比較なび(以降なびと呼びます)の開発環境をM1 Macに対応した際に行った作業をまとめました。 はじめに M1 Macで開発環境を構築していく進め方としては、既存の開発環境の構築方法をM1環境でも試して、発生し… <p>この記事はLCL Advent Calendar 2021 - 21日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>フロントエンドエンジニアの川辺です。</p> <p>早いもので私がLCLに入社してから5年以上の月日が経ちました。 入社時に購入していただいた当時の最新のMac Book Proも今ではすっかり古くなってしまったので、9月に新しいM1 Macを購入してもらいました。</p> <p>今回は弊社のメインのサイトであるバス比較なび(以降なびと呼びます)の開発環境をM1 Macに対応した際に行った作業をまとめました。</p> <h1>はじめに</h1> <p>M1 Macで開発環境を構築していく進め方としては、既存の開発環境の構築方法をM1環境でも試して、発生したエラーを都度潰していく感じで進めていきました。 作業をしているとwebpackの設定が古くなっていたり、現在は使われていないスクリプトがあったり、ついでに修正したくなる箇所がパラパラと発生したのですが、そこまでやってしまうと作業量がどんどん膨れ上がってしまうので、今回はできるだけM1環境で動かすための最小限の対応を心がけました。</p> <h1>node-sassをDartSassに変更</h1> <p>環境構築を進める中で最初に発生したエラーがこちらです。</p> <p>調べてみるとM1 Macではnode-sassを使うことができないようです。 <a href="https://github.com/sass/node-sass/issues/3033">https://github.com/sass/node-sass/issues/3033</a></p> <p>公式ページでもnode-sassの使用は非推奨になっていて、代わりにDart Sassを使用するようにと書かれていたのでそれに従うようにしました。 <a href="https://www.npmjs.com/package/node-sass">https://www.npmjs.com/package/node-sass</a></p> <pre class="code" data-lang="" data-unlink>Warning: LibSass and Node Sass are deprecated. While they will continue to receive maintenance releases indefinitely, there are no plans to add additional features or compatibility with any new CSS or Sass features. Projects that still use it should move onto Dart Sass. </pre> <p>具体的な作業内容としては以下になります。</p> <ol> <li>DartSassのインストール</li> <li>node-sassのアンインストール</li> <li>package.jsonのbuild:scssコマンドの書き換え</li> <li>node-sass-globbingの廃止</li> <li>css-loaderとsass-loaderのインストール</li> <li>各種エラーの対応</li> </ol> <h3>1. DartSassのインストール</h3> <p>以下のコマンドでDartSassをインストールします。</p> <pre class="code" data-lang="" data-unlink>npm install sass -D</pre> <h3>2. node-sassのアンインストール</h3> <p>以下のコマンドでnode-sassをアンインストールします。</p> <pre class="code" data-lang="" data-unlink>npm uninstall node-sass</pre> <h3>3. package.jsonのbuild:scssコマンドの書き換え</h3> <p>Sassコンパイルのコマンドをnode-sassからDartSassに置き換えました。 ※--importerは4で説明するプラグインのオプションです。 ※--source-commentsはコンパイルしたファイルに元ファイルの情報を追記してくれるオプションです。かなり便利だったのでDartSassでもこれに相当するオプションがないか探したのですが、見つかりませんでした。ただ、移行してしばらく経った現在では意外となくても開発にそこまで支障はありませんでした。</p> <pre class="code" data-lang="" data-unlink># node-sass &#34;build:scss&#34;: &#34;node-sass XXXX/sass/entry --importer node_modules/node-sass-globbing/index.js --output YYYY/stylesheets/dist --source-comments true&#34;, # DartSass &#34;build:scss&#34;: &#34;sass XXXX/sass/entry:YYYY/stylesheets/dist -I ./XXXX/sass/&#34;,</pre> <h3>4. node-sass-globbingの廃止</h3> <p>node-sass-globbingとはインポートする際に<code>@import "../../pc/atoms/**";</code>のようにディレクトリ配下のファイルを丸ごとインポートできるようにするプラグインです。 DartSassでもこれに代わるプラグインがないか探してみたのですが見つからなかったので、仕方なく各ディレクトリ配下に_index.scssを作成して対応しました。</p> <p>やり方はこちらの記事を参考にしました。 <a href="https://haniwaman.com/dart-sass/#index_id6">https://haniwaman.com/dart-sass/#index_id6</a></p> <h3>5. css-loaderとsass-loaderのインストール</h3> <p>node-sassからDartSassに変更したことによってコンパイル時にcss-loaderやsass-loaderが見つからないと怒られるようになりました。(逆になぜ今まで怒られずに動いていたのかわかりません。) 実際にpackage.jsonにはどちらも含まれていなかったのでインストールしましたが、その時にwebpackのバージョンの問題がりました。(なびではwebpack v4を使用しています。)</p> <h4>css-loader</h4> <p><code>css-loader v6.0.0</code>から<code>webpack v5</code>以降のサポートになったので、<code>css-loader v5.2.7</code>をインストールしました。</p> <p><a href="https://github.com/webpack-contrib/css-loader/releases/tag/v6.0.0">https://github.com/webpack-contrib/css-loader/releases/tag/v6.0.0</a></p> <h4>sass-loader</h4> <p><code>sass-loader v11.0.0</code>から<code>webpack v5</code>以降のサポートになったので、<code>sass-loader v10.2.0</code>をインストールしました。 <a href="https://github.com/webpack-contrib/sass-loader/releases/tag/v11.0.0">https://github.com/webpack-contrib/sass-loader/releases/tag/v11.0.0</a></p> <h3>6. 各種エラーの対応</h3> <p>3で設定したコマンドを実行した際に発生したエラーを、一つずつ潰していきました。 どこでどういったエラーが発生しているかはコンソールに表示されるので、地道に一つ一つ対応していきました。</p> <h3>7. @importを@useに変更</h3> <p>この対応は厳密には今回やる必要はありませんが、@importは2022年10月に廃止される予定なのでこのタイミングで@useに置き換えることにしました。</p> <p><a href="https://sass-lang.com/blog/the-module-system-is-launched#future-plans">https://sass-lang.com/blog/the-module-system-is-launched#future-plans</a></p> <pre class="code" data-lang="" data-unlink>One year after this deprecation goes into effect (1 October 2022 at latest), we will drop support for @import and most global functions entirely. This will involve a major version release for all implementations.</pre> <p>具体的なやり方としては、<code>sass-migrator</code>というツールを使用しました。 <a href="https://sass-lang.com/documentation/cli/migrator">https://sass-lang.com/documentation/cli/migrator</a></p> <p>以下のコマンドで一括置き換えします。</p> <pre class="code" data-lang="" data-unlink>npx sass-migrator --migrate-deps module frontend/src/sass/** --load-path ./frontend/src/sass/</pre> <p>上記コマンド実行後に、どこかしらのファイルでエラーが発生するので、それを個別に修正していきました。</p> <p>これでDartSassへの移行作業は完了です。</p> <h1>Node.jsのバージョンアップ</h1> <p>Node.jsのバージョンを<code>10.13.0</code>から<code>16.13.0</code>に上げました。 Node.jsはv16からM1対応になったのと、<code>16.13.0</code>からLTSになったので一気に上げてしまいました。</p> <p><a href="https://zenn.dev/ryuu/articles/node-supports-applesilicon">https://zenn.dev/ryuu/articles/node-supports-applesilicon</a> <a href="https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V16.md#16.13.1">https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V16.md#16.13.1</a></p> <p>メジャーバージョンが一気に6も上がるので、動作的な問題がないか心配でしたが、意外と何も問題なくなく上げることが出来ました。</p> <p>コードの修正は<code>.node-version</code>と<code>package.json</code>と<code>package-lock.json</code>と<code>README.md</code>くらいでした。</p> <p>それ以外に、テストサーバーやデプロイサーバーのNode.jsのバージョンを上げる必要がありました。</p> <p>デプロイサーバーでの作業は本番環境に影響するのでペアで作業を行いました。</p> <h1>gem関連のエラーの解消</h1> <p>Railsサーバーの起動時に<code>mini_racer v0.3.1</code>が原因で以下のエラーが発生しました。</p> <pre class="code" data-lang="" data-unlink>dyld: lazy symbol binding failed: Symbol not found</pre> <p>こちらのエラーは<code>mini_racer</code>のバージョンを<code>0.4.0</code>に上げたらローカル環境では動くようになったのですが、今度はテスト環境のデプロイに失敗するようになってしまいました。</p> <p>この問題は、インフラメンバーが暫定で開発環境だけlibv8-nodeのM1対応がマージされたmini_racerを参照するように変更してくれました。 <a href="https://github.com/rubyjs/mini_racer/pull/210">https://github.com/rubyjs/mini_racer/pull/210</a></p> <h1>環境構築の手順を整理</h1> <p>ここではREADME.mdや起動スクリプトの更新を行いました。</p> <p>特に書くこともないので割愛させていただきます。</p> <h1>動作確認</h1> <p>私以外のフロントチームはIntel Macを使っているので、他のメンバーのローカル環境のまっさらな状態から、なびの開発環境構築ができるかを確認してもらいました。</p> <p>また、今回サイト全体に影響する変更を行なったので、サイト全体の挙動や見た目の確認をかなりしっかりめにしました。 エンジニア内で一通り確認作業を行なった後に、ディレクターサイドでも確認をしてもらいました。</p> <h1>まとめ</h1> <p>M1 Macにしたことによって、良くも悪くも開発環境を見直す機会を得ました。 M1 Macに対応するための作業量の多さや、終わりの見えないバグの調査をしているときは大変でしたが、今思うと普段開発しているときに意識しない部分を見れたりして、個人的には良い経験ができたと思っています。 M1 Macに移行してからまだそんなに時間は経っていませんが、今のところは何の問題もなく快適に開発できています。 少しでもIntel MacからM1 Macへの移行で苦しんでいる人の役に立てれば嬉しいです。</p> <h1>採用情報</h1> <p>フロントエンドチームでは、より良いサービスを提供していくために新しい仲間を募集しています。</p> <p>カジュアルに会話できる機会もご用意できますので、もし興味がある方は気軽にご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> kavis777 日本の観光資源データを可視化してみた hatenablog://entry/13574176438042412464 2021-12-20T06:49:37+09:00 2022-07-01T15:20:59+09:00 イントロダクション LCLエンジニアチームの杉山です。自分が仕事をさせて頂いている、株式会社LCLは旅人をふやす会社をミッションの一つとして、 バス比較なび、バスツアー、格安移動、海外航空券等のサイトにて情報を掲載して、サービス利用者の皆様へ最適なツアーや交通手段を手頃な値段で探す為の、 お手伝いをさせて頂いています。現状では、情報提供のみで旅行業の登録はしておりませんが、会社では年1回の旅行業務取扱管理者の資格取得をサポートしています。 提携先には運送・旅行関係の企業も多い為、旅行業界の知識を知っておくと、旅行業法的な観点以外にも、データ連携する時に航空券・JR路線(新幹線・在来線)・船舶・… <h1>イントロダクション</h1> <p>LCLエンジニアチームの杉山です。自分が仕事をさせて頂いている、株式会社LCLは<a href="https://www.lclco.com/">旅人をふやす会社</a>をミッションの一つとして、 バス比較なび、バスツアー、格安移動、海外航空券等のサイトにて情報を掲載して、サービス利用者の皆様へ最適なツアーや交通手段を手頃な値段で探す為の、 お手伝いをさせて頂いています。現状では、情報提供のみで旅行業の登録はしておりませんが、会社では年1回の<a href="https://www.anta.or.jp/exam/shiken/setsumei.html">旅行業務取扱管理者</a>の資格取得をサポートしています。 提携先には運送・旅行関係の企業も多い為、旅行業界の知識を知っておくと、旅行業法的な観点以外にも、データ連携する時に航空券・JR路線(新幹線・在来線)・船舶・貸し切りバス等の データの意味を読み解く時にも理解し易いので、エンジニア職であっても知識として持っておいて損は無いかと思っています。</p> <p>新幹線の料金は、本州3社、JR四国、JR九州、JR北海道などに跨る場合は、改札を出ずに同一方向に乗り継いでも、会社によって特急料金を通算出来なかったり、特定の駅で乗り換えないと新幹線と在来特急を乗り継いだ場合の5割引が効かなかったり。良く利用している、東海道新幹線の「のぞみ・みずほ」で普通車自由席を利用する場合は、「ひかり・こだま」の自由席特急料金が適用されたりと、普段業務としていないと知ることが出来ない事ばかりでした。また、定められた<a href="https://www.mlit.go.jp/kankocho/shisaku/sangyou/ryokogyoho.html">基準資産と営業保証金</a>が用意出来れば、地域限定旅行業か第三種旅行も自分で起業する事も出来ますね。</p> <p><figure class="figure-image figure-image-fotolife" title="新幹線"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211213/20211213211106.png" width="438" height="167" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p> <p>国内旅行業務取扱管理者の勉強するにあたり、一番大変だったのは国内観光地・世界遺産・温泉等の観光資源を覚えるのが一番難易度が高いという印象でした。シンプルに暗記すれば良いのですが、どの都道府県にあるのか?どのような場所にあるのか?等。そこで、今回は国土交通省から提供されている観光地のデータをもとに各観光資源を地図上に可視化出来る環境を作成してみました。機能的にはまだまだ不足していますが、これから時間がある時に定期的に改良して活用頂けるような状態まで持って行ければなと考えてます。</p> <h2>環境 (WLS Ubuntu 20.04.3 LTS)</h2> <ul> <li>OS</li> </ul> <pre class="code" data-lang="" data-unlink>DESKTOP-8B:$ sudo lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal</pre> <ul> <li>Docker</li> </ul> <pre class="code" data-lang="" data-unlink>DESKTOP-8B:$ docker --version Docker version 20.10.11, build dea9396 DESKTOP-8B:$ docker-compose --version docker-compose version 1.16.1, build 6d1ac21</pre> <h3>観光地データ</h3> <p><a href="https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P12-v2_2.html">国土交通省(観光資源データ)</a></p> <p><small> 備考: 2021年夏に、「北海道・北東北の縄文遺跡群」や「奄美大島、徳之島、沖縄島北部及び西表島」等が新規に世界遺産に登録されました。 </small></p> <h3>アウトプット</h3> <table> <thead> <tr> <th style="text-align:center;">観光地リスト </th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><figure class="figure-image figure-image-fotolife" title="東京駅から±630km : Geohash:xn"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211213/20211213185853.png" width="1200" height="706" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>東京駅から±630km : Geohash:xn</figcaption></figure></td> </tr> <tr> <td style="text-align:center;">参照:Googleマップ<a href="https://www.google.co.jp/maps/?hl=ja">Google &#x30DE;&#x30C3;&#x30D7;</a></td> </tr> </tbody> </table> <h1>基本データの取り込み「PostgreSQL、MySQL」</h1> <p>先ず最初に可視化する対象となるデータを、ShapeFileからデータベースに取り込んで行きます。 Shapefileの詳細に関しては以下参照ください。</p> <blockquote><p>シェープファイル (英語: Shapefile) は、 地理情報システム(GIS)間でのデータの相互運用におけるオープン標準として用いられるファイル形式である。例えば、井戸、川、湖などの空間要素がベクター形式であるポイント、ライン、ポリゴンで示され、各要素に固有名称や温度などの任意の属性を付与できる。</p></blockquote> <p>参照:Wikiシェープファイル <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%25E3%2582%25B7%25E3%2582%25A7%25E3%2583%25BC%25E3%2583%2597%25E3%2583%2595%25E3%2582%25A1%25E3%2582%25A4%25E3%2583%25AB" title="シェープファイル - Wikipedia" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%A7%E3%83%BC%E3%83%97%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB">ja.wikipedia.org</a></cite></p> <h2>PostgreSQL (PostGIS)の場合</h2> <p><em>こちらは、LCLでも標準データベースとして利用している、オープンソースのPostgreSQLでの取り込み方法になります。</em></p> <p>❶ PostgreSQLの起動</p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/git/rdbms-docker/postgresql$ docker-compose <span class="synSpecial">-f</span> ./docker-compose-with-postgis.yml up <span class="synSpecial">-d</span> Creating network <span class="synStatement">&quot;</span><span class="synConstant">postgresql_default</span><span class="synStatement">&quot;</span> with the default driver Creating volume <span class="synStatement">&quot;</span><span class="synConstant">postgresql_postgis-store</span><span class="synStatement">&quot;</span> with default driver Creating postgresql_postgres_1 ... Creating postgresql_postgres_1 ... <span class="synError">done</span> DESKTOP-8B:~/git/rdbms-docker/postgresql$ docker-compose ps Name Command State Ports -------------------------------------------------------------------------- postgresql_postgres_1 docker-entrypoint.sh postgres Up <span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.0:5432-<span class="synStatement">&gt;</span><span class="synConstant">5432</span>/tcp,:::5432-<span class="synStatement">&gt;</span><span class="synConstant">5432</span>/tcp </pre> <p>❷ ShapeFileのダウンロード<br> <small>※ ここでは、東京の観光資源のデータのみを流し込んでいます。</small></p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/win/GIS$ unzip ~/win/GIS/P12-14_13_GML.zip Archive: /home/shinya/win/GIS/P12-14_13_GML.zip inflating: KS-META-P12_14-13.xml inflating: P12-14_13.xml inflating: P12a-14_13.dbf inflating: P12a-14_13.prj inflating: P12a-14_13.shp inflating: P12a-14_13.shx inflating: P12c-14_13.dbf inflating: P12c-14_13.prj inflating: P12c-14_13.shp inflating: P12c-14_13.shx DESKTOP-8B:~/win/GIS$ </pre> <p>❸ Host OSにpostgis関連のコマンドのみインストール<br> <small>※コンテナ側でもインストールは可能ですが、ここではシンプルな方法を選んでいます。</small></p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/git/rdbms-docker/postgresql$ sudo apt install postgis <span class="synSpecial">--no-install-recommends</span> </pre> <p>参照:<a href="https://trac.osgeo.org/postgis/wiki/UsersWikiPostGIS24UbuntuPGSQL10Apt#Installonlycommandlinetools">Install only commandline tools</a></p> <p>➍ shp2pgsqlにてShapeFileをデータベースに取り込む為のSQLに変換</p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/win/GIS$ shp2pgsql <span class="synSpecial">-s</span> <span class="synConstant">4326</span> <span class="synSpecial">-D</span> <span class="synSpecial">-i</span> <span class="synSpecial">-I</span> <span class="synSpecial">-W</span> SJIS P12a-14_13.shp <span class="synStatement">&gt;</span> kanko_point_tokyo.sql Shapefile type: Point Postgis type: POINT<span class="synStatement">[</span><span class="synConstant">2</span><span class="synStatement">]</span> DESKTOP-8B:~/win/GIS$ file kanko_point_tokyo.sql kanko_point_tokyo.sql: UTF-8 Unicode text </pre> <p>➎ 対象スキーマにてEXTENTIONを有効化</p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/win/GIS$ psql <span class="synSpecial">-h</span> <span class="synConstant">127</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">1</span> <span class="synSpecial">-p</span> <span class="synConstant">5432</span> <span class="synSpecial">-U</span> postgres POC Password <span class="synStatement">for</span> user postgres: psql <span class="synPreProc">(</span><span class="synConstant">13</span>.<span class="synConstant">5</span> <span class="synPreProc">(</span>Ubuntu <span class="synConstant">13</span>.5-2.pgdg20.<span class="synConstant">04</span>+<span class="synConstant">1</span><span class="synPreProc">)</span>, server <span class="synConstant">13</span>.<span class="synConstant">4</span> <span class="synPreProc">(</span>Debian <span class="synConstant">13</span>.4-4.pgdg110+<span class="synConstant">1</span><span class="synPreProc">))</span> Type <span class="synStatement">&quot;</span><span class="synConstant">help</span><span class="synStatement">&quot;</span> <span class="synStatement">for</span> <span class="synStatement">help</span>. <span class="synIdentifier">POC</span>=<span class="synComment"># CREATE EXTENSION IF NOT EXISTS postgis;</span> CREATE EXTENSION <span class="synIdentifier">POC</span>=<span class="synComment"># \q</span> </pre> <p>➏ 対象スキーマへデータを流し込み</p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/win/GIS$ psql <span class="synSpecial">-h</span> <span class="synConstant">127</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">1</span> <span class="synSpecial">-p</span> <span class="synConstant">5432</span> <span class="synSpecial">-U</span> postgres POC <span class="synStatement">&lt;</span> kanko_point_tokyo.sql Password <span class="synStatement">for</span> user postgres: SET SET BEGIN CREATE TABLE ALTER TABLE addgeometrycolumn ---------------------------------------------------<span class="synStatement">--</span> public.p12a-14_13.geom SRID:4326 TYPE:POINT DIMS:2 <span class="synPreProc">(</span><span class="synConstant">1</span> row<span class="synPreProc">)</span> COPY <span class="synConstant">36</span> CREATE INDEX COMMIT ANALYZE DESKTOP-8B:~/win/GIS$ </pre> <p>❼ 取り込んだデータの確認</p> <p><figure class="figure-image figure-image-fotolife" title="PostGIS"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211213/20211213220117.png" width="736" height="751" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>PostGIS</figcaption></figure></p> <h2>MySQLの場合</h2> <p><em>こちらは、個人的に最も得意なオープンソースデータベースのMySQLにおける取り込み方法になります。 MySQLでは、空間情報を扱う関数などが標準で実装されているので、特に追加のExtention等のインストールは不要ですが、 PostgreSQLと比較すると関数、ツール含めて若干不足気味ではある印象なので、ユーザーのニーズが増えて行ったら、是非拡張して行って頂ければと思います。</em></p> <p>❶ MySQLの起動</p> <p><small> ※ port 33060は Protocol Bufferベースに開発されたX Protocolのポートなので今回は利用していません。</small></p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/git/rdbms-docker/mysql$ docker-compose <span class="synSpecial">-f</span> ./docker-compose-with-volume.yml up <span class="synSpecial">-d</span> Creating network <span class="synStatement">&quot;</span><span class="synConstant">mysql_default</span><span class="synStatement">&quot;</span> with the default driver Creating volume <span class="synStatement">&quot;</span><span class="synConstant">mysql_mysql-store</span><span class="synStatement">&quot;</span> with default driver Creating mysql_db_1 ... Creating mysql_db_1 ... <span class="synError">done</span> DESKTOP-8B:~/git/rdbms-docker/mysql$ DESKTOP-8B:~/git/rdbms-docker/mysql$ docker-compose ps Name Command State Ports ---------------------------------------------------------------------- mysql_db_1 docker-entrypoint.sh mysqld Up <span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.0:3306-<span class="synStatement">&gt;</span><span class="synConstant">3306</span>/tcp,:::3306-<span class="synStatement">&gt;</span><span class="synConstant">3306</span>/tcp, <span class="synConstant">33060</span>/tcp DESKTOP-8B:~/git/rdbms-docker/mysql$ </pre> <p>❷ MySQLではデータを取り込む為の専用ツールは提供していないので<a href="https://gdal.org/">GDAL</a>を利用します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/git/rdbms-docker/mysql$ sudo apt install gdal-bin <span class="synStatement">[</span>sudo<span class="synStatement">]</span> password <span class="synStatement">for</span> shinya: Reading package lists... Done Building dependency tree Reading state information... Done The following packages were automatically installed and are no longer required: <span class="synStatement">&lt;</span>SNIP<span class="synStatement">&gt;</span> DESKTOP-8B:~/git/rdbms-docker/mysql$ ogrinfo <span class="synSpecial">--version</span> GDAL <span class="synConstant">3</span>.<span class="synConstant">0</span>.<span class="synConstant">4</span>, released <span class="synConstant">2020</span>/<span class="synConstant">01</span>/<span class="synConstant">28</span> </pre> <p>参考:<a href="https://speakerdeck.com/yoshiakiyamasaki/mysql-8-dot-0deqiang-hua-saretagisji-neng-toshi-yong-shi-li-falsegoshao-jie-a?slide=41">MySQL 8.0で強化されたGIS機能と使用事例のご紹介+α</a></p> <p>❸ ogr2ogrにてデータの変換とデータベースへのデータの取り込み</p> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/win/GIS$ ogr2ogr <span class="synSpecial">-f</span> <span class="synStatement">&quot;</span><span class="synConstant">ESRI Shapefile</span><span class="synStatement">&quot;</span> <span class="synSpecial">-lco</span> <span class="synIdentifier">ENCODING</span>=UTF-8 <span class="synSpecial">-oo</span> <span class="synIdentifier">ENCODING</span>=CP932 P12a-14_13_utf8.shp P12a-14_13.shp DESKTOP-8B:~/win/GIS$ ogr2ogr <span class="synSpecial">-f</span> <span class="synStatement">&quot;</span><span class="synConstant">MySQL</span><span class="synStatement">&quot;</span> MySQL:<span class="synStatement">&quot;</span><span class="synConstant">POC,host=127.0.0.1,user=root,password=password,port=3306</span><span class="synStatement">&quot;</span> P12a-14_13_utf8.shp </pre> <p>➍ 取り込んだデータの確認</p> <p><figure class="figure-image figure-image-fotolife" title="MySQL"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211213/20211213221958.png" width="864" height="822" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>MySQL</figcaption></figure></p> <p><em>データ(sharefile)の取り込みに関しては以上になります。</em></p> <h2>観光資源データの可視化</h2> <ul> <li>時間の都合上MySQLのみでの検証となります。<br></li> <li>東京のデータはGPS等で利用されている<a href="https://spatialreference.org/ref/epsg/4326/">SRID4326</a>か<a href="https://spatialreference.org/ref/epsg/4612/">4612</a>が設定されていたので無事に取り込めましたが,全国版のShapefileはSRIDが設定されて無い様だったので、Oracle MySQLチームの山﨑氏から紹介して頂いたshp2mysqlを使わせて頂きました。👍</small></li> </ul> <p>参照:shp2mysql(MySQL8へのインポート用SQLに変換するコマンドラインツール)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fhajime-miyauchi%2Fshp2mysql" title="GitHub - hajime-miyauchi/shp2mysql" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/hajime-miyauchi/shp2mysql">github.com</a></cite></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211215/20211215124912.png" width="526" height="60" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h5>❶ nginx, php, mysqlを構成するコンテナの起動</h5> <pre class="code lang-sh" data-lang="sh" data-unlink>DESKTOP-8B:~/git/rdbms-docker/mysql$ docker-compose ps Name Command State Ports ------------------------------ DESKTOP-8B:~/git/rdbms-docker/mysql$ docker-compose <span class="synSpecial">-f</span> ./docker-compose-with-volume-web.yml up <span class="synSpecial">-d</span> Creating network <span class="synStatement">&quot;</span><span class="synConstant">mysql_default</span><span class="synStatement">&quot;</span> with the default driver Creating mysql-container ... Creating mysql-container ... <span class="synError">done</span> Creating php-container ... Creating php-container ... <span class="synError">done</span> Creating nginx-container ... Creating nginx-container ... <span class="synError">done</span> </pre> <h5>❷ shapefileの取り込み</h5> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 1.データダウンロードと解凍</span> unzip P12-10_GML.zip <span class="synComment"># 2. shp2mysqlのダウンロード(Compile済みなので,展開してそのまま利用出来ます)</span> wget https://github.com/hajime-miyauchi/shp2mysql/releases/download/v0.<span class="synConstant">3</span>/shp2mysql-linux.tar.gz <span class="synComment"># 3.データの変換</span> ./shp2mysql-linux/shp2mysql <span class="synSpecial">-s</span> <span class="synConstant">4612</span> <span class="synSpecial">-W</span> CP932 P12-10-g_TourismResource_Point.shp <span class="synStatement">&gt;</span> P12-10-g_TourismResource_Point_convert.shp <span class="synComment"># 4.データの取り込み</span> mysql <span class="synSpecial">-h</span> <span class="synConstant">127</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">1</span> <span class="synSpecial">-u</span> root <span class="synSpecial">-p</span> POC <span class="synStatement">&lt;</span> P12-10-g_TourismResource_Point_convert.shp </pre> <h5>❸ インポートしたテーブルとデータの確認</h5> <pre class="code lang-sql" data-lang="sql" data-unlink> DESKTOP-8B$ mysql -h <span class="synConstant">127.0</span>.<span class="synConstant">0.1</span> -u root -p -e <span class="synSpecial">&quot;</span><span class="synConstant">show tables from POC</span><span class="synSpecial">&quot;</span> Enter password: +<span class="synComment">--------------------------------+</span> | Tables_in_POC | +<span class="synComment">--------------------------------+</span> | p12<span class="synConstant">-10</span>-g_tourismresource_point | +<span class="synComment">--------------------------------+</span> </pre> <table> <thead> <tr> <th style="text-align:center;">国土交通省の観光資源データ:2019件</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><figure class="figure-image figure-image-fotolife" title="ST_Geohash(ST_Y(geom),ST_X(geom),2) = &#x27;xn&#x27;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211215/20211215192728.png" width="655" height="431" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ST_Geohash(ST_Y(geom),ST_X(geom),2) = &#x27;xn&#x27;</figcaption></figure></td> </tr> </tbody> </table> <h5>➍ 表示範囲の絞り込みとGeoHash</h5> <p><em>2019件の全データを表示させると、Google Mapの表示に時間がかかったので以下の様にGeoHashを利用して範囲を絞りました。</em></p> <p><u><strong>GeoHashとは?</strong></u></p> <p><small>GeoHashは、エリアを区画に分割して、その区画をBase32でHash化した文字列で表現した値になります。 例えば、以下の例だと東京駅の緯度経度を中心として、計算した5桁のHash値は±2.4kmのエリアを選択します。 この範囲内に存在するデータを生成列で緯度・軽度から自動生成しておいて、インデックスを付与してサクッと同じエリアにあるバス停等を高速に検索する事も可能です。</small></p> <table> <thead> <tr> <th style="text-align:center;">例)東京駅を中心として±2.4kmにある観光資源</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><figure class="figure-image figure-image-fotolife" title="ST_Geohash(ST_Y(geom),ST_X(geom),5) = &#x27;xn76u&#x27;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211215/20211215193250.png" width="511" height="265" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ST_Geohash(ST_Y(geom),ST_X(geom),5) = &#x27;xn76u&#x27;</figcaption></figure></td> </tr> </tbody> </table> <table> <thead> <tr> <th style="text-align:center;">地図表示(xn76u)</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211216/20211216084331.png" width="655" height="459" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> <tr> <td style="text-align:center;">参照:<a href="https://www.movable-type.co.uk/scripts/geohash.html">Movable Type Scripts</a></td> </tr> </tbody> </table> <p>±2.4kmだと少々データ量的には寂しい感じになるので、以下のGeoHashリストから関東エリアをカバー出来そうな2桁のHash値を選択しました。</p> <table> <thead> <tr> <th style="text-align:center;">東京駅から±630kmの観光資源</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><figure class="figure-image figure-image-fotolife" title="東京駅を中心としたGeohash"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211214/20211214073913.png" width="679" height="314" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>東京駅を中心としたGeohash</figcaption></figure></td> </tr> </tbody> </table> <p><strong>PHP側にはベタで以下のSQLを埋め込んでいます</strong> <br> <small>「年末にでも時間あれば、Vue.jsで検索Boxを作り、観光資源データの拡充を図っていこうかと。。」</small></p> <pre class="code lang-sql" data-lang="sql" data-unlink>$sql = &lt;&lt;&lt;SQL <span class="synStatement">select</span> p12_003 <span class="synSpecial">as</span> <span class="synSpecial">'</span><span class="synConstant">name</span><span class="synSpecial">'</span>, p12_004 <span class="synSpecial">as</span> <span class="synSpecial">'</span><span class="synConstant">category</span><span class="synSpecial">'</span>, ST_X(geom) <span class="synSpecial">AS</span> <span class="synSpecial">&quot;</span><span class="synConstant">lat</span><span class="synSpecial">&quot;</span>,ST_Y(geom) <span class="synSpecial">AS</span> <span class="synSpecial">&quot;</span><span class="synConstant">lng</span><span class="synSpecial">&quot;</span>, <span class="synSpecial">'</span><span class="synConstant">kanko</span><span class="synSpecial">'</span> <span class="synSpecial">as</span> <span class="synSpecial">'</span><span class="synConstant">type</span><span class="synSpecial">'</span> <span class="synSpecial">FROM</span> `p12<span class="synConstant">-10</span>-g_tourismresource_point` <span class="synSpecial">where</span> ST_Geohash(ST_Y(geom),ST_X(geom),<span class="synConstant">2</span>) = <span class="synSpecial">'</span><span class="synConstant">xn</span><span class="synSpecial">'</span> SQL; </pre> <h5>➎ Google Mapでロケーションを可視化</h5> <table> <thead> <tr> <th style="text-align:center;">例)霞ケ浦:日本で琵琶湖についで、二番目に大きい面積を持つ湖</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211214/20211214135834.png" width="1200" height="566" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> </tbody> </table> <p>これで、基本的な観光資源の可視化が出来たので、データを拡充させて<a href="https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P11.html">バス停留所データ</a>等を組み合わせたりする事でも便利なコンテンツが出来そうです。</p> <table> <thead> <tr> <th style="text-align:center;">例)観光資源とバス停のデータ集約</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oss-rdbms/20211219/20211219125401.png" width="768" height="582" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></td> </tr> </tbody> </table> <p>参照:ジオハッシュ<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%25E3%2582%25B8%25E3%2582%25AA%25E3%2583%258F%25E3%2583%2583%25E3%2582%25B7%25E3%2583%25A5" title="ジオハッシュ - Wikipedia" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://ja.wikipedia.org/wiki/%E3%82%B8%E3%82%AA%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5">ja.wikipedia.org</a></cite></p> <p>その他:ジオハッシュを利用したソリューション例<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdev.classmethod.jp%2Farticles%2Fios-location-aws-lambda-geohash%2F" title="iOS アプリの位置情報から AWS Lambda で GeoHash を作成するサーバーレスアプリケーション | DevelopersIO" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://dev.classmethod.jp/articles/ios-location-aws-lambda-geohash/">dev.classmethod.jp</a></cite></p> <p><u>備考:こちらのコンテナでサクット検証する場合は以下の変更が必要です。</u></p> <table> <thead> <tr> <th>No </th> <th> 概要</th> </tr> </thead> <tbody> <tr> <td>1 </td> <td> <a href="https://github.com/rdbms-at-twitter/rdbms-docker">リポジトリー</a>をCloneしてdocker-compose-with-volume-web.ymlで環境を構築。</td> </tr> <tr> <td>2 </td> <td> 既にお持ちであれば、<a href="https://github.com/rdbms-at-twitter/rdbms-docker/blob/main/mysql/www/html/kanko.html#L116">Google Map APIキーの変更</a></td> </tr> <tr> <td>3 </td> <td> サンプルとして<a href="https://github.com/rdbms-at-twitter/rdbms-docker/blob/main/mysql/docker-compose-with-volume-web.yml">ユーザー名, パスワード,データベースをそのまま記載</a>しているので必要に応じて.envファイルに変更して下さい。</td> </tr> <tr> <td>4 </td> <td> Google Map APIを表示する為に利用するURLを、Google Cloud PlatformにてWeb Site RestrictionsにURLを登録して下さい。</td> </tr> </tbody> </table> <h2>後記</h2> <p>これまでの仕事では観光と関わる事が無かったので、旅行で行く観光地は書店に置いてある雑誌やネットをベースに調べて旅をして来ましたが、 勉強する中で、「高知県には綺麗な川が豊富にある事」、「世界遺産でもある北海道の知床半島が素晴らしいという事」、「本州にも色々な観光名所がある事」に初めて気付く事が出来ました。 家族のタイミングもあるので、なかなかまとまった時間が取れませんが、もしまとまった時間が取れたらこれまで行った事の無い都道府県に旅に行ってみたいと考えてます。 近場?だと富山あたりに行ってトロッコ列車に乗ってみたいと思っています。</p> <p>この環境を作るにあたり気付いたのですが、PHP7でmysql_connectが非推奨になっている事に初めて気付きました。 もう現場でバリバリという年齢でも無いのでエンジニア業務は殆どしていませんが、どんなに得意な事も日常で触ってないと忘れてしまうので、 週末時間が取れたら、もう少しだけ観光向けのサイトをプライベートで構築して見ようと思います。</p> <p><strong>年末迄あと少しありますが、健康に気を付けて良い年末・年始をお迎えください。</strong></p> <h2>採用情報</h2> <p>LCLではエンジニアのアイデアを生かして、更にサービスを盛り上げて行こうと思ってます!! 引き続き、生産性向上に励む仲間を募集中です。</p> <p>「現時点では、特にフロントエンドエンジニアを絶賛募集中です。」 カジュアル面談、少し話を聞いてみたい等、少しでも思われたら<a href="https://www.lclco.com/recruit/entry/?jc=3234">ご連絡</a>下さい。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> oss-rdbms ESLintのバージョンをv6.8.0からv8.4.1に上げる hatenablog://entry/13574176438042962703 2021-12-17T10:00:22+09:00 2021-12-23T17:55:48+09:00 この記事はLCL Advent Calendar 2021 - 17日目です。 qiita.com フロントエンドエンジニアのsatoshioです。 先日弊社が提供しているバスツアー検索サービスでESLintおよびPrettierのバージョンアップ対応を行ったので、今回は対応の流れやそこで遭遇したエラーとその解決方法についてまとめます。 日帰り・宿泊バスツアーの人気格安プラン検索【バス比較なび】 目標 ESLintを最新のバージョンにアップグレードする Prettierを最新のバージョンにアップグレードする 準備 現状の確認 対応開始時点でのESLintの最新バージョンはv8.4.1、Pret… <p>この記事はLCL Advent Calendar 2021 - 17日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>フロントエンドエンジニアのsatoshioです。</p> <p>先日弊社が提供しているバスツアー検索サービスでESLintおよびPrettierのバージョンアップ対応を行ったので、今回は対応の流れやそこで遭遇したエラーとその解決方法についてまとめます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftour.bushikaku.net%2F" title="日帰り・宿泊バスツアーの人気格安プラン検索【バス比較なび】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://tour.bushikaku.net/">&#x65E5;&#x5E30;&#x308A;&#x30FB;&#x5BBF;&#x6CCA;&#x30D0;&#x30B9;&#x30C4;&#x30A2;&#x30FC;&#x306E;&#x4EBA;&#x6C17;&#x683C;&#x5B89;&#x30D7;&#x30E9;&#x30F3;&#x691C;&#x7D22;&#x3010;&#x30D0;&#x30B9;&#x6BD4;&#x8F03;&#x306A;&#x3073;&#x3011;</a></p> <h1>目標</h1> <ul> <li>ESLintを最新のバージョンにアップグレードする</li> <li>Prettierを最新のバージョンにアップグレードする</li> </ul> <h1>準備</h1> <h2>現状の確認</h2> <p>対応開始時点での<a href="https://github.com/eslint/eslint/releases">ESLintの最新バージョンはv8.4.1</a>、<a href="https://github.com/prettier/prettier/releases">Prettierの最新バージョンはv2.5.1</a>のようです。</p> <p>一方で、バスツアー検索サービスで使用しているバージョンはというと...</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">dependencies</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">nuxt</span>&quot;: &quot;<span class="synConstant">2.15.8</span>&quot;, ... <span class="synError">}</span> <span class="synError"> </span>&quot;<span class="synStatement">devDependencies</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">eslint</span>&quot;: &quot;<span class="synConstant">6.8.0</span>&quot;, &quot;<span class="synStatement">prettier</span>&quot;: &quot;<span class="synConstant">2.3.0</span>&quot;, ... <span class="synSpecial">}</span> } </pre> <p>PrettierはともかくESLintは最新とかなり離れてしまっています...。</p> <h2>リリースノートの確認</h2> <h3>ESLint </h3> <p><a href="https://github.com/eslint/eslint/releases">Releases</a></p> <p>v6.8.0-v8.4.1間の更新内容を確認していきます。当たり前ですがそこそこ量がありますね...。メジャーバージョンが2つ変わっているのでBreaking changesもそれなりにあります。</p> <p><a href="https://eslint.org/docs/8.0.0/user-guide/migrating-to-7.0.0#nodejs-8-is-no-longer-supported">v7.0.0の時点でNode.js v8</a>、<a href="https://eslint.org/docs/8.0.0/user-guide/migrating-to-8.0.0#nodejs-10-13-and-15-are-no-longer-supported">v8.0.0の時点でNode.js v10, v13, v15</a>のサポートが終了していますが、バスツアー検索サービスのNode.jsは既にLTSまで上げてあるのでここは問題なさそうです。</p> <p>詳細は省略しますが、他のBreaking changesについても影響がないことを確認しました。</p> <h3>Prettier</h3> <p><a href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md">Releases</a></p> <p>v2.3.0-v2.5.1間の更新内容を確認していきます。ほとんどは既存機能のFixとImproveのようです。</p> <p><a href="https://prettier.io/blog/2021/11/25/2.5.0.html#collapse-html-class-attributes-onto-one-line-11827httpsgithubcomprettierprettierpull11827-by-jlongsterhttpsgithubcomjlongster">Collapse HTML class attributes onto one line (#11827 by @jlongster)</a>あたりの修正(厳密にはRevertですが)は良さそうですね。Tailwind CSSのように大量のクラスが付与される場合のフォーマットは難しそうです。</p> <p><a href="https://prettier.io/blog/2021/09/09/2.4.0.html#replace-jsxbracketsameline-option-with-bracketsameline-option-11006httpsgithubcomprettierprettierpull11006-by-kurtztechhttpsgithubcomkurtztech">jsxBracketSameLineが非推奨になり新たにbracketSameLineが追加されました</a>が、確認したところ今回は関係なさそうです。</p> <h1>対応</h1> <h2>ESLint</h2> <p>リリースノートも確認したので、とりあえずバージョンを上げてみます。</p> <pre class="code" data-lang="" data-unlink>npm install eslint@latest —save-dev npm run dev Module build failed (from ./node_modules/eslint-loader/dist/cjs.js): TypeError: Cannot read properties of undefined (reading &#39;getFormatter&#39;)</pre> <p>エラーが発生するようになりました。バージョンアップにはつきものですね。リリースノートを確認しているとはいえメジャーバージョンを2つもあげているのにすんなり動いたらそれはそれで怖いのでかえって安心します。</p> <h3>eslint-loader周り</h3> <p>エラー内容を読む限りどうやらeslint-loader絡みのようなのでとりあえず<a href="https://github.com/webpack-contrib/eslint-loader">eslint-loader公式</a>を確認しにいったところ、なんと<strong>eslint-loaderは2020年9月にDEPRECATED</strong>されていました。「変わりに<a href="https://github.com/webpack-contrib/eslint-webpack-plugin">eslint-webpack-plugin</a>を使用してね」とのことなので、そのように変更します。</p> <p>ただ、色々調べたところ<a href="https://github.com/nuxt-community/eslint-module">@nuxtjs/eslint-module</a>(eslint-webpack-pluginを内包している)を使用するのが良さそうだったので、今回はそちらを使用するようにしました。リリースノートからも、<a href="https://github.com/nuxt-community/eslint-module/blob/master/CHANGELOG.md#-breaking-changes">v3.0.0でeslint-loaderからeslint-webpack-pluginを使用するように変更されている</a>ことがわかります。</p> <pre class="code" data-lang="" data-unlink>npm uninstall eslint-loader npm install @nuxtjs/eslint-module --save-dev</pre> <p><code>nuxt.config.js</code>を修正します。合わせてbuildに記載されているeslint-loader用のホットリロードの設定も削除します。</p> <pre class="code lang-diff" data-lang="diff" data-unlink>export default { build: { <span class="synSpecial">- extend(config, ctx) {</span> <span class="synSpecial">- if (ctx.isDev &amp;&amp; ctx.isClient) {</span> <span class="synSpecial">- config.module.rules.push({</span> <span class="synSpecial">- enforce: 'pre',</span> <span class="synSpecial">- test: /\.(js|vue)$/,</span> <span class="synSpecial">- loader: 'eslint-loader',</span> <span class="synSpecial">- exclude: /(node_modules)/,</span> <span class="synSpecial">- options: {</span> <span class="synSpecial">- emitError: true,</span> <span class="synSpecial">- emitWarning: true,</span> <span class="synSpecial">- fix: true</span> <span class="synSpecial">- }</span> <span class="synSpecial">- })</span> <span class="synSpecial">- }</span> <span class="synSpecial">- }</span> }, <span class="synIdentifier">+ buildModules: [</span> <span class="synIdentifier">+ '@nuxtjs/eslint-module'</span> <span class="synIdentifier">+ ]</span> } </pre> <p>※ 余談: Rule can only have one resource source (provided resource and test + include + exclude) エラーが発生した場合</p> <p>今回は@nuxtjs/eslint-moduleをinstallしましたが、@nuxtjs/eslint-moduleではなくeslint-webpack-pluginをinstallし、かつinstall済みのwebpackのバージョンがv5である場合、上記のエラーに遭遇する場合があります。</p> <p>このエラーの原因は「eslint-webpack-pluginのバージョンとinstallされているwebpackのバージョンが一致していない」ことが考えられます。<a href="https://webpack.js.org/plugins/eslint-webpack-plugin/">EslintWebpackPlugin</a>に</p> <pre class="code" data-lang="" data-unlink>This is eslint-webpack-plugin 3.0 which works only with webpack 5. For the webpack 4, see the 2.x branch.</pre> <p>と記載されている通り、webpackがv5の場合はeslint-webpack-pluginはv3、webpackがv4の場合はeslint-webpack-pluginはv2を使用する必要があります。</p> <p>と言いつつ、Nuxt.js内で使用されているwebpackのバージョンはv4(nuxt@2.15.8ではwebpack@4.46.0でした)なので、eslint-webpack-pluginもv2でinstallしないとエラーが発生する、といった感じです。</p> <p>一応eslint-webpack-pluginをv2でinstallすれば解決できるのですが、新規Nuxt.jsプロジェクトをセットアップする際「ESLintを導入する」を選択した時にinstallされるのは@nuxtjs/eslint-moduleなので、そちらの設定の方が無難なのかなと思います。</p> <h3>babel-eslint周り</h3> <p>さて、これでエラーは解消されたはずなので再び実行してみます。</p> <pre class="code" data-lang="" data-unlink>npm run dev error Parsing error: require() of ES Module /foo/bar/node_modules/eslint/node_modules/eslint-scope/lib/definition.js from /foo/bar/node_modules/babel-eslint/lib/require-from-eslint.js not supported. Instead change the require of definition.js in /foo/bar/node_modules/babel-eslint/lib/require-from-eslint.js to a dynamic import() which is available in all CommonJS modules</pre> <p>別のエラーにたどり着きました。</p> <p>エラー内容を読む限りどうやらbabel-eslint絡みのようなのでとりあえず<a href="https://github.com/babel/babel-eslint">babel-eslint公式</a>を確認しにいったところ、なんと<strong>babel-eslintは2020年3月にDEPRECATED</strong>されていました。「変わりに<a href="https://github.com/babel/babel/tree/main/eslint/babel-eslint-parser">@babel/eslint-parser</a>を使用してね」とのことなので、そのように変更します。<a href="https://babeljs.io/blog/2021/10/29/7.16.0">このあたり</a>を読むと@babel/eslint-parserがESLint v8に対応したことなんかも書いてありますね。</p> <pre class="code" data-lang="" data-unlink>npm uninstall babel-eslint npm install @babel/core @babel/eslint-parser --save-dev</pre> <p><code>.eslintrc.js</code>も修正します。</p> <pre class="code lang-diff" data-lang="diff" data-unlink>{ ... &quot;parserOptions&quot;: { <span class="synSpecial">- &quot;parser&quot;: &quot;babel-eslint&quot;,</span> <span class="synIdentifier">+ &quot;parser&quot;: &quot;@babel/eslint-parser&quot;,</span> <span class="synIdentifier">+ &quot;requireConfigFile&quot;: false</span> }, ... } </pre> <p>再び実行したところ、エラーが発生することなくNuxt.jsプロジェクトが立ち上がりました。試しに適当に全角スペースを挿入して保存してみます。</p> <pre class="code" data-lang="" data-unlink>error Irregular whitespace not allowed no-irregular-whitespace</pre> <p>動作していそうですね。</p> <h2>Prettier</h2> <p>Prettierに関してはマイナーバージョンアップなのでESLintと比較すると少しだけ気が楽です。とりあえずバージョンを上げます。</p> <pre class="code" data-lang="" data-unlink>npm install prettier@latest</pre> <p>動作も問題なさそうですが、少し調べたところESLint + Prettierの推奨設定が変わっていたので対応しておきます。<a href="[https://blog.ojisan.io/prettier-eslint-cli/">こちらのサイト</a>がわかりやすかったです。</p> <p>要点をまとめると、</p> <ul> <li>eslint-plugin-prettierは非推奨になった</li> <li>eslint-config-prettierで競合ルールでoffにした後、<code>prettier &amp;&amp; eslint</code>のようにチェックするようにする</li> </ul> <p>という変更になります。バスツアー検索サービスでもeslint-plugin-prettierを使用していたので、忘れないうちにuninstallしておきます。</p> <pre class="code" data-lang="" data-unlink>npm uninstall eslint-plugin-prettier</pre> <p>また、競合ルールをoffにする記述についてですが<a href="https://github.com/prettier/eslint-config-prettier/tree/v8.1.0#installation">eslint-config-prettie v8.0.0から簡単になった</a>ようなので 、合わせて確認しておきます。</p> <pre class="code js" data-lang="js" data-unlink>{ extends: [ ... &#39;prettier&#39; // 末尾に記述する v8.0.0からは &#39;prettier&#39;のみでOK ], ... }</pre> <p>あとは対応前後でデザインや動作に差異が発生していないかを別途確認し、対応完了です。</p> <h1>まとめ</h1> <p>今は便利な時代なので少し探せば同じ問題に遭遇した誰かしらが残した解決方法にたどり着ける時も多いのですが、仕様や推奨/非推奨の変更で少し前の対応方法が最適解にならない、それだけならまだしも適用すらできないことも多々あり、今回はその点で少々苦労したのでドキュメントを残すことにしました。似た問題を抱えた方の何かしらの役に立てば幸いです。</p> <p>バージョンアップ対応はこまめに行うようにしましょう。</p> <h1>採用情報</h1> <p>フロントエンドチームでは、より良いサービスを提供していくために新しい仲間を募集しています。</p> <p>カジュアルに会話できる機会もご用意できますので、もし興味がある方は気軽にご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> lcl_sato SQS(FIFO) + Lambdaの並列実行時のポイント hatenablog://entry/13574176438042407777 2021-12-15T11:12:54+09:00 2021-12-15T18:03:36+09:00 この記事はLCL Advent Calendar 2021 - 15日目です。 qiita.com バックエンドエンジニアの高良です。 LCLへの転職を機に沖縄から上京して早2年、地元と段違いの寒さにも段々と慣れてきました。 ここ最近ではRailsでの開発に加え、インフラ周りのちょっとした調整などの作業をすることも増えてきました。 その中で1点、実装方法の調査時に手間取った箇所があったので、解決のポイントを紹介しようかと思います。 SQS(FIFO)と連携しているLambdaのスケールアウト 現在海外航空券では、SQS(FIFO)+Lambdaが連携したバッチジョブが動いています。 techb… <p>この記事はLCL Advent Calendar 2021 - 15日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>バックエンドエンジニアの高良です。<br /> LCLへの転職を機に沖縄から上京して早2年、地元と段違いの寒さにも段々と慣れてきました。</p> <p>ここ最近ではRailsでの開発に加え、インフラ周りのちょっとした調整などの作業をすることも増えてきました。<br /> その中で1点、実装方法の調査時に手間取った箇所があったので、解決のポイントを紹介しようかと思います。</p> <h2>SQS(FIFO)と連携しているLambdaのスケールアウト</h2> <p>現在海外航空券では、SQS(FIFO)+Lambdaが連携したバッチジョブが動いています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2021%2F12%2F01%2F070000" title="LCLのRailsバッチジョブ実行基盤 on AWS 21年秋冬版 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2021/12/01/070000">techblog.lclco.com</a></cite></p> <p>こちらは1日1回の実行としているのですが、条件によっては実行時間が1日を上回ってしまい、必要な処理が1日で完了しきれないという問題が発生しました。</p> <p>この時Lambdaは1台のみで稼働していたため、Lambdaインスタンスの台数を増やし並列で処理を実行させることで1日以内に処理を完了できるようになる想定でした。<br /> そのため、並列で起動できるLambdaインスタンス数の設定(Reserved Concurrency)を増やせば自動的にスケールアウトしてくれるものかと考えていました。</p> <p>しかしFIFOキューを使用している場合、その設定だけではスケールアウトせず、LambdaのReserved Concurrencyに加えてFIFOキューで用いられるメッセージグループIDの調整も必要ということが分かりました。</p> <h2>メッセージグループIDとは</h2> <p>FIFO(First in first out = 先入れ先出し)のルールを保つために必要な概念です。<br /> エンキュー時にユーザー側で各メッセージごとに固有のIDを付与しておき、デキュー時には同一のIDを持つメッセージ内で先入れ先出しのルールが適用されるという仕組みになっています。<a href="#f-899f7d62" name="fn-899f7d62" title="詳細は公式ドキュメントを参考 [https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues-understanding-logic.html:title]">*1</a></p> <h2>メッセージグループIDとLambdaの同時実行数の関係性</h2> <p>結論から言うと、<strong>同時実行可能なLambdaインスタンス数 &lt;= メッセージグループIDの数</strong>となります。 <a href="#f-895d42d0" name="fn-895d42d0" title="[https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-sqs-scaling/:title] ">*2</a></p> <p>例えば下記の図のように、メッセージグループIDを1つのみに設定していた場合、同時に実行できるLambdaインスタンスの数もMAX1台となります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takara7283/20211214/20211214101217.png" alt="f:id:takara7283:20211214101217p:plain" width="721" height="240" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この状態だとLambdaのReserved Concurrencyの数値をいくら増やそうが、Lambdaインスタンスが1台から増えることはありません。<br /> そのためLambdaを2台並列で動かすには、下記のようにメッセージグループIDも2種類以上にしておく必要があります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takara7283/20211214/20211214101225.png" alt="f:id:takara7283:20211214101225p:plain" width="722" height="236" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>このような仕様になっている理由として、一つのメッセージグループIDに対してコンシューマが2つ以上ある場合、先入れ先出しの制御が複雑になりそうな事が原因なのかもしれません。<br /> FIFOキューを使いつつコンシューマのLambdaを並列実行させたい場合は、メッセージグループIDの割当について考慮した実装にしておきましょう。</p> <p>注意点として、異なるメッセージグループIDを持ったメッセージ同士間では先入れ先出しのルールは保証されなくなります。<br /> そのためメッセージの処理順序を考慮しつつ並列処理数も増やしたい場合は、この点を考慮した設計にしておく必要があります。<br /> 今回は説明を省きますが、公式の解説ではメッセージグループ毎の細かい処理順について説明されているので、そちらを参考にしつつ実際に動作確認してみるのが良さそうです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fblogs%2Fcompute%2Fnew-for-aws-lambda-sqs-fifo-as-an-event-source%2F" title="New for AWS Lambda – SQS FIFO as an event source | Amazon Web Services" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/blogs/compute/new-for-aws-lambda-sqs-fifo-as-an-event-source/">aws.amazon.com</a></cite></p> <h2>採用情報</h2> <p>LCLでは開発メンバーを募集中です! カジュアルな面談からでも可能なので、ご興味がある方は気軽に応募してください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-899f7d62" name="f-899f7d62" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">詳細は公式ドキュメントを参考 <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues-understanding-logic.html">FIFO delivery logic - Amazon Simple Queue Service</a></span></p> <p class="footnote"><a href="#fn-895d42d0" name="f-895d42d0" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-sqs-scaling/">Amazon SQS &#x30A4;&#x30D9;&#x30F3;&#x30C8;&#x30BD;&#x30FC;&#x30B9;&#x3092;&#x4F7F;&#x7528;&#x3059;&#x308B;&#x3068;&#x304D;&#x306B; Lambda &#x306E;&#x30B9;&#x30B1;&#x30FC;&#x30EA;&#x30F3;&#x30B0;&#x3092;&#x6700;&#x9069;&#x5316;&#x3059;&#x308B;</a> </span></p> </div> takara7283 コーポレートサイトのwordprss環境をECS化した話 hatenablog://entry/13574176438041825437 2021-12-11T23:47:50+09:00 2021-12-15T12:31:23+09:00 この記事はLCL Advent Calendar 2021 - 11日目です。 qiita.com こんにちは。id:kasei_san です。今回は wordpressで動作している弊社コーポレートサイトをECS化したので、その時のハマりどころやポイントを紹介していきたいと思います。 www.lclco.com 既存のコーポレートサイト環境の問題 元々コーポレートサイトは、いろいろな事情でEC2の中のDockerで動かしている状態でした(ECS on EC2ではありません)。 そのため、運用上いろいろな問題がありました。 環境の構築手順が自動化されておらず、EC2が死んでしまうと復旧に時間が… <p>この記事はLCL Advent Calendar 2021 - 11日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>こんにちは。<a href="https://profile.hatena.ne.jp/kasei_san/">id:kasei_san</a> です。今回は wordpressで動作している<a href="https://www.lclco.com/">弊社コーポレートサイト</a>をECS化したので、その時のハマりどころやポイントを紹介していきたいと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2F" title="株式会社LCL|旅人をふやす会社。" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/">www.lclco.com</a></cite></p> <h1>既存のコーポレートサイト環境の問題</h1> <p>元々コーポレートサイトは、いろいろな事情でEC2の中のDockerで動かしている状態でした(ECS on EC2ではありません)。 そのため、運用上いろいろな問題がありました。</p> <ul> <li>環境の構築手順が自動化されておらず、EC2が死んでしまうと復旧に時間が掛かる。</li> <li>EC2側のセキュリティ対策も必要。</li> <li>環境やデプロイの方法がこのプロジェクト特有で学習コストが高い。</li> </ul> <p>ちょうど当時、社内の各アプリのTerraform化を進めようとしていたところでした。そのためコーポレートサイトも、これらの問題を解決するためTerraformを使い、ECS化を進めることとなりました。</p> <h1>更新後の環境</h1> <p>最終的には以下のような環境となりました(正確には他にもいくつかのサービスを使用しています)。</p> <p><figure class="figure-image figure-image-fotolife" title="コーポレート環境"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kasei_san/20211211/20211211220022.png" alt="f:id:kasei_san:20211211220022p:plain" width="512" height="405" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>コーポレート環境</figcaption></figure></p> <p>その中から、工夫したポイントを紹介しようと思います。</p> <h1>wordpressのofficial Imageは使用せず、独自ImageとNginxを使用</h1> <p>Fargateで動かしている Docker Imageは、<a href="https://hub.docker.com/_/wordpress">wordpressのofficial Image</a>ではなく、PHPのImageにwordpressを独自にインストールしたものと、Nginxを使用しています。</p> <p>これは、LCLで他に運用しているwebサーバがすべてNginxであるためです。コーポレートサイトだけがApacheであったため、知見が少なく、対応できるメンバーが少ないという問題がありました。そのため、これを機にLCLのwebサーバをすべてNginx としました。</p> <p>また、Nginx化するにあたり、wordpressの向けのnginx.confを作る必要がありましたが、Nginx公式が詳細な記事を上げており、スムーズに対応できました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.nginx.com%2Fresources%2Fwiki%2Fstart%2Ftopics%2Frecipes%2Fwordpress%2F" title="WordPress | NGINX" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.nginx.com/resources/wiki/start/topics/recipes/wordpress/">www.nginx.com</a></cite></p> <p>ただ、1点困ったこととして、Nginx公式の方法は、Nginxとwordpressが同一のサーバ上にある前提の設定となっていました( <code>try_files</code> を使って、wordpressのディレクトリの <code>.php</code> のファイルを探したりしています)。 そのため、1コンテナ1アプリの原則で、wordpressとNginxでコンテナを分けた場合、Nginxには静的ファイルだけではなく、wordpressのファイルもすべてCOPYする必要がありました。</p> <h1>メディアファイルのストレージとしてEFSを採用</h1> <p>wordpressでは、uploadしたメディアファイルを扱う必要があるため、そのストレージとして、EFSを使用しています。 具体的には、<code>wp-content/uploads</code> をEFSからマウントしています。 図ではS3も使用していますが、こちらは定期的に更新する必要があるファイルをupする先として使用しています。</p> <p>当初、upload先をS3とするwordpressプラグインを使用する予定でした。しかし、一番メジャーと思われる <a href="https://deliciousbrains.com/wp-offload-media/doc/assets-pull-addon/">WP Offload Media の assets-pull-addon</a> の最新バージョンでは、既存ファイルのuploadをサポートしなくなっていました…。他にもいくつかプラグインを調査しましたが、既存のファイルをS3にコンバートするちょうどよい方法は見つからず。EFSを採用することとなりました。</p> <p>EFSは速度が遅く、コストが高いというイメージでしたが、コーポレートサイトはそこまでリクエスト数も多くないため、コストが安いバーストスループットで問題なく動作し、また、速度についても CloudFront でキャッシュするため、問題ありませんでした。</p> <h1>Terraform + ECS化しての感想</h1> <p>運用方法が他のTerraform + ECSで運用している他のアプリと同様となりました。これにより、運用のためのコストが大きく下がりました。コーポレートサイトはエンジニアが手を入れることが少なく、なにかを作業をするたびに手順を思い出す必要があり、それに結構な時間を食われていました。 また、ECSで動作しているため、taskが壊れてもすぐに復旧できるのは、とても安心感がありますね。</p> <p>以上となります。今後、手軽に更新できるようになったことを生かし、素早くwordpressをupdateしていこうと思います。</p> kasei_san Macのスリープ時にGoogleから自動でログアウトする方法 hatenablog://entry/13574176438041066007 2021-12-10T00:00:00+09:00 2021-12-15T12:29:39+09:00 この記事はLCL Advent Calendar 2021 - 10日目です。 qiita.com モバイルアプリエンジニアの山下です。 今年も気がつけば12月となり、そして入社してから4年と4か月が経っていました。 入社エントリーを振り返ると勝どきのトリトンタワー40階の絶景と広々とした明るい雰囲気の空間、そして入社当時のやる気と希望に満ち溢れている様子が懐かしく思います。 techblog.lclco.com あれから4年経った今、LCLはカカクコムの子会社となり、オフィスも勝どきから恵比寿へ引っ越し、多くのメンバーの入れ替わりがありました。 変化があったのは会社だけでなく、世の中の日常も… <p>この記事はLCL Advent Calendar 2021 - 10日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>モバイルアプリエンジニアの山下です。</p> <p>今年も気がつけば12月となり、そして入社してから4年と4か月が経っていました。</p> <p>入社エントリーを振り返ると勝どきのトリトンタワー40階の絶景と広々とした明るい雰囲気の空間、そして入社当時のやる気と希望に満ち溢れている様子が懐かしく思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2017%2F11%2F02%2F122232" title="モバイルアプリエンジニアとして入社して3ヶ月が経ちました - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2017/11/02/122232">techblog.lclco.com</a></cite></p> <p>あれから4年経った今、LCLはカカクコムの子会社となり、オフィスも勝どきから恵比寿へ引っ越し、多くのメンバーの入れ替わりがありました。</p> <p>変化があったのは会社だけでなく、世の中の日常も激変しました。</p> <p>多くの会社で在宅勤務が取り入れられ、LCLでも私が入社した当時はエンジニアのみ在宅勤務が許可されていましたが、現在はエンジニアに限らず全社員が在宅勤務できる環境となっています。*この環境は一時的なもので今後も続くかはわかりません</p> <p>誰もが自宅から仕事をする環境となり、社内セキュリティの重要度が一層上がりました。<br /> 特に改めて見直されたが各サービスのログイン認証まわりです。具体的にはSSOや二段階認証の設定やPC持ち出し時の状態に決まりごとが設けられました。</p> <p>その中のひとつにグループウェアで利用しているGoogleからのログアウトがあります。</p> <p>PC持ち出し時および、終業時にログアウトをするルールが設けられました。<br /> これは<strong>在宅勤務で家から出ない人も対象でログアウトは手動</strong>で行わなければいけません。</p> <p>ログアウト状況は月次でチェックされており、忘れると評価に影響される大変お厳しいものですが、セキュリティは大切なので致し方ありません。毎日ポチッとログアウトをします。</p> <p>でも人間なので忘れます。ましてやエンジニアたるもの毎日手動で操作をしなければいけないことはしたくありません。</p> <p>上位プランであればセッション継続時間の設定をすることで自動ログアウトできるのでアップグレードしてしてほしいところですが、きっと検討の末の今の環境だと思うので手元でどうにかするしかありません。</p> <p>そこで考えたのがMacをスリープした際に、自動でログアウトするようにできないかです。<br /> <strong>エンジニアたるもの些細な面倒を放置していてはいけません。</strong></p> <p>正確には最初からスリープと連動させることを思いついたのではなく、ログアウト用URLを叩けば一発ログアウトされることを知ったのをSlackで共有したところ、チームメンバーがあるツールと組み合わせると自動ログアウトできそうと提案してくれました。これぞチームワークです。</p> <p>この世の中で同じ状況にある方がいると思うので今回は設定方法を紹介します。</p> <h2>本題の自動ログアウトの設定</h2> <p>今回使用するもの</p> <ul> <li><a href="https://formulae.brew.sh/formula/sleepwatcher">sleepwatcher</a></li> <li>ログアウト用URL:<a href="https://accounts.google.com/Logout">https://accounts.google.com/Logout</a></li> </ul> <p>sleepwatcherは、Macのスリープおよびスリープ復帰時にスクリプトを実行するツールです。ログアウト用URLはこれをブラウザで開くだけでログイン中のアカウントから即ログアウトできます。つまり、「スリープ時にこのURLをブラウザで開く」ということをします。</p> <h3>インストール</h3> <p>Homebrewでインストールできます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ brew install sleepwatcher </pre> <h3>スクリプトの追加</h3> <p>スリープ時は<code>~/.sleep</code>、スリープから復帰時は<code>~/.wakeup</code>にスクリプトを配置します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ <span class="synStatement">touch</span> ~/.sleep $ <span class="synStatement">chmod</span> <span class="synSpecial">+x</span> ~/.<span class="synStatement">sleep</span> <span class="synComment">#実行権限も与えておきます</span> </pre> <p>今回は17時以降でスリープした際に自動ログアウトされてほしいので、openコマンドでログアウト用URLをブラウザで開くスクリプトを前者に追加します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment">#!/bin/sh</span> <span class="synStatement">if [</span> <span class="synConstant">17</span> <span class="synStatement">-lt</span> <span class="synSpecial">`date +%H`</span> <span class="synStatement">];</span> <span class="synStatement">then</span> open https://accounts.google.com/Logout <span class="synStatement">fi</span> </pre> <h3>sleepwatcherを起動</h3> <p>スクリプトの追加後はsleepwatcherを起動します。</p> <pre class="code" data-lang="" data-unlink>$ brew services start sleepwatcher</pre> <p>以上で設定は完了です。</p> <p>これでスリープすれば自動ログアウトされるので忘れる心配はありません。<br /> いや、むしろログアウトしなければいけないというのことを忘れました。</p> <p>エンジニアたるもの些細な面倒を放置していてはいけません。<br /> この投稿がどこかの誰かの毎日の面倒を減らすことに役立つことを願っています。</p> yamshta フロントエンドチームの2021年振り返り hatenablog://entry/13574176438040794559 2021-12-09T11:13:07+09:00 2021-12-09T11:13:07+09:00 この記事はLCL Advent Calendar 2021 - 9日目です。 qiita.com フロントエンドエンジニアの亀田です。 アドベントカレンダーなのにここ数日ブログ更新がなかったのは予定通りで、決して自分が止めていたわけではありません。 今年も気がつけば12月なので、毎年恒例のフロントエンドチームとしての振り返りをしようと思います。 フロントエンドチームの現状 現状、フロントエンドチームのメンバーは3人で、2020年初めからメンバーに変更はなく、気が付けば現状のメンバーで1年半ほどの日々が経ちました。 今では良い意味で変に気を遣うことなく、割とカジュアルに相談できる関係性かなと思い… <p>この記事はLCL Advent Calendar 2021 - 9日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>フロントエンドエンジニアの亀田です。</p> <p>アドベントカレンダーなのにここ数日ブログ更新がなかったのは予定通りで、決して自分が止めていたわけではありません。</p> <p>今年も気がつけば12月なので、毎年恒例のフロントエンドチームとしての振り返りをしようと思います。</p> <h2>フロントエンドチームの現状</h2> <p>現状、フロントエンドチームのメンバーは3人で、2020年初めからメンバーに変更はなく、気が付けば現状のメンバーで1年半ほどの日々が経ちました。</p> <p>今では良い意味で変に気を遣うことなく、割とカジュアルに相談できる関係性かなと思います。</p> <p>今ではと書きましたが、思い返すと意外と最初からそんな関係性だった気もします。</p> <p>メンバーがお互いの考え方や意見を尊重できていると思うので、誰から見ても悪くない雰囲気のチームなんじゃないかなと思ってます。</p> <h2>2021年の振り返り</h2> <p>それでは本題、2021年のフロントエンドチームの振り返りをしていきます。</p> <p>代表的なトピックに限定していくつかまとめてみます。</p> <h3>格安移動 - 海外航空券比較サービスのリリース</h3> <p>今年7月に、既存サービスの格安移動から派生する形で、海外航空券比較サービスをリリースしました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fair.idou.me%2F" title="海外格安航空券・LCC・飛行機の料金比較予約【格安移動】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://air.idou.me/">air.idou.me</a></cite></p> <p>開発に関してでいうと、事前の要件整理から技術選定等、2020年の10月頃から徐々に始めたので、約10ヶ月に長期に渡る、間違いなく2021年で最大のプロジェクトでした。</p> <p>フロントエンドチームにとっても、今のチーム体制になってからも最も大きなプロジェクトで、自分はメイン担当としてプロジェクトに関わりましたが、開発の準備・計画から始まり、多くの課題や苦労がありました。</p> <p>なにはともあれ無事リリースできたことが何より良かったですし、過ぎてみれば、大きな経験になったと感じています。</p> <p>技術的にも今後を見据えてNext.jsを採用する等、チームにとっても新たな挑戦をしました。Next.jsに関しては次項で記載するのでここでは割愛します。</p> <p>海外航空券のサービス自体は、まだリリースして日が浅いことや、社会情勢的にも海外旅行者も少ないので、利用ユーザーとしてはまだまだ少なく、これからに期待のサービスになります。</p> <p>今後、安心して海外を行き来できるようになったときに、少しでも利用ユーザーにとって有益なサービスになるように、今しかできないこと、やるべきことが山積みなので、サービスをより良いものに成長させていけるようにしていければと思ってます。</p> <h3>Next.jsの採用</h3> <p>上述の通り、海外航空券のサービスではNext.jsを採用しました。LCLでのサービスで本格的に採用したのは今回が初めてでした。</p> <p>今や、Next.jsは特にフロントエンド界隈では知らない人がいないくらいの人気のフレームワークかと思います。</p> <p>実際に使ってみても、開発体験がとても良く、従来の他のフレームワークに比べても、ストレスが少なく開発することができました。</p> <p>一方で、Next.jsを初めて使うこともあり、フレームワーク独自の仕様を理解し、実際に使いこなすのに苦労したポイントも多かったです。</p> <p>例えば、Next.jsで代表的な機能の一つである、SSR / SSGをページによってフレキシブルに実装できる、getStaticProps / getServerSideProps は、とても強力で比較的簡単に実装できる仕様にはなっているものの、サービスの仕様等、諸々を考慮して使おうとしたときに、かなり設計に苦労した印象がありました。</p> <p>他にも色々ありますが、記事の本筋からそれるので多くは書きませんが、Next.jsに限らず、新たな技術を採用するときは、未知との遭遇があり、想像以上に大変であることを実感しましたが、同時に良い経験にもなりました。</p> <p>今回のNext.jsの採用は、今後他サービスに展開していくための、将来を見据えてある種、実験的な意味合いも込めた技術選定だったので、結果的には狙い通り多くの知見がたまりました。今では他サービスへの展開もイメージできるようになったので、開発観点で見ても、大きな成果があったと感じています。</p> <h3>サービス別のチーム体制</h3> <p>一般的な形ではあると思いますが、LCLでは、ディレクター、デザイナー、エンジニア等、職種別に部署が分かれています。</p> <p>サービスには当然、様々な職種のメンバーも関わることになりますが、これまでサービス単位での明確なチーム分けがありませんでした。</p> <p>また、これまでは比較的ディレクターが中心に考えた企画・改善案ありきで、エンジニアが実装する流れになっていました。自社サービスの開発をしている企業では、今どきだと少し珍しい(?)のかもしれません。</p> <p>今後、職種問わず、よりサービスにコミットできるように、今年の後半から、各サービス単位で職種をミックスしたチーム体制を整えることになりました。</p> <p>まだ始めたばかりの体制で、チーム作り含めてこれから色々課題等も出てくると思いますが、各サービスのメンバーが協力して、サービスをより良いものに成長させられる体制が整ったように感じます。</p> <p>自分自身は格安移動の担当ですが、エンジニアからの要望や意見を、ディレクター・デザイナーに共有したり、逆に他職種のメンバーの意見を聞く機会が増えました。</p> <p>個人的には、何より特定のサービスにコミットできるようになったことが、モチベーション的にも良い効果があるように感じます。</p> <h2>2022年に向けて</h2> <p>フロントエンドチームとしても、各メンバーがサービスにコミットすることも重要ですが、これまで以上に連携を強固にして、チームとしてレベルアップしていく必要があると感じています。</p> <p>あえてひとつだけ挙げるとしたら、LCLでのメインサービスであるバス比較なびは、歴史も長く、大きな技術負債を抱えています。技術開発部としてもフロントエンドチームとしても、長年の課題としてバス比較なびのリファクタリングをする必要性を強く感じています。</p> <p>とても難しく大きな課題ですが、来年はこの長年の課題の突破口を開けるようにチーム全体で取り組んでいければと思います。</p> <p>会社全体としても、ユーザーにより良いサービスを提供していくために、様々なプランニングがあります。</p> <p>スピード感を持ってサービスを改善していくために、フロントエンドチームでも新しい仲間を募集しています。</p> <p>もし興味がある方は、まずはカジュアルに会話する機会を設けられますので、ぜひぜひ気軽な気持ちでご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> <p>それでは、だいぶ早いですが良いお年を。来年もがんばります。</p> kabrg aws-sdk-railsでSQSをActive Jobのアダプターとして使う hatenablog://entry/13574176438038800722 2021-12-04T00:00:00+09:00 2021-12-04T00:00:07+09:00 はじめに この記事はLCL Advent Calendar 2021 - 4日目です。 qiita.com バックエンドエンジニアの星野です。このアドベントカレンダー同じ人しかいないって?気のせいです。 LCLのバッチジョブ実行基盤解説記事の最後のエントリになります。 最終日はSQSとActive Jobについてです。この記事ではActive Jobそのものについては解説しませんのでRailsガイドを適宜参照してください。 railsguides.jp Ruby on Railsの非同期処理でActive Jobを使う場合はアダプターを選択する必要があります。 SidekiqやResqueなど… <h2>はじめに</h2> <p>この記事はLCL Advent Calendar 2021 - 4日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>バックエンドエンジニアの星野です。このアドベントカレンダー同じ人しかいないって?気のせいです。</p> <p>LCLのバッチジョブ実行基盤解説記事の最後のエントリになります。 最終日はSQSとActive Jobについてです。この記事ではActive Jobそのものについては解説しませんのでRailsガイドを適宜参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frailsguides.jp%2Factive_job_basics.html" title="Active Job の基礎 - Railsガイド" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://railsguides.jp/active_job_basics.html">railsguides.jp</a></cite></p> <p>Ruby on Railsの非同期処理でActive Jobを使う場合はアダプターを選択する必要があります。 SidekiqやResqueなどRedisをバックエンドにしたアダプターが人気がありますがRedisの管理をしなければならず、シンプルに<code>perform_later</code>したいだけの要件に対して大掛かりになりすぎることがあります。<a href="#f-d29ea3cd" name="fn-d29ea3cd" title="ElastiCacheやMemoryDB for Redisのマネージドサービスもありますがインスタンスの管理は依然として必要になるので気軽には手を出しにくいと感じています。">*1</a></p> <p>そこでフルマネージド型のメッセージキューイングサービスであるAmazon SQSをActive Jobのアダプターとして使うことで運用の手軽さと可用性の両方を手に入れます。</p> <p>LCLではActive JobをSQSのアダプターとして使うためにaws-sdk-railsを選択しました。 類似のgemとして<a href="https://github.com/ruby-shoryuken/shoryuken">Shoryuken</a>がありますが、aws-sdk-railsのコードベースが小さく処理が追いやすいため扱いやすいと感じています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Faws%2Faws-sdk-rails%23aws-sqs-active-job" title="GitHub - aws/aws-sdk-rails: Official repository for the aws-sdk-rails gem, which integrates the AWS SDK for Ruby with Ruby on Rails." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/aws/aws-sdk-rails#aws-sqs-active-job">github.com</a></cite></p> <h2>設定方法</h2> <h3>SQSキューの作成</h3> <p>はじめにSQSキューを作成します。Terraformで作成する場合はこれだけです。</p> <pre class="code" data-lang="" data-unlink>resource &#34;aws_sqs_queue&#34; &#34;active_job&#34; { name = &#34;active-job&#34; redrive_policy = jsonencode({ deadLetterTargetArn = aws_sqs_queue.active_job_dlq.arn maxReceiveCount = 5 }) }</pre> <p><code>redrive_policy</code>でリトライ回数とエラー時のDLQは設定しておくと役に立つでしょう。 SQSを設定する際に調整が必要になることの多い<code>visibility_timeout_seconds</code>や<code>receive_wait_time_seconds</code>は<a href="https://github.com/aws/aws-sdk-rails/blob/main/lib/aws/rails/sqs_active_job/configuration.rb#L29">aws-sdk-rails</a>と内部で使用している<a href="https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-sqs/lib/aws-sdk-sqs/queue_poller.rb#L486">aws-sdk-ruby</a>でデフォルト値が設定されているので不要です。</p> <h3>Railsの設定</h3> <p>動かすために必要な最低限の設定はとてもシンプルでREADMEの通りに設定すれば難しいところはありません。 aws-sdk-railsをGemfileに追加してqueue_adapterで<code>:amazon_sqs</code>を選択します。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># config/application.rb</span> <span class="synPreProc">module</span> <span class="synType">YourApp</span> <span class="synPreProc">class</span> <span class="synType">Application</span> &lt; <span class="synType">Rails</span>::<span class="synType">Application</span> config.active_job.queue_adapter = <span class="synConstant">:amazon_sqs</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> </pre> <p>作成したSQSキューをconfigに書きます。ハードコードせずに環境変数でセットするのも良いでしょう。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synComment"># config/aws_sqs_active_job.yml</span> <span class="synIdentifier">queues</span><span class="synSpecial">:</span> <span class="synIdentifier">default</span><span class="synSpecial">:</span> <span class="synConstant">'https://sqs.ap-northeast-1.amazonaws.com/123456789012/active-job'</span> </pre> <p>環境によってはSQSクライアントも設定が必要になるので<a href="https://github.com/aws/aws-sdk-rails#configuration-1">configuration</a>の項を見ながら設定しましょう。</p> <h3>SQSメッセージを取得するためのIAMポリシー</h3> <p>Active Jobのプロセスに必要な最小のIAMポリシーは次の通りです。 こちらはドキュメントに記載がありませんでしたがgemが呼び出しているメソッドを調査して絞り込みました。もし不足している権限がありましたら指摘いただけると助かります。</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">Version</span>&quot;: &quot;<span class="synConstant">2012-10-17</span>&quot;, &quot;<span class="synStatement">Statement</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">Sid</span>&quot;: &quot;<span class="synConstant">1</span>&quot;, &quot;<span class="synStatement">Effect</span>&quot;: &quot;<span class="synConstant">Allow</span>&quot;, &quot;<span class="synStatement">Action</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">sqs:ReceiveMessage</span>&quot;, &quot;<span class="synConstant">sqs:DeleteMessageBatch</span>&quot;, &quot;<span class="synConstant">sqs:DeleteMessage</span>&quot;, &quot;<span class="synConstant">sqs:ChangeMessageVisibility</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">Resource</span>&quot;: &quot;<span class="synConstant">arn:aws:sqs:ap-northeast-1:123456789012/active-job</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> <p>あとはEC2インスタンスやECSタスクで上記IAMポリシーをアタッチしたIAMロールを設定してaws_sqs_active_jobを起動すれば開始できます。</p> <pre class="code shell" data-lang="shell" data-unlink>bundle exec aws_sqs_active_job --queue default</pre> <p>LCLではFargate Spotのみで構成したECSサービスをオートスケールしながらプロセスを起動しています。 SQSキューにリトライ機構が備わっているので、処理中にSpotの中断やスケールインによるエラーの発生を気にする事なく安価に高いパフォーマンスを発揮しています。</p> <h2>Active Jobを利用する</h2> <p>設定が完了したらRailsからは<code>perform_later()</code>を呼び出すだけです。この時呼び出し側は<code>sqs:sendMessage</code>のIAMポリシーが必要です</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">AwesomeJob</span>.perform_later(record) </pre> <p><figure class="figure-image figure-image-fotolife" title="さまざまな環境から呼び出せます"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosht/20211203/20211203171724.png" alt="f:id:hosht:20211203171724p:plain" width="519" height="197" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>さまざまな環境で実行されているRailsから呼び出せます</figcaption></figure></p> <p>また、SQSにsendMessageすることができれば送信元はRailsである必要がないためAWS CLIからActive Jobを実行することも可能です。</p> <p>SQSに送るJSONを<a href="https://api.rubyonrails.org/classes/ActiveJob/Core.html">ActiveJob::CoreのAttributes</a>に合わせます。最低必要なattributeは次の4つです。</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">queue_name</span>&quot;: &quot;<span class="synConstant">default</span>&quot;, &quot;<span class="synStatement">job_class</span>&quot;: <span class="synError">String</span>, &quot;<span class="synStatement">job_id</span>&quot;: <span class="synError">String</span>, &quot;<span class="synStatement">arguments</span>&quot;: <span class="synSpecial">[</span> <span class="synError">String</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> <table> <thead> <tr> <th>key</th> <th>value</th> </tr> </thead> <tbody> <tr> <td>queue_name</td> <td>基本defaultで固定。起動時に<code>--queue</code>オプションで変更している場合はそれに合わせる</td> </tr> <tr> <td>job_class</td> <td>Active Jobのクラス名を指定</td> </tr> <tr> <td>job_id</td> <td>任意の文字列</td> </tr> <tr> <td>arguments</td> <td>Jobで処理する文字列。SQSの256KB制限まで詰め込める</td> </tr> </tbody> </table> <p>例として<code>{"foo": "bar"}</code>をActive Jobで処理したい場合のメッセージは次のようになります。</p> <pre class="code lang-json" data-lang="json" data-unlink># <span class="synError">message</span>.<span class="synError">json</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">queue_name</span>&quot;: &quot;<span class="synConstant">default</span>&quot;, &quot;<span class="synStatement">job_class</span>&quot;: &quot;<span class="synConstant">AwesomeJob</span>&quot;, &quot;<span class="synStatement">job_id</span>&quot;: &quot;<span class="synConstant">12345167890</span>&quot;, &quot;<span class="synStatement">arguments</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synStatement">{\n \&quot;foo\</span>&quot;: \&quot;<span class="synConstant">bar</span><span class="synSpecial">\&quot;\n</span><span class="synConstant">}</span>&quot; <span class="synSpecial">]</span> <span class="synSpecial">}</span> </pre> <p>このJSONをAWS CLIでエンキュー可能です。</p> <pre class="code" data-lang="" data-unlink>aws sqs send-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/active-job --message-body file:///message.json</pre> <p>さらに応用としてEventBridgeを利用してSQSにメッセージが送信することで全てのAWSサービスからActive Jobをトリガーすることが可能です。</p> <p>例えばSESでメールを受信したらS3にメールを保存して、そのイベントをInput Transformerで上記のJSONに整形してSQSに送ることでAction Mailboxのように動かすことができたりします。</p> <p><figure class="figure-image figure-image-fotolife" title="SES → S3 → EventBridge → SQS → ECS(Active Job) → Aurora"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosht/20211203/20211203173944.png" alt="f:id:hosht:20211203173944p:plain" width="879" height="79" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SES → S3 → EventBridge → SQS → ECS(Active Job) → Aurora</figcaption></figure></p> <p>SQSの前にEventBridgeを挟むことでイベントのプロデューサーはコンシューマーのことを気にせずただイベントを発行するだけで良くなります。そうすることで各コンポーネントを疎結合にしてRailsのモノリシックアークテクチャの恩恵を受けながらイベント駆動パターンを実装しやすくなります。</p> <h2>まとめ</h2> <p>SQSでActive Jobを利用する方法について紹介しました。 LCLでは本番環境で直列で実行した場合に10時間を超えるバッチジョブを分割して非同期かつ並列に実行することで、毎日大量のデータを扱うことができています。</p> <p>今後はActive Jobに移行できていない大型のバッチジョブの移行を目標にしています。</p> <h2>採用情報</h2> <p>LCLでは開発メンバーを募集しております!</p> <p>もし興味をお持ちになりましたらお気軽に応募してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-d29ea3cd" name="f-d29ea3cd" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ElastiCacheやMemoryDB for Redisのマネージドサービスもありますがインスタンスの管理は依然として必要になるので気軽には手を出しにくいと感じています。</span></p> </div> hosht Ruby on Rails on Container on Lambda with Step Functions Express Workflow hatenablog://entry/13574176438038690685 2021-12-03T07:00:00+09:00 2021-12-03T17:56:57+09:00 はじめに この記事はLCL Advent Calendar 2021 - 3日目です。 qiita.com バックエンドエンジニアの星野です。ブログの締め切りが最高に盛り上がっています。🤘*1 昨日のCodeBuildのカスタムイメージに引き続きLCLのバッチジョブ実行基盤の実装パターン解説になります。 今日はContainer on Lambdaです。 この記事中ではContainer on Lambdaと言う場合AWS Lambdaのコンテナイメージサポートのことを指します。 aws.amazon.com この機能によってDockerイメージをそのままLambdaで実行することができます。… <h2>はじめに</h2> <p>この記事はLCL Advent Calendar 2021 - 3日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>バックエンドエンジニアの星野です。ブログの締め切りが最高に盛り上がっています。🤘<a href="#f-4a1eee09" name="fn-4a1eee09" title="Qiita Advent CalandarのAboutを参照 https://qiita.com/advent-calendar/2021">*1</a></p> <p>昨日のCodeBuildのカスタムイメージに引き続きLCLのバッチジョブ実行基盤の実装パターン解説になります。</p> <p>今日はContainer on Lambdaです。 この記事中ではContainer on Lambdaと言う場合AWS Lambdaのコンテナイメージサポートのことを指します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fblogs%2Fnews%2Fnew-for-aws-lambda-container-image-support%2F" title="AWS Lambda の新機能 – コンテナイメージのサポート | Amazon Web Services" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/blogs/news/new-for-aws-lambda-container-image-support/">aws.amazon.com</a></cite></p> <p>この機能によってDockerイメージをそのままLambdaで実行することができます。 Ruby on Railsを動かす場合の工夫やStep Functions Express Workflowを組み合わせる事による運用支援について解説していきます。</p> <h2>コンテナをLambdaで動かすメリット</h2> <p>AWS LambdaはRubyの実行を公式にサポートしているためわざわざコンテナで動かすのは手間ではないかと考えるかもしれません。 LCLの場合は多くのワークロードをECSで実行しており扱い慣れているDockerイメージをそのまま使い回せることが大きなメリットになります。 Lambdaで動かすためのRubyコードを別リポジトリで用意する必要もないのでコードが二重に管理されていてメンテナンスコストが上がってしまうこともありません。</p> <p>また、サーバーレスなコンテナ実行環境であるFargateやCodeBuildでは実行毎にDockerイメージをpullしてくるため起動の遅さがネックになります。 Container on Lambdaは内部的にキャッシュの仕組みがあるらしくコールドスタートした場合でも1~2秒で実行が始まります。 コールドスタートしない場合はミリ秒単位で開始するの極めて高速です。 この特性によってFargateやCodeBuildでは起動時間のバラツキにより要件を満たすのが難しかった3分に1回実行のような高頻度で実行するバッチジョブに対応可能です。<a href="#f-bb1c6eaa" name="fn-bb1c6eaa" title="3分に1回バッチジョブを動かすような要件を見直すべきと言う意見が聞こえてきそうですが歴史的なアレやコレでそうなることがあります">*2</a></p> <h2>Ruby on RailsのDockerイメージをLambdaに対応させる</h2> <p>Container on Lambdaは同じコンテナでも実行モデルが異なるため、追加の設定を少しだけ加えます。 Rubyを動かす場合はaws_lambda_ric gemをインストールする必要があり、RailsではGemfileに1行追加するだけでOKです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Faws%2Faws-lambda-ruby-runtime-interface-client" title="GitHub - aws/aws-lambda-ruby-runtime-interface-client" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/aws/aws-lambda-ruby-runtime-interface-client">github.com</a></cite></p> <p>docker runで動かすためのENTRYPOINTやCMDはリポジトリのサンプルコードのようにRuntime Interface Clientに合わせる必要があります。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">module</span> <span class="synType">App</span> <span class="synPreProc">class</span> <span class="synType">Handler</span> <span class="synPreProc">def</span> <span class="synConstant">self</span>.<span class="synIdentifier">process</span>(<span class="synConstant">event</span>:, <span class="synConstant">context</span>:) <span class="synSpecial">&quot;</span><span class="synConstant">Hello World!</span><span class="synSpecial">&quot;</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> </pre> <pre class="code Dockerfile" data-lang="Dockerfile" data-unlink>ENTRYPOINT [&#34;/usr/local/bin/aws_lambda_ric&#34;] CMD [&#34;app.App::Handler.process&#34;]</pre> <p>このままでは実行したいrails runnerやrakeタスクや複数ある場合にapp.rbファイルを量産しなければなりません。 これを回避するためにApp::Handler.processを次のように変更しています。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink> <span class="synPreProc">require_relative</span> <span class="synSpecial">'</span><span class="synConstant">../../config/environment</span><span class="synSpecial">'</span> <span class="synPreProc">module</span> <span class="synType">LambdaFunction</span> <span class="synPreProc">class</span> <span class="synType">RailsRunnerHandler</span> <span class="synPreProc">def</span> <span class="synConstant">self</span>.<span class="synIdentifier">process</span>(<span class="synConstant">event</span>:, <span class="synConstant">context</span>:) task = event[<span class="synSpecial">'</span><span class="synConstant">task</span><span class="synSpecial">'</span>] <span class="synStatement">eval</span>(<span class="synSpecial">&quot;#{</span>task<span class="synSpecial">}&quot;</span>) <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> </pre> <p>ターゲットはRailsアプリなのでprocessメソッドを実行前に<code>require_relative '../../config/environment'</code>することでrailsの環境を初期化します。 次にLambda関数実行時に渡したpayloadはeventのハッシュで取得できるので今回はtaskをキーにして実行するコマンドの文字列を渡してevalで実行しています。 module名とclass名は分かりやすくするために変更して、<code>${RAILS_ROOT}/lambda/rails_runner_handler/app.rb</code>に設置しています。</p> <p>開発者はこのイメージをデプロイしたLambda関数に対して任意のコマンドを実行できます。</p> <pre class="code shell" data-lang="shell" data-unlink>% aws lambda invoke \ --function-name rails-runner-function \ --payload &#39;{&#34;task&#34;: &#34;User.count&#34;}&#39; \ --cli-binary-format raw-in-base64-out \ /dev/null</pre> <p>rakeタスクの場合は<code>Rails.application.load_tasks</code>を呼んでからevalの代わりにinvokeします。 Container on Lambdaは一度関数が起動したら環境が終了するまでaws_lambda_ricのプロセスが動き続けるため、invoke後にreenableしないと同じコマンドを再実行できません。 この挙動はLambdaの実行とrakeタスクの挙動の両方を注意深く観察する必要がありややこしいところですが、とりあえずreenableするのを忘れなければOKです。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">'</span><span class="synConstant">rake</span><span class="synSpecial">'</span> <span class="synPreProc">require_relative</span> <span class="synSpecial">'</span><span class="synConstant">../../config/environment</span><span class="synSpecial">'</span> <span class="synType">Rails</span>.application.load_tasks <span class="synPreProc">module</span> <span class="synType">LambdaFunction</span> <span class="synPreProc">class</span> <span class="synType">RakeTaskHandler</span> <span class="synPreProc">def</span> <span class="synConstant">self</span>.<span class="synIdentifier">process</span>(<span class="synConstant">event</span>:, <span class="synConstant">context</span>:) task = event[<span class="synSpecial">'</span><span class="synConstant">task</span><span class="synSpecial">'</span>] <span class="synType">Rake</span>::<span class="synType">Task</span>[<span class="synSpecial">&quot;#{</span>task<span class="synSpecial">}&quot;</span>].invoke <span class="synType">Rake</span>::<span class="synType">Task</span>[<span class="synSpecial">&quot;#{</span>task<span class="synSpecial">}&quot;</span>].reenable <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> </pre> <p>rails runnerと同様にtaskにrakeタスクのコマンドの文字列を渡して実行します。</p> <pre class="code shell" data-lang="shell" data-unlink>% aws lambda invoke \ --function-name rake-task-function \ --payload &#39;{&#34;task&#34;: &#34;user::count&#34;}&#39; \ --cli-binary-format raw-in-base64-out \ /dev/null</pre> <h3>LambdaでSSMパラメータを取得する</h3> <p>ECSやCodeBuildではデータベースのパスワードなど秘匿情報をSSMパラメータから環境変数として読み込むことができますが、Lambdaにはその仕組みが備わっていないため自前で実装する必要があります。 前述のapp.rbの中で必要なSSMパラメータを列挙して読み込む実装では変更があった場合に修正漏れでバグを生みやすいです。</p> <p>SSMパラメータに命名規則を持ち込むことでこの問題に対応します。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">'</span><span class="synConstant">aws-sdk-ssm</span><span class="synSpecial">'</span> <span class="synComment"># SSMパラメータの取得</span> <span class="synType">Aws</span>::<span class="synType">SSM</span>::<span class="synType">Client</span>.new.get_parameters_by_path(<span class="synConstant">path</span>: <span class="synSpecial">&quot;</span><span class="synConstant">/awesome-service/app/rails/</span><span class="synSpecial">&quot;</span>, <span class="synConstant">recursive</span>: <span class="synConstant">true</span>, <span class="synConstant">with_decryption</span>: <span class="synConstant">true</span>).parameters.each <span class="synStatement">do</span> |<span class="synIdentifier">param</span>| <span class="synIdentifier">ENV</span>[param[<span class="synSpecial">'</span><span class="synConstant">name</span><span class="synSpecial">'</span>].split(<span class="synSpecial">'</span><span class="synConstant">/</span><span class="synSpecial">'</span>).last] ||= param[<span class="synSpecial">'</span><span class="synConstant">value</span><span class="synSpecial">'</span>] <span class="synStatement">end</span> </pre> <p>get_parameters_by_pathメソッドはrecursive: trueをつけることで指定した文字列に沿ってrecursiveにパラメータを取得することができます。 この時パラメータ名の末尾を<code>/awesome-service/app/rails/DATABASE_PASSWORD</code>や<code>/awesome-service/app/rails/SECRET_KEY_BASE</code>などあらかじめRailsで利用する環境変数名にしておくことで動的に環境変数のKey/Valueのペアを生成することができます。</p> <p>この実装をするにあたりこちらのBlog記事を参考にしました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.honeybadger.io%2Fblog%2Fconfiguration-with-ssm-parameter-store%2F" title="Configure Your App with SSM Parameter Store" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.honeybadger.io/blog/configuration-with-ssm-parameter-store/">www.honeybadger.io</a></cite></p> <p>最終的にapp.rbはこのようになります。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">'</span><span class="synConstant">aws-sdk-ssm</span><span class="synSpecial">'</span> <span class="synComment"># SSMパラメータの取得</span> <span class="synType">Aws</span>::<span class="synType">SSM</span>::<span class="synType">Client</span>.new.get_parameters_by_path(<span class="synConstant">path</span>: <span class="synSpecial">&quot;</span><span class="synConstant">/awesome-service/app/rails/</span><span class="synSpecial">&quot;</span>, <span class="synConstant">recursive</span>: <span class="synConstant">true</span>, <span class="synConstant">with_decryption</span>: <span class="synConstant">true</span>).parameters.each <span class="synStatement">do</span> |<span class="synIdentifier">param</span>| <span class="synIdentifier">ENV</span>[param[<span class="synSpecial">'</span><span class="synConstant">name</span><span class="synSpecial">'</span>].split(<span class="synSpecial">'</span><span class="synConstant">/</span><span class="synSpecial">'</span>).last] ||= param[<span class="synSpecial">'</span><span class="synConstant">value</span><span class="synSpecial">'</span>] <span class="synStatement">end</span> <span class="synPreProc">require_relative</span> <span class="synSpecial">'</span><span class="synConstant">../../config/environment</span><span class="synSpecial">'</span> <span class="synPreProc">module</span> <span class="synType">LambdaFunction</span> <span class="synPreProc">class</span> <span class="synType">RailsRunnerHandler</span> <span class="synPreProc">def</span> <span class="synConstant">self</span>.<span class="synIdentifier">process</span>(<span class="synConstant">event</span>:, <span class="synConstant">context</span>:) task = event[<span class="synSpecial">'</span><span class="synConstant">task</span><span class="synSpecial">'</span>] <span class="synStatement">eval</span>(<span class="synSpecial">&quot;#{</span>task<span class="synSpecial">}&quot;</span>) <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> </pre> <h2>Step Functions Express WorkflowとLambdaを組み合わてエラーハンドリングを実装する</h2> <p>Lambdaには実行エラーを通知するための仕組みが複数用意されています。 今回は成功時と失敗時にそれぞれEventBridgeに通知して通知先については任意に選択したかったのでStep Functionsを採用しました。</p> <p>LambdaからEventBridgeに通知するだけであれば<a href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invocation-async.html#invocation-async-destinations">Lambdaの送信先</a>(Destination)を使うことでも実装できますが正常終了時のレスポンスをEventBridgeのスキーマに合わせる必要があり、わずかながらコードの修正が必要だったので今回は使いませんでした。</p> <p>Step Functionsの標準Workflowはデフォルトでステートの変更毎にEventBridgeに通知してくれるのですが、実行頻度の多いContainer on Lambdaのバッチジョブと組み合わせると費用が高く着いてしまうのでExpress Workflowを使います。<a href="#f-052bbe9c" name="fn-052bbe9c" title="Express Workflowは最大実行時間がLambdaの15分より短い5分なため注意が必要です。">*3</a>Express Worflowは実行毎の費用が安い代わりにステート変更の通知が省かれているのでWorkflowの中で通知します。</p> <p><figure class="figure-image figure-image-fotolife" title="Workflow Studioによる図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosht/20211202/20211202155959.png" alt="f:id:hosht:20211202155959p:plain" width="1200" height="810" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Workflow Studioによる図</figcaption></figure></p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">Comment</span>&quot;: &quot;<span class="synConstant">Lambda with EventBridge</span>&quot;, &quot;<span class="synStatement">StartAt</span>&quot;: &quot;<span class="synConstant">Lambda Invoke</span>&quot;, &quot;<span class="synStatement">States</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Lambda Invoke</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Type</span>&quot;: &quot;<span class="synConstant">Task</span>&quot;, &quot;<span class="synStatement">Resource</span>&quot;: &quot;<span class="synConstant">arn:aws:states:::lambda:invoke</span>&quot;, &quot;<span class="synStatement">Parameters</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Payload.$</span>&quot;: &quot;<span class="synConstant">$</span>&quot;, &quot;<span class="synStatement">FunctionName</span>&quot;: &quot;<span class="synConstant">${function_name}</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">Retry</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">ErrorEquals</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">States.ALL</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">IntervalSeconds</span>&quot;: <span class="synConstant">1</span>, &quot;<span class="synStatement">MaxAttempts</span>&quot;: <span class="synConstant">2</span> <span class="synSpecial">}</span> <span class="synSpecial">]</span>, &quot;<span class="synStatement">Catch</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">ErrorEquals</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">States.ALL</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">ResultPath</span>&quot;: &quot;<span class="synConstant">$.error</span>&quot;, &quot;<span class="synStatement">Next</span>&quot;: &quot;<span class="synConstant">EventBridge PutEvents Error</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span>, &quot;<span class="synStatement">Next</span>&quot;: &quot;<span class="synConstant">EventBridge PutEvents Success</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">EventBridge PutEvents Success</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Type</span>&quot;: &quot;<span class="synConstant">Task</span>&quot;, &quot;<span class="synStatement">Resource</span>&quot;: &quot;<span class="synConstant">arn:aws:states:::events:putEvents</span>&quot;, &quot;<span class="synStatement">Parameters</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Entries</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">Detail</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Payload.$</span>&quot;: &quot;<span class="synConstant">$</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">DetailType</span>&quot;: &quot;<span class="synConstant">Lambda execution succeed</span>&quot;, &quot;<span class="synStatement">EventBusName</span>&quot;: &quot;<span class="synConstant">custom-eventbus</span>&quot;, &quot;<span class="synStatement">Source</span>&quot;: &quot;<span class="synConstant">lambda</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">Next</span>&quot;: &quot;<span class="synConstant">Success</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">Success</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Type</span>&quot;: &quot;<span class="synConstant">Succeed</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">EventBridge PutEvents Error</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Type</span>&quot;: &quot;<span class="synConstant">Task</span>&quot;, &quot;<span class="synStatement">Resource</span>&quot;: &quot;<span class="synConstant">arn:aws:states:::events:putEvents</span>&quot;, &quot;<span class="synStatement">Parameters</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Entries</span>&quot;: <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">Detail</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Payload.$</span>&quot;: &quot;<span class="synConstant">$</span>&quot;, &quot;<span class="synStatement">Error.$</span>&quot;: &quot;<span class="synConstant">$.error</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">DetailType</span>&quot;: &quot;<span class="synConstant">Lambda execution failed</span>&quot;, &quot;<span class="synStatement">EventBusName</span>&quot;: &quot;<span class="synConstant">custom-eventbus</span>&quot;, &quot;<span class="synStatement">Source</span>&quot;: &quot;<span class="synConstant">lambda</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">Next</span>&quot;: &quot;<span class="synConstant">Fail</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">Fail</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">Type</span>&quot;: &quot;<span class="synConstant">Fail</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>Lambda実行の成功・失敗時にそれぞれEventBridgeへ通知するWorkflowはこのようになります。 後ほど通知に使いたいエラーメッセージをDetailに含めています。</p> <h2>TerraformでContaienr on LambdaとStep Functionsを実装する</h2> <p>最後にLCLはTerraformによるコード化を推進しているためこれまで説明してきたリソースを作成するサンプルコードを掲載します。</p> <pre class="code" data-lang="" data-unlink>resource &#34;aws_lambda_function&#34; &#34;rails_runner&#34; { function_name = &#34;lambda-rails-runner&#34; role = aws_iam_role.lambda.arn package_type = &#34;Image&#34; image_uri = &#34;awesome-rails-image:prod&#34; memory_size = 1024 timeout = 900 vpc_config { security_group_ids = [&#34;security_group_id&#34;] subnet_ids = [&#34;subnet_id&#34;] } image_config { command = [&#34;app.LambdaFunction::RailsRunnerHandler.process&#34;] entry_point = [&#34;aws_lambda_ric&#34;] working_directory = &#34;/app/lambda/rails_runner_handler&#34; } environment { variables = { RAILS_ENV = &#34;production&#34; } } lifecycle { ignore_changes = [image_uri] } } resource &#34;aws_sfn_state_machine&#34; &#34;rails_runner&#34; { name = &#34;lambda-rails-runner&#34; type = &#34;EXPRESS&#34; role_arn = aws_iam_role.step_functions.arn definition = templatefile(&#34;step_functions_workflow_lambda.json.tpl&#34;, { function_name = &#34;${aws_lambda_function.rails_runner.arn}:${aws_lambda_function.rails_runner.version}&#34; }) logging_configuration { include_execution_data = true level = &#34;ERROR&#34; log_destination = &#34;${aws_cloudwatch_log_group.step_functions.arn}:*&#34; } }</pre> <p>IAMやセキュリティグループなど本題と関係のない一部リソースは省略しています。</p> <p>コンテナで動かす場合のポイントはimage_configで、command、entrypoint、working_directoryをそれぞれ指定します。Dockerfileで指定することもできますが、LCLではWebサーバーにも利用するDockerイメージと共通化するためにDockerfileには記載していません。 image_uriはアプリケーションのCI/CDの中ででタグを更新するため初回作成以降はlifecycleでignoreします。</p> <p>Step Functions側は前述のworkflowのjsonファイルをtemplatefile関数で読み込みます。</p> <h2>まとめ</h2> <p>DockerイメージをLambdaで実行することでこれまでのサーバーレスな実行環境では難しかった高速に起動するバッチジョブ実行基盤が実装できました。Railsを動かす場合もLambda handerの実装を少しだけ工夫することで開発体験を大きく変えずにバッチジョブを呼べるようになります。</p> <p>Railsに限らずLambdaがサポートしている言語であればContainer on Lambdaは手軽に始めることができるの是非活用してみてください。</p> <h2>採用情報</h2> <p>LCLでは開発メンバーを募集しております!</p> <p>もし興味をお持ちになりましたらお気軽に応募してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-4a1eee09" name="f-4a1eee09" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">Qiita Advent CalandarのAboutを参照 <a href="https://qiita.com/advent-calendar/2021">https://qiita.com/advent-calendar/2021</a></span></p> <p class="footnote"><a href="#fn-bb1c6eaa" name="f-bb1c6eaa" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">3分に1回バッチジョブを動かすような要件を見直すべきと言う意見が聞こえてきそうですが歴史的なアレやコレでそうなることがあります</span></p> <p class="footnote"><a href="#fn-052bbe9c" name="f-052bbe9c" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">Express Workflowは最大実行時間がLambdaの15分より短い5分なため注意が必要です。</span></p> </div> hosht カスタムイメージでCodeBuildをバッチジョブ実行基盤として使う hatenablog://entry/13574176438038357690 2021-12-02T07:00:00+09:00 2021-12-03T18:01:11+09:00 はじめに この記事はLCL Advent Calendar 2021 - 2日目です。 qiita.com バックエンドエンジニアの星野です。アドベントカレンダーを4日連続にしたことについて2日目ですでにギリギリです。 昨日の記事ではバッチジョブ実行基盤のパターンを紹介しましたので今日はその続きになります。 techblog.lclco.com 最初はCodeBuildのカスタムイメージです。 Fargateと比較した際のポイントやバッチジョブをTerraformから作成しやすくするための工夫について解説していきます。 CodeBuildをカスタムイメージで利用する CodeBuildはAWS… <h2>はじめに</h2> <p>この記事はLCL Advent Calendar 2021 - 2日目です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2021%2Flcl" title="Calendar for LCL | Advent Calendar 2021 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2021/lcl">qiita.com</a></cite></p> <p>バックエンドエンジニアの星野です。アドベントカレンダーを4日連続にしたことについて2日目ですでにギリギリです。</p> <p>昨日の記事ではバッチジョブ実行基盤のパターンを紹介しましたので今日はその続きになります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.lclco.com%2Fentry%2F2021%2F12%2F01%2F070000" title="LCLのRailsバッチジョブ実行基盤 on AWS 21年秋冬版 - LCL Engineers&#39; Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://techblog.lclco.com/entry/2021/12/01/070000">techblog.lclco.com</a></cite></p> <p>最初はCodeBuildのカスタムイメージです。 Fargateと比較した際のポイントやバッチジョブをTerraformから作成しやすくするための工夫について解説していきます。</p> <h2>CodeBuildをカスタムイメージで利用する</h2> <p>CodeBuildはAWSから提供されているDockerイメージの他にPublic RegistryやECRから任意のDockerイメージをビルド環境として使うことができます。 LCLが利用しているフレームワークであるRuby on Railsですとgemをインストール済みのDockerイメージを指定することでrails runnerやrakeタスクをすぐに実行することができます。 また、VPCをサポートしているためデータベースなどVPC内のリソースに接続するなどアプリケーションのビルドに囚われずなんでもできます。</p> <h3>CodeBuild VS Fargate</h3> <p>この用途ですとFargateを利用して単発のタスクを実行するの大きくは変わらないように聞こえます。 単発の実行においてCodeBuildがFargateよりも扱いやすいポイントを挙げていきます。</p> <h4>1. 実行コマンドの指定</h4> <p>CodeBuild: buildspec.ymlに記載</p> <p>Fargate: Dockerfileに記載したentrypointやcommandまたはタスク定義によるそれらの上書き</p> <p>Fargateの場合はDockerの作法にしたがってentrypointやcommandに実行コマンドを指定しますが1回の起動で複数のコマンドを実行する必要がある場合は別途シェルスクリプトなどを用意することがあるでしょう。 一方CodeBuildはbuildspec.ymlによって実行コマンドを連続して記載できるので追加ファイルなど不要で設定することができます。</p> <h4>2. コンテナの起動(コンソール)</h4> <p>CodeBuild: Start Buildボタン</p> <p>Fargate: タスク定義の画面からDeployボタンを押下した後にクラスタやVPCの選択が必要</p> <p>FargateはECSの性質上コンテナを起動する場合はタスク定義からクラスタやVPCの設定がする必要がある一方CodeBuildはボタンで一発です。 FargateでもStep Functionsから実行するなど工夫次第で解決することはできますがCodeBuildのみで設定が完結するのは大きいです。</p> <p><figure class="figure-image figure-image-fotolife" title="CodeBuildはボタンを押すだけ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosht/20211201/20211201121703.png" alt="f:id:hosht:20211201121703p:plain" width="1200" height="196" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CodeBuildはボタンを押すだけ</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="ECSは追加でネットワークなどを指定する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosht/20211201/20211201121739.png" alt="f:id:hosht:20211201121739p:plain" width="869" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ECSは追加でネットワークなどを指定する</figcaption></figure></p> <h4>3. コンテナの起動(CLI)</h4> <p>CodeBuild: <code>aws codebuild start-build</code>に<code>--project-name</code>をオプションで渡す</p> <p>Fargate: <code>aws ecs run-task</code>に<code>--task-definition</code>に加えて<code>--network-configuration</code>をオプションで渡す</p> <p><a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecs/run-task.html">run-taskのドキュメント</a>には<code>--network-configuration</code>が必須オプションではない記述ですがFargateはネットワークモードがawsvpc固定なので必須のオプションになります</p> <p>コンソールと同様に1度作成してしまえばCodeBuildの方が少ないステップで起動まで進むことができます。</p> <h4>4. 実行履歴とログ出力</h4> <p>CodeBuild: 実行履歴は永続化される。ログはCloudWatch LogsかS3を選択可能でコンソール画面から見やすい</p> <p>Fargate: 実行履歴は最低1時間保持された後に削除対象。ログはCloudWatch LogsまたはFireLensで柔軟に設定可能。コンソールの出来は新バージョンも後一歩な印象</p> <p>実行履歴がFaragateは1時間以上経過すると削除されてしまうのに対しCodeBuildはデフォルトでは無制限に保存されます。 ログ出力は両者ともCloudWatch Logsが使えて、CodeBuildは他にS3のみ、FargateはFireLensで柔軟に設定可能なためここはFaragateが優れているポイントです。 ただしコンソールから参照する場合はCodeBuildはリアルタイムで表示できたりUIがこなれているためCodeBuildの方がデフォルトで扱いやすい印象です。 Fargateの場合は外部SaaSやAthenaなど多少の作り込みが必要なため要件やコストとのトレードオフなると思われます。</p> <p><figure class="figure-image figure-image-fotolife" title="CodeBuildは実行履歴を遡って参照しやすい"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosht/20211201/20211201122405.png" alt="f:id:hosht:20211201122405p:plain" width="1200" height="712" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CodeBuildは実行履歴を遡って参照しやすい</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="CodeBuildは実行単位でログ出力を参照しやすい"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosht/20211201/20211201122435.png" alt="f:id:hosht:20211201122435p:plain" width="1200" height="596" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CodeBuildは実行単位でログ出力を参照しやすい</figcaption></figure></p> <h4>5. バッチビルド</h4> <p>CodeBuild: 指定したビルドの完了後に次のビルドを起動するバッチビルドを定義可能</p> <p>Fargate: Step Functionsなど他のサービスと連携が必要</p> <p>CodeBuildは単体でバッチビルドができます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.aws.amazon.com%2Fja_jp%2Fcodebuild%2Flatest%2Fuserguide%2Fbatch-build.html" title="AWS CodeBuild でのバッチビルド - AWS CodeBuild" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/batch-build.html">docs.aws.amazon.com</a></cite></p> <p>バッチビルドにより簡単なワークフローを作成することができます。Fargateは単体ではこのような機能は提供されていません。</p> <h4>6. EventBridgeに通知するイベント</h4> <p>CodeBuild: イベントにCloudWatch Logsのリンクを含む<a href="#f-d298ff9c" name="fn-d298ff9c" title="https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-build-notifications.html#sample-build-notifications-ref">*1</a></p> <p>Fargate: イベントにCloudWatch Logsのリンクは含まれない<a href="#f-76b11ec4" name="fn-76b11ec4" title="https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs_cwe_events.html#ecs_task_events">*2</a></p> <p>細かいところですがCodeBuildは状態変更イベントをEventBridgeに通知する際にCloudWatch Logsのリンクを含めてくれます。 これはSlackに通知する際に役に立ちます。</p> <h4>7. 最小マシンスペックと価格</h4> <p>CodeBuild: 2vCPU/メモリ3GiB <strong>$0.3/hour</strong><a href="#f-b3ab19b3" name="fn-b3ab19b3" title="https://aws.amazon.com/codebuild/pricing/">*3</a></p> <p>Fargate: 0.25vCPU/メモリ0.5GiB <strong>約$0.014/hour</strong> + Savings Plansの対象 + Spotで最大70%オフ<a href="#f-7b862fac" name="fn-7b862fac" title="https://aws.amazon.com/fargate/pricing/">*4</a></p> <p>いずれも記事執筆時点での東京リージョンの価格</p> <p>CodeBuildは最小のスペックのオプションが大きめでスケールアップの選択肢が少なめかつ割引のオプションもないのでコスト面ではサイズを柔軟に選択可能なFargateの圧勝です。 ちなみに最大スペックはFargateが4vCPU/メモリ30GiBに対しCodeBuildは72vCPU/メモリ144GiBなので上限は勝利しています。</p> <p>1~6までの付加価値とのトレードオフで適切に選択しましょう。</p> <h2>Terraformでバッチジョブを登録する</h2> <p>LCLではTerraformによるコード化を推進しているためバッチジョブの登録もTerraformから行います。 ここではTerraformに不慣れな開発者でも登録をしやすくする工夫を解説します。</p> <pre class="code hcl" data-lang="hcl" data-unlink>resource &#34;aws_codebuild_project&#34; &#34;batch_job&#34; { for_each = local.batch_jobs_codebuild name = &#34;batch-job-${each.value.name}&#34; description = each.value.description build_timeout = each.value.build_timeout != null ? each.value.build_timeout : 60 service_role = aws_iam_role.codebuild.arn artifacts { type = &#34;NO_ARTIFACTS&#34; } environment { compute_type = each.value.compute_type != null ? each.value.compute_type : &#34;BUILD_GENERAL1_SMALL&#34; image = each.value.default_image != true ? &#34;ecr_repository_url:prod&#34; : &#34;aws/codebuild/standard:5.0&#34; type = &#34;LINUX_CONTAINER&#34; image_pull_credentials_type = &#34;CODEBUILD&#34; privileged_mode = true } logs_config { cloudwatch_logs { group_name = aws_cloudwatch_log_group.batch_job[each.key].name stream_name = &#34;job&#34; } } source { type = &#34;NO_SOURCE&#34; buildspec = templatefile(each.value.default_image != true ? &#34;buildspec.yml.tpl&#34; : each.value.template, { account_id = data.aws_caller_identity.current.account_id region = data.aws_region.current.name task = each.value.task }) } vpc_config { vpc_id = &#34;vpc-id&#34; subnets = [&#34;subnet-id&#34;] security_group_ids = [&#34;security-group-id&#34;] } } resource &#34;aws_cloudwatch_log_group&#34; &#34;batch_job&#34; { for_each = local.batch_jobs_codebuild name = &#34;job-${each.value.name}&#34; retention_in_days = 0 }</pre> <p>TerraformでCodeBuildプロジェクトを作成する定義はこのようになります。</p> <p>最初のポイントはsourceのtypeを"NO_SOURCE"にしてbuildspecをtemplatefile関数で渡すところです。 このようにすることでterraformのrootモジュールに含めているbuildspec.yml.tplをテンプレートファイルとしてバッチジョブ毎に専用のbuildspec.ymlを生成します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">0.2</span> <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synIdentifier">shell</span><span class="synSpecial">:</span> bash <span class="synIdentifier">variables</span><span class="synSpecial">:</span> <span class="synIdentifier">RAILS_ENV</span><span class="synSpecial">:</span> production <span class="synIdentifier">RELEASE_STAGE</span><span class="synSpecial">:</span> prod <span class="synIdentifier">HOME</span><span class="synSpecial">:</span> /app <span class="synIdentifier">parameter-store</span><span class="synSpecial">:</span> <span class="synIdentifier">DATABASE_PASSWORD</span><span class="synSpecial">:</span> /awesome-service/prod/app/rails/DATABASE_PASSWORD <span class="synIdentifier">DATABASE_HOST</span><span class="synSpecial">:</span> /awesome-service/prod/app/rails/DATABASE_HOST <span class="synIdentifier">phases</span><span class="synSpecial">:</span> <span class="synIdentifier">pre_build</span><span class="synSpecial">:</span> <span class="synIdentifier">commands</span><span class="synSpecial">:</span> <span class="synStatement">- </span>cd $ROOT_PATH <span class="synIdentifier">build</span><span class="synSpecial">:</span> <span class="synIdentifier">run-as</span><span class="synSpecial">:</span> rails <span class="synIdentifier">commands</span><span class="synSpecial">:</span> <span class="synStatement">- </span>${task} </pre> <p>buildspec.yml.tplはバッチジョブ毎に${task}を上書きします。 後述するvariablesでrails runnerやrakeタスクのコマンドを渡します。</p> <pre class="code" data-lang="" data-unlink>variable &#34;awesome_job&#34; { type = map(object({ name = string description = string task = string schedule = string enabled = bool build_timeout = optional(number) compute_type = optional(string) default_image = optional(bool) template = optional(string) })) default = { awesome-job = { name = &#34;awesome-job&#34; description = &#34;すごいバッチジョブ&#34; task = &#34;rake awesome_job&#34; schedule = &#34;cron(0 0 * * ? *)&#34; enabled = true } } } variable &#34;great_job_with_addtional_config&#34; { type = map(object({ name = string description = string task = string schedule = string enabled = bool build_timeout = optional(number) compute_type = optional(string) default_image = optional(bool) template = optional(string) })) default = { great-job-with-addtional-config = { name = &#34;great-job-with-addtional-config&#34; description = &#34;すごい追加設定のジョブ&#34; task = &#34;rails runner Tasks::Great::Job&#34; schedule = &#34;cron(0 0 * * ? *)&#34; enabled = true build_timeout = 480 compute_type = &#34;BUILD_GENERAL1_2XLARGE&#34; } } }</pre> <p>Terraformの1つのvariableに対して1つのジョブを対応させます。<a href="#f-da6f57ef" name="fn-da6f57ef" title="TerraformはString型はダブルクォート必須なのでtaskの文字列をエスケープしなくてもいいようにRubyの場合は呼び出すコマンドの引数はキーワードで取るようにしておくと扱いやすいです">*5</a>ポイントとしてOptional Object Type Attributesを利用しています。 これはterraform 0.14から実験的機能として使えるようになった設定で、文字通り任意で追加できるオプションのパラメータとして定義できます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.terraform.io%2Fdocs%2Flanguage%2Fexpressions%2Ftype-constraints.html%23experimental-optional-object-type-attributes" title="Type Constraints - Configuration Language - Terraform by HashiCorp" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.terraform.io/docs/language/expressions/type-constraints.html#experimental-optional-object-type-attributes">www.terraform.io</a></cite></p> <p>Optional Object Type Attributesを利用することでデフォルト値を上書きしたい場合だけattributeに追加します。</p> <pre class="code" data-lang="" data-unlink>compute_type = each.value.compute_type != null ? each.value.compute_type : &#34;BUILD_GENERAL1_SMALL&#34;</pre> <p>例えばcompute_typeをこのように書くことでcompute_typeを明示的に指定した場合はその値を、無ければデフォルト"BUILD_GENERAL1_SMALL"を設定できます。CodeBuildではdefault_imageをtrueにすることでカスタムイメージではなくAWSマネージドなイメージで起動できるようにしたり、templateに専用のbuildspec.ymlのファイルを渡すことで特別な対応が必要な場合の設定を扱いやすくしています。</p> <p>Optional Object Type Attributesは実験的機能で今後インターフェースが変更される可能性があり利用については注意しましょう。</p> <pre class="code" data-lang="" data-unlink>locals { batch_jobs_codebuild = merge( var.awesome_job, var.great_job_with_addtional_config ) }</pre> <p>最後に複数のvariableをlocalsで一つのMap型にまとめてfor_eachで回します。 この一連のサンプルコードではvariableのscheduleとenabled attributeを利用していませんがEventBridgeから定期実行するリソースもCodeBuildプロジェクト毎に作成しています。</p> <p>駆け足での説明になりましたが、このように定義することで開発者がTerraformのことに詳しくなくてもvariableとlocalsをコピペして追加していくだけでバッチジョブを量産できる体制にしています。</p> <h2>まとめ</h2> <p>CodeBuildをバッチジョブの実行基盤として使う場合のポイントを見ていきました。 CodeBuildの運用体験は強力なものがあるので財布と相談しながら是非一度試してみてください。</p> <h2>採用情報</h2> <p>LCLでは開発メンバーを募集しております!</p> <p>もし興味をお持ちになりましたらお気軽に応募してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.lclco.com%2Frecruit%2F" title="採用情報 | 株式会社LCL(エルシーエル)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.lclco.com/recruit/">www.lclco.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-d298ff9c" name="f-d298ff9c" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-build-notifications.html#sample-build-notifications-ref">https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-build-notifications.html#sample-build-notifications-ref</a></span></p> <p class="footnote"><a href="#fn-76b11ec4" name="f-76b11ec4" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs_cwe_events.html#ecs_task_events">https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs_cwe_events.html#ecs_task_events</a></span></p> <p class="footnote"><a href="#fn-b3ab19b3" name="f-b3ab19b3" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://aws.amazon.com/codebuild/pricing/">https://aws.amazon.com/codebuild/pricing/</a></span></p> <p class="footnote"><a href="#fn-7b862fac" name="f-7b862fac" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://aws.amazon.com/fargate/pricing/">https://aws.amazon.com/fargate/pricing/</a></span></p> <p class="footnote"><a href="#fn-da6f57ef" name="f-da6f57ef" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">TerraformはString型はダブルクォート必須なのでtaskの文字列をエスケープしなくてもいいようにRubyの場合は呼び出すコマンドの引数はキーワードで取るようにしておくと扱いやすいです</span></p> </div> hosht