OPTiM TECH BLOG 2024-02-22T10:00:00+09:00 optim-tech Hatena::Blog hatenablog://blog/10257846132670826296 2024/03/06 に開催される Vue.js v-tokyo MeetupにOPTiMが会場スポンサーとして協賛します hatenablog://entry/6801883189083470583 2024-02-22T10:00:00+09:00 2024-02-22T10:00:04+09:00 サービス開発統括本部 ソリューション開発部で農業プロダクトの開発を担当している西村です。 この度、OPTiMが会場スポンサーとして、2024/03/06 に開催されますVue.js v-tokyo Meetup #19にスペースを提供させていただくことになりました!! 今回はその案内です。 今回の v-tokyoの特集は?? 毎度、さまざまなテーマを持って開催される本会ですが、今回はVue.js 3.4 をキャッチアップしよう!特集です!Vue.js コアチーム、そしてVue Fes Japanのオーガナイザーであるkazupon氏にご紹介いただきます。マイナーバージョンではありますが、私も少… <div style="text-align: center;"> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240216/20240216150803.png" width="660" height="270" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </div> <p>サービス開発統括本部 ソリューション開発部で農業プロダクトの開発を担当している西村です。</p> <p>この度、OPTiMが会場スポンサーとして、2024/03/06 に開催されます<strong>Vue.js v-tokyo Meetup #19</strong>にスペースを提供させていただくことになりました!!</p> <p>今回はその案内です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvuejs-meetup.connpass.com%2Fevent%2F309755%2F" title="Vue.js v-tokyo Meetup #19 (2024/03/06 19:00〜)" 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></p> <h2 id="今回の-v-tokyoの特集は">今回の v-tokyoの特集は??</h2> <p>毎度、さまざまなテーマを持って開催される本会ですが、今回は<strong>Vue.js 3.4 をキャッチアップしよう!特集</strong>です!Vue.js コアチーム、そして<a href="https://vuefes.jp/2023/">Vue Fes Japan</a>のオーガナイザーである<a href="https://twitter.com/kazu_pon">kazupon</a>氏にご紹介いただきます。マイナーバージョンではありますが、私も少し試してみたところ、ビルド時間はVue.js 3.3に比べて速くなっておりました。他にも様々な改善が施されているとのことでとても有益な会となっております。また、今年 2024 年の Vue Fes Japan についてのお知らせもあるようです。</p> <h2 id="会場スポンサーセッションの枠で発表させていただきます">会場スポンサーセッションの枠で発表させていただきます</h2> <p>私も会場スポンサーセッションの枠で発表させていただきます。内容は<strong>「Vue.jsを用いて数万の農地のデータを数秒で表示させるまでのカイゼンの軌跡」</strong>です。</p> <p>弊社ではピンポイントタイム散布(以下、PTS)という防除のデジタル化サービスを展開しております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Fagriculture%2Fservices%2Fpts" title="ドローン適期防除サービス | ピンポイントタイム散布 | OPTiM" 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></p> <p>PTSを裏側で支えるWebアプリケーションの開発ではVue.jsを使用しており、2023年9月ごろにはVue2系からVue3系への対応も実施しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2023%2F10%2F30%2F100000" title="Vue2系からVue3系への移行における苦労話 - OPTiM TECH 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></p> <p>2022年から始まったPTSは年々扱う農薬散布の土地面積が増えてきており、デジタル化する農地のデータの数もサービス開始当初と比べ、数倍に増えてきております。</p> <p>それに伴い、農地のデータを管理する画面では表示までに時間がかかることや1つの操作に時間を要するなど、パフォーマンスに対して課題が出てくるようになりました。それを解決すべく、チームで処理を見直し、パフォーマンス改善に努めてきました。 その過程で実施してきた<strong>カイゼンの軌跡</strong>を紹介します。</p> <p>お申込みはconnpassからお願いします!!</p> <h2 id="最後に">最後に</h2> <p>OPTiMでのVue.jsのノウハウだけでなく、様々な企業の技術やVue.jsに関する取り組みについても知ることができる会となっておりますので、興味ある方はぜひお越しください!</p> <p>また、OPTiMは会場をコミュニティに貸し出すことができるので、興味ある方はご相談ください。</p> <p>OPTiMにはフロントエンドだけなく、多方面で高い技術力を持つエンジニアが多く在籍しています。そんな方々と共に「未来により良い影響を与える」プロダクトの開発を進めていきませんか? ご興味のある方は、ぜひ一度ご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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></p> optim-yuta-nishimura IT企業インハウスデザイナーの取り組み hatenablog://entry/6801883189075504857 2024-02-08T10:00:00+09:00 2024-02-08T10:00:00+09:00 はじめに はじめまして、こんにちは。プロモーション・デザインユニット(以下プロモ・デザインU)のデザインチームです。今回はIT企業のインハウスデザイナーの仕事と、どんなことを目標として設定し、その達成に向けてどのように取り組んでいるかをご紹介したいと思います。この記事を通して何か新しい気づきを得られることを願っています。それでは、さっそく私たちの活動内容を見ていきましょう。 はじめに インハウスデザイナーのお仕事 ブランディング 子会社コーポレートアイデンティティ インナーブランディング 広告キャンペーン パッケージデザイン デザインコンセプト インハウスデザイナーの目標設定 やったこと 結果… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240205/20240205102916.png" width="1057" height="608" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>はじめまして、こんにちは。<strong>プロモーション・デザインユニット(以下プロモ・デザインU)のデザインチーム</strong>です。<br>今回はIT企業のインハウスデザイナーの仕事と、どんなことを目標として設定し、その達成に向けてどのように取り組んでいるかをご紹介したいと思います。この記事を通して何か新しい気づきを得られることを願っています。それでは、さっそく私たちの活動内容を見ていきましょう。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#インハウスデザイナーのお仕事">インハウスデザイナーのお仕事</a><ul> <li><a href="#ブランディング">ブランディング</a><ul> <li><a href="#子会社コーポレートアイデンティティ">子会社コーポレートアイデンティティ</a></li> <li><a href="#インナーブランディング">インナーブランディング</a></li> </ul> </li> <li><a href="#広告キャンペーン">広告キャンペーン</a></li> <li><a href="#パッケージデザイン">パッケージデザイン</a></li> <li><a href="#デザインコンセプト">デザインコンセプト</a></li> </ul> </li> <li><a href="#インハウスデザイナーの目標設定">インハウスデザイナーの目標設定</a><ul> <li><a href="#やったこと">やったこと</a></li> <li><a href="#結果集計と分析">結果集計と分析</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おすすめサイト">おすすめサイト</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h2 id="インハウスデザイナーのお仕事">インハウスデザイナーのお仕事</h2> <p>オプティムのクリエイティブは、プロモ・デザインUが主導し、デザインチーム、Webプロモーションチーム、UI/UXチームの三者が協力しています。 その中のデザインチームは会社のブランディング、広告キャンペーン、パッケージデザイン、デザインコンセプトなど、多岐にわたるプロジェクトに携わっています。<br> ほんの一部ですが、チームの制作物をご覧ください。なお、プロモ・デザインUの活動について詳しく知りたい方は<a href="https://tech-blog.optim.co.jp/archive/category/Design">👉過去の記事</a>をご覧ください!</p> <h3 id="ブランディング">ブランディング</h3> <h4 id="子会社コーポレートアイデンティティ">子会社コーポレートアイデンティティ</h4> <p>親会社(OPTiM)との調和を図りながら、市場での差別化を追求 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240122/20240122143608.png" width="1218" height="244" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="インナーブランディング">インナーブランディング</h4> <p>社内の人たちが企業の価値観や文化を共有し、一体感を生むための社内ブランディング <br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240122/20240122143612.png" width="1218" height="344" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="広告キャンペーン">広告キャンペーン</h3> <p>提供サービスの中で使えるソフトの紹介<br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240122/20240122143616.png" width="1218" height="238" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="パッケージデザイン">パッケージデザイン</h3> <p>栽培から販売まで手掛ける自慢のお米「スマート米」パッケージシリーズ<br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240122/20240122143546.png" width="1218" height="394" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="デザインコンセプト">デザインコンセプト</h3> <p>提案資料など視覚的効果を求められるグラフィック作成<br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240122/20240122143551.png" width="1218" height="324" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="インハウスデザイナーの目標設定">インハウスデザイナーの目標設定</h2> <p>デザインチームではご覧いただいたような制作をしながら、高い基準でデザインを提供するためチームOKRという形で目標を設定し、最終的に全社OKRへ繋がる活動をしています。 OKRについては <a href="https://tech-blog.optim.co.jp/entry/2023/09/08/100000">同じブログ内の記事</a>で詳しく紹介していますので、是非チェックしてみてください。<br></p> <p>今回はその目標の一つ、「デザインツールの操作スキルを向上させて作業効率をUP」という施策をご紹介させていただきます。<br> こちらは“同じ作業内容でも人によってスピードが違う”という課題に着目し、チーム目標の一つとして取り組んでいます。<br> デザインツールはAdobe Illustrator(以下、イラストレーター)に限定し、新機能の導入と頻繁に利用されるツールのショートカットに焦点を当て、作業効率を130%向上させる目標を立てました。</p> <h3 id="やったこと">やったこと</h3> <p>では実際どのような方法で計測するのかをご紹介します。<br> ご覧いただいたようなデザインにおいて、定性的な側面を定量的に評価することは課題でした。そこで、以下の方法でデザイン作業の効率化を具体的に数値で示すことにしました。<br> デザイナー5人に対して月4-5回の頻度で10分くらいで作れる制作物を用意しました。<br> 実際に解説やチュートリアルを学習する前と後の合計2回分の時間を計測し、学習による進歩を検証しました。</p> <ul> <li><p>削減時間の計測方法<br> 1回目と2回目の作業時間の差を出し、それを「時間差×1データあたりの作業回数×1ヶ月あたりのデータ数」で<br> 1ヶ月分に換算しました。「1データあたりの作業回数」を正確に測定するのは困難と感じたため、「10回:基礎的な動作で高頻度で使う、5回:たまにつかう 1回:あまり使わない」の3段階評価を作成しました。<br> また、作業効率化を図る上で、今まで全く作業できなかった人が作業できるようになる「チーム全体のスキルの底上げ」を目指していたので、5人の計測値のうち最大値をカウントしていました。</p></li> <li><p>出題内容<br> テストではツールショートカットキーや整列アクションキーの登録、色の再配色のショートカットキーなど、基本的な操作について出題しました。出題の工夫としては、テスト作成の工数を最低限に抑えるために、過去の業務データを加工したり、Adobe公式チュートリアルのデータを加工したりしました。<br> また、作業効率がより上がるテストを作成できるように、日々の業務の中で困ったことや効率化したいことをメモし、テストに反映させることもありました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240122/20240122143555.png" width="1600" height="842" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p></li> </ul> <h3 id="結果集計と分析">結果集計と分析</h3> <p>上期全体では56%(9.52時間)の作業効率化に成功しました。<br> 特に基本的なツールショートカットキーや整列のアクション登録、パネル呼び出しショートカットキーなどで高い時間短縮効果がありました。以下特に効果の大きかったテストのテーマと計測値です。<br> 1位 ツール呼び出しショートカットキー (選択、グラデーション、スポイトなど基本操作のショートカットキー)<br>    1ヶ月で4.16時間作業時間削減<br> 2位 パネル呼び出しショートカットキー(各パネル呼び出しショートカットキー)<br>    1ヶ月で4時間作業削減<br> 3位 ツール呼び出しショートカットキー2(長方形、ハサミ、リフレクトなど図形編集に特化したショートカットキー)<br>    1ヶ月で1.3時間作業削減<br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20240122/20240122143604.png" width="667" height="335" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <br> 現在下期の途中なので最終的な成果はまだですが、このまま順調に進めていけば目標の130%に到達できそうです。</p> <h2 id="まとめ">まとめ</h2> <p>オプティムのインハウスデザイナーが携わった制作物と目標設定の一部をご紹介しました。目標設定では、 各メンバーが新しい機能や効率的な操作方法にアクセスし、それを実践することでスキルの向上が実感できました。<br> 一見地味な活動ですが、コツコツと継続的にやることで、チーム全体のデザイン作業における生産性向上につながっていることを<br> 実感しています。<br> 各メンバーが自らの作業スタイルを見直し、新しい手法を取り入れることで、個々のスキル向上が集積され、全体の作業効率が大きく進化していると考えています。<br> これからもこのような取り組みを継続しつつツールの種類も増やし、更なる成果を上げていく予定です。</p> <h2 id="おすすめサイト">おすすめサイト</h2> <p>今回出題に使用したソース:<br> <a href="https://ferret-plus.com/834">Illustratorのショートカットキーを作業別まとめ</a>(イラレユーザーならおさえておきたい基本的なショートカット集)<br> <a href="https://creativecloud.adobe.com/cc/learn/app/illustrator?locale=ja">Illustrator を学ぶ</a>(Adobe公式チュートリアル:新しい機能も更新されています。)</p> <h2 id="おわりに">おわりに</h2> <p>オプティムでは、エンジニアだけではなくプロモ・デザインUで一緒に働いてくださるメンバーも探しています。プロモ・デザインUでは、UI/UXデザインやブランディング、Web制作、マーケティングなどオプティム製品にまつわる様々なデザインのお仕事をしています。 UI/UX、ブランディング、Webプロモーションなどに興味がある方、ぜひご応募お待ちしています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-hara LaravelのカスタムドライバでAssumeRoleしつつS3のデータを簡単に取得できるようにしたい! hatenablog://entry/4207112889965102747 2024-01-09T10:00:00+09:00 2024-01-09T10:00:02+09:00 はじめに こんにちは。お久しぶりです。技術統括本部DXビジネス開発部フィールドソリューションユニットの濱村です。 入社時はフロントエンドをメインに開発していましたが今はインフラをしていることが多いです。幅広く技術を触れる環境に毎日感謝して開発しています。 背景 Laravelを使用してS3への画像等のファイルをアップロードやダウンロードをしたいというケースが多いと思います。LaravelでS3とファイルをやりとりするための知見はネットにそこそこあり、特にS3のドライバを用いて簡単にS3とファイルのやりとりをすることができます。ただし、今回は特殊でカスタムドライバを作成する必要があったので紹介し… <h1 id="はじめに">はじめに</h1> <p>こんにちは。お久しぶりです。技術統括本部DXビジネス開発部フィールドソリューションユニットの濱村です。<br/> 入社時はフロントエンドをメインに開発していましたが今はインフラをしていることが多いです。幅広く技術を触れる環境に毎日感謝して開発しています。</p> <h2 id="背景">背景</h2> <p>Laravelを使用してS3への画像等のファイルをアップロードやダウンロードをしたいというケースが多いと思います。LaravelでS3とファイルをやりとりするための知見はネットにそこそこあり、特にS3のドライバを用いて簡単にS3とファイルのやりとりをすることができます。ただし、今回は特殊でカスタムドライバを作成する必要があったので紹介します。</p> <h1 id="カスタムドライバでS3にアクセスする">カスタムドライバでS3にアクセスする</h1> <p>AWSでは基本的にAccessKeyとSecretAccessKeyがあればAWSリソースにアクセスすることができます。ただし弊社ではAssumeRoleでリソースへのアクセスを制限しており、特定のロールから権限を委譲してもらわなければリソースに対してアクセスできないようになっています。AssumeRoleはリソースの運用とセキュリティ観点から導入しており、各環境毎にロールを切ってアクセスを制御することで1つのAWSアカウントで各環境のリソースを分けて運用できるようにしています。ということで、今回はLaravelのプロセスがAssumeRoleをして権限を委譲してもらう必要があります。<br/> 通常、Laravelが提供しているS3にアクセスできるfilesystemモジュールをComposerで持ってきます。しかし、今回はこのS3へのアクセスの際にAssumeRoleの処理を噛ませる必要があるため自前のカスタムドライバを作成します。</p> <h2 id="ServiceProviderを拡張してProviderを作成">ServiceProviderを拡張してProviderを作成</h2> <blockquote><p> app/Providers/AwsServiceProvider.php</p></blockquote> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\Providers; <span class="synType">class</span> AwsServiceProvider <span class="synType">extends</span> ServiceProvider <span class="synSpecial">{</span> <span class="synComment">/**</span> <span class="synComment"> * サービスの初期処理登録後に実行</span> <span class="synComment"> *</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">void</span> <span class="synComment"> */</span> <span class="synType">public</span> <span class="synPreProc">function</span> boot<span class="synSpecial">()</span> <span class="synSpecial">{</span> Storage<span class="synStatement">::</span>extend<span class="synSpecial">(</span><span class="synConstant">'assumeS3'</span>, <span class="synPreProc">function</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">app</span>, <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">sample_driver</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> SampleDriver<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">)</span>; <span class="synStatement">return</span> <span class="synPreProc">new</span> AwsSampleAdapter<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">sample_driver</span>, <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">)</span>; <span class="synSpecial">})</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <h2 id="AssumeRole処理とAdapterの作成">AssumeRole処理とAdapterの作成</h2> <p>カスタムドライバの枠ができたところでAssumeRoleをする処理を書いていきます。 ここで使用しているModuleがなかなかややこしいところです。 <code>AwsS3V3Adapter</code>でも<code>Illuminate\Filesystem\AwsS3V3Adapter</code>と<code>League\Flysystem\AwsS3V3\AwsS3V3Adapter</code>の2つあるため要注意です。</p> <p><a href="https://laravel.com/api/9.x/Illuminate/Filesystem/AwsS3V3Adapter.html">Illuminate\Filesystem\AwsS3V3Adapter | Laravel API</a></p> <p><a href="https://flysystem.thephpleague.com/docs/adapter/aws-s3-v3/">Aws S3 (v3) Adapter - Flysystem</a></p> <p>詳しい使い方や仕組みについては上記の公式ドキュメントに記載があります。ここでは上記2つのモジュールを組み合わせてAssumeRoleとS3アクセスを実現したコードを紹介します。</p> <blockquote><p>app/Providers/AwsServiceProvider.php</p></blockquote> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\Providers; <span class="synPreProc">use</span> Aws\Credentials\AssumeRoleCredentialProvider; <span class="synPreProc">use</span> Aws\Credentials\CredentialProvider; <span class="synPreProc">use</span> Aws\S3\S3Client; <span class="synPreProc">use</span> Aws\Sts\StsClient; <span class="synPreProc">use</span> Illuminate\Filesystem\AwsS3V3Adapter; <span class="synPreProc">use</span> Illuminate\Support\Facades\Storage; <span class="synPreProc">use</span> Illuminate\Support\ServiceProvider; <span class="synPreProc">use</span> League\Flysystem\AwsS3V3\AwsS3V3Adapter <span class="synStatement">as</span> FlyS3Adapter; <span class="synPreProc">use</span> League\Flysystem\Filesystem <span class="synStatement">as</span> FlyFilesystem; <span class="synType">class</span> AwsServiceProvider <span class="synType">extends</span> ServiceProvider <span class="synSpecial">{</span> <span class="synComment">/**</span> <span class="synComment"> * サービスの初期処理登録後に実行</span> <span class="synComment"> *</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">void</span> <span class="synComment"> */</span> <span class="synType">public</span> <span class="synPreProc">function</span> boot<span class="synSpecial">()</span> <span class="synSpecial">{</span> Storage<span class="synStatement">::</span>extend<span class="synSpecial">(</span><span class="synConstant">'assumeS3'</span>, <span class="synPreProc">function</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">app</span>, <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">assumeRoleCredentials</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> AssumeRoleCredentialProvider<span class="synSpecial">([</span> <span class="synConstant">'client'</span> <span class="synStatement">=&gt;</span> <span class="synPreProc">new</span> StsClient<span class="synSpecial">(</span> <span class="synSpecial">[</span> <span class="synConstant">'version'</span> <span class="synStatement">=&gt;</span> <span class="synConstant">'latest'</span>, <span class="synConstant">'region'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">[</span><span class="synConstant">'region'</span><span class="synSpecial">]</span>, <span class="synConstant">'credentials'</span> <span class="synStatement">=&gt;</span> <span class="synSpecial">[</span> <span class="synConstant">'key'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">[</span><span class="synConstant">'key'</span><span class="synSpecial">]</span>, <span class="synConstant">'secret'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">[</span><span class="synConstant">'secret'</span><span class="synSpecial">]</span>, <span class="synSpecial">]</span>, <span class="synSpecial">]</span> <span class="synSpecial">)</span>, <span class="synConstant">'assume_role_params'</span> <span class="synStatement">=&gt;</span> <span class="synSpecial">[</span> <span class="synConstant">'RoleArn'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">[</span><span class="synConstant">'arn'</span><span class="synSpecial">]</span>, <span class="synConstant">'RoleSessionName'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">[</span><span class="synConstant">'session'</span><span class="synSpecial">]</span>, <span class="synSpecial">]</span>, <span class="synSpecial">])</span>; <span class="synStatement">$</span><span class="synIdentifier">provider</span> <span class="synStatement">=</span> CredentialProvider<span class="synStatement">::</span>memoize<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">assumeRoleCredentials</span><span class="synSpecial">)</span>; <span class="synStatement">$</span><span class="synIdentifier">s3Client</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> S3Client<span class="synSpecial">([</span> <span class="synConstant">'region'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">[</span><span class="synConstant">'region'</span><span class="synSpecial">]</span>, <span class="synConstant">'version'</span> <span class="synStatement">=&gt;</span> <span class="synConstant">'latest'</span>, <span class="synConstant">'credentials'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">provider</span>, <span class="synSpecial">])</span>; <span class="synStatement">$</span><span class="synIdentifier">adapter</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> FlyS3Adapter<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">s3Client</span>, <span class="synStatement">$</span><span class="synIdentifier">config</span><span class="synSpecial">[</span><span class="synConstant">'bucket'</span><span class="synSpecial">])</span>; <span class="synStatement">$</span><span class="synIdentifier">driver</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> FlyFilesystem<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">adapter</span><span class="synSpecial">)</span>; <span class="synStatement">return</span> <span class="synPreProc">new</span> AwsS3V3Adapter<span class="synSpecial">(</span> <span class="synStatement">$</span><span class="synIdentifier">driver</span>, <span class="synStatement">$</span><span class="synIdentifier">adapter</span>, <span class="synStatement">$</span><span class="synIdentifier">config</span>, <span class="synStatement">$</span><span class="synIdentifier">s3Client</span>, <span class="synSpecial">)</span>; <span class="synSpecial">})</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <h2 id="LaravelのProviderを使用するための記述">LaravelのProviderを使用するための記述</h2> <p>上記で実装したカスタムドライバが使用できるようにコードを追加していきます。</p> <p>providersに作成したProviderを登録</p> <blockquote><p>config/app.php</p></blockquote> <pre class="code lang-php" data-lang="php" data-unlink>'providers' =<span class="synError">&gt;</span> [ ... App\Providers\AwsServiceProvider::class, ], </pre> <p>filesystemのdisksにassumeS3を追加</p> <blockquote><p>config/filesystem.php</p></blockquote> <pre class="code lang-php" data-lang="php" data-unlink>'disks' =<span class="synError">&gt;</span> [ 'local' =<span class="synError">&gt;</span> [ 'driver' =<span class="synError">&gt;</span> 'local', 'root' =<span class="synError">&gt;</span> storage_path('app'), 'throw' =<span class="synError">&gt;</span> false, ], 'public' =<span class="synError">&gt;</span> [ 'driver' =<span class="synError">&gt;</span> 'local', 'root' =<span class="synError">&gt;</span> storage_path('app/public'), 'url' =<span class="synError">&gt;</span> env('APP_URL').'/storage', 'visibility' =<span class="synError">&gt;</span> 'public', 'throw' =<span class="synError">&gt;</span> false, ], 'assumeS3' =<span class="synError">&gt;</span> [ 'driver' =<span class="synError">&gt;</span> 'assumeS3', 'key' =<span class="synError">&gt;</span> env('AWS_ACCESS_KEY_ID'), 'secret' =<span class="synError">&gt;</span> env('AWS_SECRET_ACCESS_KEY'), 'arn' =<span class="synError">&gt;</span> env('AWS_ROLE_ARN'), 'session' =<span class="synError">&gt;</span> env('AWS_ROLE_SESSION_NAME'), 'region' =<span class="synError">&gt;</span> env('AWS_DEFAULT_REGION'), 'bucket' =<span class="synError">&gt;</span> env('AWS_BUCKET'), ], ], </pre> <h3 id="Accessorの実装">Accessorの実装</h3> <p>assumeS3のdiskを使ってS3から署名付きURLを発行するメソッドをAccessorを実装<br/> ※ AccessorはLaravel公式のものではありません。Controllerでの実装がシンプルにするため独自に実装しています。</p> <blockquote><p>app/Accessors/AwsAccessor.php</p></blockquote> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synType">namespace</span> App\Accessors; <span class="synPreProc">use</span> DateTime; <span class="synPreProc">use</span> Illuminate\Support\Facades\Storage; <span class="synType">class</span> AwsAccessor <span class="synSpecial">{</span> <span class="synComment">/**</span> <span class="synComment"> * getPresignedUrl</span> <span class="synComment"> *</span> <span class="synComment"> * </span><span class="synPreProc">@return </span><span class="synComment">string S3 PreSinedURL</span> <span class="synComment"> *</span> <span class="synComment"> * </span><span class="synPreProc">@see </span><span class="synComment">S3 PreSignedURL https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html</span> <span class="synComment"> */</span> <span class="synType">public</span> <span class="synType">static</span> <span class="synPreProc">function</span> getPresignedUrl<span class="synSpecial">(</span> <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">fileName</span>, <span class="synType">string</span> <span class="synStatement">$</span><span class="synIdentifier">expiredAt</span> <span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">data</span> <span class="synStatement">=</span> Storage<span class="synStatement">::</span>disk<span class="synSpecial">(</span><span class="synConstant">'assumeS3'</span><span class="synSpecial">)</span><span class="synType">-&gt;</span>temporaryUrl<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">fileName</span>, <span class="synPreProc">new</span> DateTime<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">expiredAt</span><span class="synSpecial">))</span>; <span class="synStatement">return</span> <span class="synStatement">$</span><span class="synIdentifier">data</span>; <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <h3 id="Controllerの実装">Controllerの実装</h3> <blockquote><p>app/Http/Controllers/S3Controller.php</p></blockquote> <pre class="code lang-php" data-lang="php" data-unlink>public function getPresignedURL() { $presigned_url = AwsAccessor::getPresignedUrl(config('app.file_path'), config('app.s3_presigned_exp')); return $presigned_url; } </pre> <p>環境変数からS3関連の変数を定義</p> <blockquote><p>config/app.php</p></blockquote> <pre class="code lang-php" data-lang="php" data-unlink>'s3_presigned_exp' =<span class="synError">&gt;</span> env('AWS_PRESIGNED_EXP', '+ 30 minute'), 's3_file_path' =<span class="synError">&gt;</span> env('AWS_FILE_PATH', '/'), </pre> <h2 id="詰まったところ">詰まったところ</h2> <p>カスタムドライバを作る際にAWSリソースとやりとりをするモジュールとしてflysystemがあることを知りました。このflysystemが少し厄介でこれの扱いに少し手こずりました。特にAssumeRoleをする機構の知見がなかなか出回っておらずflysystemのドキュメントとにらめっこをしながらあれこれ試してようやくたどり着きました。 ただ、こうやって苦労してAssumeRoleの機構を作れたおかげでファイルを取得するAPIはController部分が1行の記述だけで実装ができています。今回のように特殊なケースでもカスタムのClassを作成することでControllerの記述をスッキリさせることができてとても満足です。</p> <h1 id="おわりに">おわりに</h1> <p>オプティムでは、世界の人々・各産業に大きく良い影響を与えたい方、身の丈に合わない大きな志を持って楽しみながら挑戦し、自ら己の可能性を広げられる方、そして、あらゆる属性を意識せず思いやりを持ってこれからのオプティムという組織・文化を創っていって頂ける方を新卒・中途問わず通年で募集しています。新卒採用では引き続きエンジニア志望(プログラミング未経験者可)、ビジネスサイド志望ともに募集しています。私たちと一緒に世界を変える大きなことにチャレンジしたいという方、是非以下をご覧になってください。私もこの理念に共感し、このリンクを踏んだ者の1人です。同じマインドをお持ちの方、一緒にお仕事ができることを楽しみにしております。以上、ご応募お待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-sogo-hamamura UIデザインの舞台裏! hatenablog://entry/6801883189064826808 2023-12-25T10:00:00+09:00 2023-12-25T10:00:12+09:00 はじめに はじめまして、こんにちは。プロモーション・デザインユニット(以下プロモ・デザインU)のUIチームです。 私たちUIチームは「全社デザイン浸透でOPTiMの思想を社内外へ正しく伝え、よりカッコよく魅力的に成長させる」というミッションの中で、ユーザーになりきる「NARIKIRI」という思想を取り入れたUI/UXデザインの実現を目標としています。 プロモ・デザインUの活動について詳しく知りたい方は👉過去の記事をご覧ください! はじめに 今までのUIチームの動き 1.ユーザーFB反映 2.コンポーネントシステムの開発 3.実装中のUIレビュー 振り返りと展望 2023年の振り返り 1.プロダ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-hyemiyoon/20231207/20231207144218.png" width="550" height="382" loading="lazy" title="" class="hatena-fotolife" style="width:450px" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>はじめまして、こんにちは。<strong>プロモーション・デザインユニット(以下プロモ・デザインU)のUIチーム</strong>です。</p> <p>私たちUIチームは「全社デザイン浸透でOPTiMの思想を社内外へ正しく伝え、よりカッコよく魅力的に成長させる」というミッションの中で、ユーザーになりきる「NARIKIRI」という思想を取り入れたUI/UXデザインの実現を目標としています。</p> <p>プロモ・デザインUの活動について詳しく知りたい方は<a href="https://tech-blog.optim.co.jp/archive/category/Design">👉過去の記事</a>をご覧ください!</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#今までのUIチームの動き">今までのUIチームの動き</a><ul> <li><a href="#1ユーザーFB反映">1.ユーザーFB反映</a></li> <li><a href="#2コンポーネントシステムの開発">2.コンポーネントシステムの開発</a></li> <li><a href="#3実装中のUIレビュー">3.実装中のUIレビュー</a></li> </ul> </li> <li><a href="#振り返りと展望">振り返りと展望</a><ul> <li><a href="#2023年の振り返り">2023年の振り返り</a><ul> <li><a href="#1プロダクト単位の統一">1.プロダクト単位の統一</a></li> <li><a href="#2改修時のコストについて">2.改修時のコストについて</a></li> </ul> </li> <li><a href="#2024年これからのOPTiMのUIデザイン">2024年、これからのOPTiMのUIデザイン</a><ul> <li><a href="#1デザインシステム導入の検討">1.デザインシステム導入の検討</a></li> <li><a href="#2新しいツールの活用Adobe-XDからFigmaへ">2.新しいツールの活用:Adobe XDからFigmaへ</a></li> </ul> </li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h2 id="今までのUIチームの動き">今までのUIチームの動き</h2> <p>💭製品の画面が出来上がるまで、UIデザイナーはどんなことをしているの?<br/> 💭かっこいいUIデザインは見た目だけじゃない?<br/> UIチーム舞台裏、<strong>スムーズな開発と運用のために実施している三つの施策</strong>を紹介します!</p> <h3 id="1ユーザーFB反映">1.ユーザーFB反映</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231208/20231208105952.png" width="550" height="142" loading="lazy" title="" class="hatena-fotolife" style="width:450px" itemprop="image"></span><br/> <span style="font-size: 80%">顧客の改善要望に応じて行なった配色改善</span></p> <p>UIチームは多くのプロダクト開発に関わっており、複数のプロダクトに最適化されたUI/UXを研究しています。<br/> ユーザーからのポジティブなFBに繋がった改善を他のプロダクトにも展開することで、全体的により洗練されたUXの提供を目指しました!</p> <h3 id="2コンポーネントシステムの開発">2.コンポーネントシステムの開発</h3> <p><strong>OPTiM Cloud IoT OS(以下CIOS)</strong>の設計時に、CIOS向けの独自コンポーネントシステムとして開発チーム・デザインチーム共同で<strong>「URSUS tool kit」</strong>を開発しました。<br/> 当初、PolymerというオープンソースのJavaScriptライブラリで「app-tool-kit」という共通コンポーネントが実装されていましたが、Polymerの脆弱性発覚を理由にvueに置換えを行う必要がありました。置き換えと同時に、コンポーネント粒度をVuetifyのatomicDesignを参考に見直しを行い、ライブラリフルスクラッチによりまるっと作り直したことで「URSUS tool kit」が生まれました。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231208/20231208105957.png" width="550" height="318" loading="lazy" title="" class="hatena-fotolife" style="width:450px" itemprop="image"></span><br/> <span style="font-size: 80%">URSUS tool kit</span></p> <p>これを作成したことによりプロダクト内のデザインを統一し、実装コストを大幅に削減することができました。<br/> しかし、あくまで一部のプロダクト向けのコンポーネントカタログに過ぎず、全てのプロダクトを賄うほどの汎用性はありませんでした。<br/> また、企業理念などのブランディングは設計に取り込まれていないため、改めて全社的に「OPTiMらしさ」を踏襲したデザインシステムを作成する必要がありました。</p> <h3 id="3実装中のUIレビュー">3.実装中のUIレビュー</h3> <p>UIデザイナーはエンジニアとコミュニケーションを取りながら開発に関わってます。<br/> 実装中に発生したデザインの疑問点・懸念点について確認を行いながら、使いやすいUIの完成を目指します!</p> <p>開発スピード向上のため、場合によってはStorybookなどのツールを活用しています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231208/20231208110003.png" width="550" height="305" loading="lazy" title="" class="hatena-fotolife" style="width:450px" itemprop="image"></span><br/> <span style="font-size: 80%">StorybookでUI実装レビューを行った際のFBコメント</span></p> <h2 id="振り返りと展望">振り返りと展望</h2> <h3 id="2023年の振り返り">2023年の振り返り</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231208/20231208110009.png" width="300" height="125" loading="lazy" title="" class="hatena-fotolife" style="width:250px" itemprop="image"></span></p> <p>「ユーザーFB反映」「コンポーネントシステムの開発」「デザイン/UIレビュー」の三つの取り組みについて振り返り、<br/> 実施後の感想を集めてみました。<strong>良かった点👍</strong>と今年の経験を活かしてさらに<strong>改善したい点💬</strong>を出してみました!</p> <h4 id="1プロダクト単位の統一">1.プロダクト単位の統一</h4> <p>👍ユーザーFBより、改善に対して好評の声をいただいた!<br/> 💬コンポーネントシステムはメンテナンスの継続と社内認知度向上のためアナウンスが重要!<br/> 💬一部製品の共通コンポーネントは全社的な観点では活用が難しい時があった。</p> <h4 id="2改修時のコストについて">2.改修時のコストについて</h4> <p>👍コンポーネントシステムはUIUX改善だけでなく、実装工数も大幅に削減できて良かった。<br/> 💬共通化の担保はできるがレビューに時間がかかってしまう!<br/> ⁨⁨⁩💬デザイン原則が明文化されておらず、レビューがUIデザイナーに属人化されてる傾向があった。</p> <h3 id="2024年これからのOPTiMのUIデザイン">2024年、これからのOPTiMのUIデザイン</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231208/20231208110015.png" width="300" height="143" loading="lazy" title="" class="hatena-fotolife" style="width:250px" itemprop="image"></span></p> <p>振り返りを経て、UIチームでは<strong>デザインシステム</strong>と<strong>Figma</strong>の導入を検討/実施しています!</p> <h4 id="1デザインシステム導入の検討">1.デザインシステム導入の検討</h4> <p>デザインシステムとは「デザイン原則」「デザインガイドライン」そしてそれらに沿ってデザインされた「UIコンポーネントとその実装コード」を体系的にまとめたもの。</p> <p><strong>デザインシステム導入のメリット</strong><br/> <strong>・一貫性:</strong>ユーザーが迷わず操作できることによって、優れたUXを提供できる。<br/> <strong>・効率性:</strong>再利用できるコンポーネントはコードも含めてルール化されており、属人化解消・生産性の向上が見込まれる。 <br/> <strong>・コミュニケーション促進:</strong>デザインシステムを共通言語として、スムーズかつ建設的なコミュニケーションを可能とする。</p> <p>一貫性、効率性、コミュニケーション促進はデザイン共通化推進に欠かせないものです!<br/> 今までのデザイン共通化の取り組みの経験を活かして、デザインシステムを導入に向けて動いています。</p> <h4 id="2新しいツールの活用Adobe-XDからFigmaへ">2.新しいツールの活用:Adobe XDからFigmaへ</h4> <p>Adobe XDの単体プラン販売終了やメンテナンスモードへの切り替え(機能アップデート終了)の動きとともに、<br/> UIチームではデザインシステム導入検討動きに合わせてAdobe XDからFigmaへツールを移行中です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231208/20231208110021.png" width="550" height="295" loading="lazy" title="" class="hatena-fotolife" style="width:450px" itemprop="image"></span><br/> <span style="font-size: 80%">Figmaで実装中のコンポーネント集の一部</span></p> <p>Adobe XDと比較しても作業効率向上が見込めたり、Figmaコミュニティのデザインリソースや設計プロセスをFigmaデータで確認できる点、実装画面に近い再現率の高さなどのメリットがあることから移行を決定しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231219/20231219104928.png" width="800" height="676" loading="lazy" title="" class="hatena-fotolife" style="width:450px" itemprop="image"></span> <br/> <span style="font-size: 80%">WebのCSS実装とAdobe XD、Figmaの再現度を比較調査したもの</span></p> <p>2023年に新機能として追加された<strong>開発モード</strong>や<strong>オートレイアウト</strong>はプロトタイプの制作に絶賛活用中!<br/> デザイン共通化や生産性アップにつながる機能についてまたの機会に紹介します!</p> <h2 id="まとめ">まとめ</h2> <p>「OPTiMをかっこよく魅力的に成長させる」ミッションの達成のために、UIチームで取り組んでいるアクションとこれからのOPTiMのUIデザインの展望を紹介しました。<br/> 今後も一貫性のあるUIの提供によるブランドの向上・より強化されたデザインプロセスの確立で優れたユーザー体験を実現し、産業全体の課題を解消するDX推進に寄与できるように頑張ります!</p> <h2 id="おわりに">おわりに</h2> <p>オプティムでは、エンジニアだけではなくプロモ・デザインUで一緒に働いてくださるメンバーも探しています。<br/> プロモ・デザインUでは、UI/UXデザインやブランディング、Web制作、マーケティングなどオプティム製品にまつわる様々なデザインのお仕事をしています。<br/> UI/UX、ブランディング、Webプロモーションなどに興味がある方、ぜひご応募お待ちしています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-hyemiyoon Google Cloud Next Tokyo ’23 アプリケーション開発環境について hatenablog://entry/6801883189059514416 2023-11-22T10:00:00+09:00 2023-11-22T10:00:00+09:00 はじめまして。今年(2023年)の新卒入社の白木です。Google Cloud Next Tokyo ’23に参加したので、プログラミング未経験で入社した新卒社員が拙いながらもフレッシュな目線で、Google Cloudのアプリケーション開発のユースケースと技術解説をします。 本記事では、「【初級】2023 年版: アプリケーションをどこで動かすべきか」のセッションから学んだ、Google Cloudが目指すものと開発のユースケース紹介、各機能の解説をします。 目次 目次 Google Cloud Next Tokyo ’23とは タイムスケージュール 【初級】2023 年版: アプリケーショ… <p> はじめまして。今年(2023年)の新卒入社の白木です。Google Cloud Next Tokyo ’23に参加したので、プログラミング未経験で入社した新卒社員が拙いながらもフレッシュな目線で、<code>Google Cloud</code>のアプリケーション開発のユースケースと技術解説をします。<br> 本記事では、「【初級】2023 年版: アプリケーションをどこで動かすべきか」のセッションから学んだ、<code>Google Cloud</code>が目指すものと開発のユースケース紹介、各機能の解説をします。</p> <h1 id="目次">目次</h1> <ul> <li><a href="#%E7%9B%AE%E6%AC%A1">目次</a> <ul> <li><a href="#google-cloud-next-tokyo-23%E3%81%A8%E3%81%AF">Google Cloud Next Tokyo ’23とは</a></li> <li><a href="#%E3%82%BF%E3%82%A4%E3%83%A0%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB">タイムスケージュール</a> <ul> <li><a href="#%E5%88%9D%E7%B4%9A2023-%E5%B9%B4%E7%89%88-%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E3%81%A9%E3%81%93%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%99%E3%81%B9%E3%81%8D%E3%81%8B">【初級】2023 年版: アプリケーションをどこで動かすべきか</a> <ul> <li><a href="#google%E3%81%8C%E7%9B%AE%E6%8C%87%E3%81%99%E3%82%82%E3%81%AE">Googleが目指すもの</a></li> <li><a href="#cloud-workstations">Cloud Workstations</a></li> <li><a href="#cloud-build">Cloud Build</a></li> <li><a href="#cloud-deploy">Cloud Deploy</a></li> <li><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%83%AC%E3%82%B9%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%AE%E4%BD%BF%E7%94%A8%E4%BE%8B%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">その他サーバーレスプラットフォームの使用例について</a></li> <li><a href="#google-kubernetes-engine-gke">Google Kubernetes Engine (GKE)</a></li> <li><a href="#cloud-run">Cloud Run</a></li> </ul> </li> <li><a href="#%E4%BB%8A%E5%9B%9E%E5%8F%82%E5%8A%A0%E3%81%97%E3%81%9F%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%86%85%E5%AE%B9%E3%81%8B%E3%82%89%E4%BB%8A%E5%BE%8C%E3%81%AF%E3%81%A9%E3%82%93%E3%81%AA%E3%81%93%E3%81%A8%E3%81%AB%E7%B9%8B%E3%81%92%E3%81%A6%E3%81%84%E3%81%91%E3%81%9D%E3%81%86%E3%81%AA%E3%81%AE%E3%81%8B">今回参加したセッションの内容から、今後はどんなことに繋げていけそうなのか?</a></li> <li><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></li> </ul> </li> </ul> </li> </ul> <h2 id="Google-Cloud-Next-Tokyo-23とは">Google Cloud Next Tokyo ’23とは</h2> <p> 4 年ぶりにオフラインでの開催となった今回のGoogle Cloud Next Tokyo ’23は東京ビッグサイトで開催されました。<code>Google Cloud</code> のAIや機械学習の技術にフォーカスを当てて、新技術のリリースやAIや機械学習の技術の活用事例の紹介などを講演します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231120/20231120095310.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231120/20231120095322.jpg" width="1200" height="1600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="タイムスケージュール">タイムスケージュール</h2> <p> 私は下記のようなタイムスケジュールでセッションに参加しました。</p> <table> <thead> <tr> <th style="text-align:center;"> セッション名 </th> <th style="text-align:center;">時間</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;"> DAY1 基調講演 </td> <td style="text-align:center;"> 10:00 ~ 11:40 </td> </tr> <tr> <td style="text-align:center;"> 【中級】AWS と Google Cloud の垣根を超えてデータを活用する方法 </td> <td style="text-align:center;"> 12:00 ~ 12:40 </td> </tr> <tr> <td style="text-align:center;"> 【中級】ノーコード ツール AppSheet 活用企業と学ぶ、データ活用最新情報 </td> <td style="text-align:center;"> 13:00 ~ 13:40 </td> </tr> <tr> <td style="text-align:center;"> 【初級】2023 年版: アプリケーションをどこで動かすべきか </td> <td style="text-align:center;"> 14:00 ~ 14:40 </td> </tr> <tr> <td style="text-align:center;"> 【初級】Google Cloud を活用したカインズ流需要予測とは </td> <td style="text-align:center;"> 15:00 ~ 15:50 </td> </tr> <tr> <td style="text-align:center;"> 【初級】金融システムのセキュリティ対策要点と最新版 FISC リファレンスのご紹介 </td> <td style="text-align:center;"> 16:00 ~ 16:40 </td> </tr> <tr> <td style="text-align:center;"> 【初級】今さら聞けない!ベクトル検索超入門 ~データベース ユーザーは何をおさえておけばいいのか?~ </td> <td style="text-align:center;"> 17:00 ~ 17:00 </td> </tr> </tbody> </table> <p>本記事では、 <code>【初級】2023 年版: アプリケーションをどこで動かすべきか</code> をピックアップしてお伝えします。</p> <h3 id="初級2023-年版-アプリケーションをどこで動かすべきか">【初級】2023 年版: アプリケーションをどこで動かすべきか</h3> <h4 id="Google-Cloudが目指すもの">Google Cloudが目指すもの</h4> <p> <code>Google Cloud</code>はアプリケーション開発に便利な様々な機能を提供しています。そんな<code>Google Cloud</code>が目指すものは、<strong>開発者がいつでもどこでも開発できる環境を提供すること</strong>です。<br> 従来の開発はlocalのPCで、アプリケーションを開発したりサーバーを立てたりしていました。 しかし、この開発方法では、大きく3つの問題を抱えてしまいます。</p> <ol> <li>アプリケーションのbuild&amp;deployの手間</li> <li>サーバーの設定メンテナンスの労力</li> <li>localのPCの環境に依存する この後紹介する<code>Google Cloud Platform</code>を使うと、この3つの問題を解決できます。 以降の内容では、開発プロセス順に沿ってツールの紹介をします。</li> </ol> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231120/20231120095304.png" width="1224" height="468" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>※Googleのアーキテクチャ図用のソリューションのアイコンセットに<a href="https://cloud.google.com/icons?hl=ja"><code>Cloud Workstations</code></a>の公式アイコンがなかったため、<code>Cloud Workstations</code>は公式アイコンなしというアイコンで代替させていただきます</p> <h4 id="Cloud-Workstations">Cloud Workstations</h4> <p><figure class="figure-image figure-image-fotolife" title="Cloud Workstations"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231120/20231120095257.png" width="1224" height="470" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud Workstations</figcaption></figure>  リモートな統合開発環境です。大きな特徴として、<code>DuetAI</code>をによる<code>Google Cloud</code>の開発者支援ができます。(<code>Cloud Workstations with DuetAI</code>)</p> <p><code>DuetAI</code>による開発者支援機能</p> <ul> <li>ポイラープレートコードの生成</li> <li>インラインでのコード補完</li> <li>コードの解説</li> <li>コードのセキュリティガードレール</li> <li>企業向けカスタマイズ</li> </ul> <p><code>Cloud Workstations</code>はもちろん、<code>Cloud Shell</code>や<code>Cloud Code</code>、<code>VSCode</code>向けに提供中または提供予定です。</p> <p>※ポイラープレートコードとは複数の場所で繰り返される定型コードのこと ※セキュリティガードレールとは社内でクラウド利用のルールを定義し、 ルールを遵守していない利用を制限または検知できるようにすること</p> <h4 id="Cloud-Build">Cloud Build</h4> <p><figure class="figure-image figure-image-fotolife" title="Cloud Build"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231120/20231120095245.png" width="1224" height="468" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud Build</figcaption></figure>  サーバーレスの<code>CI/CD</code>プラットフォームです。<code>Google Cloud Platform</code>で構成しているサービスのサービスアカウントで権限管理できる。</p> <ul> <li>デベロッパーフレンドリー</li> <li>柔軟なビルドステップ</li> <li>フルマネージドCIプラットフォーム</li> </ul> <h4 id="Cloud-Deploy">Cloud Deploy</h4> <p><figure class="figure-image figure-image-fotolife" title="Cloud Deploy"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231120/20231120095251.png" width="1224" height="468" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Cloud Deploy</figcaption></figure></p> <p> ソフトウェアやアプリケーションを<code>Google Kubernetes Engine</code>や<code>Cloud Run</code>へデプロイするサービスです。</p> <ul> <li>継続的デリバリー(CD)に特化したサービス</li> <li>ベストプラクティスを実装</li> <li>アプリケーションの厳密な管理</li> <li>重要指標の可視化</li> </ul> <p>※継続的デリバリー(CD)とは、結合テストなどを自動的に行い、設定した環境へデプロイすることを保証するプロセスです。</p> <h4 id="その他サーバーレスプラットフォームの使用例について">その他サーバーレスプラットフォームの使用例について</h4> <p> アプリケーションが実行環境に上がったら、<code>Google Kubernetes Engine</code>や<code>Cloud Run</code>を使うことで、アプリケーションの管理やユーザーがアプリケーションを使えるようになります。</p> <h4 id="Google-Kubernetes-Engine-GKE">Google Kubernetes Engine (GKE)</h4> <p> クラウド上でを実行するためのマネージャーおよびオーケストレーションシステムです。<br> <code>Google Cloud</code>上で、Dockerコンテナなどのコンテナ化されたアプリケーションのデプロイ、運用管理を行うためのシステムです。名前からわかるように、Kubernetesを拡張したサービスです。</p> <ul> <li>Standardモードでおすすめのユースケース <ul> <li>ノードの設定変更が必要なゲームのバックエンド</li> <li>GPU, TPUを使うMLパイプライン</li> </ul> </li> <li>Autopilotモードでおすすめのユースケース <ul> <li>スケーラブルな負荷テストツール用環境</li> <li>必要リソースに変動の少ないシステム</li> </ul> </li> </ul> <h4 id="Cloud-Run">Cloud Run</h4> <p> <code>Cloud Run</code>コンテナを、<code>Google Cloud</code>が用意したサーバー上で動かすことができます。手軽にWebサイト/Web APIサーバーを作れて、スケーリングも勝手に面倒見てくれます。</p> <ul> <li>サーバーレスで柔軟性を持ったアプリケーションを作れる</li> <li>コンテナをデプロイするだけで外部から到達可能なURLが発行される</li> <li>イベント駆動処理</li> <li>HTTP/2、WebSocket、gRPCに対応</li> </ul> <p>※スケーリングとは、ユーザ数が一時的に増えても、サーバーが落ちないように、サーバーの増減しサービスのアーキテクチャが機能できるようにすることです。</p> <p>業務でテスト環境や本番環境などにデプロイすることがあり、<code>Google Cloud</code>でも同じような機能を使えることを初めて知りました。<br> <code>CI/CD</code>の理解を深めるためにも、<code>Cloud Build</code>や<code>Cloud Deploy</code>で勉強するのもありだと思いました。</p> <h3 id="今回参加したセッションの内容から今後はどんなことに繋げていけそうなのか">今回参加したセッションの内容から、今後はどんなことに繋げていけそうなのか?</h3> <p> 業務では、既存の整備された実行環境でデプロイしていましたが、本記事を執筆する中で実行環境の作り方を知らなかったことに気づきました。これを機に<code>Google Cloud</code>で、アプリケーションの実行環境を作ってみたいと思いました。</p> <h3 id="最後に">最後に</h3> <p> オプティムでは、世界の人々・各産業に大きく良い影響を与えたい方、身の丈に合わない大きな志を持って楽しみながら挑戦し、自ら己の可能性を広げられる方、そして、あらゆる属性を意識せず思いやりを持ってこれからのオプティムという組織・文化を創っていって頂ける方を新卒・中途問わず通年で募集しています。新卒採用では引き続きエンジニア志望(プログラミング未経験者可)、ビジネスサイド志望ともに募集しています。私たちと一緒に世界を変える大きなことにチャレンジしたいという方、是非以下をご覧になってください。ご応募お待ちしております。</p> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy" data-gtm-yt-inspected-30="true"></iframe> optim-takuma-shiraki Laravelのタスクスケジュール機能を使った定期実行のやり方 hatenablog://entry/6801883189054653382 2023-11-08T10:00:00+09:00 2023-11-08T10:00:04+09:00 はじめに はじめまして、DXビジネス開発部フィールドソリューションユニットの田村です。 テックブログ初投稿です、温かい目で読んでもらえますと幸いです。 普段の業務ではバックエンドの開発を行っており、主にGolangやphp(Laravel)などを使用しています。 今回はLaravelのタスクスケジュール機能を利用する機会がありましたのでその方法について記載したいと思います。 目次 はじめに 目次 Laravelのタスクスケジュールとは 基本的な使い方 artisan commandの作成・登録 scheduleの実行 単一サーバー上での実行について onOneServerメソッドについて キャ… <h2 id="はじめに">はじめに</h2> <p>はじめまして、DXビジネス開発部フィールドソリューションユニットの田村です。<br/> テックブログ初投稿です、温かい目で読んでもらえますと幸いです。</p> <p>普段の業務ではバックエンドの開発を行っており、主にGolangやphp(Laravel)などを使用しています。 今回はLaravelのタスクスケジュール機能を利用する機会がありましたのでその方法について記載したいと思います。</p> <h2 id="目次">目次</h2> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#目次">目次</a></li> <li><a href="#Laravelのタスクスケジュールとは">Laravelのタスクスケジュールとは</a></li> <li><a href="#基本的な使い方">基本的な使い方</a><ul> <li><a href="#artisan-commandの作成登録">artisan commandの作成・登録</a></li> </ul> </li> <li><a href="#scheduleの実行">scheduleの実行</a></li> <li><a href="#単一サーバー上での実行について">単一サーバー上での実行について</a><ul> <li><a href="#onOneServerメソッドについて">onOneServerメソッドについて</a></li> <li><a href="#キャッシュドライバの設定について">キャッシュドライバの設定について</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> </ul> <h2 id="Laravelのタスクスケジュールとは">Laravelのタスクスケジュールとは</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Flaravel.com%2Fdocs%2F10.x%2Fscheduling%23running-the-scheduler" title="Laravel - The PHP Framework For Web Artisans" 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://laravel.com/docs/10.x/scheduling#running-the-scheduler">laravel.com</a></cite></p> <p>Laraveに標準で搭載されている機能でLaravelアプリケーションの中で定期実行したい処理を登録できます。つまりLaravelアプリケーションを立ち上げたサーバーを一つ用意し設定すればば、従来のようにサーバー内で複数cron書式の設定をする必要がなく、cron用のサーバーを別で管理する必要もなくなります。</p> <h2 id="基本的な使い方">基本的な使い方</h2> <p>スケジュール機能の基本的な使い方について紹介します。<br/> まず実行したい処理は基本的に<code>app/Console/Kernel.php</code>のscheduleメソッドの中に書きます。<br/> 以下は1時間おきに「こんにちは」という文字列を出力するという内容です。<br/> 気分がいいですね。</p> <pre class="code" data-lang="" data-unlink>class Kernel extends ConsoleKernel { /** * Define the application&#39;s command schedule. * * @return void */ protected function schedule(Schedule $schedule) { $schedule-&gt;call(function (){ Log::info(&#39;こんにちは&#39;) })-&gt;hourly(); } 〜〜以下略〜〜</pre> <p>時間の指定は割と柔軟に指定できるようなので公式ドキュメントをご確認いただけますと幸いです。</p> <h3 id="artisan-commandの作成登録">artisan commandの作成・登録</h3> <p>scheduleメソッドの中に書く処理はartisan commandとして登録したものを実行するのが一般的なようです。</p> <p>以下でartisan commandを登録する雛形を作成できます。</p> <pre class="code" data-lang="" data-unlink>php artisan make:command Greeting</pre> <p><code>app/Console/Commands</code>にファイルが作成されます。 実行する処理についてはhandleメソッドに書きます。 またcommandの名前や説明を追加できます。</p> <pre class="code" data-lang="" data-unlink>&lt;?php namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; class Greeting extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = &#39;app:greeting&#39;; /** * The console command description. * * @var string */ protected $description = &#39;&#39;; /** * Execute the console command. */ public function handle() { Log::info(&#39;こんにちは&#39;) } } </pre> <p>コマンドを作成した後に、 作成したコマンドが登録されているか確認します</p> <pre class="code" data-lang="" data-unlink>php artisan</pre> <p>artisanコマンドの一覧が表示されると思います。 さきほど登録した<code>app:greeting</code>があれば問題ありません。</p> <h2 id="scheduleの実行">scheduleの実行</h2> <p>Command登録後はscheduleメソッド内で先ほど登録したCommandを利用するようにします。</p> <pre class="code" data-lang="" data-unlink>protected function schedule(Schedule $schedule) { $schedule-&gt;command(&#39;app:greeting&#39;) -&gt;hourly(); }</pre> <p>ここでスケジュールが正確に行われるか確認するコマンドを紹介します。<br/> 以下で処理が実行される時間やコマンド名などを確認できます</p> <pre class="code" data-lang="" data-unlink>php artisan schedule:list</pre> <p>期待する処理がスケジュールされているようであれば処理を実行します。<br/> サーバー側で1分おきに<code>php artisan schedule:run</code>コマンドを叩くようにcronエントリを設定する必要があります。 <br/> 複数のcron処理をLaravel側で登録し、実行時はcronの設定を一つ書くだけになるので非常に便利ですね。</p> <p>以下公式ドキュメントから抜粋</p> <pre class="code" data-lang="" data-unlink>* * * * * cd /path-to-your-project &amp;&amp; php artisan schedule:run &gt;&gt; /dev/null 2&gt;&amp;1</pre> <p>またローカル環境で実行したい場合は以下のコマンドが利用できます。</p> <pre class="code" data-lang="" data-unlink>php artisan schedule:work </pre> <h2 id="単一サーバー上での実行について">単一サーバー上での実行について</h2> <p>続いて単一サーバー上での実行方法について紹介します。 ここまでのやり方で実際に運用をしていくとなると一つ問題があります。<br/> 一つのサーバーだけを起動して運用する場合であればこれまでの方法で問題ありませんが、複数のサーバーを起動するとなるとそうはいきません。<br/> 例えば負荷分散等で今回作ったLaravelアプリケーションのサーバーを3つ起動させる場合、それぞれのサーバーでスケジュールしていた処理が実行されてしまい、計3回の処理が走ってしまいます。それはさすがにまずいので1つのサーバーだけスケジュールした処理を実行するようにしたいですね。</p> <h3 id="onOneServerメソッドについて">onOneServerメソッドについて</h3> <p>そこで登場するのがonOneServerメソッドです。<br/> 名前の通り1つのサーバーでのみ実行するように制御できるメソッドです。<br/> 使い方としては以下のように追加するだけです。</p> <pre class="code" data-lang="" data-unlink>protected function schedule(Schedule $schedule) { $schedule-&gt;command(&#39;app:greeting&#39;) -&gt;hourly() -&gt;onOneServer(); }</pre> <h3 id="キャッシュドライバの設定について">キャッシュドライバの設定について</h3> <p>この機能を利用するにあたってLaravel側でキャッシュドライバの設定が必要になります。 DB, Redis, Memcacheなどいろいろ利用できますが、今回はDBをキャッシュドライバとして使っていきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Flaravel.com%2Fdocs%2F10.x%2Fcache%23driver-prerequisites" title="Laravel - The PHP Framework For Web Artisans" 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://laravel.com/docs/10.x/cache#driver-prerequisites">laravel.com</a></cite></p> <p>以下コマンドでキャッシュ用のテーブルを作成するマイグレーションファイルを作成します。</p> <pre class="code" data-lang="" data-unlink>php artisan cache:table</pre> <p>テーブル作成後はLaravelアプリケーション側でキャッシュドライバの設定を変更します。 <code>config/cache.php</code>にあるcacheのdefault設定を今回はdatabaseにします。</p> <pre class="code" data-lang="" data-unlink>&#39;default&#39; =&gt; env(&#39;CACHE_DRIVER&#39;, &#39;database&#39;),</pre> <p>以上で設定完了です。<br/> 実際に複数サーバーの環境で実行してみるとキャッシュドライバとして作成したcacheテーブルにレコードが追加されていると思います。</p> <p>おそらく一番最初に実行しようとしたサーバーがDBにレコードを追加し、それ以降のサーバーはDBにレコードがあるため処理を行わないという流れになっていると思われます。 実際にサーバーのログを確認すると一つのサーバーだけ処理が走り、それ以外のサーバーではスキップされていることが確認できるかと思います。</p> <h2 id="おわりに">おわりに</h2> <p>今回はLaravelのタスクスケジュールと単一サーバー上で実行させる方法について紹介しました。 注意事項として処理が大きすぎる場合はサーバーのリソースが不足する可能性がありますのでご注意ください。<br/> とても手軽に実装できるのでLaravelをお使いの方は是非利用してみてください!</p> <p>OPTiMではエンジニアを募集しております。 <br/> Laravelに限らず様々な言語やフレームワークを使ってプロダクト開発をしています、ご興味のある方は是非こちらをご覧ください。</p> <p>  <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-haruto-tamura Kaigi on Rails 2023に参加しました! hatenablog://entry/6801883189054968069 2023-11-06T10:00:00+09:00 2023-11-06T10:00:19+09:00 こんにちは! Optimal Biz開発チームの伊藤、片岡です。 今回はオプティムから2名でKaigi on Rails 2023に参加してまいりましたので、会場の雰囲気や内容をレポートします! 会場の様子 Kaigi on Rails 2023は、Ruby on Railsを中心とした技術カンファレンスで、「初学者から上級者までが楽しめるWeb系の技術カンファレンス」というコンセプトで運営されています。 2020年以降、毎年オンラインで開催されてきましたが、今回初めてオフラインでの開催となりました! 今回の会場は、浅草橋にある浅草橋ヒューリックホール&カンファレンスでした。 建物も新しく素敵… <p>こんにちは! Optimal Biz開発チームの伊藤、片岡です。 今回はオプティムから2名でKaigi on Rails 2023に参加してまいりましたので、会場の雰囲気や内容をレポートします!</p> <h1 id="会場の様子">会場の様子</h1> <p>Kaigi on Rails 2023は、Ruby on Railsを中心とした技術カンファレンスで、「初学者から上級者までが楽しめるWeb系の技術カンファレンス」というコンセプトで運営されています。 2020年以降、毎年オンラインで開催されてきましたが、今回初めてオフラインでの開催となりました!</p> <p>今回の会場は、浅草橋にある浅草橋ヒューリックホール&amp;カンファレンスでした。 建物も新しく素敵な会場でした!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231031/20231031185155.jpg" width="1201" height="1600" loading="lazy" title="" class="hatena-fotolife" style="width:500px" 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/o/optim-tech/20231031/20231031191032.jpg" width="1200" height="1600" loading="lazy" title="" class="hatena-fotolife" style="width:500px" 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/o/optim-tech/20231031/20231031185206.jpg" width="1600" height="1201" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span></p> <p>講演の内容は初心者向けが多く、どの講演からもRuby愛・Rails愛が伝わってきました! RubyやRailsに携わる方々の雰囲気を感じる事ができ、講演内容も業務に近しい内容が多いため、Ruby on Rails初学者におすすめのイベントです!</p> <h1 id="気になった講演の紹介">気になった講演の紹介</h1> <h3 id="Exceptional-Rails"><a href="https://kaigionrails.org/2023/talks/willnet/">Exceptional Rails</a></h3> <p>Ruby on Railsにおける例外処理の考え方や具体的なやり方を解説した講演でした。 私(伊藤)は長く大きなRuby on Railsプロダクトに携わっていますが、共感できるところが多くありました。 Railsに関わるエラーハンドリングを勉強したい初心者〜中級者にとてもおすすめです!</p> <h3 id="Hotwire的な設計を追求してWeb紙芝居に行き着いた話"><a href="https://kaigionrails.org/2023/talks/nay3/">Hotwire的な設計を追求して「Web紙芝居」に行き着いた話</a></h3> <p>Hotwireのための設計指針を紹介した講演でした。 指針として大きく分けて画面的アプローチと部品的アプローチがあり、Hotwireは画面的アプローチの方が向いているというお話でした。 ReactなどのSPAとは違い、Ruby on Rails/Hotwireではすべてサーバサイドに寄せた方がいいとわかり、設計思想の違いが面白いなと思いました。</p> <h1 id="おわりに">おわりに</h1> <p>2日目のクロージングの際に、来年の開催についての案内がありました。 来年の会場は……</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231031/20231031185144.jpg" width="1600" height="1201" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span></p> <p>404でした! 会場はまだ決まっていないとのことですが、今から楽しみです!</p> <p>OPTiMでは、Ruby on Railsを利用したアプリケーションを開発しています。ご興味のある方は、ぜひ一度ご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-kento-kataoka ポテンシャル新卒採用とIT研修 (2023年度版) hatenablog://entry/6801883189053097111 2023-11-01T10:00:00+09:00 2023-11-01T10:00:10+09:00 久方ぶりです。中野です。 本日は、オプティムのポテンシャル採用向けの新卒研修の近況について紹介したいと思います。 過去同様の記事を出しておりますが、これらの2023年度版になります。 tech-blog.optim.co.jp tech-blog.optim.co.jp また、採用ページにも研修の紹介をした記事がありますので、ご興味あれば御覧ください。 www.optim.co.jp www.optim.co.jp 研修は毎年改善・アップデートしておりますので、本記事、過去記事ともに、あくまでその時の研修がこうだった・・という履歴となりますのでご留意ください。 ポテンシャル採用とは オプティム… <p>久方ぶりです。中野です。<br/> 本日は、オプティムのポテンシャル採用向けの新卒研修の近況について紹介したいと思います。</p> <p>過去同様の記事を出しておりますが、これらの2023年度版になります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2019%2F08%2F14%2F163000" title="プログラミング未経験者が3か月で開発エンジニアに!~オプティムが本気で取り組む IT人財教育プログラムの全貌〜 - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2019/08/14/163000">tech-blog.optim.co.jp</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2022%2F07%2F25%2F100000" title="IT未経験者が3か月で開発エンジニアに!4年目を迎えた新卒IT研修の内容をお伝えします - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2022/07/25/100000">tech-blog.optim.co.jp</a></cite></p> <p>また、採用ページにも研修の紹介をした記事がありますので、ご興味あれば御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2Finterview%2Fstaff06%2F%3Fbutton%3Dinterview" title="IT未経験人材を最速でエンジニアに! オプティムのトップエンジニアが作るIT研修とその創設秘話に迫る | スタッフインタビュー | OPTiMの採用情報" 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.optim.co.jp/recruit/interview/staff06/?button=interview">www.optim.co.jp</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2Finterview%2Fstaff05%2F%3Fbutton%3Dinterview" title="未経験でも3カ月でエンジニアに! 理系出身若手社員が語るオプティム独自のエンジニア育成プログラム 「IT研修」と学び合い・高め合いカルチャー | スタッフインタビュー | OPTiMの採用情報" 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.optim.co.jp/recruit/interview/staff05/?button=interview">www.optim.co.jp</a></cite></p> <p>研修は毎年改善・アップデートしておりますので、本記事、過去記事ともに、あくまでその時の研修がこうだった・・という履歴となりますのでご留意ください。</p> <h2 id="ポテンシャル採用とは">ポテンシャル採用とは</h2> <p>オプティムの新卒採用は、大きくエンジニア採用とポテンシャル採用(総合職)の2つの区分があります。 ちなみに、営業、法務、経理、、なども個別に積極採用しております。詳細は是非こちらをご覧ください。 <a href="https://www.optim.co.jp/recruit/new-graduate/">https://www.optim.co.jp/recruit/new-graduate/</a></p> <p>エンジニア採用とは、エンジニア配属が確定した区分での採用となります。 基本的には情報系出身でプログラミング経験がある程度ある候補者が該当します。 これに対してポテンシャル採用とは、ビジネス配属(営業・企画)やエンジニア配属など様々な配属の可能性がある区分となります。 最終的な配属先は、入社後の研修期間中に決定されます。 ポテンシャル採用は非情報系出身者も多くおられます。</p> <p>まとめると以下のようになります。</p> <table> <thead> <tr> <th>採用区分</th> <th colSpan="3">研修</th> <th>配属</th> </tr> </thead> <tbody> <tr> <td>エンジニア採用</td> <td rowSpan="3">共通研修</td> <td colSpan="2">→ エンジニア研修(現場OJTと同時並行)</td> <td rowSpan="2">エンジニア配属</td> </tr> <tr> <td rowSpan="2">ポテンシャル採用</td> <td rowSpan="2">→ IT研修</td> <td>→ IT研修</td> </tr> <tr> <td>→ ビジネス研修</td> <td>ビジネス配属</td> </tr> </tbody> </table> <h2 id="新卒研修の流れ">新卒研修の流れ</h2> <p>オプティムの新卒研修は、最初は職種横断の共通研修から始まりますが、研修が進むにつれてポテンシャル採用向けとエンジニア採用向けで分岐していきます。 (本記事ではポテンシャル採用向けに実施しているIT研修にフォーカスを当てて紹介します)</p> <p>共通研修はオプティムの事業や制度に関するオリエンテーションなどを中心に、同期同士の繋がりを深めて頂く場としても職種毎に分けずに全員合同で実施します。研修は毎年改善されており共通研修の期間も年度によって異なりますが、およそ2週間程度となります。そののち、ポテンシャル採用向けの研修とエンジニア採用向けの研修は分岐し、それぞれの形で行っていくことになります。本記事では詳細は割愛しますが、エンジニア採用の方々は共通研修後に現場配属し、実業務に従事しながら実際に感じる課題感を週1ペースの集合研修でフォローしていく、OJTとのハイブリッド型の研修スタイルをとっています。</p> <p>ポテンシャル採用向けの研修はIT研修とも呼ばれ、この研修はSTEP1〜4の4ステップに分かれています。このSTEP1〜4のどこかのタイミングでそれまでの研修の結果と希望など様々な点を考慮してエンジニア配属かビジネス配属かの方向性が決定されます。この決定を元にポテンシャル採用向けの研修は更に分岐し、IT研修とビジネス研修に分かれることになります。エンジニア配属となる方は、引き続きIT研修の残りのコンテンツを実施していく形となります。</p> <h2 id="IT研修の内容">IT研修の内容</h2> <p>前提として、このIT研修はポテンシャル人材に向けた研修です。ポテンシャル人材のバックボーンは様々で、情報系でなく全くプログラミングをしたこともない人もいれば、同じく情報系ではないけれども大学の研究の兼ね合いで多少なりともプログラミングできるという人までいます。</p> <p>このIT研修はそのようなさまざまなバックボーンを持つ人達に、IT全般の知識を伝え、またプログラミングの基礎をインプットする場として設けてあります。</p> <p>期間は約2.5ヶ月です。ただし、GWなどを含めると正味2ヶ月程度となります。この期間で、インプットしうる限りをインプットしてもらうことになります。</p> <p>具体的な内容は2つのコンテンツからなります。座学と実習です。前述のSTEP1〜4というのは実習のことで、段階的にプログラミングの基礎を扱います。座学は実習に並行して行い、プログラミングの下地となるIT全般の知識をつけてもらいます。</p> <p>本研修のゴールは、研修修了時点でエンジニアとしていきなり即戦力<a href="#f-ef4699dc" name="fn-ef4699dc" title=" ここで言う即戦力とは、例えば最低限下記のようなことができるようになった状態を言います。 見様見真似ではなく、多少なりとも良し悪しを自分で判断でき、観点を抑えた最良なコードを書いて機能を実現することができる Gitやチケット管理を最良な形で行えて、チーム開発ができる ">*1</a>になれるようになる、といったものではありません。もちろん、それが叶えば最高ですが、2.5ヶ月という期間を考慮するとそれは無茶な目標です。</p> <p>ポテンシャル採用からエンジニアとして配属される方は半年〜約1年かけて現場でのOJTを通して徐々にエンジニアとして立ち上がって頂く事をイメージしております。どうしても知識や技術が定着するには数をこなし試行錯誤が必要で、時間が必要となるからです。</p> <p>そのためこの研修では、最初の立ち上がり期間を走り切るための下準備、あるいは時に道標となるような内容を重視しています。それぞれの領域をじっくり教え込むことはしません。基礎を固める、ということもしてません。基礎は反復訓練によって固まるものであり、この短い研修時間だけでは達成し得ないためです。この研修では、色々な技術や知識に対するインデキシングにウェイトが置かれています。技術や知識は身についておらずとも、いざ必要になったらキーワードを思い出して調べることができる状態を目指します。</p> <h3 id="座学">座学</h3> <p>座学では、プログラミングにとどまらず、IT全般の知識を得ることを目的にしています。 あくまでポテンシャル採用者が対象となるため、高度で専門的な内容ではなく、幅広く全般的な内容が中心になります。 (ただし、エンジニアがふとした時に見直すために利用できる資料にもなっています)</p> <p>コンテンツは大雑把にわけると21に分類できます。またそれぞれが前後編に別れていたり、いくつかの小分類に別れていたりします。これらの内容について、それぞれ動画<a href="#f-44d6209c" name="fn-44d6209c" title="この動画はVOICEPEAKによるAI読み上げで作成されており、アップデートなどがやりやすいようになっています">*2</a>や資料形式で教材が用意されていて、それぞれのタイミングで視聴・閲覧していただく形になります。動画形式であるため、いつでも見返す事ができます。</p> <ol> <li>Getting Started​​​​​​​ <ul> <li>PCセットアップ、Markdown記法、など</li> </ul> </li> <li>業務開発 <ul> <li>業務開発概論、Git、チケット管理、など</li> </ul> </li> <li>エンジニアリング基礎 <ul> <li>情報の探し方、バグとは、オープンソースソフトウェアとは、など</li> </ul> </li> <li>プログラミングの基本 <ul> <li>プログラミング基礎、プログラミング言語、など</li> </ul> </li> <li>コンピュータ基礎 <ul> <li>コンピュータのインターフェース、データ形式など</li> </ul> </li> <li>コンピュータアーキテクチャ <ul> <li>コンピュータの仕組み、など</li> </ul> </li> <li>オペレーティングシステム</li> <li>オブジェクト指向</li> <li>並行と並列</li> <li>ドキュメンテーション (UML)</li> <li>データ構造とアルゴリズム <ul> <li>計算量、データ構造、ソートアルゴリズムなど</li> </ul> </li> <li>ネットワーク <ul> <li>ネットワーク概論、TCP/IP、DNS、URLなど</li> </ul> </li> <li>WEB <ul> <li>WEB概論、HTTP、WEBアプリケーションフレームワーク、フォーム、Cookieなど</li> </ul> </li> <li>IDと認証・認可</li> <li>データベース <ul> <li>データベース基礎、RDB、SQL、DB設計など</li> </ul> </li> <li>セキュリティ</li> <li>SSL/TLS・PKI</li> <li>非機能と品質</li> <li>ソフトウェアテスト <ul> <li>テスト概論、単体テストなど</li> </ul> </li> <li>インフラ設計</li> <li>仮想化技術 <ul> <li>Docker、Docker Compose、Kubernetesなど</li> </ul> </li> </ol> <h3 id="実習">実習</h3> <p>実習では、実際にプログラミングを学びます。</p> <p>実習でどの言語を使ってもらうか、なにを作ってもらうかは、毎年少しずつ事業の状況に応じて変わっています。2023年度はJavaScript/TypeScriptを採用しましたが、過去はGo、Ruby、Javaなどもありました。</p> <p>実習は前述の通り4ステップに分かれています。最後のSTEP4がメインコンテンツであり、STEP1〜3はその下準備的な内容となっています。</p> <ul> <li>STEP1 <ul> <li>約3週間</li> <li>プログラミングの基礎、いろはを学ぶ (JavaScript)</li> <li>タイピングがスムーズにできるようになる (寿司打など)</li> <li>演習課題3つ</li> </ul> </li> <li>STEP2 <ul> <li>2日</li> <li>SQLでデータベースを操作できるようになる (PostgreSQL)</li> <li>Dockerも触る (Compose中心)</li> </ul> </li> <li>STEP3 <ul> <li>2日</li> <li>NoCodeでWEBアプリを作ってみる (Bubble.io)</li> <li>STEP4に備えも兼ねて、WEBアプリの構造、UI要素や振る舞いを知る</li> </ul> </li> <li>STEP4 <ul> <li>約1.5ヶ月</li> <li>チーム開発でWEBアプリをスクラッチ開発</li> <li>お題はエンタープライズ向けチャットアプリ</li> <li>TypeScript、Vue、Node、Express、PostgreSQL</li> </ul> </li> </ul> <p>オプティムの製品にはエンタープライズ向けのもの(いわゆるB2B)が多くあります。エンタープライズ向けのアプリは仕様に一癖あります。<br/> Step4では以下のようなデータ構造<a href="#f-b4b0e401" name="fn-b4b0e401" title="この仕様は実際のプロダクト向けではありません">*3</a>で定義されるエンタープライズ向けチャットアプリを作ることで、エンタープライズ向けの仕様への理解を深めることも狙いです。</p> <p><a href="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231025/20231025154031.png" class="http-image"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231025/20231025154031.png" class="http-image" alt="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231025/20231025154031.png"></a></p> <p>今年度の研修組が、配属後にこのアプリに関連する記事を執筆していたりしますのでこちらもご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2023%2F09%2F26%2F100000" title="IT研修で作成した業務用チャットアプリをEKSにデプロイしました - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2023/09/26/100000">tech-blog.optim.co.jp</a></cite></p> <h3 id="最後に">最後に</h3> <p>オプティムでは、世界の人々・各産業に大きく良い影響を与えたい方、身の丈に合わない大きな志を持って楽しみながら挑戦し、自ら己の可能性を広げられる方、そして、あらゆる属性を意識せず思いやりを持ってこれからのオプティムという組織・文化を創っていって頂ける方を新卒・中途問わず通年で募集しています。新卒採用では引き続きエンジニア志望(プログラミング未経験者可)、ビジネスサイド志望ともに募集しています。私たちと一緒に世界を変える大きなことにチャレンジしたいという方、是非以下をご覧になってください。ご応募お待ちしております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-ef4699dc" name="f-ef4699dc" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ここで言う即戦力とは、例えば最低限下記のようなことができるようになった状態を言います。</p> <ul> <li>見様見真似ではなく、多少なりとも良し悪しを自分で判断でき、観点を抑えた最良なコードを書いて機能を実現することができる</li> <li>Gitやチケット管理を最良な形で行えて、チーム開発ができる</li> </ul> <p></span></p> <p class="footnote"><a href="#fn-44d6209c" name="f-44d6209c" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">この動画はVOICEPEAKによるAI読み上げで作成されており、アップデートなどがやりやすいようになっています</span></p> <p class="footnote"><a href="#fn-b4b0e401" name="f-b4b0e401" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">この仕様は実際のプロダクト向けではありません</span></p> </div> optim-nakano Vue2系からVue3系への移行における苦労話 hatenablog://entry/820878482972504097 2023-10-30T10:00:00+09:00 2023-10-30T10:00:00+09:00 はじめまして この記事について 移行前の環境 移行の流れ Vue 3.3 移行について 破壊的変更の対応について 1. v-model v-bind.syncについて 2. v-on.native 修飾子の削除について 3. vue-i18n 8.x → 9.x の破壊的変更対応 移行時の苦労 担当者曰く Vuetify2 から Vuetify3への移行について 全体的な変更 移行時、移行後の苦労 移行後の環境 移行後の使用感 最後に はじめまして サービス開発統括本部 ソリューション開発部で農業プロダクトの開発を担当している西村です。初めてのTECH BLOGです。 私は現在、ピンポイントタ… <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="#Vue-33-移行について">Vue 3.3 移行について</a><ul> <li><a href="#破壊的変更の対応について">破壊的変更の対応について</a><ul> <li><a href="#1-v-model-v-bindsyncについて">1. v-model v-bind.syncについて</a></li> <li><a href="#2-v-onnative-修飾子の削除について">2. v-on.native 修飾子の削除について</a></li> <li><a href="#3-vue-i18n-8x--9x-の破壊的変更対応">3. vue-i18n 8.x → 9.x の破壊的変更対応</a></li> </ul> </li> <li><a href="#移行時の苦労">移行時の苦労</a><ul> <li><a href="#担当者曰く">担当者曰く</a></li> </ul> </li> </ul> </li> <li><a href="#Vuetify2-から-Vuetify3への移行について">Vuetify2 から Vuetify3への移行について</a><ul> <li><a href="#全体的な変更">全体的な変更</a></li> <li><a href="#移行時移行後の苦労">移行時、移行後の苦労</a></li> </ul> </li> <li><a href="#移行後の環境">移行後の環境</a><ul> <li><a href="#移行後の使用感">移行後の使用感</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="はじめまして">はじめまして</h1> <p>サービス開発統括本部 ソリューション開発部で農業プロダクトの開発を担当している西村です。初めてのTECH BLOGです。 私は現在、<a href="https://www.optim.co.jp/agriculture/services/pts">ピンポイントタイム散布</a> (以下、PTS)というサービスで使用するアプリの開発を担当しています。今回の記事では農業要素はあまりないですが、 農業プロダクトで使用されている技術について触れていきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Fagriculture%2Fservices%2Fpts" title="ドローン適期防除サービス | ピンポイントタイム散布 | OPTiM" 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></p> <h1 id="この記事について">この記事について</h1> <p>我々農業チームではフロントエンドでVue.jsを利用しています。Vue2系は2023 年 12 月 31 日に End of Life に到達します。</p> <p>Vue.js 公式ドキュメント<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>より</p> <blockquote><p>2022 年 7 月に出荷された Vue 2.7 は、Vue 2 のバージョン範囲における最後のマイナーリリースとなります。Vue 2 は現在メンテナンスモードに移行しており、新しい機能は出荷されませんが、2.7 のリリース日から 18 か月間、重要なバグ修正とセキュリティアップデートが継続されます。これは、Vue 2 が 2023 年 12 月 31 日に End of Life に到達することを意味します。 <br/> これは、ほとんどのエコシステムが Vue 3 に移行するための十分な時間を提供するものだと考えています。しかし、セキュリティーおよびコンプライアンス要件を満たす必要がありながら、このスケジュールまでにアップグレードできないチームやプロジェクトがあることも理解しています。私たちは業界の専門家と提携し、そのようなニーズを持つチームのために Vue 2 の拡張サポートを提供しています。もしあなたのチームが 2023 年末以降も Vue 2 を使用する予定であれば、前もって計画を立て、Vue 2 Extended LTS について詳細を学んでください。</p></blockquote> <p>これに対応すべく、8月から9月にかけて、Vue2.7からVue3.3に移行しました。その過程での苦労について、チーム全員の意見と共にまとめようと思います。 <strong>結論だけ先に伝えると、Vuetify2からVuetify3への移行が最も苦労したとの認識で一致しました!</strong></p> <h1 id="移行前の環境">移行前の環境</h1> <ul> <li>移行前の環境は以下の通りです</li> <li>Vue2.7 + Typescript + Vuetify2.6 + Vite3.1 + Pinia2.0で動いていました</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synConstant">&quot;dependencies&quot;</span><span class="synSpecial">:</span> <span class="synSpecial">{</span> <span class="synConstant">&quot;pinia&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^2.0.22&quot;</span>, <span class="synConstant">&quot;vue&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^2.7.10&quot;</span>, <span class="synConstant">&quot;vue-router&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^3.6.5&quot;</span>, <span class="synConstant">&quot;vuetify&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^2.6.10&quot;</span> <span class="synSpecial">}</span>, <span class="synConstant">&quot;devDependencies&quot;</span><span class="synSpecial">:</span> <span class="synSpecial">{</span> <span class="synConstant">&quot;@vue/vue2-jest&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^29.1.1&quot;</span>, <span class="synConstant">&quot;storybook-builder-vite-vue2&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^0.1.32&quot;</span>, <span class="synConstant">&quot;vite-plugin-vue2&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^2.0.2&quot;</span>, <span class="synConstant">&quot;vite&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^3.1.1&quot;</span>, <span class="synConstant">&quot;vue-template-compiler&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^2.7.10&quot;</span>, <span class="synConstant">&quot;@vue/eslint-config-prettier&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^6.0.0&quot;</span>, <span class="synConstant">&quot;@vue/eslint-config-typescript&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^9.1.0&quot;</span>, <span class="synConstant">&quot;eslint-plugin-vue&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^9.4.0&quot;</span>, <span class="synConstant">&quot;vuetify-loader&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^1.9.2&quot;</span> <span class="synSpecial">}</span> </pre> <ul> <li>プロジェクトの立ち上げ時からComposition API<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>で書くようにしていました</li> </ul> <p>以下のような書き方です</p> <pre class="code vue" data-lang="vue" data-unlink>&lt;script setup&gt; import { ref, onMounted } from &#39;vue&#39; // リアクティブな状態 const count = ref(0) // 状態を変更し更新トリガーする関数 function increment() { count.value++ } // ライフサイクルフック onMounted(() =&gt; { console.log(`The initial count is ${count.value}.`) }) &lt;/script&gt; &lt;template&gt; &lt;button @click=&#34;increment&#34;&gt;Count is: {{ count }}&lt;/button&gt; &lt;/template&gt;</pre> <p>従来の書き方 (Options API) は以下の通りです</p> <pre class="code vue" data-lang="vue" data-unlink>&lt;script&gt; import { defineComponent } from &#39;vue&#39;; export default defineComponent({ data() { return { count: 1 } }, methods: { // 状態を変更し更新トリガーする関数 increment() { count.value++ } }, mounted() { console.log(`The initial count is ${count.value}.`); }, }) &lt;/script&gt; &lt;template&gt; &lt;button @click=&#34;increment&#34;&gt;Count is: {{ count }}&lt;/button&gt; &lt;/template&gt;</pre> <h1 id="移行の流れ">移行の流れ</h1> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20231025/20231025201314.png" width="1600" height="441" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="Vue-33-移行について">Vue 3.3 移行について</h1> <ul> <li>以下を参考にしました <ul> <li>Vue 3 移行ガイド<sup id="fnref:3"><a href="#fn:3" rel="footnote">3</a></sup>を参考にしました</li> <li>破壊的変更の対応<sup id="fnref:4"><a href="#fn:4" rel="footnote">4</a></sup></li> <li>各種関連パッケージのバージョンアップ</li> </ul> </li> </ul> <h2 id="破壊的変更の対応について">破壊的変更の対応について</h2> <h3 id="1-v-model-v-bindsyncについて">1. v-model v-bind.syncについて</h3> <ul> <li>.sync 修飾子 が消えて v-model に変更<sup id="fnref:5"><a href="#fn:5" rel="footnote">5</a></sup></li> <li>v-model の value を modelValueに、更新の emit を update:modelValue に変更<sup id="fnref:6"><a href="#fn:6" rel="footnote">6</a></sup></li> </ul> <h3 id="2-v-onnative-修飾子の削除について">2. v-on.native 修飾子の削除について</h3> <ul> <li>v-on の .native 修飾子は削除された<sup id="fnref:7"><a href="#fn:7" rel="footnote">7</a></sup></li> </ul> <h3 id="3-vue-i18n-8x--9x-の破壊的変更対応">3. vue-i18n 8.x → 9.x の破壊的変更対応</h3> <ul> <li>多言語対応で使用する<code>vue-i18n</code>の使い方が変更された</li> </ul> <pre class="code js" data-lang="js" data-unlink> // vue-i18n 8.x まで import VueI18n from &#39;vue-i18n&#39;; const vueI18n = new VueI18n(); const hoge1 = vueI18n.t(&#39;hoge.message&#39;).toString(); const hoge2 = vueI18n.tc(&#39;hoge.message&#39;); // vue-i18n 9.x から import { createI18n } from &#39;vue-i18n&#39;; const { t } = createI18n(); const hoge1 = t(&#39;hoge.message&#39;);</pre> <h2 id="移行時の苦労">移行時の苦労</h2> <h3 id="担当者曰く">担当者曰く</h3> <ul> <li>Vue 3 のバージョンアップ自体は正直そこまで大変な部分はなかった <ul> <li>下記の点から移行が容易だった <ul> <li>PTS は2022年に Vue 2.6 → Vue 2.7 へのバージョンアップ + Vite にビルドツールへの移行を実施したこと</li> <li>全て Composition API で書くことを徹底したこと</li> </ul> </li> <li>ほとんどの対応は公式の移行ガイドに従いつつ、 ESLint で指摘されたことを修正することで移行作業が進む</li> <li>vue-router や vue-i18n なども破壊的変更が入っているが、それも各公式の移行ガイドを見れば基本的に問題ない</li> </ul> </li> <li><p>農業プロダクトで利用しているGoogle Maps API との相性の悪さが目立つ</p> <ul> <li>Google Maps API を使いマップを表示しながらVue Devtools を操作すると、タブがほぼ 100 % フリーズするようになった <ul> <li>こちらの問題に関しては、ユーザーの利用には影響がありません。</li> <li>開発者がデバッグする際に使用するツール (Vue Devtools)を使う場合は注意が必要である</li> </ul> </li> <li>ポリゴンやマーカーなどの Google Maps API のオブジェクト<sup id="fnref:8"><a href="#fn:8" rel="footnote">8</a></sup> をリアクティブにすると、オブジェクトをマップ上から消せない場合がある <ul> <li>Vue2 → Vue3 でオブジェクトのリアクティビティーの仕組みが Proxy 使う形になったことが影響していると考えられる</li> </ul> </li> </ul> <pre><code class="``ts"> const polygon = ref(new google.maps.Polygon()); // ポリゴン をマップ上に表示 polygon.value.setMap(map); // ポリゴン をマップ上から削除 // =&gt; 本来これで消えるが、ref() でラップしリアクティブオブジェクトになっていると消えない場合がある polygon.value.setMap(null); </code></pre></li> <li><p>一番苦労したのは Vuetify2系 → Vuetify3系 へのバージョンアップである</p></li> </ul> <p>とのことで、次章に続きます</p> <h1 id="Vuetify2-から-Vuetify3への移行について">Vuetify2 から Vuetify3への移行について</h1> <ul> <li>以下の記事を参考に基本的な移行作業を進めました <ul> <li>Upgrade Guide<sup id="fnref:9"><a href="#fn:9" rel="footnote">9</a></sup></li> <li>主力製品の Vue 3 &amp; Vuetify 3 へのマイグレーション全記録<sup id="fnref:10"><a href="#fn:10" rel="footnote">10</a></sup></li> <li>Trying to use VDataTable from labs but getting 'VDataTable' is not exported by node_modules/vuetify/lib/components/index.mjs error<sup id="fnref:11"><a href="#fn:11" rel="footnote">11</a></sup></li> <li>SFC CSS Features<sup id="fnref:12"><a href="#fn:12" rel="footnote">12</a></sup></li> </ul> </li> </ul> <h2 id="全体的な変更">全体的な変更</h2> <ul> <li>Upgrade Guideを参考に修正を行いました <ul> <li>対応内容やコンポーネントについて以下に記載します <ul> <li>共通 <ul> <li>props の <code>value</code> が <code>model-value</code> に変更</li> <li><code>@input</code> が <code>@update:model-value</code> に変更</li> <li><code>background-color</code>の prop がリネームして bg-color に変更</li> <li>一部コンポーネントの <code>dense</code> の prop が density="'default' | 'comfortable' | 'compact'" に変更</li> <li><code>activator={ attrs, on }</code> が <code>#activator={ props }</code> に変更され、v-on="on" が削除 &amp; v-bind="attrs" が v-bind="props" に変更</li> <li>filled / outlined / solo の prop が variant の props に統合</li> <li>success と success-messages props は廃止</li> <li>validate-on-blur の prop が validate-on="blur" に変更</li> </ul> </li> <li>PTSの中で使っているコンポーネントの中で破壊的変更が入っているもの <ul> <li>v-icon</li> <li>v-btn</li> <li>v-menu</li> <li>v-alert</li> <li>v-checkbox</li> <li>v-radio</li> <li>v-switch</li> <li>v-form</li> <li>v-list</li> <li>v-select</li> <li>v-combobox</li> <li>v-autocomplete</li> <li>v-tabs</li> <li>v-img</li> <li>v-menu</li> <li>v-dialog</li> </ul> </li> <li>PTSでは使用していないが破壊的変更が入っているもの <ul> <li>v-form</li> <li>v-simple-table</li> <li>v-slider</li> <li>v-range-slider</li> <li>v-skeleton-loader</li> <li>v-snackbar</li> <li>v-expansion-panel</li> </ul> </li> </ul> </li> </ul> </li> </ul> <p>完了後に各画面の表示崩れや対応漏れがないかを確認し、仕様書通りに動くように修正しました</p> <h2 id="移行時移行後の苦労">移行時、移行後の苦労</h2> <ul> <li>既存で使用していたコンポーネントがなくなる <ul> <li>使っていた <code>v-calendar</code>、<code>v-datepicker</code>(将来追加予定らしい) が無くなった</li> <li>他のプラグイン等を調べてもしっくりくるものがないので、自前で作ることになった</li> </ul> </li> <li>デザインが崩壊していた <ul> <li><code>v-select</code> や <code>v-autocomplete</code> など、置換しただけではデザインが壊れてまともに使えないコンポーネントが多かった</li> </ul> </li> <li>デザインの崩壊を deep セレクタで無理やり直す <ul> <li>壊れている部分を治すために、無理やり css で修正しているので、自前で作り直したくなった</li> </ul> </li> <li>linter に任せて修正しても漏れが多い + そもそもVuetifyのコンポーネントの内部実装が丸がわりしていて同じ意味の props を指定しても元のような見た目になっていない <ul> <li>結局移行ガイドを見ながら一つずつ確認して修正していた</li> <li>PTS はまだそこまで大きくないからできたけど、大規模なプロジェクトだと無理だと思う</li> </ul> </li> <li>そもそもまだ Vuetify3 に移行完了していないコンポーネントも多くて、自作する羽目になったコンポーネントもあった</li> </ul> <h1 id="移行後の環境">移行後の環境</h1> <ul> <li>移行後の環境は以下の通りです。</li> <li>Vue3.3 + Typescript + Vuetify3.3 + Vite4.4 + Pinia2.1</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synConstant">&quot;dependencies&quot;</span><span class="synSpecial">:</span> <span class="synSpecial">{</span> <span class="synConstant">&quot;pinia&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^2.1.4&quot;</span>, <span class="synConstant">&quot;vue&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^3.3.4&quot;</span>, <span class="synConstant">&quot;vue-router&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^4.2.4&quot;</span>, <span class="synConstant">&quot;vuetify&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^3.3.15&quot;</span> <span class="synSpecial">}</span>, <span class="synConstant">&quot;devDependencies&quot;</span><span class="synSpecial">:</span> <span class="synSpecial">{</span> <span class="synConstant">&quot;@vue/vue2-jest&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^29.1.1&quot;</span>, <span class="synConstant">&quot;storybook-builder-vite-vue2&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^0.1.32&quot;</span>, <span class="synConstant">&quot;@vitejs/plugin-vue&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^4.2.3&quot;</span>, <span class="synConstant">&quot;vite&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^4.4.1&quot;</span>, <span class="synConstant">&quot;@vue/compiler-sfc&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^3.1.0&quot;</span> <span class="synConstant">&quot;@vue/eslint-config-prettier&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^7.1.0&quot;</span>, <span class="synConstant">&quot;@vue/eslint-config-typescript&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^11.0.3&quot;</span>, <span class="synConstant">&quot;eslint-plugin-vue&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^9.15.1&quot;</span>, <span class="synConstant">&quot;eslint-plugin-vuetify&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^2.0.3&quot;</span>, <span class="synConstant">&quot;vite-plugin-vuetify&quot;</span><span class="synSpecial">:</span> <span class="synConstant">&quot;^1.0.2&quot;</span>, <span class="synSpecial">}</span> </pre> <h2 id="移行後の使用感">移行後の使用感</h2> <ul> <li><p>メンバーからのヒアリングおよび自身の感想です</p> <ul> <li>script setupタグを使うことで、コードの記載が楽になりました <ul> <li>setup関数ではtemplateタグ内で使用するデータやメソッドをreturnしなければならなかったので、使用されているメソッドが何であるかわかりやすかった。一方で不要なデータやメソッドをreturnしても気づかないため、保守のしにくさにつながる恐れがある</li> <li>script setupタグは上記の問題を解消してくれます。returnしなくてもtemplateタグ内で使用するデータやメソッドを認識してくれるので、何が必要で何が不要かがわかりやすいです。</li> </ul> </li> <li>emit処理の定義がわかりやすくなった <ul> <li><code>defineEmits</code>で定義することで、emitで親に渡すデータの型がわかりやすくなりました。</li> </ul> <pre><code class="js"> const emit = defineEmits&lt;{ (e: 'change', id: number): void (e: 'update', value: string): void }&gt;() </code></pre></li> </ul> </li> <li><p>研修中はOptionsAPIで書いていたので最初はComposition APIの<code>script setup</code>の書き方に戸惑いましたが、慣れればこちらのほうがわかりやすいと感じました。 特に、optionsだとdataやmoutedごとに書いていたので1つのロジックに関するコードが分散していたのに対し、compositionだとロジックごとに書くことができるので書きやすいです。</p></li> <li><p>純粋に Vue 2 系からの脱却ができた点は嬉しい</p> <ul> <li>調べて出てくる情報は Vue 3系のものが多い</li> <li>Vue 関連パッケージで Vue2 だと対応してないなどを気にしなくて済むようになる</li> </ul> </li> <li>Teleport<sup id="fnref:13"><a href="#fn:13" rel="footnote">13</a></sup>、Suspense<sup id="fnref:14"><a href="#fn:14" rel="footnote">14</a></sup>、トップレベル await<sup id="fnref:15"><a href="#fn:15" rel="footnote">15</a></sup> が使えるように</li> <li>Map や Set でもリアクティビティーが効くようになった</li> <li>今後 <strong>Vue 2 系だから</strong>で悩むことが無くなるのは嬉しい</li> </ul> <p>このように移行後のメリットは様々な点でメンバーが感じているようですし、私も感じます。</p> <h1 id="最後に">最後に</h1> <p>我々はVue2からVue3への移行で苦労したというよりは、Vuetify2からVuetify3への移行に苦労しました。ですが、皆様の協力もあって、なんとか対応しました。 メンバーの中には、Vuetify3移行に伴って使えなくなったコンポーネントを自作してくれた方もいます。また、挙動が期待通りにならないコンポーネントを別のコンポーネントで代用してくれた方もいます。技術力が高いと思う方々に囲まれて幸せです。</p> <p>OPTiMにはフロントエンドだけなく、多方面で高い技術力を持つエンジニアが多く在籍しています。そんな方々と共に「未来により良い影響を与える」プロダクトの開発を進めていきませんか? ご興味のある方は、ぜひ一度ご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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></p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> <a href="https://ja.vuejs.org/about/faq.html#is-vue-2-still-supported">https://ja.vuejs.org/about/faq.html#is-vue-2-still-supported</a><a href="#fnref:1" rev="footnote">&#8617;</a></li> <li id="fn:2"> <a href="https://ja.vuejs.org/guide/extras/composition-api-faq.html">https://ja.vuejs.org/guide/extras/composition-api-faq.html</a><a href="#fnref:2" rev="footnote">&#8617;</a></li> <li id="fn:3"> <a href="https://v3-migration.vuejs.org/ja/">https://v3-migration.vuejs.org/ja/</a><a href="#fnref:3" rev="footnote">&#8617;</a></li> <li id="fn:4"> <a href="https://v3-migration.vuejs.org/ja/breaking-changes/">https://v3-migration.vuejs.org/ja/breaking-changes/</a><a href="#fnref:4" rev="footnote">&#8617;</a></li> <li id="fn:5"> <a href="https://v3-migration.vuejs.org/ja/breaking-changes/v-model.html#%E7%A7%BB%E8%A1%8C%E6%89%8B%E9%A0%86">https://v3-migration.vuejs.org/ja/breaking-changes/v-model.html#%E7%A7%BB%E8%A1%8C%E6%89%8B%E9%A0%86</a><a href="#fnref:5" rev="footnote">&#8617;</a></li> <li id="fn:6"> <a href="https://v3-migration.vuejs.org/ja/breaking-changes/v-model.html#%E7%A7%BB%E8%A1%8C%E6%89%8B%E9%A0%86)">https://v3-migration.vuejs.org/ja/breaking-changes/v-model.html#%E7%A7%BB%E8%A1%8C%E6%89%8B%E9%A0%86)</a><a href="#fnref:6" rev="footnote">&#8617;</a></li> <li id="fn:7"> <a href="https://v3-migration.vuejs.org/ja/breaking-changes/v-on-native-modifier-removed.html">https://v3-migration.vuejs.org/ja/breaking-changes/v-on-native-modifier-removed.html</a><a href="#fnref:7" rev="footnote">&#8617;</a></li> <li id="fn:8"> <a href="https://developers.google.com/maps/documentation/javascript/reference/event?hl=en#MVCObject">https://developers.google.com/maps/documentation/javascript/reference/event?hl=en#MVCObject</a><a href="#fnref:8" rev="footnote">&#8617;</a></li> <li id="fn:9"> <a href="https://vuetifyjs.com/en/getting-started/upgrade-guide/">https://vuetifyjs.com/en/getting-started/upgrade-guide/</a><a href="#fnref:9" rev="footnote">&#8617;</a></li> <li id="fn:10"> <a href="https://zenn.dev/longrun_jp/articles/c951f1546873d4">https://zenn.dev/longrun_jp/articles/c951f1546873d4</a><a href="#fnref:10" rev="footnote">&#8617;</a></li> <li id="fn:11"> <a href="https://stackoverflow.com/questions/75680201/trying-to-use-vdatatable-from-labs-but-getting-vdatatable-is-not-exported-by-n">https://stackoverflow.com/questions/75680201/trying-to-use-vdatatable-from-labs-but-getting-vdatatable-is-not-exported-by-n</a><a href="#fnref:11" rev="footnote">&#8617;</a></li> <li id="fn:12"> <a href="https://ja.vuejs.org/api/sfc-css-features.html#scoped-css">https://ja.vuejs.org/api/sfc-css-features.html#scoped-css</a><a href="#fnref:12" rev="footnote">&#8617;</a></li> <li id="fn:13"> <a href="https://ja.vuejs.org/guide/built-ins/teleport.html">https://ja.vuejs.org/guide/built-ins/teleport.html</a><a href="#fnref:13" rev="footnote">&#8617;</a></li> <li id="fn:14"> <a href="https://ja.vuejs.org/guide/built-ins/suspense.html#suspense">https://ja.vuejs.org/guide/built-ins/suspense.html#suspense</a><a href="#fnref:14" rev="footnote">&#8617;</a></li> <li id="fn:15"> <a href="https://ja.vuejs.org/guide/built-ins/suspense.html#async-setup">https://ja.vuejs.org/guide/built-ins/suspense.html#async-setup</a><a href="#fnref:15" rev="footnote">&#8617;</a></li> </ol> </div> optim-yuta-nishimura Googleアナリティクスやめました hatenablog://entry/820878482970461664 2023-09-29T10:00:00+09:00 2023-09-29T10:00:10+09:00 はじめに はじめまして、こんにちは。 プロモーション・デザインユニット(以下プロモ・デザインU)のプロモーションチームに所属している竹内、牧山、安田です。 私たちはオプティムのWebサイト、メルマガ、広告などといった、Webプロモーション全般を担当しています。 2023年7月1日は、Webサイトの運営者にとって非常に重要な日でした。 長らくアクセス解析の分野でトップであったGoogleアナリティクス(以下GA)の「ユニバーサルアナリティクス(以下UA)」がこの日をもってサービスを終了することになったからです。 私たちはサービス終了の1年前からUAの後継であるGoogle アナリティクス 4(以… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230925/20230925135655.jpg" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>はじめまして、こんにちは。<br> プロモーション・デザインユニット(以下プロモ・デザインU)のプロモーションチームに所属している竹内、牧山、安田です。<br> 私たちはオプティムのWebサイト、メルマガ、広告などといった、Webプロモーション全般を担当しています。</p> <p>2023年7月1日は、Webサイトの運営者にとって非常に重要な日でした。<br> 長らくアクセス解析の分野でトップであったGoogleアナリティクス(以下GA)の「ユニバーサルアナリティクス(以下UA)」がこの日をもってサービスを終了することになったからです。</p> <p>私たちはサービス終了の1年前からUAの後継であるGoogle アナリティクス 4(以下GA4)への移行作業を進めていました。<br> 今回はGA4への移行作業の詳細と、その後Web解析がどうなったかをご紹介します。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#アクセス解析とは">アクセス解析とは</a><ul> <li><a href="#アクセス解析によく出てくる用語">アクセス解析によく出てくる用語</a></li> </ul> </li> <li><a href="#UAの終了について">UAの終了について</a></li> <li><a href="#GA4への移行作業まとめ">GA4への移行作業まとめ</a></li> <li><a href="#UAとGA4の違い">UAとGA4の違い</a></li> <li><a href="#GA以外のアクセス解析ツール">GA以外のアクセス解析ツール</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h2 id="アクセス解析とは">アクセス解析とは</h2> <p>「アクセス解析」とは、WebサイトやWebアプリケーションの利用状況を把握し、改善ポイントを分析し見つけ出すことです。<br> アクセス解析でよくみられる情報は下記です。</p> <ol> <li>トラフィックの計測:ユーザー数、ページビュー数、セッションの長さ、離脱率 / 直帰率など</li> <li>ユーザー行動の分析:特定のページの訪問、クリック、ダウンロード、購入など</li> <li>ユーザーの属性情報:パソコンかスマートフォンか、モニターの画面サイズなど</li> <li>キーワード分析:Yahoo!やGoogleなどの検索エンジンでどのキーワードで検索してWebサイトにアクセスしてきたのか</li> <li>コンバージョン:Webサイトのゴール(お問い合わせ完了や商品購入、アカウント登録)などの達成状況</li> </ol> <p>取得した情報をもとに、更新予定ページの優先度や改善方針などを検討し、日々更新をしています。<br> アクセス解析ツールにはさまざまな種類がありますが、オプティムではGAを用いてアクセス解析を行っていました。</p> <h3 id="アクセス解析によく出てくる用語">アクセス解析によく出てくる用語</h3> <table> <thead> <tr> <th style="text-align:left;">用語</th> <th style="text-align:left;">内容</th> </tr> </thead> <tbody> <tr> <td style="text-align:left;">ユーザー数(UU)</td> <td style="text-align:left;">Webサイトを訪問したユーザーの数のこと。同一期間内に同じユーザーが同一デバイスで何度Webサイトを訪問した場合でも、ユーザー数は1とカウントされる</td> </tr> <tr> <td style="text-align:left;">ページビュー数(PV)</td> <td style="text-align:left;">Webサイトに訪問したユーザーがページにアクセスした数のこと。同じユーザーが100ページを閲覧した場合、PV数は100とカウントされる。</td> </tr> <tr> <td style="text-align:left;">セッション</td> <td style="text-align:left;">アクセスの開始から終了までの一連の通信のこと。<br>基本的にセッションは30分経過することで通信終了とされる。</td> </tr> <tr> <td style="text-align:left;">直帰率</td> <td style="text-align:left;">Webサイトに訪問したユーザーが、最初の1ページのみを閲覧して離脱した割合。</td> </tr> <tr> <td style="text-align:left;">コンバージョン数(CV)</td> <td style="text-align:left;">コンバージョンの略。マーケティング用語で、顧客に達成してもらいたい目標のこと。<br>オプティムでは主にお問い合わせの完了やリード獲得のことを指す。</td> </tr> </tbody> </table> <h2 id="UAの終了について">UAの終了について</h2> <p>冒頭でもお話ししたように、GAが提供していたシステムバージョンである、「UA」は、2023年7月1日で計測が終了になる旨のアナウンスが発表されました。<br> 継続してアクセス解析を行う場合には新しいバージョンの「GA4」への移行が必須作業となったのですが…これがまた難儀でした…</p> <h2 id="GA4への移行作業まとめ">GA4への移行作業まとめ</h2> <p>対応した内容は、以下の通りです。</p> <ol> <li> GA4の移行アシスタントを全て管理させる <ol style="list-style-type: lower-alpha;"> <li> データの収集が完了していること<br> → データストリームの管理で受信していればOKとしました。<br> → すべてのUA・GA4で確認を行いました。 </li> <li>Googleシグナルを有効にする</li> <li> Google広告のコンバージョンをGA4にする。<br> → GA4のコンバージョンに基づいて入札を完了しました。 </li> <li> プロパティの設定>コンバージョンを設定<br> → Google タグ マネージャー(以下、GTM)でUAになっているものをGA4に紐付け直しました。 </li> <li> オーディエンスの確認<br> → UA側で設定されていて、必要なものあればGA4で設定しました。<br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230925/20230925135615.png" width="1543" height="798" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </li> </ol> </li> <li>GTMで不要なタグを削除する<br>UAタグは不要になるため、計測終了を確認してから削除対応を行いました</li> </ol> <p style="margin-top: 3em;"> 当社ではGTMを使いGAを設置しているため、GA側とGTM側での作業が発生しました。<br> 全部で23コンテナあり、管理表を作成しダブルチェックを実施しながら対応しました。 </p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230925/20230925135630.png" width="1184" height="662" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="UAとGA4の違い">UAとGA4の違い</h2> <p>オプティムではアクセス解析を専門としている企業に委託せず、内製で行っています。<br> 今回のアップデートは、エンジニアではないプロモ・デザインUの我々から見るとかなり衝撃的で、GA4へ移行したものの、UAから大きな変更点がありました。</p> <ol> <li> ダッシュボードの見え方(UI変更)<br> <div style="width: 500px; max-width: 100%;"> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230925/20230925135623.png" width="1003" height="768" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </div> 添付の通りサイドメニューが、ガラッと変わりました。<br> また、UAでは設定せずとも見たい情報がまとまっているレポート画面が用意されていました。<br> しかし、GA4では各自でデータをカスタマイズしてレポート画面を作る必要があります。 </li> <li> 計測指標<br> 「直帰率」が「エンゲージメントのなかったセッションの割合」に変更になり、「離脱率」「ページ/セッション」「ページ別訪問数」「ページの価値」が廃止になりました。 </li> <li> 数値に乖離が発生する<br> GA4になることで、日付が変更になったタイミングでセッションが途切れる、表示回数にしきい値が適用される、同一セッションにも関わらず都度CVを計測するなど、PV<sup>※</sup>・CV<sup>※</sup>での数値の乖離が発生しました。<br> ※ PV(ページビュー数)とは、訪問者が実際にサイト内でページにアクセスした数のこと。<br> ※ CV(コンバージョン数)とは、主にお問い合わせの完了やリード獲得の数のこと。<br> </li> </ol> <p>上記にあげたものは変更点の一部ではありますが、これまで見れていたデータが確認できなくなってしまったことや、操作方法を新たに学習する必要がありました。</p> <p>また、オプティムでは「<a href="https://cloud.google.com/looker-studio?hl=ja">Looker Studio</a>(旧:データポータル)」でUAと連携し、Webアクセスレポートを作成していました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230925/20230925135637.png" width="1003" height="548" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>用途に合わせて細かくレポートを設定していたため、全てのレポートのUA連携をGA4に移行するには作業ボリュームも多くコストも高いため、UAの廃止に合わせて運用をストップしました。</p> <h2 id="GA以外のアクセス解析ツール">GA以外のアクセス解析ツール</h2> <p>GA4への移行作業とは別で、2021年頃からGA以外の有料Webアクセス解析ツールを導入しました。<br> 導入のきっかけは、全社的なWebマーケティングを強化するために、ページ単位ではなく、コンテンツエリア単位の細かい計測が必要になったことでした。</p> <p>例えば企画部門やマーケティング部門から「あるページの中に新しいコンテンツエリアを追加したり、表示位置を入れ替えた時に、その効果を知りたい」と言われる場面が増え、それらの情報をわかりやすく共有するために、ページごとにコンテンツエリアの熟読率やクリック数をヒートマップで可視化できることが求められました。</p> <p>これをGAで実現することは簡単ではなかったため、初期機能でヒートマップ機能があるツールを新たに導入することで解決することにしたのです。</p> <p>2021年後半から2022年はGA4への移行を進めながら、UA、GA4、さらに別のWeb解析ツールの3つを同時に使用してユーザーの行動データを分析していました。<br> <span style='font-weight: bold; font-size: 120%; padding: .5em 0; display: inline-block; font-family: "ヒラギノ角ゴ Pro" , "Hiragino Kaku Gothic Pro" , "Meiryo UI" , "メイリオ" , Meiryo , "游ゴシック" , "Yu Gothic" , "游ゴシック体" , "YuGothic" , "MS Pゴシック" , "MS PGothic" , "HelveticaNeue-Regular" , sans-serif'>その際に、事件が起きます。</span><br> 各ツールで計測しているサイトのページビュー数(ページの表示回数)に、2倍近い大きな差異が生じたのです。<br> 調査をしたところ、最初は単純な設定の漏れが原因ではないかと思いましたが、設定を修正してもまだツール毎にそれぞれ1.5倍ほどの差異が発生していました。<br> さらに詳しく調査して、徐々に「スパムとして事前に計測から除外するIPアドレスが、ツールによって異なっている」など、GA4による仕様上の問題が明らかになっていきました。</p> <p>どのツールのデータを正とするべきか、また、どのツールが使いやすいか。<br> チーム内だけでなく、マーケティング部門とも協議を行いました。<br> 最終的に、現在の私たちのプロモーション施策には、GAではない別のツールの方が適しているという結論に至りました。<br> そのため、現在、オプティムのWebサイトのアクセス解析では別のツールをメインで利用しています。</p> <div style="width: 600px; margin-left: auto; margin-right: auto; max-width: 100%;"> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230925/20230925135643.png" width="1600" height="973" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> </div> <p>一方で、サービスの開発をするエンジニアにとってGA4は、UAや別のツールよりも使いやすく、無償のため導入しやすいツールになっているようです。<br> 現在オプティムでは一部のサービスでGA4を導入し、データを解析しながらより良いユーザー体験を提供すべく、開発を進めています。</p> <h2 id="まとめ">まとめ</h2> <p>色々調査を進めていく中で、プロモ・デザインUではアクセス解析にGA4を使わない選択をしました。<br> メリット・デメリットを理解した上でチームに最適なツール選びをし、オプティムのファンを増やすべく今後もWebサイトの改善をしていきたいと思います。</p> <h2 id="おわりに">おわりに</h2> <p>オプティムでは、エンジニアだけではなくプロモ・デザインUで一緒に働いてくださるメンバーも探しています。<br> プロモ・デザインUでは、UI/UXデザインやブランディング、Web制作、マーケティングなど<span style='color: #008EEE; font-weight: bold; font-family: "ヒラギノ角ゴ Pro" , "Hiragino Kaku Gothic Pro" , "Meiryo UI" , "メイリオ" , Meiryo , "游ゴシック" , "Yu Gothic" , "游ゴシック体" , "YuGothic" , "MS Pゴシック" , "MS PGothic" , "HelveticaNeue-Regular" , sans-serif''>"オプティムの思想を魅力的に伝える"</span>お仕事をしています。<br> UI/UX、ブランディング、Webプロモーションなどに興味がある方、ぜひご応募お待ちしています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-yasuda IT研修で作成した業務用チャットアプリをEKSにデプロイしました hatenablog://entry/820878482963713169 2023-09-26T10:00:00+09:00 2023-09-26T10:00:01+09:00 自己紹介 はじめに 使用ツール 1. Kubernetes 2.Helm 3.Terraform 4. AWS関連 Amazon EKS Amazon VPC Amazon RDS チャットアプリデプロイの流れ stage0: IT研修終了時のチャットアプリの構成 stage1: chatappのコンテナ化と環境変数を設定 stage2: chatappとデータベースサーバーをKubernetesでコントロール initContainerの導入 stage3: chatappのmanifestをHelmでtemplate化 stage4: chatappのコンテナイメージをGitLabコンテナ… <ul class="table-of-contents"> <li><a href="#自己紹介">自己紹介</a></li> <li><a href="#はじめに">はじめに</a></li> <li><a href="#使用ツール">使用ツール</a><ul> <li><a href="#1-Kubernetes">1. Kubernetes</a></li> <li><a href="#2Helm">2.Helm</a></li> <li><a href="#3Terraform">3.Terraform</a></li> <li><a href="#4-AWS関連">4. AWS関連</a><ul> <li><a href="#Amazon-EKS">Amazon EKS</a></li> <li><a href="#Amazon-VPC">Amazon VPC</a></li> <li><a href="#Amazon-RDS">Amazon RDS</a></li> </ul> </li> </ul> </li> <li><a href="#チャットアプリデプロイの流れ">チャットアプリデプロイの流れ</a><ul> <li><a href="#stage0-IT研修終了時のチャットアプリの構成">stage0: IT研修終了時のチャットアプリの構成</a></li> <li><a href="#stage1-chatappのコンテナ化と環境変数を設定">stage1: chatappのコンテナ化と環境変数を設定</a></li> <li><a href="#stage2-chatappとデータベースサーバーをKubernetesでコントロール">stage2: chatappとデータベースサーバーをKubernetesでコントロール</a><ul> <li><a href="#initContainerの導入">initContainerの導入</a></li> </ul> </li> <li><a href="#stage3-chatappのmanifestをHelmでtemplate化">stage3: chatappのmanifestをHelmでtemplate化</a></li> <li><a href="#stage4-chatappのコンテナイメージをGitLabコンテナレジストリにアップロード">stage4: chatappのコンテナイメージをGitLabコンテナレジストリにアップロード</a></li> <li><a href="#stage5-chatappをEKSにデプロイ">stage5: chatappをEKSにデプロイ</a></li> <li><a href="#stage6-PostgreSQLをRDS上で起動させchatappと接続">stage6: PostgreSQLをRDS上で起動させ、chatappと接続</a></li> </ul> </li> <li><a href="#結果学び">結果、学び</a></li> <li><a href="#終わりに">終わりに</a></li> </ul> <h2 id="自己紹介">自己紹介</h2> <p> 初めまして。23新卒で入社いたしました、技術統括本部プラットフォームサービス開発部インフラユニットの菅野です。私は弊社に入社するまではほとんどプログラミングに触れることがありませんでしたが、アプリケーションの下支えであるインフラユニットにて勤務しています。配属されてから2ヵ月間の私の仕事は、「山のように積んである参考書を使った勉強」、「勉強会での技術の学習」、「実際にアプリケーションをデプロイして、業務で使うツールの理解を深める」でした。インフラエンジニアはアプリケーションの開発よりも階層が低い、アプリケーションの土台となるネットワークやサーバーを扱います。それまで触れてこなかったネットワークやサーバーについての基礎知識の習得が仕事の始まりでした。今回はアプリケーションをデプロイしたことに着目して、お伝えしていきます。</p> <h2 id="はじめに">はじめに</h2> <p> 6月末まで、私は<a href="https://tech-blog.optim.co.jp/entry/2022/07/25/100000">IT研修</a>を受けていました。IT研修の後半では、2人1組で業務用チャットアプリを開発しました。仕様書通りに実装できなかった機能もあり、いまだ開発途中です。例えば、写真添付などの機能を実装する予定です。今回はこのチャットアプリをEKSにデプロイします。デプロイする過程で、Kubernetes、Helm、Terraform、AWS等の実務で使う機能を把握することが目的です。<br/>  まずは、使用したツールの説明をし、私が行った作業の流れをstageごとに紹介します。</p> <h2 id="使用ツール">使用ツール</h2> <h4 id="1-Kubernetes">1. Kubernetes</h4> <p> Kubernetesはコンテナオーケストレーションエンジンです。複数のコンテナをpodという単位で管理します。podが乗る仮想コンピュータがnodeです。Kubernetesは指示された通りに、podを維持します。Kubernetesへの指示はmanifestで行います。今回はチャットアプリ(以降はchatappとします)とデータベースサーバーを管理してもらいます。<br/> <a href="https://kubernetes.io/">参考: Kubernetes</a></p> <h4 id="2Helm">2.Helm</h4> <p> HelmはKubernetesのパッケージマネージャーです。Kubernetesのmanifestをテンプレート化して、Chartとして管理できます。Chartをダウンロードして、手元の環境に適した値を入れることで、誰でもアプリケーションを使えるようになります。アプリケーションによってはリポジトリに公開されています。今回はHelmに併せて、Helmfileというツールを使用します。Helmfileを用いると、コードでChartをダウンロードし、値を入力することができます。今回はchatapp、nginx、image-pull-secretをChartとしてダウンロードするために用いました。image-pull-secretについては、<a href="#stage4-chatapp%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92GitLab%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%83%AC%E3%82%B8%E3%82%B9%E3%83%88%E3%83%AA%E3%81%AB%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89">stage4</a>で詳細を説明します。<br/> <a href="https://helmfile.readthedocs.io/en/latest/">参考: Helm</a><br/> <a href="https://helmfile.readthedocs.io/en/latest/">参考: Helmfile</a></p> <h4 id="3Terraform">3.Terraform</h4> <p> Terraformはコードとして、宣言的にインフラを管理できるツールです。AWS、Azure、Google Cloud Platform (GCP)、Kubernetes、Helm、GitHub、Splunk、Datadogなどさまざまなサービスのリソースをコードで管理できます。また、バージョン管理を簡単に行うことができます。今回はRDSを構築するために使用しました。<br/> <a href="https://www.terraform.io/">参考: Terraform</a></p> <h4 id="4-AWS関連">4. AWS関連</h4> <p> AWSではインフラストラクチャテクノロジーから機械学習、AI、データレイクと分析、IoT などの最新鋭のテクノロジーに至るまで、様々なサービスが提供されています。AWSを利用することでクラウド型のサーバー設計できます。今回はその中でも、EKS、VPC、RDSを主に用いました。</p> <h5 id="Amazon-EKS">Amazon EKS</h5> <p> AWSでKubernetesを実行するために使用できるマネージドサービスです。EKSを導入すればEC2(AWSの仮想サーバー)と、EC2のリソースを制御する機能が使えます。chatapp、その周辺のデータベースサーバーをEKSにデプロイしました。<br/> <a href="https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/container-platform-management/amazon-eks.html">参考: Amazon EKS</a></p> <h5 id="Amazon-VPC">Amazon VPC</h5> <p> AWS内の論理的に隔離された仮想ネットワークです。VPCに割り当てたIPアドレスのホスト部を割ることで、サブネットを作成することができます。サブネットをインターネットゲートウェイに接続するとパブリックサブネット、接続しないとプライベートサブネットとなります。今回はchatappとデータベースであるRedis、PostgreSQLをプライベートサブネットに置きました。  また、VPCの中でセキュリティグループを作成できます。セキュリティグループはインスタンスのファイアウォールです。<br/> <a href="https://docs.aws.amazon.com/ja_jp/quicksight/latest/user/vpc-amazon-virtual-private-cloud.html">参考:VPC </a></p> <h5 id="Amazon-RDS">Amazon RDS</h5> <p> クラウド内でリレーショナルデータベースを簡単に設定、運用、スケールするためのPaaSのクラウドサービスです。今回はchatappのメッセージなどを保持しているPostgreSQL(リレーショナルデータベース)をRDSにデプロイします。<br/> <a href="https://docs.aws.amazon.com/ja_jp/whitepapers/latest/how-aws-pricing-works/amazon-rds.html">参考: Amazon RDS</a></p> <h2 id="チャットアプリデプロイの流れ">チャットアプリデプロイの流れ</h2> <h3 id="stage0-IT研修終了時のチャットアプリの構成">stage0: IT研修終了時のチャットアプリの構成</h3> <p> IT研修終了時のchatappは図1の構成になっています。データベースサーバーをコンテナ化して、作業者のマシンのローカルでchatappからアクセスしていました。データベースサーバーにはセッション情報を記録するRedisと、メッセージ、ユーザー情報などを記録するPostgreSQLを用いました。backendサーバーにはExpress.jsを用いて、frontendではVue.jsでブラウザに表示するhtmlを作成していました。また、frontendからbackendへのリクエストにはAxiosを用いていました。<br/> <br></p> <p><strong>図1 IT研修終了時のchatapp構成</strong> <br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230921/20230921132224.png" width="544" height="340" loading="lazy" title="" class="hatena-fotolife" style="width:320px" itemprop="image"></span></p> <h3 id="stage1-chatappのコンテナ化と環境変数を設定">stage1: chatappのコンテナ化と環境変数を設定</h3> <p> chatappとデータベースサーバーをそれぞれ別のコンテナに分離して、起動するようにしました。chatappのコンテナイメージはDockerfileで作成し、ローカルのコンテナイメージを指定してコンテナを起動しています。この時、使用者の環境によって値が変わるものは、使用者ごとに入力できるように、環境変数で設定します。いちいち環境に合わせてソースコードを直してコンテナイメージをビルドし直すのは手間がかかるからです。今回は、EKSにデプロイする際に依存するサーバーのエンドポイントが変わるため、依存サーバーのエンドポイントを環境変数化します。 <br></p> <p><strong>図2 stage1の構成図</strong><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230901/20230901151749.png" width="657" height="363" loading="lazy" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span></p> <h3 id="stage2-chatappとデータベースサーバーをKubernetesでコントロール">stage2: chatappとデータベースサーバーをKubernetesでコントロール</h3> <p> アクセス数が多かったり、本来予測していない動作を与えられたとき、サーバーのコンテナはダウンします。stage1の段階では、サーバーダウンを人間が検知し、ダウンするごとにコンテナを起動し直す必要があります。しかし毎度毎度人間がコンテナの様子を確認し、PostgreSQL, Redis,chatappと一つ一つサーバーを起動し直すのは手間です。この問題を解決するために、Kubernetesを導入します。<br/>   Kubernetesに維持してほしい状態を指定する際の命令書がmanifestです。deploymentはどのようなリソースをどのように維持するかを指定します。例えばchatappのpodを2個維持してほしいと指定します。serviceはL4ロードバランサー、ingressはL7ロードバランサーです。ingress controllerにはnginxというWebサーバーを用いています。それぞれのmanifestを作成し、masterに指示することで、masterが全体を制御します。 <br></p> <p><strong>図3 stage2の構成図</strong><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230921/20230921130701.png" width="1600" height="985" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></p> <h4 id="initContainerの導入">initContainerの導入</h4> <p> manifestでmasterに指示を出すと、nginx(ingress)、chatapp、Redis、PostgreSQLが起動します。chatappは起動時、データベースに初期値を投入する必要があります。そこで、データベースが起動し、接続できるようになった後にchatappが起動する必要があります。<br/>  データベース、chatappの順で起動させるために、initContainersを導入しました。initContainerはKubernetesの機能の一つです。Pod内でアプリケーションコンテナの前に実行される特別なコンテナです。initContainerの設定は、「chatappコンテナが依存するデータベースサーバーに、ヘルスチェックを目的としたリクエストを断続的に行い、利用可能であるという応答が返却されたら終了する」としました。</p> <h3 id="stage3-chatappのmanifestをHelmでtemplate化">stage3: chatappのmanifestをHelmでtemplate化</h3> <p> 利用者ごとに一部の設定を変更するだけで、アプリケーションを起動できるようにHelmを導入します。 今回はchatappをtemplate化して、GitLabのリポジトリとして切り出しました。このリポジトリにsshで接続し、Chartをダウンロードし、自分の利用環境に合わせた設定をvalues.yamlに書き込むことにしました。 <br></p> <p><strong>図4 stage3の構成図</strong><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230921/20230921130707.png" width="1600" height="1443" loading="lazy" title="" class="hatena-fotolife" style="width:800px" itemprop="image"></span></p> <h3 id="stage4-chatappのコンテナイメージをGitLabコンテナレジストリにアップロード">stage4: chatappのコンテナイメージをGitLabコンテナレジストリにアップロード</h3> <p> これまではローカルでビルドしたコンテナイメージを用いてコンテナを起動していました。chatappをAWSにデプロイするために、誰でもアクセスできる場所にコンテナイメージを置く必要があります。そこで、GitLabのコンテナレジストリに保存したコンテナイメージをダウンロードし、コンテナを起動する構成にしました。<br/>  GitLabのコンテナレジストリからコンテナイメージを取得するためには認証が必要です。コンテナイメージを置いたリポジトリでレジストリを読み取る認証(デプロイトークン)を発行し、secretで認証情報を定義し、chatappに付与します。このときsecretを与えるchart(image-pull-secret)をGitLabからダウンロードして、使う構成にしました。<br/>  stage4までの間、chatapp、image-pull-secret、nginxを外部からダウンロードして使う構成にしています。関連図を図5にまとめました。 <br></p> <p><strong>図5 コンテナイメージ、チャートの構成図</strong><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230921/20230921130658.png" width="823" height="752" loading="lazy" title="" class="hatena-fotolife" style="width:320px" itemprop="image"></span></p> <h3 id="stage5-chatappをEKSにデプロイ">stage5: chatappをEKSにデプロイ</h3> <p>  ついにchatappをEKSにデプロイします。今回はすでに作成されているEKSインスタンスにデプロイする形になります。認証情報などをconfigファイルに書き込み、EKSに向き先を変えたkubeconfigを生成します。また、今までingress controllerとしてnginxを用いていましたが、このEKSで元々使われていた、Istioを代わりに使用します。最後にHelmfileをEKS環境にapplyして、chatappがEKS環境で動くようになりました。 <br></p> <p><strong>図6 stage5の構成図</strong><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230915/20230915181908.png" width="1600" height="1239" loading="lazy" title="" class="hatena-fotolife" style="width:800px" itemprop="image"></span></p> <h3 id="stage6-PostgreSQLをRDS上で起動させchatappと接続">stage6: PostgreSQLをRDS上で起動させ、chatappと接続</h3> <p> 今まではPostgreSQLのpodを起動し直すたびに、データがリセットされていました。データを永続的に保存しようとすると、手元のマシンのスペックによって上限が決まってしまいます。そこで、ストレージのスケーラビリティに富んでおり、高い可用性があるRDS上にPostgreSQLを置くことにしました。Terraformで、"RDS for PostgreSQL"を置くサブネットとセキュリティグループを設定し、chatappが属するセキュリティグループからのみリクエストを受け付けるように設定しました。RDSのインスタンスタイプやストレージを設定し、インスタンスを起動します。最後にchatappと接続確認しました。これでchatappのデプロイを完了したと言って良いでしょう。 <br></p> <p><strong>図7 stage6の構成図</strong><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230915/20230915181918.png" width="1600" height="1445" loading="lazy" title="" class="hatena-fotolife" style="width:800px" itemprop="image"></span></p> <h2 id="結果学び">結果、学び</h2> <p> 今回デプロイしたchatappの物理構成図は図8に示しています。赤く塗りつぶされている枠はセキュリティグループを表しています。1つのRDSインスタンスを、ap-northeast-1aまたはap-northeast-1cのどちらかのサブネットに置くようにしました。データベースのセキュリティグループには、chatappのセキュリティグループからのインバウンドのトラフィックを許可するように設定しました。また、実際のchatappの画面を図9に示しています。<br/>  EKSへデプロイしRDSを利用したことで、IT研修で作成したchatappを、より可用性の高い構成にすることができました。また、Kubernetes、Helm、Terraform、AWSの基礎知識を学ぶことができました。chatappのソースコードだけを書いていた際は、手元で動かせることがゴールでした。しかし配属後は、私がパソコンに向き合っていない時でも、常時動く設計にすることがゴールでした。今回、EKSにチャットアプリをデプロイする過程で、障害に耐えうる設計にするという、運用に必須な考え方を学ぶことができました。 <br></p> <p><strong>図8 chatappの物理構成図</strong><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230915/20230915181819.png" width="1183" height="1362" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><br/> <strong>図9 chatappの実際の画面</strong><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230901/20230901151813.png" width="1600" height="905" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></p> <h2 id="終わりに">終わりに</h2> <p> 配属後、chatappをデプロイするまでにユニットの皆様から様々なサポートをしていただきました。ツールの勉強会を開いていただいたり、chatappをもっと良くするために意見出しをしていただきました。この場を借りて改めて御礼申し上げます。<br/>  IT研修からEKS環境へのデプロイまで、アプリケーションの開発を一気通貫で行いました。ツールを完全に理解する道のりはまだまだ先の長いものだと実感しています。しかし、配布されたパソコンで恐る恐る環境構築を行い、まっさらなエディターにソースコードを書き込んだ2ヵ月前を考えると、大きく前進することができた様に思います。今回得た学びをもとに、日々研鑽し、皆様が安心して使えるサービスを提供できるように頑張ります。ご覧いただきありがとうございました。</p> <hr /> <p>IT研修では、私のようなエンジニア志望のプログラミング未経験者が、グッとエンジニアリングの基礎力を成長させることができます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2022%2F07%2F25%2F100000" title="IT未経験者が3か月で開発エンジニアに!4年目を迎えた新卒IT研修の内容をお伝えします - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2022/07/25/100000">tech-blog.optim.co.jp</a></cite></p> <p>OPTiMでは随時、エンジニアを募集しております。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-kanno トレンドのOKRをデザイン組織で本格的に導入してみた hatenablog://entry/820878482954262964 2023-09-08T10:00:00+09:00 2023-09-08T10:00:01+09:00 はじめに みなさんこんにちは、プロモーション・デザインユニット(※以下、プロモ・デザインU)でマネージャーをしている加藤です。 1年振りの投稿となる今回は、会社のUXを担うポジションとして実践してみる!のシリーズ第2弾と称し、 デザイン組織でOKRを本格的に導入した際の気づきを簡単にご紹介します。 前回の記事はこちら tech-blog.optim.co.jp はじめに OKRとは 背景・課題 目的 やってわかったこと/感じたこと まとめ 最後に OKRとは OKR(Objectives and key results)は、個人や組織の目標設定のためのフレームワークです。 1つのobjecti… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230823/20230823203752.jpg" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>みなさんこんにちは、プロモーション・デザインユニット(※以下、プロモ・デザインU)でマネージャーをしている加藤です。</p> <p>1年振りの投稿となる今回は、会社のUXを担うポジションとして実践してみる!のシリーズ第2弾と称し、<br> デザイン組織でOKRを本格的に導入した際の気づきを簡単にご紹介します。</p> <p>前回の記事はこちら <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2022%2F07%2F29%2F100000" title="社内でデザイン思考ワークショップを通じて得られたもの - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2022/07/29/100000">tech-blog.optim.co.jp</a></cite> <br></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#OKRとは">OKRとは</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> <h2 id="OKRとは">OKRとは</h2> <p>OKR(Objectives and key results)は、個人や組織の目標設定のためのフレームワークです。 1つのobjectiveと3-5個のkey resultsで構成されるのが一般的で、objectiveは具体的で明確に定義された「目標」であり、key resultsはその目標の達成度を測るための定量的で測定可能な「指標」である必要があります。<br> ※詳しくはこちらをご覧ください:<a href="https://ja.wikipedia.org/wiki/OKR">OKR - Wikipedia</a></p> <h2 id="背景課題">背景・課題</h2> <ol> <li>チームの成果を最大化、チーム業績から全社業績への貢献度(明確な連続性)を示したい<br></li> <li>個人目標設定の経験値が低いチームメンバーは苦手意識が芽生えており、設定に時間がかかり生産性も低い<br></li> <li>デザイナーは数字に弱いという印象を持たれがち(定性的なものも付加価値としては必要)<br></li> <li>そもそも従来のMBO(=目標管理)との違いがわかっていない<br></li> </ol> <h2 id="目的">目的</h2> <p><span style="font-size: 120%"> OKRを習慣化し、日常的に組み込むレベルまで浸透させることで、目標達成の好循環を生み出す。 </span><br></p> <ul> <li>OKRを実践して学習、メリット/デメリットを把握することでチームビルドに活かす<br></li> <li>合意形成した上での定量的・定性的での評価のベースを作る<br></li> <li>目標管理のチーム内透明性の実現<br></li> <li>主体性(やり方の自由度)によるエンゲージメントUP<br></li> <li>OKRツール導入に向けた課題抽出<br> → 先ずは既存ツールを正しく定常的に全員が運用できているか<br> → 既存ツールでの代用が効かない機能面の洗い出し<br></li> </ul> <h2 id="やってわかったこと感じたこと">やってわかったこと/感じたこと</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230823/20230823203825.jpg" width="1200" height="890" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span style="font-size: 85%">▲2022年度下期チームOKR</span><br> <br></p> <p><span style="font-size: 125%">① 中規模チームでのディスカッション</span><br> 大きくUI/UX、デザイン制作、プロモーションと3つの業務範囲に分かれているチームの体制に対し、3つの軸でObjectivesを設定しました。 それぞれのチームで議論を深め目標を定めていくのですが、マネージャーとしては非常に時間のかかるタフな取り組みでした。</p> <p><span style="font-size: 125%">② ファシリテーション</span><br> 週次での目標管理として、チェックインMTGとウィンセッションを実施。<br> 毎週、プロジェクト管理ツールへの達成率と状況コメント更新から予定/実績の発表、それらの進捗管理をする負荷も、チームとしてタフな作業になります。</p> <p><span style="font-size: 130%">③ OKRと人事評価との突き合わせ</span><br> 複数人で成果を出すKRを設定したが、これを個人目標に落とし込むと人事評価として成立するのか?という疑問が湧きました。<br> 答えは、1つのKR=1目標は否!(※実際は可能ですが、事前にそれを見越したKRを立案する必要があります)そのため、複数KRをメンバーそれぞれが再度グルーピング、改めて個人目標を作り直すことで2度手間となり、時間を浪費してしまった点は反省です。</p> <p><span style="font-size: 125%">④ やるべきこと(会社)、やってほしいこと(チーム)、やりたいこと(課題感)のバランス</span><br> チームでの上期課題を中心に据えて検討したことで、3つのチームの内1つが改善や新たな挑戦に寄り過ぎてしまいました。<br> 結果、業績と直接の結びつきが不明瞭、且つチーム内の業務としても売上貢献という全社目標から乖離しているように見えてしまい、3つの軸の比率をしっかりと定義することが重要だと感じました。</p> <p><span style="font-size: 125%">⑤ OKR運用に関するツール</span><br> 我々のチームでは普段からプロジェクト管理ツールであるRedmineを、目標管理ツールとしても代用しました。<br> 基本的な進行管理で大きな問題こそありませんでしたが、達成状況の連続性を可視化するツリー構造、一覧性の悪さ、細かな達成(1%単位)が簡単には現せないといった問題に直面しました。 また、これらが全社OKRとなると、透明性という性質を実現するには閲覧権限といった機能も必要になり、目標管理という点での運用はさらに複雑で難易度が上がるのではないでしょうか。</p> <p><span style="font-size: 125%">⑥ チーム一丸で成果を追い求める点</span><br> 今回の最大の振り返りポイント。実施期間中にチームメンバー増減が考慮できておらず、途中で欠員が出るという事態に陥りました。さらにストレッチを効かせた目標を掲げていたため、その穴を埋めるだけの余力がチームに残されていませんでした。<br> こういったケースは誰にでも起こり得る事であり、OKRのウィークポイントだとも強く感じました。このような事態を想定したバックアップ体制(担当補佐ポジションを置く)を敷く、達成条件や完了定義を明確にする、などを考慮しておくことで解決できそうです。また増員という場合においても目標の難易度が変わってしまうため、時期やチーム状況に応じて定期的に目標のメンテナンスすることも重要性です。</p> <p><span style="font-size: 125%">⑦ 導入して改善されたこと</span><br> メンバー1人1人が目標に向き合う時間が増えたことで、総じて目標管理が習慣化されました。<br> 更にOKRでは、達成に至るまでのプロセスといった点で定性的な側面でも成果を生み出せる点、それも評価の対象となる点など、 デザイナーにとってはマッチしやすい目標管理フレームワークだと感じることができました。</p> <h2 id="まとめ">まとめ</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230802/20230802200922.jpg" width="1600" height="690" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span style="font-size: 85%">▲KPT法による振返り実施と改善案の策定</span><br></p> <p>"OKRは一度失敗してからが本当のOKRである"という記事を目にし、その通りだと強く感じました。<br> 但しこれについては、どんな物事に関しても言えることであり、失敗を受け入れ改善していくことが成長するという事だと理解していますので、必然ではないかと。 その成長という点において、以下に体験したからこその気づけた改善点をまとめてみます。<br></p> <ul> <li>KRを立てる上で各自がOKRについて自己学習を行っておく</li> <li><p>期初から正式スタートが切れるようにスケジューリングし施策を立てる時間を確保する<br> <span style="color: #00cccc; font-weight: 900">・週5.5時間のブレスト会を1ヶ月間実施(他チームは始業2時間前に週2回など工夫必須)</span><br> <span style="color: #00cccc; font-weight: 900">・コミュニケーションの時間と各自宿題として検討する時間を作る</span><br> <span style="color: #00cccc; font-weight: 900">・計測は年度始めから実施するため検討フェーズもおおよそは加味してKRを設定する</span><br> <span style="color: #00cccc; font-weight: 900">・KR担当を数名配置してメンバー増減にも耐え得る状態を保つ</span><br></p></li> <li><p>KRは案件起因のものを重点的に、また平均的に担当アサインをして貢献度=成果になるよう設定する<br> <span style="color: #00cccc; font-weight: 900">・案件起因(売上/コスト削減) :70%</span><br> <span style="color: #00cccc; font-weight: 900">・チーム内改善 :20%</span><br> <span style="color: #00cccc; font-weight: 900">・キャリアアップやチームビルド:10%</span><br> <span style="color: #00cccc; font-weight: 900">・キャリアデザインを伴う目標は別途で設ける必要がある</span><br></p></li> <li><p>人事制度との整合性を取り成果を共通化させる<br> <span style="color: #00cccc; font-weight: 900">・OKRは年次の目標にし、上期は中間達成率/達成度のKRを設定する</span><br> <span style="color: #00cccc; font-weight: 900">・達成難易度の妥当性を全員で精査する</span><br> <span style="color: #00cccc; font-weight: 900">・KRチケットのタイトルには、必ず必達数値を記載して常に目に入るようにする</span><br> <span style="color: #00cccc; font-weight: 900">・個人目標への落とし込みはMgrにアグリーメントを取り手戻りを防ぐ</span><br></p></li> <li><p>運用フローの改善<br> <span style="color: #00cccc; font-weight: 900">・週次でのチェックイン/ウィンセッションのタイミングはチーム特性に合わせる</span><br> <span style="font-size: 85%"> ※週初めにチェックインのプランを立て火曜に発表、金曜に成果発表とするサイクルにしています</span><br> <span style="color: #00cccc; font-weight: 900">・四半期に見直しの機会を実施する(KPIの妥当性チェック含む)</span><br> <span style="font-size: 85%"> ※メンバー増減によるOKR精査もこのタイミングで実施する</span><br> <span style="color: #00cccc; font-weight: 900">・業務状況を鑑みてサイクルを週次と2週間の2パターンで運用する</span><br></p></li> <li><p>Redmineは引き続き活用<br> <span style="color: #00cccc; font-weight: 900">・業務用のプロジェクトとは別にOKR専用のプロジェクトを設置する</span><br> <span style="color: #00cccc; font-weight: 900">・チケットタイトルの命名規則は一覧性に富んだもので再定義する</span><br> <span style="color: #00cccc; font-weight: 900">・案件に紐づけられるものはその工数と付き合わせられるように設定しておく</span><br> <span style="color: #00cccc; font-weight: 900">・改善などのチーム活動は工数が積み上げられるチケットを各自が用意する</span><br></p></li> </ul> <h2 id="最後に">最後に</h2> <p>現在では全社OKRとして、経営層からスタッフ1人1人がつながり、企業の成長と個人の成果の結び付きがより明確で分かりやすくなっています。 我々のチームでも、主体性の底上げから定量と定性の両側面をしっかり成果として見据え、日々の小さな積み重ねが全社戦略に寄与、貢献していることがわかる様に今期もOKRに取り組んでいます。<br> <br> <br> オプティムでは、プロモ・デザインUで一緒に働いてくださるメンバーも探しています。 <br> UI/UXデザインやWeb制作によるブランディングから、Webマーケティングなど <span style="color: #008EEE; font-weight: 900">"オプティムの思想を魅力的に伝える"</span>お仕事をしています。 ブランディング、UI/UX、Webプロモーションに興味ある方、ぜひご応募をお待ちしております。<br></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-katoh React+threejsで3Dモデルのビューアーを作る!! hatenablog://entry/820878482941505034 2023-08-28T10:00:00+09:00 2023-08-28T10:00:03+09:00 概要 はじめまして!テックセンター飯塚の株式会社オプティムのアルバイトスタッフの下前です。普段は次期ストアのタスクを行っています。 この記事はC3との合同イベントのハンズオン資料です。 three.js, react-three-fiber, react-three-dreiなどのjsライブラリを用いて3Dモデルを表示るビュアーを作成していきます。 C3(Composite Computer Club)は九州工業大学情報工学部のコンピューターでモノづくりをするサークルです。情報工学部のキャンパスの目の前に OPTiMのオフィスがあるということもあり、私も含めた何人かのC3メンバーがアルバイトス… <h1 id="概要">概要</h1> <p>はじめまして!テックセンター飯塚の株式会社オプティムのアルバイトスタッフの下前です。普段は次期ストアのタスクを行っています。<br/> この記事は<a href="https://compositecomputer.club/">C3</a>との<a href="https://compositecomputer.club/news/1KB6nHLVmYxB84FdikDFRq">合同イベント</a>のハンズオン資料です。<br/> three.js, react-three-fiber, react-three-dreiなどのjsライブラリを用いて3Dモデルを表示るビュアーを作成していきます。</p> <p>C3(Composite Computer Club)は九州工業大学情報工学部のコンピューターでモノづくりをするサークルです。情報工学部のキャンパスの目の前に OPTiMのオフィスがあるということもあり、私も含めた何人かのC3メンバーがアルバイトスタッフとして勤務しています。 そこで九州工業大学前アルバイトスタッフとC3との共同でハンズオンイベントを行いました。</p> <p>イベントの他の記事はこちら</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2023%2F07%2F06%2F100000" title="スキャンした点群データを3Dモデルデータに変換してみよう! - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2023/07/06/100000">tech-blog.optim.co.jp</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2023%2F08%2F24%2F100000" title="3Dスキャンしたモデルで簡単なゲーム制作! - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2023/08/24/100000">tech-blog.optim.co.jp</a></cite></p> <h1 id="前提">前提</h1> <p>↓の記事でやっているようにLiDARセンサーで取得したlas形式の点群データをply形式に変換し、さらにobj形式に変換した3Dモデルが手元にある前提で行います。一般的なobj形式の3Dモデルでも問題ありません。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2023%2F07%2F06%2F100000" title="スキャンした点群データを3Dモデルデータに変換してみよう! - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2023/07/06/100000">tech-blog.optim.co.jp</a></cite></p> <ul> <li>OSはWindowsを想定</li> <li><a href="https://compositecomputer.club/news/1KB6nHLVmYxB84FdikDFRq">事前準備</a>で以下が出来るようになっていること</li> <li>Dockerが使える</li> <li>VSCodeが使える</li> <li>GitHubからクローンができる</li> </ul> <p>※ Docker Desktop on windowsでの環境ではホットリロードが非常に遅いです。wsl(Ubuntuなど)やmac osの環境がある場合はそちらを使うことをお勧めします。上記は環境はイベント参加者がwindowsユーザーが多いことやwslセットアップが出来ていないことを考慮してのものですので、特別な理由がない限りはwsl(Ubuntuなど)やmac osでやることをお勧めします。</p> <h2 id="Dockerについて">Dockerについて</h2> <p>一言で言うとオーバーヘッドの少ない(軽い)仮想環境です。今回これを使うのは開発環境を作るのにさまざまな作業が必要なため、それを回避するのに使用しています。ソフトウェアの開発ではよく用いられています。</p> <blockquote><p>コンテナは仮想化技術の1つの形態と言われていますが、実際には一般的な仮想マシンとはアーキテクチャーが異なります。まずコンテナと仮想マシンの違いについて具体的に触れていきましょう。</p> <p>コンテナの特徴は、ハードウェア上に存在するOSが1つのみであり、その上で複数のコンテナが稼働している仕組みになっていることです。コンテナアプリはOSから分離しているため、OSに起因するオーバーヘッドがかからずにリソースを有効活用できます。IPアドレスは自動設定で、外部とはNAT(Network Address Translation)で接続します。</p> <p>一方で、一般的な仮想マシンではハードウェアのリソースを、ハイパーバイザーを介してLinuxやWindowsなど複数のOS環境に分けて、それぞれで仮想マシンが稼働します。仮想マシンごとにOSを用意するため、アプリに合わせてOSを選択できます。また、物理と同様に、IP管理も実施できます。仮想マシンは、物理環境で実装可能な機能はほぼすべて実現できるという意味で、信頼の厚い手法と言えるでしょう。実際に、現在のエンタープライズアプリケーションの稼働環境として、最も広く使われています。</p></blockquote> <p>参考:<a href="https://frontier.networld.co.jp/useful-container/virtualmachine-vs-container/">&#x4EEE;&#x60F3;&#x30DE;&#x30B7;&#x30F3;&#x3068;&#x30B3;&#x30F3;&#x30C6;&#x30CA;&#x306E;&#x9055;&#x3044;&#x3068;&#x306F;&#xFF1F;&#xFF5E;&#x6BD4;&#x8F03;&#x304B;&#x3089;&#x898B;&#x3048;&#x308B;&#x305D;&#x308C;&#x305E;&#x308C;&#x306E;&#x7279;&#x5FB4;&#xFF5E; | VMware Cloud Frontier by Networld</a></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102144.png" width="735" height="303" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><br/> 参照:<a href="https://tech-blog.rakus.co.jp/entry/20221007/docker">Dockerとは一体何なんだ?【初心者向け】</a><br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102142.png" width="1058" height="335" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><br/> 参照:<a href="https://cpptake.com/archives/491">Docker初心者がDockerfile,Image,Containerの関係を感覚的に理解する</a></p> <p>詳細は↓</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.kagoya.jp%2Fhowto%2Fcloud%2Fcontainer%2Fdocker%2F" title="【入門】Dockerとは?概要やメリット、インストール方法をわかりやすく解説 - カゴヤのサーバー研究室" 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.kagoya.jp/howto/cloud/container/docker/">www.kagoya.jp</a></cite></p> <h1 id="手順">手順</h1> <h2 id="配布リポジトリのアプリを起動">配布リポジトリのアプリを起動</h2> <p>↓今回使うリポジトリ<br/> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Foptim-corp%2Ftechblog-c3optim-event-react-threejs-sample" title="GitHub - optim-corp/techblog-c3optim-event-react-threejs-sample" 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://github.com/optim-corp/techblog-c3optim-event-react-threejs-sample">github.com</a></cite></p> <p>このリポジトリは以下の2つのブランチが存在します。完成版を見たい方はdemoブランチにアクセスしてください。<br/> main:ハンズオン用のテンプレートコード<br/> demo:完成版</p> <ul> <li>GitHubからソースコードをローカルにクローン(ダウンロード)する</li> </ul> <pre class="code shell" data-lang="shell" data-unlink>git clone https://github.com/optim-corp/techblog-c3optim-event-react-threejs-sample.git</pre> <ul> <li>ソースコードをVSCodeで開く</li> </ul> <pre class="code shell" data-lang="shell" data-unlink>code techblog-c3optim-event-react-threejs-sample</pre> <ul> <li><p>VSCodeのターミナルを開く 以下のように上部のメニューからTerminalを選択し、New Tarminalを選択する <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102139.png" width="588" height="369" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><br/> そうすると以下のようにターミナルが開く <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102137.png" width="1200" height="342" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p></li> <li><p>Dockerのimageを作成 開いたターミナルで以下のコマンドを実行する</p></li> </ul> <pre class="code shell" data-lang="shell" data-unlink>docker compose build</pre> <p>正常に終了すると以下のようになる <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102134.png" width="1200" height="292" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>Dockerのコンテナを起動 ビルドが完了したら以下のコマンドを実行する</li> </ul> <pre class="code shell" data-lang="shell" data-unlink>docker compose up -d</pre> <p>実行すると以下のようになる <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102132.png" width="1200" height="65" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>ブラウザで動作確認 <a href="http://localhost:3000">http://localhost:3000</a> にアクセスして、以下のようなページが表示されればOK <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102129.png" width="1200" height="632" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></li> </ul> <h2 id="VSCodeで開発環境を作る">VSCodeで開発環境を作る</h2> <ul> <li>VSCodeの拡張機能を開く 以下の画像のように左のメニューバーから拡張を選択し、開く</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102126.png" width="568" height="1032" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>ワークスペース推奨の拡張機能をすべて入れる 以下の画像のように検索ボックスに<code>@recommended</code>を入れるとワークスペース推奨が表示されるので、表示されたすべての拡張をインストールする</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102123.png" width="315" height="519" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>インストールした後に<code>Reload Required</code>と表示されたら、そのボタンをおして画面の再読み込みを行う。もし画面の再読み込みをしないと拡張機能が動作しない。</p> <ul> <li>DevContainerでDockerコンテナに入る 画像のようにVSCodeの左のメニューでRemote Explorerを選択し、表示された画面の上部にあるプルダウンでDev Containersを選択する。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102121.png" width="378" height="392" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>表示された中に<code>techblog-c3optim-....</code>があるので、それを選択すると新しいウィンドウが表示される。接続までに時間がかかるので少し待つ。</p> <ul> <li>アプリのディレクトリを開く 以下の画像のようにVSCodeの上部のメニューでFileを選択し、Open Folderを選択する</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102119.png" width="347" height="236" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>そうすると以下のようなモーダルが表示されるので、<code>/threejs/</code>を入れOKを押す。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102207.png" width="645" height="233" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>以下のようになればOK</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102205.png" width="401" height="636" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="アプリの説明">アプリの説明</h2> <h3 id="概要-1">概要</h3> <p>3Dモデルを表示し、ボタンで表示を切り替えれるようなビューアー</p> <h3 id="環境">環境</h3> <p>node: v20.2.0<br/> 言語: TypeScript<br/> パッケージ管理: pnpm<br/> ビルドツール: vite<br/> nodeバージョン管理: volta<br/> コードフォーマッター: prettier<br/> 使用パッケージ:</p> <pre class="code lang-json" data-lang="json" data-unlink>&quot;<span class="synStatement">@react-three/drei</span>&quot;: &quot;<span class="synConstant">^9.74.1</span>&quot;, &quot;<span class="synStatement">@react-three/fiber</span>&quot;: &quot;<span class="synConstant">^8.13.0</span>&quot;, &quot;<span class="synStatement">leva</span>&quot;: &quot;<span class="synConstant">^0.9.34</span>&quot;, &quot;<span class="synStatement">react</span>&quot;: &quot;<span class="synConstant">^18.2.0</span>&quot;, &quot;<span class="synStatement">react-dom</span>&quot;: &quot;<span class="synConstant">^18.2.0</span>&quot;, &quot;<span class="synStatement">react-icons</span>&quot;: &quot;<span class="synConstant">^4.9.0</span>&quot;, &quot;<span class="synStatement">three</span>&quot;: &quot;<span class="synConstant">^0.152.2</span>&quot;, &quot;<span class="synStatement">three-stdlib</span>&quot;: &quot;<span class="synConstant">^2.23.8</span>&quot;, &quot;<span class="synStatement">typescript</span>&quot;: &quot;^4.9.5&quot; </pre> <h3 id="Reactについて">Reactについて</h3> <p>ReactはWeb開発で用いるJSのフレームワークです。ウェブに必要な機能などを簡単に実装できるようになるため、多くのウェブ開発で使用されています。React以外にもWeb制作に使うJSライブラリが多くあるので、興味があれば確認してください。</p> <p>参考<br/> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.kagoya.jp%2Fhowto%2Fit-glossary%2Fdevelop%2Freact%2F" title="【初心者向け】Reactとは?注目されている背景や特徴・メリットを解説 - カゴヤのサーバー研究室" 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.kagoya.jp/howto/it-glossary/develop/react/">www.kagoya.jp</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fja.legacy.reactjs.org%2F" title="React – ユーザインターフェース構築のための JavaScript ライブラリ" 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://ja.legacy.reactjs.org/">ja.legacy.reactjs.org</a></cite></p> <h3 id="ディレクトリ説明">ディレクトリ説明</h3> <pre class="code" data-lang="" data-unlink>/ ├ .vscode(ワークスペースのVSCodeの設定) │ └ ... ├ build(ビルドしたファイルを格納) │ └... ├ node_modules(パッケージコード) │ └ ... ├ public(アセットなど) │ └ ... ├ src(Reactのソースコード) │ ├ App.css │ ├ App.tsx │ ├ Fotter.tsx │ ├ Fotter.css │ ├ index.css │ ├ index.tsx │ ├ ModelView.tsx │ └ vite-app-env.d.ts ├ .gitignore(github管理を無視する設定) ├ compose.yml(docker composeの設定ファイル) ├ Dockerfile ├ index.html ├ package.json(パッケージ管理) ├ pnpm-lock.yaml(インストール済みのパッケージ情報) ├ README.md(ドキュメント) ├ tsconfig.json(TSの設定) └ vite.config.ts(viteの設定)</pre> <h2 id="アプリ作成">アプリ作成</h2> <p>目次<br/> 1. 3Dモデルのダウンロード<br/> 2. 3Dモデルを表示する<br/> 3. 光を追加する<br/> 4. 影をつける<br/> 5. 背景をつける<br/> 6. ローディング表示を追加する<br/> 7. xyz軸の表示を消す<br/> 8. ボタンで表示するモデルを切り替えれるようにする</p> <h3 id="3Dモデルのダウンロード">3Dモデルのダウンロード</h3> <p>今回使用する以下の3Dモデルをダウンロードし、プロジェクトの<code>public</code>フォルダ内に入れてください。</p> <p>↓今回使用する3Dモデル<br/> <a href="https://public.compositecomputer.club/c3optim/model1.obj">https://public.compositecomputer.club/c3optim/model1.obj</a><br/> <a href="https://public.compositecomputer.club/c3optim/model2.obj">https://public.compositecomputer.club/c3optim/model2.obj</a><br/> <a href="https://public.compositecomputer.club/c3optim/model3.obj">https://public.compositecomputer.club/c3optim/model3.obj</a></p> <h3 id="3Dモデルを表示する">3Dモデルを表示する</h3> <ul> <li><p>ModelView.tsxファイルを作成する <br/> 画像のようにsrcフォルダを一度クリックし、赤枠のファイル追加ボタンを押す。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102203.png" width="296" height="552" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><br/> 入力が求められるので、<code>ModelView.tsx</code>と記入する</p></li> <li><p>モデルビューアーを定義する<br/> <code>ModelView.tsx</code>ファイルを開き、以下のコードをコピペする。</p></li> </ul> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// パッケージのインポート</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useRef<span class="synStatement">,</span> Suspense <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/fiber&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Mesh <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OrbitControls <span class="synStatement">as</span> OrbitControlImpl <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three-stdlib&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OBJLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three/examples/jsm/loaders/OBJLoader&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OrbitControls<span class="synStatement">,</span> Preload<span class="synStatement">,</span> PerspectiveCamera <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/drei&quot;</span><span class="synStatement">;</span> <span class="synComment">// ModelViewのpropsの型定義</span> <span class="synStatement">type</span> ModelViewProps <span class="synStatement">=</span> <span class="synIdentifier">{</span> model_url: <span class="synType">string</span><span class="synStatement">;</span> key: <span class="synType">number</span><span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synComment">// モデルローダー</span> <span class="synType">const</span> Model <span class="synStatement">=</span> <span class="synStatement">(</span>args: <span class="synType">any</span><span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synType">const</span> model <span class="synStatement">=</span> useLoader<span class="synStatement">(</span>OBJLoader<span class="synStatement">,</span> args.model_url <span class="synStatement">as</span> <span class="synType">string</span><span class="synStatement">);</span> model.rotation.x <span class="synStatement">=</span> <span class="synConstant">-1</span>.<span class="synConstant">5</span><span class="synStatement">;</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>primitive <span class="synType">object</span><span class="synStatement">=</span><span class="synIdentifier">{</span>model<span class="synIdentifier">}</span> ref<span class="synStatement">=</span><span class="synIdentifier">{</span>args.mesh <span class="synStatement">as</span> Mesh<span class="synIdentifier">}</span> scale<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synIdentifier">]}</span> <span class="synIdentifier">{</span>...args<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synStatement">function</span> ModelView<span class="synStatement">(</span>props: ModelViewProps<span class="synStatement">)</span> <span class="synIdentifier">{</span> <span class="synComment">// コントローラー用Refの定義</span> <span class="synType">const</span> controlRef <span class="synStatement">=</span> useRef<span class="synStatement">&lt;</span>OrbitControlImpl<span class="synStatement">&gt;(</span><span class="synIdentifier">{}</span> <span class="synStatement">as</span> OrbitControlImpl<span class="synStatement">);</span> <span class="synComment">// メッシュ定義</span> <span class="synType">const</span> mesh <span class="synStatement">=</span> useRef<span class="synStatement">(</span><span class="synIdentifier">{}</span> <span class="synStatement">as</span> Mesh<span class="synStatement">);</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ローディングが終わるまでは何も表示しない */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Suspense fallback<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synType">null</span><span class="synIdentifier">}</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* 上記で定義したモデルローダーで表示される3Dモデル */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Model mesh<span class="synStatement">=</span><span class="synIdentifier">{</span>mesh<span class="synIdentifier">}</span> model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>props.model_url<span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Preload <span class="synStatement">all</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* 遠近カメラ */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>PerspectiveCamera makeDefault args<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">35</span><span class="synStatement">,</span> <span class="synSpecial">window</span>.innerWidth / <span class="synSpecial">window</span>.innerHeight<span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">2000</span><span class="synIdentifier">]}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">-5</span><span class="synStatement">,</span> <span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Suspense<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* マウスでの操作を可能にする */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>OrbitControls makeDefault ref<span class="synStatement">=</span><span class="synIdentifier">{</span>controlRef<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* xyz軸を表示 */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>axesHelper /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span> <span class="synComment">// ModelViewコンポーネントとして出力</span> <span class="synStatement">export</span> <span class="synStatement">default</span> ModelView<span class="synStatement">;</span> </pre> <ul> <li>表示する<br/> <code>App.tsx</code>を開き、以下のように変更する</li> </ul> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// パッケージのインポート</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useState <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Canvas <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/fiber&quot;</span><span class="synStatement">;</span> <span class="synComment">// コンポーネントのインポート</span> <span class="synStatement">import</span> Footer <span class="synStatement">from</span> <span class="synConstant">&quot;./Footer&quot;</span><span class="synStatement">;</span> <span class="synComment">// ↓ここを追加</span> <span class="synStatement">import</span> ModelView <span class="synStatement">from</span> <span class="synConstant">&quot;./ModelView&quot;</span><span class="synStatement">;</span> <span class="synComment">// cssのインポート</span> <span class="synStatement">import</span> <span class="synConstant">&quot;./App.css&quot;</span><span class="synStatement">;</span> <span class="synStatement">function</span> App<span class="synStatement">()</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>div className<span class="synStatement">=</span><span class="synConstant">&quot;App&quot;</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ↓ここを追加 */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Canvas className<span class="synStatement">=</span><span class="synConstant">&quot;canvas&quot;</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ModelView model_url<span class="synStatement">=</span><span class="synConstant">&quot;/model1.obj&quot;</span> key<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Canvas<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Footer /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/div<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span> <span class="synStatement">export</span> <span class="synStatement">default</span> App<span class="synStatement">;</span> </pre> <p><code>Canvas</code>はreact-three-fiberのコンポーネントでthreejsで描画する領域を定義するためのコンポーネントです。</p> <p>先ほど定義した<code>ModelView</code>コンポーネントを<code>import ModelView from "./ModelView";</code>でインポートし、</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">&lt;</span>ModelView model_url<span class="synStatement">=</span><span class="synConstant">&quot;/model1.obj&quot;</span> key<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> </pre> <p>propsとして<code>model_url</code>に3Dモデルのパス、keyに番号を渡している。</p> <p>WebGLでは物体、光源、カメラが最低限必要になってくる。WebGLにおいて描画されるのはカメラから見た構図となる。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102200.png" width="476" height="442" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>参考 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fics.media%2Fentry%2F14771%2F" title="最新版で学ぶThree.js入門 - 手軽にWebGLを扱える3Dライブラリ - ICS MEDIA" 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://ics.media/entry/14771/">ics.media</a></cite></p> <ul> <li>ブラウザで確認<br/> 画像のように表示されるはずです。黒い塊が表示されていますが、これが3Dモデルです。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102157.png" width="1200" height="717" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><br/> 黒い塊と座標軸が表示されているだけでとても見やすいとは言えないと思います。<br/> 3Dモデルが黒く表示されているのは光源の設定をしていないため、暗闇の中にある物体をカメラで見ていることになり、何も光が反射してこないので黒く見えているのです。私たちが目で見ているものはすべて光源からの光や物体からの反射光によるものですが、threejs(OpenGL)でも同じ原理になっています。</li> </ul> <h3 id="光を追加する">光を追加する</h3> <p>物体の本来の色を見たいので、光を追加していきます。</p> <ul> <li>環境光を追加する<br/> <code>ModelView.tsx</code>に<code>ambientLight</code>を以下のように追加する。</li> </ul> <pre class="code lang-typescript" data-lang="typescript" data-unlink> <span class="synStatement">&lt;&gt;</span> <span class="synStatement">&lt;</span>Suspense fallback<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synType">null</span><span class="synIdentifier">}</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Model mesh<span class="synStatement">=</span><span class="synIdentifier">{</span>mesh<span class="synIdentifier">}</span> model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>props.model_url<span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Preload <span class="synStatement">all</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>PerspectiveCamera makeDefault args<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">35</span><span class="synStatement">,</span> <span class="synSpecial">window</span>.innerWidth / <span class="synSpecial">window</span>.innerHeight<span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">2000</span><span class="synIdentifier">]}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">-5</span><span class="synStatement">,</span> <span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ↓ここを追加 */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>ambientLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.5</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ここまで */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/Suspense<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>OrbitControls makeDefault ref<span class="synStatement">=</span><span class="synIdentifier">{</span>controlRef<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>axesHelper /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> </pre> <p>ambient lightは環境光を意味します。環境光とは現実世界における自然光の乱反射を再現する光のことです。intensityは明るさを調整するパラメータです。<br/> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwgld.org%2Fd%2Fwebgl%2Fw022.html" title="wgld.org | WebGL: 環境光によるライティング |" 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://wgld.org/d/webgl/w022.html">wgld.org</a></cite></p> <ul> <li>平行光源を追加する<br/> <code>ModelView.tsx</code>に<code>directionalLight</code>を以下のように追加する。</li> </ul> <pre class="code lang-typescript" data-lang="typescript" data-unlink> <span class="synStatement">&lt;&gt;</span> <span class="synStatement">&lt;</span>Suspense fallback<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synType">null</span><span class="synIdentifier">}</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Model mesh<span class="synStatement">=</span><span class="synIdentifier">{</span>mesh<span class="synIdentifier">}</span> model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>props.model_url<span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Preload <span class="synStatement">all</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>PerspectiveCamera makeDefault args<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">35</span><span class="synStatement">,</span> <span class="synSpecial">window</span>.innerWidth / <span class="synSpecial">window</span>.innerHeight<span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">2000</span><span class="synIdentifier">]}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">-5</span><span class="synStatement">,</span> <span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ambientLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.5</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ↓ここを追加 */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>directionalLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.8</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">1</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ここまで */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/Suspense<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>OrbitControls makeDefault ref<span class="synStatement">=</span><span class="synIdentifier">{</span>controlRef<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>axesHelper /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> </pre> <p>directional lightは平行光源を意味します。平行光源とは無限遠から物体を照らしている光源のことです。例えば、太陽は平行光源です。<br/> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwgld.org%2Fd%2Fwebgl%2Fw021.html" title="wgld.org | WebGL: 平行光源によるライティング |" 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://wgld.org/d/webgl/w021.html">wgld.org</a></cite></p> <p>光源の設定ができたので以下のようにモデルの本来の色が見えるようになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102154.png" width="1200" height="592" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="モデルを中央に持ってくる">モデルを中央に持ってくる</h3> <p>現在このように表示されていると思います。モデルの端がxyz軸の原点にあると思います。マウスなどでモデルを動かすと原点を中心に支店が変わると思います。この状態だと違和感があるので、モデルの中心を原点にしたいと思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102152.png" width="675" height="475" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以下のように<code>Center</code>コンポーネントを追加するとモデルの中心が原点に配置されます</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span> useRef<span class="synStatement">,</span> Suspense <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/fiber&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Mesh <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OrbitControls <span class="synStatement">as</span> OrbitControlImpl <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three-stdlib&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OBJLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three/examples/jsm/loaders/OBJLoader&quot;</span><span class="synStatement">;</span> <span class="synComment">// ↓ここを変更</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Center<span class="synStatement">,</span> OrbitControls<span class="synStatement">,</span> Preload<span class="synStatement">,</span> PerspectiveCamera <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/drei&quot;</span><span class="synStatement">;</span> ... <span class="synStatement">&lt;&gt;</span> <span class="synStatement">&lt;</span>Suspense fallback<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synType">null</span><span class="synIdentifier">}</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ↓ここを変更 */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Center<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Model mesh<span class="synStatement">=</span><span class="synIdentifier">{</span>mesh<span class="synIdentifier">}</span> model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>props.model_url<span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Center<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ここまで */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Preload <span class="synStatement">all</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>PerspectiveCamera makeDefault args<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">35</span><span class="synStatement">,</span> <span class="synSpecial">window</span>.innerWidth / <span class="synSpecial">window</span>.innerHeight<span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">2000</span><span class="synIdentifier">]}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">-5</span><span class="synStatement">,</span> <span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ambientLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.5</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>directionalLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.8</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">1</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Suspense<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>OrbitControls makeDefault ref<span class="synStatement">=</span><span class="synIdentifier">{</span>controlRef<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>axesHelper /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> </pre> <p>これで3Dモデルの中心が原点になるように描画されたと思います。<br/> このコンポーネントはいい感じに計算してポジションを決めてくれるので、簡単にモデルを中心に持ってくることができます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102148.png" width="1200" height="655" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="影をつける">影をつける</h3> <p>以下のように<code>ContactShadows</code>コンポーネントを追加する。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span> useRef<span class="synStatement">,</span> Suspense <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/fiber&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Mesh <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OrbitControls <span class="synStatement">as</span> OrbitControlImpl <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three-stdlib&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OBJLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three/examples/jsm/loaders/OBJLoader&quot;</span><span class="synStatement">;</span> <span class="synComment">// ↓ここを変更</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Center<span class="synStatement">,</span> ContactShadows<span class="synStatement">,</span> OrbitControls<span class="synStatement">,</span> Preload<span class="synStatement">,</span> PerspectiveCamera <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/drei&quot;</span><span class="synStatement">;</span> ... <span class="synStatement">&lt;&gt;</span> <span class="synStatement">&lt;</span>Suspense fallback<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synType">null</span><span class="synIdentifier">}</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Center<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Model mesh<span class="synStatement">=</span><span class="synIdentifier">{</span>mesh<span class="synIdentifier">}</span> model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>props.model_url<span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Center<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Preload <span class="synStatement">all</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>PerspectiveCamera makeDefault args<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">35</span><span class="synStatement">,</span> <span class="synSpecial">window</span>.innerWidth / <span class="synSpecial">window</span>.innerHeight<span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">2000</span><span class="synIdentifier">]}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">-5</span><span class="synStatement">,</span> <span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ambientLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.5</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>directionalLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.8</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">1</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ↓ここを追加 */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>ContactShadows opacity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.7</span><span class="synIdentifier">}</span> scale<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">15</span><span class="synIdentifier">}</span> blur<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">2.2</span><span class="synIdentifier">}</span> far<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">20</span><span class="synIdentifier">}</span> resolution<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">256</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">-0</span>.<span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ここまで */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/Suspense<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>OrbitControls makeDefault ref<span class="synStatement">=</span><span class="synIdentifier">{</span>controlRef<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>axesHelper /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> </pre> <p>追加すると以下のような影が表示されます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102146.png" width="868" height="449" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="背景をつける">背景をつける</h3> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span> useRef<span class="synStatement">,</span> Suspense <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/fiber&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Mesh <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OrbitControls <span class="synStatement">as</span> OrbitControlImpl <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three-stdlib&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OBJLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three/examples/jsm/loaders/OBJLoader&quot;</span><span class="synStatement">;</span> <span class="synComment">// ↓ここを変更</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Backdrop<span class="synStatement">,</span> Center<span class="synStatement">,</span> ContactShadows<span class="synStatement">,</span> OrbitControls<span class="synStatement">,</span> Preload<span class="synStatement">,</span> PerspectiveCamera<span class="synStatement">,</span> <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/drei&quot;</span><span class="synStatement">;</span> ... <span class="synStatement">&lt;&gt;</span> <span class="synStatement">&lt;</span>Suspense fallback<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synType">null</span><span class="synIdentifier">}</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ↓ここを追加 */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Backdrop receiveShadow scale<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">20</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> floor<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">1</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">-0</span>.<span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">-2</span><span class="synIdentifier">]}</span> <span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>meshPhysicalMaterial roughness<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">1</span><span class="synIdentifier">}</span> color<span class="synStatement">=</span><span class="synConstant">&quot;#a0d8ef&quot;</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Backdrop<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ここまで */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Center<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Model mesh<span class="synStatement">=</span><span class="synIdentifier">{</span>mesh<span class="synIdentifier">}</span> model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>props.model_url<span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Center<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Preload <span class="synStatement">all</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>PerspectiveCamera makeDefault args<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">35</span><span class="synStatement">,</span> <span class="synSpecial">window</span>.innerWidth / <span class="synSpecial">window</span>.innerHeight<span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">2000</span><span class="synIdentifier">]}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">-5</span><span class="synStatement">,</span> <span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ambientLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.5</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>directionalLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.8</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">1</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ContactShadows opacity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.7</span><span class="synIdentifier">}</span> scale<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">15</span><span class="synIdentifier">}</span> blur<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">2.2</span><span class="synIdentifier">}</span> far<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">20</span><span class="synIdentifier">}</span> frames<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">1</span><span class="synIdentifier">}</span> resolution<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">256</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">-0</span>.<span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Suspense<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>OrbitControls makeDefault ref<span class="synStatement">=</span><span class="synIdentifier">{</span>controlRef<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>axesHelper /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> </pre> <p>これによって以下のように撮影スタジオのような背景ができます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102143.png" width="1200" height="713" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="ローディング表示を追加する">ローディング表示を追加する</h3> <p>3Dモデルの読み込み・描画には時間がかかるため、ページ読み込み時には何も表示されません。そこで、ローディング中に何かしらの要素を表示したいと思います。<br/> 以下のようにコードを追加します。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink>... Backdrop<span class="synStatement">,</span> Center<span class="synStatement">,</span> ContactShadows<span class="synStatement">,</span> OrbitControls<span class="synStatement">,</span> Preload<span class="synStatement">,</span> PerspectiveCamera<span class="synStatement">,</span> <span class="synComment">// ↓ここを追加</span> useProgress<span class="synStatement">,</span> Html<span class="synStatement">,</span> <span class="synComment">// ここまで</span> <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/drei&quot;</span><span class="synStatement">;</span> <span class="synComment">// ↓ここを追加</span> <span class="synComment">// ローディング要素</span> <span class="synStatement">function</span> Loading<span class="synStatement">()</span> <span class="synIdentifier">{</span> <span class="synType">const</span> <span class="synIdentifier">{</span> progress <span class="synIdentifier">}</span> <span class="synStatement">=</span> useProgress<span class="synStatement">();</span> <span class="synSpecial">console</span>.log<span class="synStatement">(</span>progress + <span class="synConstant">&quot;% loading...&quot;</span><span class="synStatement">);</span> <span class="synStatement">return</span> <span class="synStatement">&lt;</span>Html className<span class="synStatement">=</span><span class="synConstant">&quot;loading&quot;</span><span class="synStatement">&gt;&lt;</span>/Html<span class="synStatement">&gt;;</span> <span class="synIdentifier">}</span> <span class="synComment">// ここまで</span> <span class="synComment">// モデルローダー</span> <span class="synType">const</span> Model <span class="synStatement">=</span> <span class="synStatement">(</span>args: <span class="synType">any</span><span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> ... <span class="synStatement">&lt;&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ↓ここを変更 */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Suspense fallback<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synStatement">&lt;</span>Loading /<span class="synStatement">&gt;</span><span class="synIdentifier">}</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Backdrop receiveShadow scale<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">20</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> floor<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">1</span><span class="synIdentifier">}</span> </pre> <p><code>useProgress()</code>はreact-three-dreiのメソッドで、読み込みのパーセンテージを返り値として取得できます。<code>Html</code>コンポーネントは読み込み中に表示する要素を記述するためのものです。</p> <p>今回はcssアニメーションで立方体を動かしています。cssはすでに記述済みなので、<code>className</code>で<code>loading</code>クラスを付与するだけでよい。<br/> これによって、3Dモデルの読み込み中にアニメーションを表示できる。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102140.png" width="1183" height="650" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="xyz軸の表示を消す">xyz軸の表示を消す</h3> <p>座標軸の表示は邪魔なので、この辺で消しておきます。<code>&lt;axesHelper /&gt;</code>の記述を削除すると表示されなくなります。</p> <h3 id="ボタンで表示するモデルを切り替えれるようにする">ボタンで表示するモデルを切り替えれるようにする</h3> <p>ここまででモデルのビューアーは完成しました。ここでは、ボタンで表示する3Dモデルの表示を切り替えれるようにしたいと思います。</p> <p><code>App.tsx</code>を開いて、以下のようにコードを修正します。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">import</span> <span class="synIdentifier">{</span> useState <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Canvas <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/fiber&quot;</span><span class="synStatement">;</span> <span class="synComment">// コンポーネントのインポート</span> <span class="synStatement">import</span> Footer <span class="synStatement">from</span> <span class="synConstant">&quot;./Footer&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> ModelView <span class="synStatement">from</span> <span class="synConstant">&quot;./ModelView&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synConstant">&quot;./App.css&quot;</span><span class="synStatement">;</span> <span class="synStatement">function</span> App<span class="synStatement">()</span> <span class="synIdentifier">{</span> <span class="synComment">// ↓ここから追加</span> <span class="synComment">// 表示する3Dモデルのインデックスの状態</span> <span class="synType">const</span> <span class="synIdentifier">[</span>selectedModel<span class="synStatement">,</span> setSelectedModel<span class="synIdentifier">]</span> <span class="synStatement">=</span> useState<span class="synStatement">&lt;</span><span class="synType">number</span><span class="synStatement">&gt;(</span><span class="synConstant">0</span><span class="synStatement">);</span> <span class="synComment">// 3Dモデルのパスを保持する変数</span> <span class="synType">const</span> models: <span class="synType">string</span><span class="synIdentifier">[]</span> <span class="synStatement">=</span> <span class="synIdentifier">[</span><span class="synConstant">&quot;/model1.obj&quot;</span><span class="synStatement">,</span> <span class="synConstant">&quot;/model2.obj&quot;</span><span class="synStatement">,</span> <span class="synConstant">&quot;/model3.obj&quot;</span><span class="synIdentifier">]</span><span class="synStatement">;</span> <span class="synComment">// ここまで</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>div className<span class="synStatement">=</span><span class="synConstant">&quot;App&quot;</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ↓ここから追加 */</span><span class="synIdentifier">}</span> <span class="synIdentifier">{</span><span class="synComment">/* 3Dモデル切り替え用ボタン */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>div className<span class="synStatement">=</span><span class="synConstant">&quot;model_switch_button&quot;</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* 繰り返し処理 */</span><span class="synIdentifier">}</span> <span class="synIdentifier">{</span>models.map<span class="synStatement">((</span>_<span class="synStatement">,</span> index<span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>div className<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">`</span><span class="synSpecial">${</span>index === selectedModel ? <span class="synConstant">&quot;selected&quot;</span> : <span class="synConstant">&quot;&quot;</span><span class="synSpecial">}</span><span class="synConstant">`</span><span class="synIdentifier">}</span> key<span class="synStatement">=</span><span class="synIdentifier">{</span>index<span class="synIdentifier">}</span> <span class="synComment">// クリックイベントの処理</span> <span class="synSpecial">onClick</span><span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synStatement">()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> setSelectedModel<span class="synStatement">(</span>index<span class="synStatement">);</span> <span class="synIdentifier">}}</span> <span class="synStatement">&gt;</span> <span class="synIdentifier">{</span>index + <span class="synConstant">1</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/div<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span><span class="synStatement">)</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/div<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ここまで */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Canvas className<span class="synStatement">=</span><span class="synConstant">&quot;canvas&quot;</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ModelView model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>models<span class="synIdentifier">[</span>selectedModel<span class="synIdentifier">]}</span> key<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Canvas<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Footer /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/div<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span> <span class="synStatement">export</span> <span class="synStatement">default</span> App<span class="synStatement">;</span> </pre> <p><code>useState</code>で状態管理用の変数とメソッドを定義します。<code>selectedModel</code>が変数で、<code>setSelectedModel</code>が<code>selectedModel</code>に値を格納するためのメソッドです。</p> <p>ボタンを3Dモデルの数(<code>models</code>の配列の長さ)だけ表示したいので、<code>map</code>関数を使って繰り返し処理を行います。<code>map</code>関数はJavaScript/TypeScriptの組み込み関数でfor文のような処理を行えます。この繰り返し処理の中でHTML要素を<code>return</code>することで、3Dモデルの数だけボタンを表示することができます。</p> <p>次にクリックしたときにモデルを切り替える方法ですが、<code>onClick</code>という属性がreactにあり、これは<code>onClick</code>を記述したHTML要素がクリックされたときにそのイベントをキャッチして、指定された処理を行うことができます。</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synSpecial">onClick</span><span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synStatement">()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> setSelectedModel<span class="synStatement">(</span>index<span class="synStatement">);</span> <span class="synIdentifier">}}</span> </pre> <p>今回の場合は<code>selectedModel</code>に<code>models</code>の<code>index</code>を格納し、</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synStatement">&lt;</span>ModelView model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>models<span class="synIdentifier">[</span>selectedModel<span class="synIdentifier">]}</span> key<span class="synStatement">=</span><span class="synIdentifier">{</span>selectedModel<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> </pre> <p><code>ModelView</code>コンポーネントのpropsの<code>model_url</code>に選択されているボタンに対応する3Dモデルのパスを渡している。</p> <p>これによって左上にボタンが表示されたと思います。選択中以外のボタンを選択すると他の3Dモデルが読み込まれて表示されます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102242.png" width="1200" height="635" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="最終的な実装">最終的な実装</h2> <p>ModelView.tsx</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// パッケージのインポート</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useRef<span class="synStatement">,</span> Suspense <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/fiber&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Mesh <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OrbitControls <span class="synStatement">as</span> OrbitControlImpl <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three-stdlib&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> OBJLoader <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;three/examples/jsm/loaders/OBJLoader&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Backdrop<span class="synStatement">,</span> Center<span class="synStatement">,</span> ContactShadows<span class="synStatement">,</span> OrbitControls<span class="synStatement">,</span> Preload<span class="synStatement">,</span> PerspectiveCamera<span class="synStatement">,</span> useProgress<span class="synStatement">,</span> Html<span class="synStatement">,</span> <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/drei&quot;</span><span class="synStatement">;</span> <span class="synComment">// ModelViewのpropsの型定義</span> <span class="synStatement">type</span> ModelViewProps <span class="synStatement">=</span> <span class="synIdentifier">{</span> model_url: <span class="synType">string</span><span class="synStatement">;</span> key: <span class="synType">number</span><span class="synStatement">;</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synComment">// ローディング要素</span> <span class="synStatement">function</span> Loading<span class="synStatement">()</span> <span class="synIdentifier">{</span> <span class="synType">const</span> <span class="synIdentifier">{</span> progress <span class="synIdentifier">}</span> <span class="synStatement">=</span> useProgress<span class="synStatement">();</span> <span class="synSpecial">console</span>.log<span class="synStatement">(</span>progress + <span class="synConstant">&quot;% loading...&quot;</span><span class="synStatement">);</span> <span class="synStatement">return</span> <span class="synStatement">&lt;</span>Html className<span class="synStatement">=</span><span class="synConstant">&quot;loading&quot;</span><span class="synStatement">&gt;&lt;</span>/Html<span class="synStatement">&gt;;</span> <span class="synIdentifier">}</span> <span class="synComment">// モデルローダー</span> <span class="synType">const</span> Model <span class="synStatement">=</span> <span class="synStatement">(</span>args: <span class="synType">any</span><span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synType">const</span> model <span class="synStatement">=</span> useLoader<span class="synStatement">(</span>OBJLoader<span class="synStatement">,</span> args.model_url <span class="synStatement">as</span> <span class="synType">string</span><span class="synStatement">);</span> model.rotation.x <span class="synStatement">=</span> <span class="synConstant">-1</span>.<span class="synConstant">5</span><span class="synStatement">;</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>primitive <span class="synType">object</span><span class="synStatement">=</span><span class="synIdentifier">{</span>model<span class="synIdentifier">}</span> ref<span class="synStatement">=</span><span class="synIdentifier">{</span>args.mesh <span class="synStatement">as</span> Mesh<span class="synIdentifier">}</span> scale<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synIdentifier">]}</span> <span class="synIdentifier">{</span>...args<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span><span class="synStatement">;</span> <span class="synStatement">function</span> ModelView<span class="synStatement">(</span>props: ModelViewProps<span class="synStatement">)</span> <span class="synIdentifier">{</span> <span class="synComment">// コントローラー用Refの定義</span> <span class="synType">const</span> controlRef <span class="synStatement">=</span> useRef<span class="synStatement">&lt;</span>OrbitControlImpl<span class="synStatement">&gt;(</span><span class="synIdentifier">{}</span> <span class="synStatement">as</span> OrbitControlImpl<span class="synStatement">);</span> <span class="synComment">// メッシュ定義</span> <span class="synType">const</span> mesh <span class="synStatement">=</span> useRef<span class="synStatement">(</span><span class="synIdentifier">{}</span> <span class="synStatement">as</span> Mesh<span class="synStatement">);</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* ローディングが終わるまでは何も表示しない */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Suspense fallback<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synStatement">&lt;</span>Loading /<span class="synStatement">&gt;</span><span class="synIdentifier">}</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Backdrop receiveShadow scale<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">20</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> floor<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">1</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">-0</span>.<span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">-2</span><span class="synIdentifier">]}</span> <span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>meshPhysicalMaterial roughness<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">1</span><span class="synIdentifier">}</span> color<span class="synStatement">=</span><span class="synConstant">&quot;#a0d8ef&quot;</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Backdrop<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Center<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* 上記で定義したモデルローダーで表示される3Dモデル */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>Model mesh<span class="synStatement">=</span><span class="synIdentifier">{</span>mesh<span class="synIdentifier">}</span> model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>props.model_url<span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Center<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Preload <span class="synStatement">all</span> /<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* 遠近カメラ */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>PerspectiveCamera makeDefault args<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">35</span><span class="synStatement">,</span> <span class="synSpecial">window</span>.innerWidth / <span class="synSpecial">window</span>.innerHeight<span class="synStatement">,</span> <span class="synConstant">0.1</span><span class="synStatement">,</span> <span class="synConstant">2000</span><span class="synIdentifier">]}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">-5</span><span class="synStatement">,</span> <span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ambientLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.5</span><span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>directionalLight color<span class="synStatement">=</span><span class="synConstant">&quot;white&quot;</span> intensity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.8</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">1</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synStatement">,</span> <span class="synConstant">5</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ContactShadows opacity<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">0.7</span><span class="synIdentifier">}</span> scale<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">15</span><span class="synIdentifier">}</span> blur<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">2.2</span><span class="synIdentifier">}</span> far<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">20</span><span class="synIdentifier">}</span> frames<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">1</span><span class="synIdentifier">}</span> resolution<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">256</span><span class="synIdentifier">}</span> position<span class="synStatement">=</span><span class="synIdentifier">{[</span><span class="synConstant">0</span><span class="synStatement">,</span> <span class="synConstant">-0</span>.<span class="synConstant">4</span><span class="synStatement">,</span> <span class="synConstant">0</span><span class="synIdentifier">]}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Suspense<span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* マウスでの操作を可能にする */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>OrbitControls makeDefault ref<span class="synStatement">=</span><span class="synIdentifier">{</span>controlRef<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span> <span class="synComment">// ModelViewコンポーネントとして出力</span> <span class="synStatement">export</span> <span class="synStatement">default</span> ModelView<span class="synStatement">;</span> </pre> <p>App.tsx</p> <pre class="code lang-typescript" data-lang="typescript" data-unlink><span class="synComment">// パッケージのインポート</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> useState <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;react&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> <span class="synIdentifier">{</span> Canvas <span class="synIdentifier">}</span> <span class="synStatement">from</span> <span class="synConstant">&quot;@react-three/fiber&quot;</span><span class="synStatement">;</span> <span class="synComment">// コンポーネントのインポート</span> <span class="synStatement">import</span> Footer <span class="synStatement">from</span> <span class="synConstant">&quot;./Footer&quot;</span><span class="synStatement">;</span> <span class="synStatement">import</span> ModelView <span class="synStatement">from</span> <span class="synConstant">&quot;./ModelView&quot;</span><span class="synStatement">;</span> <span class="synComment">// caaのインポート</span> <span class="synStatement">import</span> <span class="synConstant">&quot;./App.css&quot;</span><span class="synStatement">;</span> <span class="synStatement">function</span> App<span class="synStatement">()</span> <span class="synIdentifier">{</span> <span class="synComment">// 表示する3Dモデルのインデックスの状態</span> <span class="synType">const</span> <span class="synIdentifier">[</span>selectedModel<span class="synStatement">,</span> setSelectedModel<span class="synIdentifier">]</span> <span class="synStatement">=</span> useState<span class="synStatement">&lt;</span><span class="synType">number</span><span class="synStatement">&gt;(</span><span class="synConstant">0</span><span class="synStatement">);</span> <span class="synComment">// 3Dモデルのパスを保持する変数</span> <span class="synType">const</span> models: <span class="synType">string</span><span class="synIdentifier">[]</span> <span class="synStatement">=</span> <span class="synIdentifier">[</span><span class="synConstant">&quot;/model1.obj&quot;</span><span class="synStatement">,</span> <span class="synConstant">&quot;/model2.obj&quot;</span><span class="synStatement">,</span> <span class="synConstant">&quot;/model3.obj&quot;</span><span class="synIdentifier">]</span><span class="synStatement">;</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>div className<span class="synStatement">=</span><span class="synConstant">&quot;App&quot;</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* 3Dモデル切り替え用ボタン */</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>div className<span class="synStatement">=</span><span class="synConstant">&quot;model_switch_button&quot;</span><span class="synStatement">&gt;</span> <span class="synIdentifier">{</span><span class="synComment">/* 繰り返し処理 */</span><span class="synIdentifier">}</span> <span class="synIdentifier">{</span>models.map<span class="synStatement">((</span>_<span class="synStatement">,</span> index<span class="synStatement">)</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">(</span> <span class="synStatement">&lt;</span>div className<span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synConstant">`</span><span class="synSpecial">${</span>index === selectedModel ? <span class="synConstant">&quot;selected&quot;</span> : <span class="synConstant">&quot;&quot;</span><span class="synSpecial">}</span><span class="synConstant">`</span><span class="synIdentifier">}</span> key<span class="synStatement">=</span><span class="synIdentifier">{</span>index<span class="synIdentifier">}</span> <span class="synComment">// クリックイベントの処理</span> <span class="synSpecial">onClick</span><span class="synStatement">=</span><span class="synIdentifier">{</span><span class="synStatement">()</span> <span class="synStatement">=&gt;</span> <span class="synIdentifier">{</span> setSelectedModel<span class="synStatement">(</span>index<span class="synStatement">);</span> <span class="synIdentifier">}}</span> <span class="synStatement">&gt;</span> <span class="synIdentifier">{</span>index + <span class="synConstant">1</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/div<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span><span class="synStatement">)</span><span class="synIdentifier">}</span> <span class="synStatement">&lt;</span>/div<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Canvas className<span class="synStatement">=</span><span class="synConstant">&quot;canvas&quot;</span><span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>ModelView model_url<span class="synStatement">=</span><span class="synIdentifier">{</span>models<span class="synIdentifier">[</span>selectedModel<span class="synIdentifier">]}</span> key<span class="synStatement">=</span><span class="synIdentifier">{</span>selectedModel<span class="synIdentifier">}</span> /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/Canvas<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>Footer /<span class="synStatement">&gt;</span> <span class="synStatement">&lt;</span>/div<span class="synStatement">&gt;</span> <span class="synStatement">);</span> <span class="synIdentifier">}</span> <span class="synStatement">export</span> <span class="synStatement">default</span> App<span class="synStatement">;</span> </pre> <h2 id="デモ">デモ</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230719/20230719102221.gif" width="685" height="360" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="最後に">最後に</h1> <p>コンテナを起動させた状態だと非常にPCが遅くなるので、コンテナを落とします。<br/> 以下のコマンドを起動時と同じディレクトリの中で実行します。</p> <pre class="code shell" data-lang="shell" data-unlink>docker compose down -v</pre> <p>再度以下を実行すればアプリを動かせます</p> <pre class="code shell" data-lang="shell" data-unlink>docker compose up -d</pre> <p>以上でハンズオンは終了です!</p> <p>OPTiMではエンジニアを随時募集しております。ご興味のある方はこちらをご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> <h1 id="参考">参考</h1> <h3 id="HTML--CSS">HTML &amp; CSS</h3> <p><a href="https://saruwakakun.com/html-css/basic">HTML&amp;CSS&#x5165;&#x9580;&#xFF1A;&#x30A4;&#x30C1;&#x304B;&#x3089;Web&#x30C7;&#x30B6;&#x30A4;&#x30F3;&#x3092;&#x7FD2;&#x5F97;&#x3059;&#x308B;&#x8B1B;&#x5EA7;</a> <a href="https://chot.design/html-css-beginner/">&#x306F;&#x3058;&#x3081;&#x3066;&#x306E;Web&#x30C7;&#x30B6;&#x30A4;&#x30F3;&#x300E;HTML&#x30FB;CSS&#x300F;&#x5165;&#x9580; | chot.design</a></p> <h3 id="REACT">REACT</h3> <p><a href="https://ja.legacy.reactjs.org/tutorial/tutorial.html">&#x30C1;&#x30E5;&#x30FC;&#x30C8;&#x30EA;&#x30A2;&#x30EB;&#xFF1A;React &#x306E;&#x5C0E;&#x5165; &ndash; React</a> <a href="https://zenn.dev/yumemi_inc/articles/2020-09-21-react-basics">React&#x5165;&#x9580; &#xFF5E;&#x57FA;&#x790E;&#x7DE8;&#xFF5E;</a></p> <h3 id="Docker">Docker</h3> <p><a href="https://tech-blog.rakus.co.jp/entry/20221007/docker">Docker&#x3068;&#x306F;&#x4E00;&#x4F53;&#x4F55;&#x306A;&#x3093;&#x3060;&#xFF1F;&#x3010;&#x521D;&#x5FC3;&#x8005;&#x5411;&#x3051;&#x3011; - RAKUS Developers Blog | &#x30E9;&#x30AF;&#x30B9; &#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x30D6;&#x30ED;&#x30B0;</a> <a href="https://cpptake.com/archives/491">Docker&#x521D;&#x5FC3;&#x8005;&#x304C;Dockerfile,Image,Container&#x306E;&#x95A2;&#x4FC2;&#x3092;&#x611F;&#x899A;&#x7684;&#x306B;&#x7406;&#x89E3;&#x3059;&#x308B; | &#x3057;&#x3043;&#x305F;&#x3051; LOG</a> <a href="https://www.kagoya.jp/howto/cloud/container/docker/">&#x3010;&#x5165;&#x9580;&#x3011;Docker&#x3068;&#x306F;&#xFF1F;&#x6982;&#x8981;&#x3084;&#x30E1;&#x30EA;&#x30C3;&#x30C8;&#x3001;&#x30A4;&#x30F3;&#x30B9;&#x30C8;&#x30FC;&#x30EB;&#x65B9;&#x6CD5;&#x3092;&#x308F;&#x304B;&#x308A;&#x3084;&#x3059;&#x304F;&#x89E3;&#x8AAC; - &#x30AB;&#x30B4;&#x30E4;&#x306E;&#x30B5;&#x30FC;&#x30D0;&#x30FC;&#x7814;&#x7A76;&#x5BA4;</a></p> <h3 id="コンピュータグラフィックス">コンピュータグラフィックス</h3> <p><a href="https://3dcg-school.pro/computer-graphics-theory/">&#x30B3;&#x30F3;&#x30D4;&#x30E5;&#x30FC;&#x30BF;&#x30FC;&#x30B0;&#x30E9;&#x30D5;&#x30A3;&#x30C3;&#x30AF;&#x30B9;&#x306F;&#x3069;&#x3046;&#x3084;&#x3063;&#x3066;&#x4F5C;&#x3089;&#x308C;&#x308B;&#x306E;&#x304B;&#xFF1F;CG&#x306E;&#x57FA;&#x790E;&#x539F;&#x7406;&#x3092;&#x5B8C;&#x5168;&#x306B;&#x7406;&#x89E3;&#x3059;&#x308B;&#xFF01; | 3DCG school</a> <a href="http://www.ieice-hbkb.org/files/02/02gun_03hen_01.pdf">http://www.ieice-hbkb.org/files/02/02gun_03hen_01.pdf</a></p> <h3 id="threejs">threejs</h3> <p><a href="https://ics.media/tutorial-three/">Three.js&#x5165;&#x9580;&#x30B5;&#x30A4;&#x30C8; - ICS MEDIA</a> <a href="https://threejs.org/docs/index.html#manual/en/introduction/Installation">three.js docs</a> <a href="https://www.mlit.go.jp/plateau/learning/tpc12-2/">TOPIC 12&#xFF5C;Three.js&#x3067;&#x6D3B;&#x7528;&#x3059;&#x308B;[2/2]&#xFF5C;React&#x3067;Three.js&#x3092;&#x6271;&#x3046; | How To Use | PLATEAU [&#x30D7;&#x30E9;&#x30C8;&#x30FC;]</a> <a href="https://ics.media/entry/14771/">&#x6700;&#x65B0;&#x7248;&#x3067;&#x5B66;&#x3076;Three.js&#x5165;&#x9580; - &#x624B;&#x8EFD;&#x306B;WebGL&#x3092;&#x6271;&#x3048;&#x308B;3D&#x30E9;&#x30A4;&#x30D6;&#x30E9;&#x30EA; - ICS MEDIA</a> <a href="https://ics.media/entry/14771/">&#x6700;&#x65B0;&#x7248;&#x3067;&#x5B66;&#x3076;Three.js&#x5165;&#x9580; - &#x624B;&#x8EFD;&#x306B;WebGL&#x3092;&#x6271;&#x3048;&#x308B;3D&#x30E9;&#x30A4;&#x30D6;&#x30E9;&#x30EA; - ICS MEDIA</a></p> <h3 id="react-threefiber">react-three/fiber</h3> <p><a href="https://docs.pmnd.rs/react-three-fiber/getting-started/introduction">React Three Fiber Documentation</a> <a href="https://liginc.co.jp/587025">&#x9A5A;&#x304F;&#x307B;&#x3069;&#x7C21;&#x5358;&#x306B;3D&#x30B7;&#x30FC;&#x30F3;&#x3092;&#x69CB;&#x7BC9;&#xFF01;React Three Fiber&#x3092;&#x4F7F;&#x3063;&#x3066;&#x307F;&#x305F; | &#x682A;&#x5F0F;&#x4F1A;&#x793E;LIG(&#x30EA;&#x30B0;)&#xFF5C;DX&#x652F;&#x63F4;&#x30FB;&#x30B7;&#x30B9;&#x30C6;&#x30E0;&#x958B;&#x767A;&#x30FB;Web&#x5236;&#x4F5C;</a> <a href="https://qiita.com/FumioNonaka/items/be00620c14e8955ea869">React Three Fiber + TypeScript: 3&#x6B21;&#x5143;&#x7A7A;&#x9593;&#x3067;&#x7ACB;&#x65B9;&#x4F53;&#x3092;&#x56DE;&#x3057;&#x3066;&#x307F;&#x308B; - Qiita</a></p> <h3 id="react-threedrei">react-three/drei</h3> <p><a href="https://drei.pmnd.rs/?path=/docs/staging-accumulativeshadows--docs">@storybook/cli - Storybook</a> <a href="https://zenn.dev/ryotarohada/articles/e3322dcdf80b66">react-three-fiber/drei&#x3067;3D&#x30E2;&#x30C7;&#x30EB;&#x30D3;&#x30E5;&#x30FC;&#x3092;&#x624B;&#x8EFD;&#x306B;&#x5B9F;&#x88C5;&#x3059;&#x308B;</a></p> optim-simo 3Dスキャンしたモデルで簡単なゲーム制作! hatenablog://entry/820878482941279958 2023-08-24T10:00:00+09:00 2023-08-24T10:00:11+09:00 はじめに こんにちは、オプティムアルバイトスタッフの岡村です。 先日、OPTiM × C3の合同イベントとしてUnityのハンズオンを行ったのでそのハンズオン用の記事を書きました。 C3は九州工業大学のコンピューターを使ってモノづくりをするサークルです。 OPTiMのオフィスが九州工業大学前にあるということもあり、OPTiMにはアルバイトとして勤務しているC3メンバーがいます。 そこで九州工業大学前アルバイトスタッフとC3との共同でハンズオンイベントを行いました。 今回行ったイベントのほかの記事はこちら↓ tech-blog.optim.co.jp 事前準備の記事はこちら compositec… <h3 id="はじめに">はじめに</h3> <p>こんにちは、オプティムアルバイトスタッフの岡村です。 先日、OPTiM × C3の合同イベントとしてUnityのハンズオンを行ったのでそのハンズオン用の記事を書きました。</p> <p>C3は九州工業大学のコンピューターを使ってモノづくりをするサークルです。 OPTiMのオフィスが九州工業大学前にあるということもあり、OPTiMにはアルバイトとして勤務しているC3メンバーがいます。 そこで九州工業大学前アルバイトスタッフとC3との共同でハンズオンイベントを行いました。</p> <p>今回行ったイベントのほかの記事はこちら↓</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2023%2F07%2F06%2F100000" title="スキャンした点群データを3Dモデルデータに変換してみよう! - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2023/07/06/100000">tech-blog.optim.co.jp</a></cite></p> <p>事前準備の記事はこちら</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcompositecomputer.club%2Fnews%2F1KB6nHLVmYxB84FdikDFRq" title="お知らせ | C3×OPTiM合同ハンズオンイベント 〜スキャンした点群データをUnityや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://compositecomputer.club/news/1KB6nHLVmYxB84FdikDFRq">compositecomputer.club</a></cite></p> <h3 id="ハンズオン準備">ハンズオン準備</h3> <ul> <li>前提条件として、Unity 2021.3.25f1がインストールされているものとします。</li> <li><a href="https://github.com/optim-corp/techblog-c3optim-event-unity-point-cloud-sample">こちら</a>にアップロードされているUnityPackageをダウンロードしてください。ダウンロードはCodeのDownload ZIPからできます。ダウンロード後、zipファイルを解凍しておいてください。</li> <li>UnityHubのプロジェクトタブから新しいプロジェクトを選択。すべてのテンプレートの中の3D(コア)を選択して任意のプロジェクト名でプロジェクトを作成してください。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230620/20230620132348.png" width="1536" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></li> <li>プロジェクト作成後、画面上部のAssetsからImportPackage->Custom Package ...を選択して先ほどダウンロードした<code>C3Event.unitypackage</code>をインポートしてください。その時、importの選択画面が出てくると思いますがそのままで大丈夫です。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230620/20230620132355.png" width="611" height="632" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></li> <li>そのあと、シーンビュー下部のProjectウィンドウからScenesフォルダを開き、その中のC3_Eventをダブルクリックします。</li> <li>準備が完了するとこのような画面になっていると思います。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230620/20230620132401.png" width="1600" height="824" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></li> <li>そのあと、画面上部のScene、Gameと書かれたタブの中でGameを選択して、FreeAspectになっている部分をFullHDにしましょう。FullHDにした後はSceneに戻してください。</li> </ul> <h2 id="的あてゲームの作成">的あてゲームの作成</h2> <p>今回作成してもらうのは簡単な的あてゲームです。点群データを利用して作成したステージ上で制限時間内に動く的にボールをぶつけるとスコアが上がるといったようなものです。</p> <h3 id="1-マウスのFPS操作を作る">1. マウスのFPS操作を作る。</h3> <ul> <li>今回、カメラの動きはカメラのオブジェクトのtransformの回転を変更することで作成したいと思います。</li> <li>まずは、マウスを操作すると画面が動くシステムを作成します。ProjectウィンドウからScriptsフォルダを開いてください。</li> <li>その中で右クリックを押し、Create->C#Scriptを左クリックして選択してください。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230620/20230620132410.png" width="602" height="167" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></li> </ul> <p>すると、画像のような#の文字が書かれたファイルが作成されるので名前を<code>CameraMove</code>に変更してください。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230620/20230620132451.png" width="343" height="158" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>ファイル作成後、ダブルクリックでVisualStudioが起動されるので、そのスクリプトに次のプログラムをコピー&amp;ペーストしてください。</li> </ul> <pre class="code lang-cs" data-lang="cs" data-unlink><span class="synStatement">using</span> System.Collections; <span class="synStatement">using</span> System.Collections.Generic; <span class="synStatement">using</span> UnityEngine; <span class="synType">public</span> <span class="synType">class</span> <span class="synType">CameraMove </span><span class="synStatement">:</span> MonoBehaviour { [Header(<span class="synConstant">&quot;カメラ感度&quot;</span>)] <span class="synType">public</span> <span class="synType">float</span> Sensitivity <span class="synStatement">=</span> <span class="synConstant">2f</span>; <span class="synType">private</span> <span class="synType">float</span> _mouseX; <span class="synType">private</span> <span class="synType">float</span> _mouseY; <span class="synType">void</span> Start() { <span class="synComment">//マウスの移動量の初期化</span> _mouseX <span class="synStatement">=</span> <span class="synConstant">0</span>; _mouseY <span class="synStatement">=</span> <span class="synConstant">0</span>; } <span class="synType">void</span> Update() { <span class="synComment">//マウスの移動量の取得</span> <span class="synStatement">if</span>(Cursor.visible <span class="synStatement">==</span> <span class="synConstant">false</span>) <span class="synComment">//もしカーソルが見えなかったら(ゲームが操作可能だったら)</span> { _mouseX <span class="synStatement">=</span> Input.GetAxis(<span class="synConstant">&quot;Mouse X&quot;</span>) <span class="synStatement">*</span> Sensitivity; <span class="synComment">//マウスのX方向の移動量×カメラ感度</span> _mouseY <span class="synStatement">=</span> Input.GetAxis(<span class="synConstant">&quot;Mouse Y&quot;</span>) <span class="synStatement">*</span> Sensitivity; <span class="synComment">//マウスのy方向の移動量×カメラ感度</span> } <span class="synStatement">else</span> { <span class="synStatement">if</span> (Input.GetKeyDown(KeyCode.Mouse0)) <span class="synComment">//そうでないとき、左クリックでマウス固定</span> { Cursor.visible <span class="synStatement">=</span> <span class="synConstant">false</span>; Cursor.lockState <span class="synStatement">=</span> CursorLockMode.Locked; } } <span class="synComment">//Escキーでカーソル表示</span> <span class="synStatement">if</span> (Input.GetKeyDown(KeyCode.Escape)) { Cursor.visible <span class="synStatement">=</span> <span class="synConstant">true</span>; Cursor.lockState <span class="synStatement">=</span> CursorLockMode.None; } } <span class="synType">private</span> <span class="synType">void</span> FixedUpdate() { <span class="synComment">//カメラの回転</span> <span class="synStatement">this</span>.transform.eulerAngles <span class="synStatement">+=</span> <span class="synStatement">new</span> Vector3(<span class="synStatement">-</span>_mouseY, _mouseX, <span class="synConstant">0</span>); <span class="synComment">//カメラをマウスの移動量×カメラ感度の分だけ回転させる。</span> } } </pre> <ul> <li>プログラムを作成した後、ドラッグして左上のHierarchy内の<code>Main Camera</code>にドロップしてください。すると右のInspectorの表示が次のようになります。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230620/20230620132456.png" width="449" height="894" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>画面中央上部の再生マークを押すとゲームが始まり、画面を左クリックするとマウスが固定されてマウスでFPSのようなカメラの回転ができるようになります。また、操作をやめたいときは、Escキーを押すとマウスの固定が解除されます。また操作したいときはもう一度画面を左クリックしてください。</li> <li>このプログラムの構造としては、マウスの移動量を取得した後、それにあらかじめ指定したカメラの感度を掛け合わしたものを使ってカメラのゲームオブジェクトを回転させています。</li> <li>しかし、そのままではゲームを起動した瞬間ずっとマウスの移動に合わせて回転し続けるためバグを生みやすいです。そこで、カメラが動くか動かないかのトリガーとしてマウスが固定されているかどうかで分けています。</li> </ul> <h3 id="2-ボールを発射する">2. ボールを発射する。</h3> <ul> <li>まずは発射するボールを作成しましょう。ヒエラルキーの何もないところで右クリックして、3DObject->Sphereをクリックします。名前はわかりやすく<code>Ball</code>としましょう。作成したものが見当たらないときは右上のInspector上のTransformのPositionを(X : 0 Y : 3 Z : 2)あたりに設定すると目の前に現れます。</li> <li>見るとわかりますが発射するにはボールが大きすぎるのでTransformのScaleをxyzすべて0.2にしましょう。</li> <li>そのあと、ボールにタグをつけましょう。Inspector上部にTagの欄があり、<code>Untagged</code>となっていると思うので、それを<code>Ball</code>に変更します。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230620/20230620132503.png" width="437" height="137" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>ボールは物理エンジンを使って動かすので、その後Inspectorの下部にあるAdd ComponentからRididbodyを選択して物理エンジンをボールにもたせましょう。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230614/20230614185956.png" width="230" height="254" 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/o/optim-tech/20230614/20230614190002.gif" width="1200" height="619" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>さてボールをどのようにして発射するかといった点についてですが、いくつか方法はあるのですが今回はボールがゲーム内に登場した瞬間発射されるという風にしたいと思います。</p> <ul> <li>まず、ボールが発射されるスクリプトを作成しましょう。先ほどと同様にして、Scriptのフォルダ内で右クリック->Create->C# Scriptから<code>ShootBall</code>という名前で作成します。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230614/20230614190012.png" width="412" height="133" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>その後、スクリプトをダブルクリックしてVisualStudioで以下のように作成します。</li> </ul> <pre class="code lang-cs" data-lang="cs" data-unlink><span class="synStatement">using</span> System.Collections; <span class="synStatement">using</span> System.Collections.Generic; <span class="synStatement">using</span> UnityEngine; <span class="synType">public</span> <span class="synType">class</span> <span class="synType">ShootBall </span><span class="synStatement">:</span> MonoBehaviour { [Header(<span class="synConstant">&quot;ボールのスピード&quot;</span>)][SerializeField] <span class="synType">private</span> <span class="synType">float</span> _speed <span class="synStatement">=</span> <span class="synConstant">20</span>; <span class="synComment">//ボールの発射速度</span> <span class="synType">private</span> Transform _camera; <span class="synComment">//プレイヤーのカメラ</span> <span class="synType">private</span> Rigidbody _rb; <span class="synComment">//ボールのRigidbody</span> <span class="synType">private</span> <span class="synType">void</span> Awake() { _camera <span class="synStatement">=</span> GameObject.Find(<span class="synConstant">&quot;Main Camera&quot;</span>).transform; <span class="synComment">//カメラを取得</span> _rb <span class="synStatement">=</span> GetComponent&lt;Rigidbody&gt;(); <span class="synComment">//ボールのRigidbodyを取得</span> Vector3 _direction <span class="synStatement">=</span> _camera.forward; <span class="synComment">//カメラの正面を_directionに代入</span> _rb.AddForce(_direction <span class="synStatement">*</span> _speed, ForceMode.Impulse); <span class="synComment">//_direcitonに向かってボールを発射する。</span> Invoke(<span class="synConstant">&quot;Destroy&quot;</span>, <span class="synConstant">1.5f</span>); <span class="synComment">//1.5秒後ゲームオブジェクトを削除</span> } <span class="synType">private</span> <span class="synType">void</span> Destroy() { GameObject.Destroy(<span class="synStatement">this</span>.gameObject); } } </pre> <p>このプログラムのミソは<code>_rb.AddForce(_direction * _speed, ForceMode.Impulse);</code>です。 これは先ほど追加したRigidbodyの中のAddForce()という関数を使用しています。これは、その名の通り「力を加える」という関数で、Unity内部の物理エンジンを使用して物体に瞬発的な力を加えています。これはさまざまなゲームで重要となってきますので覚えておくと良いでしょう。 また、覚えておくと良い関数としてInvoke関数があります。これはn秒後にある関数を実行するというもので、今回はボールが出現してから1.5秒後にDestroy関数を起こすと書かれています。よってボールは発射されてから1.5秒後に自動的に削除されます。</p> <p>さて、ボールを発射するスクリプトが作成されたので、これをアタッチして動かしてみましょう。 アタッチの方法は作成したスクリプトをドラッグして、作成したBallにドロップしてみてください。</p> <p>また、折角なのでボールの色を黄色にしようと思います。Assetsフォルダ内のMaterialフォルダを参照してみてください。そこにBallと書かれた黄色い丸のようなものがあるのがわかると思います。これはマテリアルと呼ばれ、3Dモデルにおける色の部分にあたります。これをドラッグして再びBallにドロップしてみましょう。 画像のようになっていれば大丈夫です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230614/20230614190018.png" width="449" height="573" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>これでゲームを実行した瞬間ボールが発射されるのがわかるかと思います。</p> <h3 id="3-ボールのプレハブ化">3. ボールのプレハブ化</h3> <p>ここではボールをプレハブというものに変換させます。まず、プレハブについて説明しましょう。プレハブとはUnity上で作成したゲームオブジェクトを複製したり、別のシーンで使いまわしたりしたいと考えるとき、簡単に複製できるように保存しておくシステムのことです。例えば、今回発射するボールはマウス左クリックで無限に発射させたいですね。しかし、今のままではボールは1.5秒で消えてしまいますし、発射も一度限りです。これを何個もプレイヤーの手元で作成出来たらたくさんボールを放つことができゲームとして扱いやすくなります。</p> <ul> <li>それではまず、ProjectウィンドウからAssets->Prefabsを開いてください。</li> <li>そのあと、Hierarchy上で先ほど作成したBallをドラッグしてProjectウィンドウのPrefabsフォルダ内にドロップしてください。</li> <li>すると、Hierarchy上では青色になって、Prefabsフォルダに<code>Ball</code>というものが作成されていると思います。これでプレハブ化は成功です。</li> <li>ただし、このままでは配置したままの位置が保存されているので作成したプレハブをクリックして右に表示されるInspectorの中からPositionをx,y,zすべて0にしましょう。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230614/20230614190030.png" width="1600" height="824" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>また、すでにHierarchy上にあるボールは不要なので右クリックからDeleteで削除してしまいましょう。</li> </ul> <h3 id="4-左クリックでボールを発射する">4. 左クリックでボールを発射する。</h3> <p>次はボールを左クリックで発射するようにしましょう。スクリプト自体は簡単で左クリックを感知してボールプレハブをゲーム上に複製するだけです。</p> <ul> <li>再びScriptフォルダに移動して、その中で右クリック->Create->C# scriptよりC#のスクリプトを作成します。その時、名前はPlayerManagerとしてください。</li> <li>その後、スクリプトをダブルクリックしてVisualStudioを起動して、以下のコードを記述してください。</li> </ul> <pre class="code lang-cs" data-lang="cs" data-unlink><span class="synStatement">using</span> System.Collections; <span class="synStatement">using</span> System.Collections.Generic; <span class="synStatement">using</span> UnityEngine; <span class="synType">public</span> <span class="synType">class</span> <span class="synType">PlayerManager </span><span class="synStatement">:</span> MonoBehaviour { [SerializeField] <span class="synType">private</span> GameObject _ballPrefab; <span class="synComment">//ボールのプレハブ</span> <span class="synType">void</span> Update() { <span class="synStatement">if</span> (Input.GetKeyDown(KeyCode.Mouse0) <span class="synStatement">&amp;&amp;</span> Cursor.visible <span class="synStatement">==</span> <span class="synConstant">false</span>) { Instantiate(_ballPrefab, transform.position, Quaternion.identity); <span class="synComment">//ゲームオブジェクトの作成</span> } } } </pre> <p>簡単に解説すると、<code>[SerializeField]</code>を用いてボールのプレハブをインスペクター上から参照して、Update関数ないで常に左クリックが押されているかどうかと、カーソルが非表示になっていて操作可能になっているかどうかを確認しておいて、もし操作可能かつ、マウス左クリックが押されている場合は、Instantiate関数を用いて、Ballのプレハブを、<code>transform.position</code>(右手のボールの位置)で、特に回転させずに(Quaternion.identityで)作成します。すると、ボールは先ほどの<code>ShootBall.cs</code>スクリプトで勝手に飛んでいくという仕組みです。</p> <p><code>Input.GetKeyDown(KeyCode.任意のキーコード)</code>というコードはとても重要でキーボードやマウスなどあらゆる入力を検知できるので様々なゲームに応用が利きます。ぜひ覚えておいてください。</p> <ul> <li>スクリプトが作成出来たらそれをドラッグして今度はHierarchy上の<code>HandBall</code>にアタッチしましょう。画面に映っていない人はドラッグする前に<code>Main Camera</code>のトグルを開いておきましょう。</li> <li>アタッチ出来たら、その中のPlayerManagerの中のBallPrefabが空白となっていると思うので、ProjectウィンドウからPrefabsを開いてその中のBallをドラッグして先ほどの部分にドロップしましょう。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230614/20230614190039.png" width="445" height="871" 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/o/optim-tech/20230620/20230620132712.gif" width="1200" height="620" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ただし、このままだとボールがマップを貫通してしまいます。なぜこうなるかというと、3Dモデルに対応した当たり判定はそのままだと不安定だからです。ゲーム内で物理エンジンが処理を行われる前に、物体が貫通してしまい、当たり判定が正常に行われないことが多々あります。これを改善するためにRigidBodyの設定を見直しましょう。</p> <ul> <li>Prefabsフォルダを開いて、Ballプレハブを選択してください。</li> <li>RigidBodyの<code>Collision Detection</code>を選んで<code>Discrete</code>から<code>Continuous Speculative</code>に変更してください。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230614/20230614190024.png" width="438" height="299" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><code>Collision Detection</code>は衝突の検知方法を変更するオプションでこれを離散的から連続投機的に変更することで、一定のタイミングでしか検知しなかったものを現在の速度に基づいて検知させることで正常に衝突を検知させることができます。</p> <p>これで基本となる部分は終了です。</p> <h3 id="5-的の編集">5. 的の編集</h3> <p>ここからは余裕のある方向けですがぜひ挑戦してみてください。今回のゲームの的を少し改造してみましょう。</p> <p>今回使用している的は<code>TargetA</code>としてHierarchy上に描画されていると思います。これにはTargetとTargetMove1というスクリプトで制御しています。Targetには当たった時のスコアの加算が、TargetMove1には的の移動について記述しています。スコアを変更したいときはTargetAをクリックすると表示されるInspector上で加算されるスコアを変更できるようにしています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230620/20230620132753.png" width="443" height="70" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>好きな値にしてみてください。 では、的の移動はどうすればよいでしょうか?今回の記述は次のようになっています。</p> <pre class="code lang-cs" data-lang="cs" data-unlink><span class="synStatement">using</span> System.Collections; <span class="synStatement">using</span> System.Collections.Generic; <span class="synStatement">using</span> UnityEngine; <span class="synType">public</span> <span class="synType">class</span> <span class="synType">TargetMove1 </span><span class="synStatement">:</span> MonoBehaviour { [SerializeField] <span class="synType">private</span> <span class="synType">float</span> _speed; <span class="synComment">//移動スピード</span> [SerializeField] <span class="synType">private</span> <span class="synType">float</span> _range; <span class="synComment">//移動幅</span> <span class="synType">private</span> Vector3 _position; <span class="synComment">//初期位置</span> <span class="synType">void</span> Start() { _position <span class="synStatement">=</span> transform.position; <span class="synComment">//初期位置の取得</span> } <span class="synComment">// Update is called once per frame</span> <span class="synType">void</span> Update() { transform.position <span class="synStatement">=</span> <span class="synStatement">new</span> Vector3(_range <span class="synStatement">*</span> Mathf.Sin(Time.time <span class="synStatement">*</span> _speed) <span class="synStatement">+</span> _position.x, _position.y, _position.z); <span class="synComment">//振幅運動</span> } } </pre> <p>解説すると、まず変数として的の速度を<code>_speed</code>、移動幅を<code>_range</code>としています。 ゲームが開始されたとき現在の位置を取得して、そこからx座標の位置を<code>_range * Mathf.Sin(Time.time * _speed)</code>と記述して移動させています。この<code>Mathf.Sin()</code>はC#において数学のSin関数を利用しています。経過時間をSinの内部に入れることで、時間の長さに応じたsinの値になります。つまり、GameObjectを簡単に周期的な動きをさせることができるのです。</p> <p>例えば今回はnew Vector3の中のx軸部分にSin関数を入れていますが、これをy軸、z軸にもすることができますね。 また、次のようにすると、周期的に大きさが変化します。transform.localScaleはGameObjectの大きさを変化させられます。</p> <p><code>transform.localScale = (Mathf.Sin(2 * Mathf.PI * Time.time) + 1) * 0.5f * _maxScale;</code></p> <p>先ほどのコードに追記すると</p> <pre class="code lang-cs" data-lang="cs" data-unlink><span class="synStatement">using</span> System.Collections; <span class="synStatement">using</span> System.Collections.Generic; <span class="synStatement">using</span> UnityEngine; <span class="synType">public</span> <span class="synType">class</span> <span class="synType">TargetMove1 </span><span class="synStatement">:</span> MonoBehaviour { [SerializeField] <span class="synType">private</span> <span class="synType">float</span> _speed; <span class="synComment">//移動スピード</span> [SerializeField] <span class="synType">private</span> <span class="synType">float</span> _range; <span class="synComment">//移動幅</span> <span class="synType">private</span> Vector3 _position; <span class="synComment">//初期位置</span> <span class="synType">private</span> Vector3 _maxScale <span class="synStatement">=</span> <span class="synStatement">new</span> Vector3(<span class="synConstant">3</span>, <span class="synConstant">3</span>, <span class="synConstant">0.0967f</span>); <span class="synComment">//最大の大きさ</span> <span class="synType">void</span> Start() { _position <span class="synStatement">=</span> transform.position; <span class="synComment">//初期位置の取得</span> } <span class="synComment">// Update is called once per frame</span> <span class="synType">void</span> Update() { transform.position <span class="synStatement">=</span> <span class="synStatement">new</span> Vector3(_range <span class="synStatement">*</span> Mathf.Sin(Time.time <span class="synStatement">*</span> _speed) <span class="synStatement">+</span> _position.x, _position.y, _position.z); <span class="synComment">//振幅運動</span> transform.localScale <span class="synStatement">=</span> (Mathf.Sin(<span class="synConstant">2</span> <span class="synStatement">*</span> Mathf.PI <span class="synStatement">*</span> Time.time) <span class="synStatement">+</span> <span class="synConstant">1</span>) <span class="synStatement">*</span> <span class="synConstant">0.5f</span> <span class="synStatement">*</span> _maxScale; } } </pre> <p>このようにして、いろいろ試してみてほしいです。こんな風に動かしたいと思ったら軽く検索をかけるだけでも出てくると思うのでいろいろ試して、ゲームとして面白くしてみましょう。プレハブを使って的の数を増やしてみたり、的の位置や大きさをInspectorから変えてみたりしてそれに応じて得点をつけたりするのもいいと思います。</p> <h2 id="終わりに">終わりに</h2> <p> 最後になりましたが、今回の講座でUnityで的あてゲームの作り方がわかっていただければ幸いです。今回のシステムはFPSゲームなどにも通じる所があると思うので、3Dゲームの基本を学ぶのにはよかったのではないかと思います。また、点群データを用いることでリアルなマップを作成することができます。点群データは、フリーで公開されているものネット上に公開されているので興味のある方はぜひお試しください。</p> <hr /> <p>OPTiMでは運用改善やDevOpsに興味があるエンジニア、新卒からどんどんチャレンジしていきたいエンジニア学生を随時募集しております。少しでもご興味のある方はこちらも合わせてご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-okamura IT企業で社内デザイン勉強会を実施してみた💊 hatenablog://entry/820878482952434831 2023-07-27T10:00:00+09:00 2023-07-27T10:00:01+09:00 こんにちは プロモーション・デザインユニット(以下プロモ・デザインU)の清水です。3年ぶり2回目の投稿となります。 前回の記事はこちら tech-blog.optim.co.jp はじめに 非デザイナー:デザイナー=98:2の弊社で、デザイン初心者向けの勉強会を立ち上げました。 今回は実施のきっかけ・やってみての感想をつらつら書きたいとおもいます✍️ <h2 id="こんにちは">こんにちは</h2> <p>プロモーション・デザインユニット(以下プロモ・デザインU)の清水です。3年ぶり2回目の投稿となります。</p> <p>前回の記事はこちら</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2020%2F12%2F02%2F100000" title="おかげさまでオプティム創立20周年🎉 記念ロゴができるまで - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2020/12/02/100000">tech-blog.optim.co.jp</a></cite></p> <h2 id="はじめに">はじめに</h2> <p>非デザイナー:デザイナー=98:2の弊社で、デザイン初心者向けの勉強会を立ち上げました。</p> <p>今回は実施のきっかけ・やってみての感想をつらつら書きたいとおもいます✍️</p> <ul class="table-of-contents"> <li><a href="#こんにちは">こんにちは</a></li> <li><a href="#はじめに">はじめに</a></li> <li><a href="#デザインサプリとは">デザインサプリとは?</a><ul> <li><a href="#一言で言うと">一言で言うと</a></li> <li><a href="#立ち上げたきっかけターゲットの切り分け">立ち上げたきっかけ①ターゲットの切り分け</a></li> <li><a href="#立ち上げたきっかけテコ入れチーム内練習">立ち上げたきっかけ②テコ入れ&amp;チーム内練習</a></li> </ul> </li> <li><a href="#立ち上げの準備">立ち上げの準備</a><ul> <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> </li> <li><a href="#実際にやってみた">実際にやってみた</a></li> <li><a href="#振り返ってみた">振り返ってみた</a></li> <li><a href="#みんなの感想と反省">みんなの感想と反省</a><ul> <li><a href="#良かった点">😀良かった点</a></li> <li><a href="#改善したい点">😣改善したい点</a></li> <li><a href="#さっそく改善しようと思っている点">🔨さっそく改善しようと思っている点</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h2 id="デザインサプリとは">デザインサプリとは?</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230724/20230724182354.jpg" width="641" height="361" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="一言で言うと">一言で言うと</h3> <p>デザイナー以外の人がデザインについての知識をもっと気軽に学ぼう!ということから生まれた初心者向け勉強会です。 名前の由来は「サプリメントのように気軽にデザイン知識を皆さんにプラスしたい」 という気持ちが込められています💊</p> <h3 id="立ち上げたきっかけターゲットの切り分け">立ち上げたきっかけ①ターゲットの切り分け</h3> <p>もともとはエンジニアとデザイナーが情報を共有しあう「デザイン&開発情報共有会」を社内で不定期開催していました。</p> <p>開催を重ねていくにつれ、意識の高いいわゆる”デザイン思考やったるでバリバリガチ勢”と デザインの基本を知りたいけど内容についていけない…という”「デザイン」全然わからないライト勢”に分かれていってしまっていました。</p> <p>そこで、目的を整理して勉強会を分断し、”ライト勢”向けに講義形式での勉強会を実施する運びとなりました。</p> <h3 id="立ち上げたきっかけテコ入れチーム内練習">立ち上げたきっかけ②テコ入れ&amp;チーム内練習</h3> <p>もうちょっと詳細に言うと、「任意で実施していた勉強会の運営メンバーにも中弛みがでてきた」というのも要因の1つであります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230724/20230724182400.jpg" width="800" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>隔週開催するにあたってテーマを決めるのも、そのテーマに沿って講義を用意するのもなかなか大変…🥺 業務逼迫している場合はなおのこと会議にも参加できない……今回はスキップ……という事態が相次いでいました。</p> <p>そのため一旦リフレッシュして、まずはプロモ・デザインUが主体になって会を継続実施しよう! どうせなら今まで発表したことがない人が発表を練習する場にもしよう! という2つの目標のもと発足を決意しました。</p> <h2 id="立ち上げの準備">立ち上げの準備</h2> <p>前述のデザイン&開発共有会はエンジニアチームが発足したものだったので、実はプロモ・デザインU主導で勉強会を立ち上げたことがありませんでした。 とりあえず必要なものなんだっけ?というのをリストアップしつつ、 チーム内メンバーに確認してもらいながら、見様見真似で手探りな状態で始動しました。</p> <h3 id="ロゴの作成">💊ロゴの作成</h3> <p>全社的に勉強会を広めるために、まずはシンボルとなるロゴの作成を行いました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230724/20230724182403.jpg" width="459" height="107" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>コミュニケーションの吹き出しのシンボルを左右に配置し、プロモ・デザインUと参加者が交わる様子を表しました。 「サプリ」とあるようにカプセルのイメージの中には、OPTiMの「O」とDesignの「D」も読み取れるようになっています。 カラーはオプティムのコーポレートカラーのブルーと、プロモ・デザインユニットが好んで使っている赤色をモチーフにしています。</p> <p>このロゴからさらに派生させて、アイキャッチに使える画像もいくつか作成しました。 ビジュアルからも色んな人に覚えてもらえるような会を目指しました。</p> <h3 id="テーマの決定">💊テーマの決定</h3> <p>勉強会をやるにあたって毎回テーマを決める必要があります。そのため、事前に ・なるべく用意に時間をかけない ・全社的に興味が沸きそうな話題 という2点を踏まえて、初回は誰もが通る道、「パワーポイント」の講義内容に決まりました。 プロモ・デザインUでは、社内のデザインに関するあれこれを担当しているので、 大きくカテゴリ分けして「Web」「アプリ」「ビジュアル」の3つで交代に実施することも決定しました。</p> <h3 id="会場の準備">💊会場の準備</h3> <p>オプティムには「コミュニケーションポータル」というコミュニケーションを主体として利用できるバーラウンジを携えたスペースがあります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230724/20230724182405.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230724/20230724182415.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ちょうど発足の半年前ほどに開設されたばかりだったので、せっかくなら利用してみようという気持ちで会場に選定しました。</p> <p>また、弊社ではリモート勤務・出社勤務が併用されているため、 開催当日に出社の予定がない人でも参加していただけるようにTeams会議でリモートの方にも同時配信を行うことを決めました。 リモート開催することで「ちょっとだけ覗いてみようかな…」という人にも気軽に見てもらえる会を目指すと同時に、 当日参加できなかった人へ後日配信できるように録画しながらの開催を決定しました。</p> <h3 id="全社アナウンス">💊全社アナウンス</h3> <p>対象者は「職種問わずデザイン初心者の方」だったので、別拠点を含めた全社に開催予告メールを送信しました。 社内への発信も慣れていないメンバーが多いので、勉強会に参加できなくても開催者の名前だけでも目にとめていってくださいね!という気持ちも含んでいます。</p> <h3 id="専用ページグループの準備">💊専用ページ・グループの準備</h3> <p>当日参加できなかった人への案内ページや、定期的に勉強会に参加したい!という人に情報を届けるための 専用Teamsグループ・SharePointページを準備しました。 毎回毎回しつこく参加URLを貼ることで定期宣伝を繰り返しています。</p> <h2 id="実際にやってみた">実際にやってみた</h2> <p>2023年1月に初回開催をし、半年間は以下のタイトルで毎月最終水曜日定時後・月1ペース開催しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230724/20230724182424.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <table> <thead> <tr> <th> 開催日 </th> <th> タイトル </th> <th> 内容 </th> </tr> </thead> <tbody> <tr> <td> 1月</td> <td> 見やすくなれ👊パワポ力技改善フロー </td> <td> PPTについて </td> </tr> <tr> <td> 2月 </td> <td> コミュニケーション強化👊miroの使い方講座 </td> <td> Miroについて </td> </tr> <tr> <td> 3月 </td> <td> アジャイル改修の成果はいかに? Web改善の事例紹介 </td> <td> Web改善事例について </td> </tr> <tr> <td> 4月 </td> <td> ドドド基本🔰デザイン四大原則 </td> <td> デザイン基礎について </td> </tr> <tr> <td> 5月 </td> <td> ワイヤーフレーム作成のお供にどうぞ。誰でも簡単!UIデザイン </td> <td> UI設計について </td> </tr> <tr> <td> 6月 </td> <td> いざと言うときに慌てない!フォトストック含む著作権あるある </td> <td> 画像権利について </td> </tr> </tbody> </table> <p>「まずは毎月やってみる!」が小さい目標だったので、そこはなんとか達成できたのかな〜と感じています。</p> <p>参加者はオンラインメインでの参加が多く、出社して参加くださる方はお菓子を食べながら視聴いただくなどしていました🙏 それぞれの回で、約20名程度ご参加いただいていました。 定時時間外の任意参加勉強会にしてはまずまずな参加率だったと思います、感謝!</p> <h2 id="振り返ってみた">振り返ってみた</h2> <p>何事もやりっぱなしはよくないですよね! チーム内の個人個人でよかったな〜と思っていることや、ここは改善したいな?と思っていることがないか、 Miroで事前に意見を出してもらう→みんなでMiroを見ながら具体的な改善案・アクションプランを決めました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230724/20230724182433.jpg" width="400" height="400" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="みんなの感想と反省">みんなの感想と反省</h2> <h3 id="良かった点">😀良かった点</h3> <pre><code>• 話題作りになる ○ 「発信する」活動をしていること自体がプロモ・デザインUの印象に繋がっており、チーム外の人から度々声かけていただきました ○ 「プロモ・デザインUです」と自己紹介したときに「デザインサプリみました〜」と会話のきっかけになることが増えました ○ デザインに興味ある!って人を誘うのにちょうどいいなと感じています • 自分自身のためになる ○ 資料をまとめることで知識の再確認や気づきがあったので、自身のためにもなりました ○ デザインの基礎知識を振り返れるし、チームメンバーの考え方を知れて、学びになりました ○ 業務で登壇するような機会がないので、訓練の場としていい機会だと思います </code></pre> <h3 id="改善したい点">😣改善したい点</h3> <pre><code>• 開催時間が悩ましい ○ 時短勤務なので定時後イベントつらい…という声がありました ○ 1時間、なかなか喋れなくて30分で終わってしまうこともありました • 準備時間が足りない ○ 1ヶ月ある!とおもってもあっという間で、資料を用意するのに時間がかかってしまいました ○ 設営を初めてやると、不明点にぶつかったときに慌ててしまったという声もありました • もっと参加してほしい🥺 ○ なんだかんだ徐々に人数が減ってしまうもの…というのを実感しました </code></pre> <h3 id="さっそく改善しようと思っている点">🔨さっそく改善しようと思っている点</h3> <pre><code>• BGMの検討 ○ 開始前に無音で寂しい&喋っている時に反応がないと登壇者が辛いため、雰囲気作りのために検討しています。 ○ フリーの音楽を採用して流そうと思います。 • 開催を30分に凝縮 ○ 1時間だと時間がもたない・参加が難しい声を受けたため改善します ○ 1時間分の資料を作成するのも大変のため。ぎゅっと要点をまとめたいと思います • チーム内での読み合わせ会を実施 ○ 登壇する側も初めてのことが多いので実際にやってみて、流れをしっかり確認したいと思います • 定時後ではなく昼休みに実施 ○ プロモ・デザインUは時短勤務の方も多いため、これまでは調整いただいていました ○ ランチを食べながら見てもらえるような、気軽に参加できる機会を増やしたいと思います • 「ターゲット」「ゴール」をのサマリー画像を作成 ○ 勉強会の内容がわからないという声を耳にしました ○ 具体的にどういった職種の人におすすめか?を一言でまとめて見やすく画像にしたいと思います </code></pre> <h2 id="まとめ">まとめ</h2> <p>実際に勉強会を発足してみて、会社全体にチームの活動内容や基本的な知識の共有を行えるだけでなく、運営するチームメンバーにもにいい影響があるなととても感じました! まだまだ手探りな部分も多く、新しく参加するメンバーが増えていないところは課題なので、引き続き改善に努めていきたいと思います。</p> <p>社内では他にもさまざまな勉強会が実施されています。 デザインの勉強会に限らず、みんなで知識を共有し合える環境の取り組みに貢献していきたいですね💪 まずはデザサプの社内認知度向上・参加者の平均数増加を目標に引き続き頑張ろうと思います!</p> <h2 id="おわりに">おわりに</h2> <p>オプティムでは、エンジニアだけではなくプロモ・デザインUで一緒に働いてくださるメンバーも探しています。 プロモ・デザインUでは、UI/UXデザインやブランディング、Web制作、マーケティングなどオプティム製品にまつわる様々なデザインのお仕事をしています。 UI/UX、ブランディング、Webプロモーションなどに興味がある方、ぜひご応募お待ちしています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-shimizu nginx+php-fpmからLaravel Octaneに変えてみた hatenablog://entry/820878482947570203 2023-07-24T10:00:00+09:00 2023-07-24T10:00:02+09:00 こんにちは。元AI・IoTサービス開発部 から変わり、DXビジネス開発部となりました青木です。 Laravel Octaneなるものをドキュメントで見つけてからいつか手を出そうと思い時間が経ってしましました。 今回はOctaneの導入と、パフォーマンス改善結果などにも触れていきたいと思います。 Laravel Octaneとは laravel.com Laravel Octane supercharges your application's performance by serving your application using high-powered application serve… <p>こんにちは。元AI・IoTサービス開発部 から変わり、DXビジネス開発部となりました青木です。</p> <p>Laravel Octaneなるものをドキュメントで見つけてからいつか手を出そうと思い時間が経ってしましました。 今回はOctaneの導入と、パフォーマンス改善結果などにも触れていきたいと思います。</p> <h2 id="Laravel-Octaneとは">Laravel Octaneとは</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Flaravel.com%2Fdocs%2F10.x%2Foctane" title="Laravel - The PHP Framework For Web Artisans" 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://laravel.com/docs/10.x/octane">laravel.com</a></cite></p> <blockquote><p>Laravel Octane supercharges your application's performance by serving your application using high-powered application servers, including Open Swoole, Swoole, and RoadRunner.</p></blockquote> <p><a href="https://openswoole.com/">Open Swoole</a> か <a href="https://roadrunner.dev/">RoadRunner</a>の仕組みの上にLaravelアプリケーションの起動が出来るようになる仕組みです。</p> <p>今回は<a href="https://openswoole.com/">Open Swoole</a>で開発を行ったため<a href="https://openswoole.com/">Open Swoole</a>中心に語ります。</p> <p>従来では <a href="https://www.php.net/manual/ja/install.fpm.php">PHP-FPM</a> + <a href="https://nginx.org/en/">nginx</a> の構成が一般的でしたが、<a href="https://openswoole.com/">Open Swoole</a>では予めプロセスを立ち上げておき、そのプロセスに対して非同期で処理を行う事で高速に処理が出来るようになる仕組みを持っており、リクエスト毎にプロセスを立ち上げていた従来の方法よりも高速に処理することが出来るようになります。</p> <p>が、従来はプロセスを使い捨てしていた関係上メモリリークなどが起こりづらい状況が作れており、ある意味安全な状態だったものの<a href="https://openswoole.com/">Open Swoole</a>に置き換えることでそのあたりも考慮する必要が出てきました。<a href="https://openswoole.com/">Open Swoole</a>を利用する際に詰まる部分なので注意してください。</p> <h2 id="Laravel-Octaneの導入">Laravel Octaneの導入</h2> <p>既にLaravelでアプリケーションを開発している方も、新規で開発される方も以下のコマンドでサクッと導入出来ます。</p> <pre class="code" data-lang="" data-unlink>composer require laravel/octane php artisan octane:install</pre> <p>開発時は普段通り <code>php artisan serve</code> で立てられますが、Laravel Octane独自の機能を利用している場合は開発時もOctane経由で起動する必要があります。</p> <pre class="code" data-lang="" data-unlink>php artisan octane:start</pre> <p>このコマンドは、サーバアプリケーションを提供する場合にも利用するコマンドとなります。</p> <blockquote><p><code>php artisan serve</code>で起動した場合は開発時のみの利用にとどめてください</p></blockquote> <p><code>octane:start</code> ではローカルにOpenSwooleを入れるなどの対応が必要で少々面倒なのでDockerで実行します。</p> <h3 id="Laravel-OctaneをDockerで実行する">Laravel OctaneをDockerで実行する</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fexaco%2Flaravel-octane-dockerfile" title="GitHub - exaco/laravel-octane-dockerfile: Production-ready Dockerfile for Laravel Octane powered web services and microservices. Done right." 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://github.com/exaco/laravel-octane-dockerfile">github.com</a></cite></p> <p>有志の方がご提供頂いているDockerfileを利用してみます。</p> <p>Dockerfileをプロジェクトルートに入れ以下のコマンドで起動します。</p> <pre class="code" data-lang="" data-unlink>docker build -t octane_test . docker run --name octane_test -p 8000:8000 --env-file .env octane_test</pre> <p>必要に応じてVolumeMountを実施してください</p> <h2 id="Octaneに変えた効果">Octaneに変えた効果</h2> <h3 id="良かった点">良かった点</h3> <ul> <li>レスポンスが早くなった</li> <li>リソース消費量が減った</li> <li>PHP-FPMとnginxの両方を管理しなくて良くなった</li> </ul> <h4 id="レスポンスが早くなった-リソース消費量が減った">レスポンスが早くなった, リソース消費量が減った</h4> <p>マシンスペックにもよりますが、単純にレスポンスを返却するだけの簡単なエンドポイントであれば概ね <code>10ms ~ 50ms</code> 程度で返却されるようになりました。 nginx+php-fpmでは同スペックで <code>100ms ~ 200ms</code> 程度でしたので相当早くなっていることがわかります。</p> <p>レスポンスが早いこととリソース消費量が減ったのは相関関係にあり、ある程度スペックを絞ってもそれなりのレスポンスを返却してくれるようになりました。</p> <p>開発のみの環境ですが、k8s上のリソース指定は以下のものでも十分に動作しています。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synIdentifier">resources</span><span class="synSpecial">:</span> <span class="synIdentifier">limits</span><span class="synSpecial">:</span> <span class="synIdentifier">cpu</span><span class="synSpecial">:</span> 100m <span class="synIdentifier">memory</span><span class="synSpecial">:</span> 128Mi <span class="synIdentifier">requests</span><span class="synSpecial">:</span> <span class="synIdentifier">cpu</span><span class="synSpecial">:</span> 50m <span class="synIdentifier">memory</span><span class="synSpecial">:</span> 64Mi </pre> <h4 id="PHP-FPMとnginxの両方を管理しなくて良くなった">PHP-FPMとnginxの両方を管理しなくて良くなった</h4> <p>タイトルの通りで、k8sでも複数のPodやServiceに分かれてデプロイされている関係上リソース管理が2倍になっていたところ、上記記載のDockerImageに変更する事で管理を一つにすることが出来るので便利です。</p> <p>(nginx ingress を使うことでそのあたりは避けられる可能性もありますが....)</p> <h3 id="気になった点">気になった点</h3> <ul> <li>メモリリークしている</li> </ul> <h4 id="メモリリークしている">メモリリークしている</h4> <p>開発時に利用している閉鎖環境でしばらく様子をうかがっていたところ、メモリーリークを発見しました。 k8sにデプロイしているPodのメモリをDatadogにて集計し、確認をしているのですが画像のように確かにメモリリークされています。</p> <p><a href="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230713/20230713154954.png" class="http-image"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230713/20230713154954.png" class="http-image" alt="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230713/20230713154954.png"></a></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Flaravel.com%2Fdocs%2F10.x%2Foctane%23managing-memory-leaks" title="Laravel - The PHP Framework For Web Artisans" 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://laravel.com/docs/10.x/octane#managing-memory-leaks">laravel.com</a></cite></p> <p>公式ドキュメントでも言及されていますが、普段通りのPHPの使い方を気にせず利用していると意図しないメモリーリークが発生する可能性があります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Flaravel%2Foctane%2Fissues%2F481" title="Memory leak when using Http facade · Issue #481 · laravel/octane" 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://github.com/laravel/octane/issues/481">github.com</a></cite></p> <p>こちらのようにLaravelでは非常に利用されるHttpファサードを普通に使うとメモリリークしてしまいます。 今まで普通に利用していたファサードなどを使う場合は簡単にメモリリークされてしまうので要注意です。</p> <p>GuzzleClientを使うことで解消されるようですが、既存のLaravelサービスをOctaneに変更する等の場合は必ず影響調査を実施した方が良さそうです。</p> <p>多少のメモリーリークであれば以下の <code>--max-requests</code> の値を変更するなどしてWorkerの再起動設定を実施すれば通常的に運用する事は可能ですが、 不慮の事故を避けるため解消しておいた方が良いかと思われます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fexaco%2Flaravel-octane-dockerfile%2Fblob%2Fmain%2Fdeployment%2Foctane%2Fsupervisord.app.conf%23L9" title="laravel-octane-dockerfile/deployment/octane/supervisord.app.conf at main · exaco/laravel-octane-dockerfile" 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://github.com/exaco/laravel-octane-dockerfile/blob/main/deployment/octane/supervisord.app.conf#L9">github.com</a></cite></p> <p>本件ではHttpファサードを使わないようにしてみたところ以下のようなメトリクスとなり症状は改善されました。</p> <p><a href="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230713/20230713154957.png" class="http-image"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230713/20230713154957.png" class="http-image" alt="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230713/20230713154957.png"></a></p> <h2 id="さいごに">さいごに</h2> <p>Laravel Octane自体は公式ドキュメントにも記載されているようにアプリケーション高速化のデファクトになっていくと思いますので、なるべくキャッチアップしていこうと思います。</p> <hr /> <p>オプティムでは、一緒に働く仲間を募集しています。興味のある方は、こちらをご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-yusuke-aoki スキャンした点群データを3Dモデルデータに変換してみよう! hatenablog://entry/820878482941579509 2023-07-06T10:00:00+09:00 2023-07-06T10:00:04+09:00 はじめに こんにちは!テックセンター飯塚のアルバイトスタッフの吉田です。 先日、アルバイトスタッフ主導でハンズオンイベントを開催いたしました! 今回の内容は、「スキャンした点群データをUnityやWebアプリで扱おう!」でした。 具体的には、スキャンした点群データをweb上で表示したり、Unity上でゲームのマップとして取り込むというものになります。 この記事では、点群データのスキャン及び、点群データ(las)をwebゃUnity上で扱える形(obj)に変換する方法を解説したいと思います。 点群データについて 点群データとは3次元座標(x, y, z)と色の情報(R, G, B)からなる点のデ… <h2 id="はじめに">はじめに</h2> <p>こんにちは!テックセンター飯塚のアルバイトスタッフの吉田です。 先日、アルバイトスタッフ主導でハンズオンイベントを開催いたしました! 今回の内容は、「スキャンした点群データをUnityやWebアプリで扱おう!」でした。 具体的には、スキャンした点群データをweb上で表示したり、Unity上でゲームのマップとして取り込むというものになります。 この記事では、点群データのスキャン及び、点群データ(las)をwebゃUnity上で扱える形(obj)に変換する方法を解説したいと思います。</p> <h2 id="点群データについて">点群データについて</h2> <p>点群データとは3次元座標(x, y, z)と色の情報(R, G, B)からなる点のデータになります。 点群データのスキャンにはLiDAR(Light Detection And Ranging)を用いています。LiDARはレーザー光を対象の物体に当て、その跳ね返りの時間から対象物の形を計測する技術になります。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230628/20230628133524.png" width="866" height="205" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>LiDARでスキャンできる端末には以下のようなものがあります。</p> <ul> <li>iPhone 12 Proシリーズ</li> <li>iPhone 13 Proシリーズ</li> <li>iPad Pro(2020年発売以降のモデルのみ)</li> </ul> <p>スキャンできる端末がない場合には、以下のサイトから点群データをダウンロードすることができます。 最近では多くの地方自治体が点群データを公開してきています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcatalog.data.metro.tokyo.lg.jp%2Fdataset%2Ft000029d0000000008%2Fresource%2F5dc5e605-81c7-445a-818a-e85aed6f1365" title="都営大江戸線「都庁前駅」3D点群データ - [LAS] 都庁前駅 地下3階(ホーム階) 3D点群データ - 東京都オープンデータカタログサイト" 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://catalog.data.metro.tokyo.lg.jp/dataset/t000029d0000000008/resource/5dc5e605-81c7-445a-818a-e85aed6f1365">catalog.data.metro.tokyo.lg.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fopennagasaki.nerc.or.jp%2Fmap.html" 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://opennagasaki.nerc.or.jp/map.html">opennagasaki.nerc.or.jp</a></cite></p> <h2 id="OPTiM-Geo-Scanについて">OPTiM Geo Scanについて</h2> <p>点群データは弊社の3次元測量アプリ<a href="https://www.optim.co.jp/construction/optim-geo-scan/">OPTiM Geo Scan</a>を用いてスキャンしました。 LiDARセンサー搭載のiPhoneとGNSSレシーバー取得の位置情報を組み合わせて、 短時間で高精度な測量を行える3次元測量アプリです。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230628/20230628133522.png" width="873" height="436" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>LiDARを用いただけでは問題点があり、点群データの位置にずれが生じてしまい、正確な対象物の点群データをスキャンすることが難しくなります。この問題を解決するために衛星からの信号を受信しGNSS(Global Navigation Satellite System)レシーバを用い、点群のずれを補正します。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230628/20230628133258.png" width="1148" height="449" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="活用事例">活用事例</h3> <p>主に土木・建設現場での測量で利用されています。小規模現場において利用頻度の高い光波測量(トータルステーションによる測量)と比較すると、測量作業時間を最大90%削減 さらにドローンやレーザースキャナに比較し、作業コストを最大80%以上削減できています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Fconstruction%2Foptim-geo-scan%2Fproducts%2Foptim-geo-scan%2F" title="製品紹介 「OPTiM Geo Scan」 | OPTiM Geo Scan | これならホンモノの働き方改革ができる! 圧倒的な生産性向上を実現できる3次元測量アプリ" 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.optim.co.jp/construction/optim-geo-scan/products/optim-geo-scan/">www.optim.co.jp</a></cite></p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/xM5hsjyVmd4?start=153&feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="【OPTiM Geo Scan】6つの導入事例をご紹介"></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=xM5hsjyVmd4&t=153s">www.youtube.com</a></cite></p> <h2 id="lasファイルからplyファイルへの変換">lasファイルからplyファイルへの変換</h2> <ol> <li>Las2Meshのダウンロードから、v2をダウンロード<a href="https://github.com/ksasao/Las2Mesh">https://github.com/ksasao/Las2Mesh</a></li> <li>las2mesh.exe に 点群ファイル(.las) を Drag&amp;Drop</li> <li>しばらく待つとplyファイルがダウンロードしたフォルダと同階層に出力される</li> </ol> <h2 id="plyファイルからobjファイルへの変換">plyファイルからobjファイルへの変換</h2> <ol> <li>Meshlabをインストール <a href="https://www.meshlab.net/#download">https://www.meshlab.net/#download</a></li> <li>Meshlabを起動し、作成したplyファイルをインポートする</li> <li>File > Export Mesh as...をクリックし、ファイルの種類でobjファイルを選択 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230628/20230628133517.png" width="1411" height="837" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></li> <li>Option選択画面が出てくるが、そのままOKをクリック</li> </ol> <h2 id="まとめ">まとめ</h2> <p>本記事では点群データのスキャンから3Dモデルデータに変換するところまでを解説しました。無料で簡易的に点群スキャンできるアプリもあるので、LiDARが搭載された端末をお持ちの方は探してみてください。</p> <p>また、現場での活用をお考えの方はぜひOPTiM Geo Scanの利用を検討いただきたいです。</p> <p>オプティムではAI・IoT技術によりビジネス課題・社会的課題の解決を目指しています。ぜひ以下の採用情報もご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2F" title="OPTiM(オプティム)|AI・IoTであらゆる産業のDXを実現するリーディングカンパニー" 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.optim.co.jp/">www.optim.co.jp</a></cite></p> optim-yoshida Elastic Container Registryにライフサイクルポリシーを導入し75%のコスト削減してみました hatenablog://entry/4207112889974375944 2023-05-24T10:00:00+09:00 2023-05-24T10:00:00+09:00 こんにちは、AI・IoTサービス開発部のがんがんこと岩丸です。 エンジニア界隈はChatGPTで盛り上がっていますが、1つ前の話題として「円安」があったかと思います。2023年に入って以降は130円/ドル台が続いており、引き続き円安のことを意識する必要があります。 そこで今回はECRのライフサイクルポリシーを調査した際の備忘録と考えられるバッドパターン(障害を招くこともある)の紹介をしたいと思います。 <h2 id="はじめに">はじめに</h2> <p>こんにちは、21新卒の<a href="https://twitter.com/gangan_nikki">がんがん</a>こと岩丸です。</p> <p>皆様、春の風はいかがお過ごしでしょうか。私は遂に花粉症を発症し、例に漏れず花粉症と日々闘っております。暖かい春は待ち遠しくもあり、花粉症民からすると防御の構えが続くそんな季節ですね。</p> <p>5月に入ってからは花粉対策と合わせて暑さ対策も急務ですね。体調変化が日々忙しい限りです。</p> <p><br> エンジニア界隈はChatGPTで盛り上がっていますが、1つ前の話題として「円安」があったかと思います。2023年に入って以降は130円/ドル台が続いており、引き続き円安のことを意識する必要があります。</p> <p>この話はAWSの<strong>Amazon Elastic Container Registry (ECR)</strong>、GCPの<strong>Artifact Registry</strong>にも同様のことが言えます。ECRの料金はRDS・EC2に比べると高くないですが円安が継続する昨今においては気にしておきたいコストです。</p> <p><br> そこで今回はECRのライフサイクルポリシーを調査した際の備忘録と考えられるバッドパターン(障害を招くこともある)の紹介をしたいと思います。</p> <h2 id="やりたかったこと">やりたかったこと</h2> <p>EKSで利用するイメージは全てECRに格納されており、このECRイメージはテストフェーズに入るとちょっとずつ増えていく形になっています。また、開発環境で実験を行う際のイメージもECRイメージを利用しており、気づいたら増えている形になります。</p> <p>例えば、あるリポジトリには約1300コのイメージが保持されていました。ただ、実際に使っているのは4〜5コです。過去ログ用のイメージを残しておくとしても最大10コくらいでいいのかなと思います(もっと少なくていい)。</p> <p><br> イメージが増えていく原因として<strong>不要になったイメージを削除する</strong>という機構がないことが挙げられます。今回はこの削除する機構としてECRのライフサイクルポリシーを採用してみました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230519/20230519093525.png" width="1600" height="1248" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#やりたかったこと">やりたかったこと</a></li> <li><a href="#今回導入したポリシールールと適応した結果">今回導入したポリシールールと適応した結果</a><ul> <li><a href="#今回導入したポリシールール">今回導入したポリシールール</a></li> <li><a href="#AWSコンソールからECRライフサイクルポリシーを設定する">AWSコンソールからECRライフサイクルポリシーを設定する</a></li> </ul> </li> <li><a href="#適応した結果">適応した結果</a></li> <li><a href="#Terraformでの実装例">Terraformでの実装例</a></li> <li><a href="#気をつけておきたいECRライフサイクルポリシールール例">気をつけておきたいECRライフサイクルポリシールール例</a><ul> <li><a href="#保持するイメージ数をN個に設定する">保持するイメージ数をN個に設定する</a></li> <li><a href="#保持するイメージの経過日数をN日に設定する">保持するイメージの経過日数をN日に設定する</a></li> <li><a href="#ライフサイクルポリシールールによる障害発生を防ぐには">ライフサイクルポリシールールによる障害発生を防ぐには?</a></li> </ul> </li> <li><a href="#参考">参考</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h2 id="今回導入したポリシールールと適応した結果">今回導入したポリシールールと適応した結果</h2> <h3 id="今回導入したポリシールール">今回導入したポリシールール</h3> <p>今回は以下のようなルールを設定しました。</p> <ul> <li><strong>work</strong>がつくイメージの経過日数が<strong>14日</strong>を超えていた場合は削除する</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230324/20230324175611.png" width="527" height="541" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="AWSコンソールからECRライフサイクルポリシーを設定する">AWSコンソールからECRライフサイクルポリシーを設定する</h3> <p>設定手順のキャプチャは今回割愛しています。以下の記事に画面キャプチャを用いた導入手順がありますのでそちらを参照ください。</p> <ul> <li><a href="https://qiita.com/akiko-pusu/items/bf62256e5732156f0cec">Qiita / ECRのライフサイクルの設定メモ</a></li> </ul> <h2 id="適応した結果">適応した結果</h2> <p>ライフサイクルポリシーを適応した結果、<strong>75%</strong>のコスト削減に成功しました。 EC2やRDSに比べると削減量は目に見えて多くありませんが、無駄なコストを確実に削減することができました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230519/20230519093540.png" width="1077" height="404" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="Terraformでの実装例">Terraformでの実装例</h2> <p>今回はAWSコンソールより設定を行いましたが、Terrafromを用いている環境であればTerrafromで設定した方が楽です。 Terrafromの<a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_lifecycle_policy">公式ドキュメント</a>を参考にしました。JSONはAWSコンソールで出力したものです。</p> <p>公式ドキュメントを見る限り<code>tagPrefixList</code>は<code>list[string]</code>型となっていました。そのため、正規表現でゴニョゴニョやるのは厳しそうでした。</p> <pre class="code lang-terraform" data-lang="terraform" data-unlink><span class="synIdentifier">ecr_repository_policy</span> = <span class="synSpecial">{</span> <span class="synIdentifier">policy</span> = <span class="synIdentifier">jsonencode</span>(<span class="synSpecial">{</span> <span class="synConstant">&quot;rules&quot;</span>: <span class="synSpecial">[</span> <span class="synSpecial">{</span> <span class="synConstant">&quot;action&quot;</span>: <span class="synSpecial">{</span> <span class="synConstant">&quot;type&quot;</span>: <span class="synConstant">&quot;expire&quot;</span> <span class="synSpecial">}</span>, <span class="synConstant">&quot;selection&quot;</span>: <span class="synSpecial">{</span> <span class="synConstant">&quot;countType&quot;</span>: <span class="synConstant">&quot;sinceImagePushed&quot;</span>, <span class="synConstant">&quot;countUnit&quot;</span>: <span class="synConstant">&quot;days&quot;</span>, <span class="synConstant">&quot;countNumber&quot;</span>: <span class="synConstant">14</span>, <span class="synConstant">&quot;tagStatus&quot;</span>: <span class="synConstant">&quot;tagged&quot;</span>, <span class="synConstant">&quot;tagPrefixList&quot;</span>: <span class="synSpecial">[</span> <span class="synConstant">&quot;work&quot;</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span>, <span class="synConstant">&quot;description&quot;</span>: <span class="synConstant">&quot;workイメージを14日後に自動削除する&quot;</span>, <span class="synConstant">&quot;rulePriority&quot;</span>: <span class="synConstant">1</span> <span class="synSpecial">}</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span>) <span class="synSpecial">}</span> </pre> <h2 id="気をつけておきたいECRライフサイクルポリシールール例">気をつけておきたいECRライフサイクルポリシールール例</h2> <p>ECRのイメージを自動で整理してくれるライフサイクルポリシーですが、時としてこの設定が<strong>時限爆弾</strong>になることもあります。 このライフサイクルポリシーを設定をした人がチームにいない場合、障害発生の原因が<strong>イメージ自動削除</strong>によるものと切り分け・特定するのは容易ではないでしょう。</p> <p>そこで気をつけておきたい例を何点か記載します。明日は我が身ですね。</p> <h3 id="保持するイメージ数をN個に設定する">保持するイメージ数をN個に設定する</h3> <p>例えば以下のような設定です。</p> <div style="text-align:center;"> <b>ECR保持イメージを計30コとする</b> </div> <p>公式でもexampleが記載されている設定ケースです。一見おかしいところはない設定に感じますが以下だとどうでしょうか。</p> <div style="text-align:center;"> <b>ECR保持イメージを最新ものから計30コとする。<span style="color:red">31コ目以上のものは使用の可否に関わらず削除する</span></b> </div> <p><br></p> <p>お気づきの方もいると思いますが<strong>Nコ目以上のものは使用の可否に関わらず削除する</strong>という部分が重要です。これは本番環境で利用しているイメージが気づいたら削除されるかもしれないということです。</p> <p>例えば、テストフェーズで開発環境用のイメージをどんどんデプロイしたとします。デプロイを繰り返した結果、ステージング環境で利用しているイメージがNコ以内に含まれなくなりました。その結果、次のライフサイクル時にステージング環境で利用しているイメージが削除され、ステージング環境で障害が発生します。</p> <p>対象のステージング環境を複数の部署が利用している場合、各部署から障害報告アラートが飛んでくることでしょう。もし本番環境で同様の事象が発生したと考えると、正直考えたくないですね。</p> <p><br> シンプルな設定であるが故に注意しておきたい設定方法です。</p> <h3 id="保持するイメージの経過日数をN日に設定する">保持するイメージの経過日数をN日に設定する</h3> <p>こちらもシンプルな設定であるが故に注意しておきたい設定方法です。</p> <p>本設定の場合、<strong>しばらく更新しないが稼働し続ける必要がある</strong>みたいなケースで時限爆弾となり得ます。チームの開発ライフサイクルに合わせて注意する必要があります。</p> <h3 id="ライフサイクルポリシールールによる障害発生を防ぐには">ライフサイクルポリシールールによる障害発生を防ぐには?</h3> <p>2023.05現在のライフサイクルポリシーの設定では特定のイメージをロックしてライフサイクル対象から外す設定やコンテナの利用可否を判断する仕組みがありません。 そのため、利用者側できちんとルールを設定し運用するしかありません。例えば、以下のようなものが挙げられるかと思います。</p> <ul> <li>開発環境/本番環境ごとにECR環境を用意する</li> <li>本番リリース用イメージにはライフサイクルルールに該当しないprefixを付ける</li> </ul> <p>運用改善のために設定したルールが起因して障害が発生すると非常に悲しいですよね。 そのため、コンテナのタグ命名ルール・運用ルールはチーム内できちんと議論・検討していきましょう。</p> <h2 id="参考">参考</h2> <ul> <li><a href="https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/LifecyclePolicies.html">AWS公式 / ライフサイクルポリシー</a></li> <li><a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_lifecycle_policy">Terraform公式 / aws_ecr_lifecycle_policy</a></li> <li><a href="https://qiita.com/akiko-pusu/items/bf62256e5732156f0cec">Qiita / ECRのライフサイクルの設定メモ</a></li> </ul> <h2 id="おわりに">おわりに</h2> <p>今回はAmazon Elastic Container Registry(ECR)のコスト削減事例について紹介しました。不要なリソース・コストはどんどん削減し、より投資できるところに回していきたいお気持ちです。私が日々実施している効率化・自動化も同様のマインドで行っています。</p> <hr /> <p>OPTiMでは運用改善やDevOpsに興味があるエンジニア、新卒からどんどんチャレンジしていきたいエンジニア学生を随時募集しております。少しでもご興味のある方はこちらも合わせてご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-shimpei-iwamaru RubyKaigi 2023 参加レポートと講演の感想 hatenablog://entry/4207575160648236706 2023-05-23T10:00:00+09:00 2023-05-23T10:00:00+09:00 はじめまして。半田、片岡、吉村、原です。 私たちは主にRubyを使ってWebアプリケーションの開発をしています。 5/11~13に開催されたRubyKaigi 2023に参加してきたので、会場の雰囲気や印象に残った講演の感想についてレポートしていきたいと思います。 RubyKaigi 2023について RubyKaigiは毎年開催されるRubyの開発者やコミッターが集まるイベントです。前年に引き続き今年もオンライン、オフラインのハイブリッドでの開催となりました。 元々、松本市での開催は2020年に予定されていましたが、残念ながら中止となってしまったので、3年越しのリベンジという形で今年はまつも… <p>はじめまして。半田、片岡、吉村、原です。</p> <p>私たちは主にRubyを使ってWebアプリケーションの開発をしています。</p> <p>5/11~13に開催された<a href="https://rubykaigi.org/2023/">RubyKaigi 2023</a>に参加してきたので、会場の雰囲気や印象に残った講演の感想についてレポートしていきたいと思います。</p> <h2 id="RubyKaigi-2023について">RubyKaigi 2023について</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513135108.png" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>RubyKaigiは毎年開催されるRubyの開発者やコミッターが集まるイベントです。前年に引き続き今年もオンライン、オフラインのハイブリッドでの開催となりました。</p> <p>元々、松本市での開催は2020年に予定されていましたが、残念ながら中止となってしまったので、3年越しのリベンジという形で今年はまつもと市民芸術館で開催という形になりました。</p> <p>会場から歩いて15分くらいに松本城があり、城下町の風情を感じられました。 また雄大な山脈が松本市を取り囲んでおり、大自然と歴史に囲まれた中でRubyKaigiを楽しむことができました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230512/20230512210824.png" width="1151" height="682" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230512/20230512210322.png" width="966" height="691" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>松本市での開催ということで、松本城とRubyがデザインされたTシャツがノベルティとして配布されました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513132744.png" width="517" height="689" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>他にも七味やお箸、ステッカーなどが配布されました。<br/> たくさんのノベルティが貰えるのもRubyKaigiの魅力ですね。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230515/20230515111825.png" width="1167" height="880" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>RubyKaigiは海外からの参加者も多く、半分以上が英語での講演でした。日本語の講演であっても、英訳が表示されてるモニターや日→英の同時翻訳が聞けるヘッドセットの貸し出しがあり、日本語がわからない方でも参加しやすい環境だったと思います。<br/> ちなみに日本人の参加者は体感6~7割ほどでしたが、英語から日本語の翻訳が聞けるヘッドセットの貸し出しはありませんでした。<br/> また会場では日本人エンジニアと海外のエンジニアの交流もたくさん見られました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513133145.png" width="320" height="328" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>今年はお弁当の配布はなく、近辺の飲食店で使えるクーポンが3000円分配布されました。 その他にも、観光地の無料招待券や当日収穫した新鮮なりんごなど長野県のこだわり食材が会場で配布されており、地域密着型のイベントだと感じました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230512/20230512161138.jpg" width="960" height="1280" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513133826.png" width="916" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="印象に残った講演の感想">印象に残った講演の感想</h2> <h3 id="Ractor-reconsidered">&quot;Ractor&quot; reconsidered</h3> <p>Koichi Sasada による講演: <a href="https://rubykaigi.org/2023/presentations/ko1.html#day1">https://rubykaigi.org/2023/presentations/ko1.html#day1</a></p> <p>RactorはRuby3.0で導入されたThread、Fiber、Processとは別の並列処理の仕組みです。<br/> 安全かつ効率よく並列処理を行う仕組みですが、あまり使用されることはありませんでした。<br/> Ractor自体が並列処理を扱う難しい概念であり、エコシステムが育たなかったり、フィードバックが少なかったりしたためです。<br/> 根本的な原因として、Ractorのコード品質が低いため、CIが落ちるなどの問題があったこと、ネイティブスレッドを利用して動作していたため、パフォーマンスが遅いケースがあることが挙げられていました。<br/> それに対して、実用段階に向けてたくさんの挑戦がなされているようです!</p> <h3 id="Learn-Ractor">Learn Ractor</h3> <p>Masatoshi SEKI による講演: <a href="https://rubykaigi.org/2023/presentations/m_seki.html#day2">https://rubykaigi.org/2023/presentations/m_seki.html#day2</a></p> <p>こちらでは、Ractorを実際に使ってみた例が述べられていました。<br/> Ractorは、Threadと比較しCPU負荷が高い処理を行う上で有利であるため、そのようなケースで実際に効果を発揮したとのことでした。<br/> しかし、Ractorは共有可能なオブジェクトに制限があるため、既存の実装をRactorにリファクタリングするのはコストが高いようです。<br/> まだ課題はありそうですが、並列処理を手軽に書けるようになるのは何より楽しい!!ため、今後に期待ですね。</p> <h3 id="Generating-RBIs-for-dynamic-mixins-with-Sorbet-and-Tapioca">Generating RBIs for dynamic mixins with Sorbet and Tapioca</h3> <p>Emily Samp による講演: <a href="https://rubykaigi.org/2023/presentations/egiurleo.html#day1">https://rubykaigi.org/2023/presentations/egiurleo.html#day1</a></p> <p>Rubyの型情報を生成するにあたって、動的mixin (Objectのinclude, prepend, extend) をうまく扱うにはどうすればよいか?という話でした。<br/> 日頃フロントエンドの開発をしている私は、タイトルに含まれているツールを (少し強引ですが) TypeScriptに当てはめて、以下のように理解しました。</p> <ul> <li><a href="https://github.com/Shopify/rbi">RBI</a> (Ruby Interface) はTypeScriptでいうところの <code>.d.ts</code> みたいなもの</li> <li><a href="https://sorbet.org">Sorbet</a> が <code>tsc</code></li> <li><a href="https://github.com/Shopify/tapioca">Tapioca</a> が <code>tsconfig.json</code> の <code>“declaration”: true</code> で <code>.d.ts</code> を生成してくれる機能を実現するもの</li> </ul> <p>動的mixinを行なっている部分を探索し、ObjectとmixinするModuleを吸い上げて、その情報をTapiocaのキューに渡すようです。<br/> まだ理解しきれていない部分も多いので、実際に手を動かして触ってみながら、TypeScriptとどういった違いがあるのかあるのか見てみたいです。</p> <h3 id="Implementing--operator-stepping-into-parsey">Implementing &quot;++&quot; operator, stepping into parse.y</h3> <p>Misaki Shioi による講演: <a href="https://rubykaigi.org/2023/presentations/coe401_.html#day2">https://rubykaigi.org/2023/presentations/coe401_.html#day2</a></p> <p>Rubyは標準の時点で非常に多くのメソッドが用意されていますが、大抵のプログラミング言語にあるはずの++(インクリメント)演算子がありません。今後も『インクリメントの操作に対して、オブジェクト指向のセマンティクスを定義することができない』というMatzの方針を踏襲して++が実装される予定はありません。<br/> ですが今回はあえて、もしこの演算子を実装するにはRubyをどのように改造するかという切り口で、実際にコード改変・コンパイルを繰り返すことを通して、パーサに触れてゆく内容の講演でした。実装の度に問題点が見つかりそれをクリアしてゆくという楽しい形式の話でした。<br/> ちなみに、立ち見が生まれるほどの大人気講演で、私も氏のファンになり声をかけてしまいました!</p> <h2 id="スポンサーブース">スポンサーブース</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513131538.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513131518.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>RubyKaigiはたくさんのスポンサーブースがあり、参加者を楽しませてくれます。<br/> お菓子類、モバイルバッテリーなどのノベルティの配布やRubyに関するクイズ等などのイベントがありました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513112147.png" width="613" height="590" loading="lazy" title="" class="hatena-fotolife" style="width:1000px" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513134248.png" width="526" height="704" loading="lazy" title="" class="hatena-fotolife" style="width:1000px" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230512/20230512161136.jpg" width="960" height="1280" loading="lazy" title="" class="hatena-fotolife" style="width:1000px" itemprop="image"></span></p> <p><a href="https://wed.company/">WED, Inc.</a>様ではレシートを提供、撮影したらガチャを回して景品が貰えるといったような企業のサービスに関連した企画を楽しむことができました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513134529.png" width="806" height="604" loading="lazy" title="" class="hatena-fotolife" style="width:1000px" itemprop="image"></span></p> <p><a href="https://info.cookpad.com/">クックパッド株式会社</a>様ではその場でCTOカジュアル面談を実施されており、かなり話題になっておりました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513131458.jpg" width="1200" height="1600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>また、会場には意外とコンセントがそこまでなかったので、<a href="https://corp.helpfeel.com/ja/home">株式会社Helpfeel</a>様が提供されていたコンセントがある休憩スペースの提供がとてもありがたかったです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230513/20230513132341.png" width="1421" height="974" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="おわりに">おわりに</h1> <p>来年のRubyKaigi 2024は5/15~17に沖縄県那覇市で行われると発表されました。</p> <p>いまだに進化し続けて魅力を増してるRubyから目が離せませんね!</p> <p>OPTiMでは、そんな魅力的なRubyで開発している大型プロジェクトがございます。Ruby好きなメンバーで開発していますので、ご興味のある方は、ぜひ一度ご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.optim.co.jp" title="OPTiM(オプティム)|AI・IoTであらゆる産業のDXを実現するリーディングカンパニー" 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="http://www.optim.co.jp">www.optim.co.jp</a></cite></p> optim-soichirohara RubyKaigi 2023 RactorとThread、Ractor local GCについて hatenablog://entry/4207575160648042687 2023-05-22T10:00:00+09:00 2023-05-22T10:00:04+09:00 はじめまして、Optimal Biz開発チームの片岡です。 私は業務では主にRubyを使ってWebアプリケーションの開発をしています。 5/11~13に開催されたRubyKaigi 2023 に参加してきました。 私は今回、Ractorに注目して参加していたため、Koichi Sasadaさんの講演 "Ractor" reconsidered を紹介します。 RactorとThread 講演の紹介の前に、RactorとThreadについて確認します。 Threadとは Rubyでは、並行・並列処理を行う仕組みとしてRactor/Thread/Fiber/Processをサポートしています。 例… <p>はじめまして、Optimal Biz開発チームの片岡です。 私は業務では主にRubyを使ってWebアプリケーションの開発をしています。</p> <p>5/11~13に開催されたRubyKaigi 2023 に参加してきました。 私は今回、Ractorに注目して参加していたため、Koichi Sasadaさんの講演 <a href="https://rubykaigi.org/2023/presentations/ko1.html#day1">"Ractor" reconsidered</a> を紹介します。</p> <h1 id="RactorとThread"><a href="https://github.com/ruby/ruby/blob/master/doc/ractor.md">Ractor</a>とThread</h1> <p>講演の紹介の前に、RactorとThreadについて確認します。</p> <h2 id="Threadとは">Threadとは</h2> <p>Rubyでは、並行・並列処理を行う仕組みとしてRactor/Thread/Fiber/Processをサポートしています。</p> <p>例として<a href="https://docs.ruby-lang.org/ja/latest/doc/spec=2fthread.html">Thread</a>を確認します。 Threadは、ネイティブスレッド(カーネルスレッド)を用いて、同時実行を行います。</p> <blockquote><p><a href="https://docs.ruby-lang.org/ja/latest/doc/spec=2fthread.html">Ruby 3.2 リファレンスマニュアル Thread</a><br /><br /> ネイティブスレッドを用いて実装されていますが、現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行されるネイティブスレッドは常にひとつです。<br /> Rubyにおける特徴として、Giant VM lock(以下GVL)を有しているため、同時に実行されるネイティブスレッドは常にひとつです。<br /> ただし、IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。その場合にはスレッドは同時に実行され得ます。</p></blockquote> <p>この性質上、ThreadはIO待ちが発生する処理ではマルチコアを活用して性能を発揮しますが、 CPU負荷が高い計算処理の場合、シングルコアのみを利用するため、CPUの性能を最大限発揮出来ないことになります。</p> <h2 id="Ractorとは">Ractorとは</h2> <p>Ruby3.0で導入されたRactorでは、GVLをRactorオブジェクトごとに有しています。 これにより、IO処理を含まないCPU負荷が高い計算処理のみの場合でも、マルチコアを活用するため、CPUの性能を最大限発揮出来ます。</p> <p>他にも、RactorはThreadがオブジェクトをすべて共有することに対して、共有可能なオブジェクトが制限されているという特徴があります。 これにより、Threadは<a href="https://docs.ruby-lang.org/ja/3.2/class/Thread=3a=3aMutex.html">Mutax</a>などを用いて開発者が意識して排他ロックをかける必要がありますが、 Ractorでは共有するオブジェクトを明示する必要があるため、比較的安全に並列処理を書く事ができます。</p> <h2 id="RactorとThreadの比較">RactorとThreadの比較</h2> <p>Ractorは重い計算処理を得意としていることを実際に確認します。</p> <ul> <li>以下コードはRuby 3.2.0で実行</li> </ul> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">'</span><span class="synConstant">benchmark</span><span class="synSpecial">'</span> <span class="synPreProc">def</span> <span class="synIdentifier">cpu_bound</span> (<span class="synConstant">1</span>..<span class="synConstant">100_000_000</span>).each <span class="synStatement">do</span> |i| <span class="synConstant">100_000</span> % i <span class="synStatement">end</span> <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synIdentifier">io</span> sleep <span class="synConstant">30</span> <span class="synPreProc">end</span> <span class="synType">Benchmark</span>.bm <span class="synConstant">10</span> <span class="synStatement">do</span> |r| r.report <span class="synSpecial">&quot;</span><span class="synConstant">Ractor CPU Bound</span><span class="synSpecial">&quot;</span> <span class="synStatement">do</span> ractors = <span class="synConstant">10</span>.times.map <span class="synStatement">do</span> <span class="synType">Ractor</span>.new { cpu_bound } <span class="synStatement">end</span> ractors.each(&amp;<span class="synConstant">:take</span>) <span class="synStatement">end</span> r.report <span class="synSpecial">&quot;</span><span class="synConstant">Ractor IO</span><span class="synSpecial">&quot;</span> <span class="synStatement">do</span> ractors = <span class="synConstant">10</span>.times.map <span class="synStatement">do</span> <span class="synType">Ractor</span>.new { io } <span class="synStatement">end</span> ractors.each(&amp;<span class="synConstant">:take</span>) <span class="synStatement">end</span> r.report <span class="synSpecial">&quot;</span><span class="synConstant">Thread CPU Bound</span><span class="synSpecial">&quot;</span> <span class="synStatement">do</span> threads = <span class="synConstant">10</span>.times.map <span class="synStatement">do</span> <span class="synType">Thread</span>.new { cpu_bound } <span class="synStatement">end</span> threads.each(&amp;<span class="synConstant">:join</span>) <span class="synStatement">end</span> r.report <span class="synSpecial">&quot;</span><span class="synConstant">Thread IO</span><span class="synSpecial">&quot;</span> <span class="synStatement">do</span> threads = <span class="synConstant">10</span>.times.map <span class="synStatement">do</span> <span class="synType">Thread</span>.new { io } <span class="synStatement">end</span> threads.each(&amp;<span class="synConstant">:join</span>) <span class="synStatement">end</span> <span class="synStatement">end</span> </pre> <p>初めに、CPU負荷部分のみの実行結果です。</p> <pre class="code bash" data-lang="bash" data-unlink> user system total real Ractor CPU Bound 62.664348 0.019670 62.684018 ( 16.036564) Ractor CPU Bound 73.585763 0.267800 73.853563 ( 18.937455) Ractor CPU Bound 68.076373 0.035880 68.112253 ( 17.674539) Thread CPU Bound 35.877646 0.024894 35.902540 ( 35.848599) Thread CPU Bound 35.387314 0.048037 35.435351 ( 35.366753) Thread CPU Bound 32.595941 0.038544 32.634485 ( 32.580958)</pre> <p>RactorのCPU消費の様子<br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230515/20230515170253.png" width="534" height="106" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ThreadのCPU消費の様子<br /> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230515/20230515170256.png" width="541" height="105" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Ractorで実行している最中はマルチコアを活かしていますが、 Threadの場合はマルチコアを活かせていないことが分かります。</p> <p>次に、IO処理部分のみの実行結果です。</p> <pre class="code bash" data-lang="bash" data-unlink> user system total real Ractor IO 0.000000 0.002835 0.002835 ( 30.001762) Ractor IO 0.003486 0.000000 0.003486 ( 30.001915) Ractor IO 0.003617 0.000003 0.003620 ( 30.029683) Thread IO 0.003583 0.000002 0.003585 ( 30.001737) Thread IO 0.004577 0.000005 0.004582 ( 30.002423) Thread IO 0.004311 0.000000 0.004311 ( 30.002121)</pre> <p>このように、IO処理の場合は、どちらもマルチコアを使用してスレッドが停止せずに実行されていることが確認出来ました。</p> <h1 id="Ractor-reconsidered"><a href="https://rubykaigi.org/2023/presentations/ko1.html#day1">&quot;Ractor&quot; reconsidered</a></h1> <p>そして<strong>今回のRubyKaigi 2023 にて行われた講演の内容です</strong>。</p> <h2 id="Ractorはあまり使用されなかった">Ractorはあまり使用されなかった</h2> <p>RactorはRuby3.0から登場しましたが、あまり使用されることはありませんでした。 この原因を次のように述べられていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230515/20230515170241.png" width="1019" height="570" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> Koichi Sasada. <a href="https://www.atdot.net/~ko1/activities/2023_rubykaigi2023.pdf">2023_rubykaigi2023.pdf p16</a> 2023-05-13閲覧.</p> <ul> <li>エコシステムが育たない</li> <li>APIに対するフィードバックがない</li> </ul> <p>なぜなら</p> <ul> <li>誰も試していないから</li> </ul> <p>なぜなら</p> <ul> <li>コードの品質とパフォーマンスに問題があるから</li> </ul> <p>そのため、手始めにコードの品質とパフォーマンスの改善が行われているようです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230515/20230515170250.png" width="1148" height="645" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> Koichi Sasada. <a href="https://www.atdot.net/~ko1/activities/2023_rubykaigi2023.pdf">2023_rubykaigi2023.pdf p17</a> 2023-05-13閲覧.</p> <p>問題に関しては、それぞれ改善が行われているようですが、ここではさらなるパフォーマンスの改善として紹介されていたガベージコレクション(以下GC)の問題に注目していきます。</p> <h2 id="GC時にRactorが遅くなる問題">GC時にRactorが遅くなる問題</h2> <p>RactorではGCが行われている間、すべてのRactorが停止してしまう問題があり、パフォーマンスが悪くなることがあるそうです。</p> <p>実際に確認してみます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># GC.disable | GC.enable</span> <span class="synType">Benchmark</span>.bm <span class="synConstant">10</span> <span class="synStatement">do</span> |r| r.report <span class="synSpecial">&quot;</span><span class="synConstant">Ractor</span><span class="synSpecial">&quot;</span> <span class="synStatement">do</span> array = <span class="synConstant">1000</span>.times.map { |i| { <span class="synConstant">name</span>: <span class="synSpecial">&quot;</span><span class="synConstant">ユーザー</span><span class="synSpecial">#{</span>i<span class="synSpecial">}&quot;</span> } } ractors = <span class="synConstant">10000</span>.times.map <span class="synStatement">do</span> <span class="synType">Ractor</span>.new(array) <span class="synStatement">do</span> |array| <span class="synStatement">while</span> array.size &gt; <span class="synConstant">0</span> array.pop <span class="synComment"># arrayのpopを行い、HashをGCの対象にする</span> <span class="synStatement">end</span> <span class="synStatement">end</span> <span class="synStatement">end</span> ractors.each(&amp;<span class="synConstant">:take</span>) <span class="synStatement">end</span> <span class="synStatement">end</span> </pre> <p>以下実行結果。</p> <pre class="code bash" data-lang="bash" data-unlink> user system total real # GC.enable Ractor 13.172216 0.198747 13.370963 ( 12.767346) # GC.disable Ractor 6.743534 2.101403 8.844937 ( 7.113329)</pre> <p>なんとGCが有効な場合、無効な場合と比べてrealtimeが約1.7倍が遅い結果がでました。</p> <p>次はThreadと比較してみます。 本来、計算処理のみの場合はRactorに分があるはずですが、GCによってRactorが停止するのであれば、 Threadとの差がそこまで生まれないであろうことが推測出来ます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># GC.disable | GC.enable</span> <span class="synType">Benchmark</span>.bm <span class="synConstant">10</span> <span class="synStatement">do</span> |r| r.report <span class="synSpecial">&quot;</span><span class="synConstant">Ractor</span><span class="synSpecial">&quot;</span> <span class="synStatement">do</span> ractors = <span class="synConstant">10000</span>.times.map <span class="synStatement">do</span> <span class="synType">Ractor</span>.new <span class="synStatement">do</span> array = <span class="synConstant">1000</span>.times.map { |i| { <span class="synConstant">name</span>: <span class="synSpecial">&quot;</span><span class="synConstant">ユーザー</span><span class="synSpecial">#{</span>i<span class="synSpecial">}&quot;</span> } } <span class="synStatement">while</span> array.size &gt; <span class="synConstant">0</span> array.pop <span class="synStatement">end</span> <span class="synStatement">end</span> <span class="synStatement">end</span> ractors.each(&amp;<span class="synConstant">:take</span>) <span class="synStatement">end</span> <span class="synStatement">end</span> </pre> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># GC.disable | GC.enable</span> <span class="synType">Benchmark</span>.bm <span class="synConstant">10</span> <span class="synStatement">do</span> |r| r.report <span class="synSpecial">&quot;</span><span class="synConstant">Thread</span><span class="synSpecial">&quot;</span> <span class="synStatement">do</span> threads = <span class="synConstant">10000</span>.times.map <span class="synStatement">do</span> <span class="synType">Thread</span>.new <span class="synStatement">do</span> <span class="synComment"># 先ほどの例と違い、オブジェクトが共有されるため、スレッド内部で配列を生成するように統一</span> array = <span class="synConstant">1000</span>.times.map { |i| { <span class="synConstant">name</span>: <span class="synSpecial">&quot;</span><span class="synConstant">ユーザー</span><span class="synSpecial">#{</span>i<span class="synSpecial">}&quot;</span> } } <span class="synStatement">while</span> array.size &gt; <span class="synConstant">0</span> array.pop <span class="synStatement">end</span> <span class="synStatement">end</span> <span class="synStatement">end</span> threads.each(&amp;<span class="synConstant">:join</span>) <span class="synStatement">end</span> <span class="synStatement">end</span> </pre> <pre class="code bash" data-lang="bash" data-unlink> user system total real # Ractor GC.enable Ractor 12.738336 2.014264 14.752600 ( 8.334714) Ractor 12.454810 1.888897 14.343707 ( 7.873710) Ractor 13.093284 2.103435 15.196719 ( 8.617364) # Thread GC.enable Thread 5.636691 3.252349 8.889040 ( 8.136317) Thread 8.707594 4.816458 13.524052 ( 12.326214) Thread 5.729613 5.416915 11.146528 ( 10.281520) # Ractor GC.disable Ractor 6.096241 1.409719 7.505960 ( 2.162732) Ractor 6.180175 1.374718 7.554893 ( 2.193687) Ractor 6.255012 1.312275 7.567287 ( 2.188180) # Thread GC.disable Thread 5.067560 2.358393 7.425953 ( 6.526075) Thread 6.016626 2.496383 8.513009 ( 7.559076) Thread 4.351179 2.050537 6.401716 ( 5.606849)</pre> <p>この結果をまとめると、</p> <p>GCが有効の場合、realtimeはRactorとThreadにほとんど差はなく、Ractorの方が少し高速でした。 RactorがGCによって処理が停止するため、本来のRactorの高速性が発揮されず差が生まれにくいことが分かります。</p> <p>GCが無効の場合、realtimeはThreadよりもRactorの方が約3倍高速でした。 Ractorはマルチコアを使用して並列処理が行われるため、とても高速な結果が得られました!</p> <h2 id="Ractor-local-GC">Ractor local GC</h2> <p>この問題を解消するため、Ractorごとにほとんどのオブジェクトが分離されていることを利用し、 GCをRactorごとに並列して実行するというアプローチが考案されていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230515/20230515170244.png" width="1145" height="645" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> Koichi Sasada. <a href="https://www.atdot.net/~ko1/activities/2023_rubykaigi2023.pdf">2023_rubykaigi2023.pdf p31</a> 2023-05-13閲覧.</p> <p>これにより、GC時、すべてのRactorを止めずに実行することが可能になります。</p> <p>しかし、Ractorは一部のオブジェクトを共有可能であるため、図のようにRactorをまたいだオブジェクトが存在します。 そのため、ただ単にRactorごとにGCを動かすのではなく、影響がない程度に分散したGCが必要とのことです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230515/20230515170247.png" width="1149" height="648" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> Koichi Sasada. <a href="https://www.atdot.net/~ko1/activities/2023_rubykaigi2023.pdf">2023_rubykaigi2023.pdf p32</a> 2023-05-13閲覧.</p> <p>これが実現すると、Threadでは行えない利点が生まれるため、Ractorが積極的に採用されるようになるかもしれませんね!</p> <h1 id="おわりに">おわりに</h1> <p>Ractorの改善のプロジェクトが進んでいるようなので、 手軽に並列処理の出来るRubyを速く製品開発でも使ってみたいですね!</p> <p>OPTiMでは、そんな魅力的なRubyで開発している大型プロジェクトがございます。Ruby好きなメンバーで開発していますので、ご興味のある方は、ぜひ一度ご連絡ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2F" title="OPTiM(オプティム)|AI・IoTであらゆる産業のDXを実現するリーディングカンパニー" 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.optim.co.jp/">www.optim.co.jp</a></cite></p> optim-riku-kataoka 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.4 hatenablog://entry/4207575160648164562 2023-05-19T17:00:00+09:00 2023-05-19T17:37:17+09:00 はじめに はじめまして。 プラットフォームサービス開発部の宮木です。 弊社AIサービスのインフラ基盤運用を行っています。 AWS Summit Tokyo 2023 関連のこの連載も最後、4本目となります。 私はこの手のイベントには初めて参加しました。Day 2 のみでしたが、クラウド活用に高い関心を持つ方々が一堂に会して示された事例や未来像は興味深く、お祭りのような大規模会場の雰囲気が楽しかったです。 part.3 まではこちら 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.1 - OPTiM TECH BLOG 若手エンジニアがAWS Summi… <h2 id="はじめに">はじめに</h2> <p>はじめまして。 プラットフォームサービス開発部の宮木です。<br/> 弊社AIサービスのインフラ基盤運用を行っています。</p> <p><a href="https://aws.amazon.com/jp/summits/tokyo/">AWS Summit Tokyo 2023</a> 関連のこの連載も最後、4本目となります。<br/> 私はこの手のイベントには初めて参加しました。Day 2 のみでしたが、クラウド活用に高い関心を持つ方々が一堂に会して示された事例や未来像は興味深く、お祭りのような大規模会場の雰囲気が楽しかったです。</p> <p>part.3 まではこちら</p> <ul> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/18/100000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.1 - OPTiM TECH BLOG</a></li> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/18/170000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.2 - OPTiM TECH BLOG</a></li> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/19/100000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.3 - OPTiM TECH BLOG</a></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230512/20230512110409.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>既にさまざまな方がセッションレポート等を公開されていますが、私も以下のセッションについてお伝えできればと思います。</p> <ul> <li>Everything fails, all the time:分散システムにおける耐障害性のある設計について</li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjpsummit.awsevents.com%2Fpublic%2Fsession%2Fview%2F91" title="AWS Summit Tokyo" 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://jpsummit.awsevents.com/public/session/view/91">jpsummit.awsevents.com</a></cite></p> <p>分散システムの特徴の一部について理解するのに良い内容でした。 ここでは全体的な流れと詳細は他の記事にお譲りし、私のような初心者向けにいくつかのキーワードを取り上げて記載したいと思います。</p> <ul> <li>エクスポテンシャルバックオフ</li> <li>ジッター</li> <li>サーキットブレイカーパターン</li> <li>ポリグロット・パーシステンス</li> <li>Sagaパターン</li> <li>カオスエンジニアリング</li> </ul> <h2 id="分散システムと耐障害性に関するキーワード">分散システムと耐障害性に関するキーワード</h2> <p>分散システムにおける連携には、ネットワークを介したメッセージの要求/応答 が用いられており、システム全体は複雑なものになります。 従って分散システムには多くのネットワーク由来の問題が引き起こされます。<br/> セッションはこれらをいかに防ぎ対処するかというお話でした。 それらに関する基本的な概念やキーワードを簡単に解説します。</p> <h3 id="エクスポテンシャルバックオフ">エクスポテンシャルバックオフ</h3> <p>ネットワークの通信エラーに対してリクエストを再試行する際に一定の待機時間を設け、それを徐々に長くすることです。 再試行の頻度低下によって通信処理の負荷を軽減できます。</p> <h3 id="ジッター">ジッター</h3> <p>ネットワークにおいては主に、レイテンシーの通常の測定値からの揺らぎのことを指します。 ストリーミング等において、ジッターが大きいことでデータが失われたり順序が乱れたりすることで挙動が不安定になるので、好ましく無いものとされています。</p> <ul> <li><a href="https://jp.vcube.com/sdk/blog/what-is-jitter">&#x30B8;&#x30C3;&#x30BF;&#x30FC;&#xFF08;&#x30B8;&#x30C3;&#x30BF;&#xFF09;&#x3068;&#x306F;&#xFF1F;&#xFF5C;Agora Go Real&#xFF5C;&#x30AA;&#x30F3;&#x30E9;&#x30A4;&#x30F3;&#x30A4;&#x30D9;&#x30F3;&#x30C8;&#x30FB;&#x30A6;&#x30A7;&#x30D3;&#x30CA;&#x30FC;&#x30FB;Web&#x4F1A;&#x8B70;&#x30D6;&#x30A4;&#x30AD;&#x30E5;&#x30FC;&#x30D6;</a></li> </ul> <p>セッションにおいては、通信負荷を更に低減する要素として取り上げられていました。 エクスポテンシャルバックオフの待機時間にランダム性を追加し、再試行のタイミングを広く分散させることができます。</p> <ul> <li><a href="https://aws.amazon.com/jp/builders-library/timeouts-retries-and-backoff-with-jitter">&#x30B8;&#x30C3;&#x30BF;&#x30FC;&#x3092;&#x4F34;&#x3046;&#x30BF;&#x30A4;&#x30E0;&#x30A2;&#x30A6;&#x30C8;&#x3001;&#x518D;&#x8A66;&#x884C;&#x3001;&#x304A;&#x3088;&#x3073;&#x30D0;&#x30C3;&#x30AF;&#x30AA;&#x30D5;</a></li> </ul> <h3 id="サーキットブレイカーパターン">サーキットブレイカーパターン</h3> <p>分散システムの設計パターンの一つで、サービスの障害状況に応じて呼び出しを停止するかどうか制御します。 アプリケーションが失敗する可能性のある操作を繰り返し試行するのを防ぎ、障害の伝搬を抑制してサービスの安定性と回復性を向上させます。<br/> アプリケーション側にライブラリを用いたり、サービスメッシュを用いて実装できるようです。</p> <ul> <li><a href="https://learn.microsoft.com/ja-jp/azure/architecture/patterns/circuit-breaker">&#x30B5;&#x30FC;&#x30AD;&#x30C3;&#x30C8; &#x30D6;&#x30EC;&#x30FC;&#x30AB;&#x30FC; &#x30D1;&#x30BF;&#x30FC;&#x30F3; - Azure Architecture Center | Microsoft Learn</a></li> </ul> <p>セッションでは、<a href="https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/welcome.html">AWS Step Function</a> 等のワークフロー管理ツールを用いる方法が紹介されました。</p> <h3 id="ポリグロットパーシステンス">ポリグロット・パーシステンス</h3> <p>各マイクロサービスに独自のデータストアを持たせて分散化することを指します。 それぞれの特性に応じた最適なデータストアを選択でき、性能改善が期待できます。 一方で課題として、データの一貫性や整合性が保たれるように構造化することが必要です。</p> <ul> <li><a href="https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/modernization-data-persistence/welcome.html">&#x30DE;&#x30A4;&#x30AF;&#x30ED;&#x30B5;&#x30FC;&#x30D3;&#x30B9;&#x306B;&#x304A;&#x3051;&#x308B;&#x30C7;&#x30FC;&#x30BF;&#x6C38;&#x7D9A;&#x6027;&#x306E;&#x5B9F;&#x73FE; - AWS &#x306E;&#x898F;&#x7BC4;&#x7684;&#x30AC;&#x30A4;&#x30C0;&#x30F3;&#x30B9;</a></li> </ul> <p>セッションでは、後述する Sage パターンを用いてこれを有効化する方法について取り上げられていました。</p> <h3 id="Sagaパターン">Sagaパターン</h3> <p>複数のマイクロサービス間のトランザクションを調節して、データの一貫性を管理する方法です。Saga はこのシーケンスのことを指します。<br/> トランザクションの実行がサービス間を連鎖していく中で、いずれかが失敗したりタイムアウトしたりした場合、それ以前のトランザクションによる変更の取り消しを逆順に処理することによってデータの一貫性を保ちます。<br/> 留意すべき点としては、デバックが難しく構成要素が増えると複雑さがさらに増すことです。発生する可能性のある一連の障害を処理できるためには、処理の冪等性と可観測性が重要になります。</p> <ul> <li><a href="https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/modernization-data-persistence/saga-pattern.html">Saga&#x30D1;&#x30BF;&#x30FC;&#x30F3; - AWS &#x306E;&#x898F;&#x7BC4;&#x7684;&#x30AC;&#x30A4;&#x30C0;&#x30F3;&#x30B9;</a></li> </ul> <h3 id="カオスエンジニアリング">カオスエンジニアリング</h3> <p>昔 Netflix による取り組みとして有名になったようで、以下のように紹介されています。</p> <blockquote><p>カオスエンジニアリングは、システムが本番環境における不安定な状態に耐える能力へ自信を持つためにシステム上で実験を行う訓練方法です。</p></blockquote> <ul> <li><a href="https://principlesofchaos.org/ja/">&#x30AB;&#x30AA;&#x30B9;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x30EA;&#x30F3;&#x30B0;&#x306E;&#x539F;&#x5247; - Principles of chaos engineering</a></li> </ul> <p>詳しい話はちょっと自信が無いのですが、簡単に言うと、カオスは比較的簡単な規則に支配された不規則振動のことを指します。<br/> 初期条件の微小な違いが長期的に全く異なる振る舞いをもたらすので、システムの変化を予測することは困難になります。(初期値敏感性と予測不可能性) ただ、カオスは決定論的であり、これに当てはまるのであれば挙動の予測は不可能でも法則は明らかにできるかもしれません。<br/> つまり、分散システムはカオス的に振る舞うことがあるため、この条件を経験によって探り、対策しようということです。</p> <h2 id="感想">感想</h2> <p>難しかったです。 普段の業務でAWSに触れている際にはあまり意識していませんでしたが、サービスの根底にはさまざまな研究があることを感じました。<br/> また、開かれた場でそうした考えに触れるのも有意義な体験だったと思います。皆で知見を持ち寄って追究するというのがとても楽しそうなので、理解を深めて自分も議論に参加できるようになりたいものです。</p> <h2 id="おわりに">おわりに</h2> <p>これで AWS Summit Tokyo 2023 関連のこの連載も終了です。ご覧いただきありがとうございました。<br/> マイクロサービスのより良い実践を目指します。</p> <p>OPTiMではクラウドサービスを活用したインフラ構築・運用に関する活動に興味のある方を募集しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-yuki-miyagi 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.3 hatenablog://entry/4207575160649353367 2023-05-19T10:00:00+09:00 2023-05-19T17:44:35+09:00 はじめに はじめまして。 サービスオペレーション部 二年目の井上です。 普段の業務は、弊社の代表サービスである Optimal Biz のインフラ基盤やリリース関連業務を担当しています。 AWS Summit Tokyo 2023 関連のこの連載、3本目となります。このようなイベントには参加したことはありませんでしたが、インフラに携わる立場として、「何か勉強になることはあるかな」と思い、意を決して参加しました。 aws.amazon.com part.1 part.2 part.4はこちら 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.1 - OPT… <h3 id="はじめに">はじめに</h3> <p>はじめまして。 サービスオペレーション部 二年目の井上です。</p> <p>普段の業務は、弊社の代表サービスである Optimal Biz のインフラ基盤やリリース関連業務を担当しています。</p> <p>AWS Summit Tokyo 2023 関連のこの連載、3本目となります。このようなイベントには参加したことはありませんでしたが、インフラに携わる立場として、「何か勉強になることはあるかな」と思い、意を決して参加しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fsummits%2Ftokyo%2F" title="AWS Summit Tokyo | オンデマンド登録受付中" 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://aws.amazon.com/jp/summits/tokyo/">aws.amazon.com</a></cite></p> <p>part.1 part.2 part.4はこちら</p> <ul> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/18/100000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.1 - OPTiM TECH BLOG</a></li> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/18/170000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.2 - OPTiM TECH BLOG</a></li> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/19/170000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.4 - OPTiM TECH BLOG</a></li> </ul> <h3 id="AWS-Summit-2023-に参加した目的">AWS Summit 2023 に参加した目的</h3> <ul> <li>弊社サービスのインフラを触る機会が多く、業務に何かを活かしたい。(3割)</li> <li>社内外で普段関わりの無いエンジニアの方々とコミュニケーションをとる。(7割)</li> </ul> <h3 id="参加したセッションDay2">参加したセッション(Day2)</h3> <p>AWS Summitのブースは初心者・中級者・上級者、多分野にわたる企業向けに分かれており、それぞれ興味を持ったセッションを予約して参加します。ちなみに画像撮影はOK、動画撮影はNGでした。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230518/20230518095616.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>セッション題目の一覧は以下のURLから閲覧できます。</p> <p>発表者の皆さん、インフラ構成図やスライドの説明など、撮影されることが前提の発表資料で、作り込んでいるんだな〜と感激しました。チケットやスライドを利用して、チーム外に情報を共有する場が多いと思いますが、どうすれば情報がわかりやすく伝わるのか、を工夫されており、技術面以外でも学びが得られると思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fsummits%2Ftokyo%2Fagenda%2F" title="セッション - AWS Summit Tokyo | AWS" 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://aws.amazon.com/jp/summits/tokyo/agenda/">aws.amazon.com</a></cite></p> <h4 id="セッション1なぜ清水建設の社員が-IoTAI-を駆使できるようになったのか土木現場の施工分析ダッシュボード">セッション1:なぜ、清水建設の社員が IoT・AI を駆使できるようになったのか?~土木現場の施工分析ダッシュボード~</h4> <p>こちらのセッションはITシステム開発全くの未経験だった建設エンジニアの方が、土木現場内の建機の稼働状況を AWS 上に構築したデータ分析基盤により施工管理・運用を行った事例の紹介でした。弊社でも建設xITの取り組みに積極的に参画しておりますが、建設業界では業務改善の問題は急務です。清水建設さんでは、センサや計測技術に関するノウハウを活かし、社員自らITシステムの構築にチャレンジする<strong>内製化</strong>がキーワードでした。</p> <p>ダンプに取り付けられたセンサから位置情報を検知し、ダンプアップ検知システムを実現したという話でした。システムを運用し始めてまもなく、現場の利用者から「取得データが見えづらい」という要望があったそうです。そこで、AWS EC2の分析データを可視化するよう、Pythonで実装されたオープンソース・Steamlitを活用して、ダッシュボード作成を実現しました。このように、ミニマムな機能から開発を進めていき、最終的には現場社員の声も取り入れ、徐々にシステムの充実度は増しているようです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjpsummit.awsevents.com%2Fpublic%2Fsession%2Fview%2F160" title="AWS Summit Tokyo" 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://jpsummit.awsevents.com/public/session/view/160">jpsummit.awsevents.com</a></cite></p> <h4 id="セッション2自治体がガバメントクラウド利用に向けておさえておきたい-10-のこと">セッション2:自治体がガバメントクラウド利用に向けておさえておきたい 10 のこと</h4> <p>こちらのセッションは、「旧態依然の情報管理から、クラウドを活用して、業務効率や情報セキュリティ強化に努めていこうぜ!」という政府主導の動きに倣ったAWS開発基盤の紹介でした。しかし、自治体としては、いきなりクラウドサービスへシステムを完全移行!、となるとハードルが高い... ...という課題があります。ややこしい事務手続きはAWSとデジタル庁の方で連携して、自治体はデジタル庁に申請するだけで良いので、事務手続きがシンプルになる!という話でした。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjpsummit.awsevents.com%2Fpublic%2Fsession%2Fview%2F115" title="AWS Summit Tokyo" 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://jpsummit.awsevents.com/public/session/view/115">jpsummit.awsevents.com</a></cite></p> <p>こちらの講演を聞いていると、キーワードは<strong>「AWS Well-Architected」</strong>でした。AWS・インフラを勉強されている方には馴染みの深いキーワードです。<strong>「AWS Well-Architected」</strong>は、下記の6つの柱に基づいて、AWSを導入した企業とそのパートナー様の間でアーキテクチャを評価し、持続可能性の高いシステムを組み上げるかの指標を濃縮したプラクティスです。</p> <ul> <li>優れた運用効率</li> <li>セキュリティ</li> <li>信頼性</li> <li>パフォーマンス効率</li> <li>コストの最適化</li> <li>持続可能性</li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Farchitecture%2Fwell-architected%2F%3Fwa" title="AWS Well-Architected – 安全で効率的なクラウドアプリケーション" 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://aws.amazon.com/jp/architecture/well-architected/?wa">aws.amazon.com</a></cite></p> <p>こちらのセッションで紹介されていた「AWS Well-Architected」に深く関連するAWSサービスを紹介していきます。</p> <h3 id="サービスの紹介">サービスの紹介</h3> <p>上記で述べた「ガバメントクラウドの活用」セッションで紹介されていたサービスについて触れていきます。</p> <h4 id="1-AWS-Trust-Advisor">1. AWS Trust Advisor</h4> <p>AWS Trust Adivisorは、利用状況と環境を分析し、コストの最適化・パフォーマンス・セキュリティ・耐障害性・サービスの制限 の5部門において、ベストプラクティスであるかを、ダッシュボード上で可視化します。</p> <p>AWSのサービスは構築が比較的簡易で、そのため無駄なリソースや運用者が把握できていないリソースが発生しがちです。せっかく購入したリソースが活用されていないパターンや、 在るべき設定ではないゆえ、無駄が生じているパターン、といった利用者が見落としがちな無駄に気付きやすくなりメリットなどが挙げられます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fpremiumsupport%2Ftechnology%2Ftrusted-advisor%2F" title="AWS Trusted Advisor" 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://aws.amazon.com/jp/premiumsupport/technology/trusted-advisor/">aws.amazon.com</a></cite></p> <h4 id="2-AWS-Cost-Explorer">2. AWS Cost Explorer</h4> <p>AWS のコストと使用量の経時的変化を可視化をしてくれるサービスです。レポートの将来時間範囲を選択、コスト予測も実現、そこから、さらにレポートという形で出力します。</p> <p>月額定額のホスティングサービスであれば、コスト予測は比較的 立てやすいですが、AWSの場合、使用量に応じて、リソースが使用量に応じてコストが変動します。そこで、過去の使用データに基づいて、将来のコスト予測のエビデンスを生成できるメリットなどが挙げられます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Faws-cost-management%2Faws-cost-explorer%2F" title="AWS Cost Explorer(コストと使用状況の経時的変化を可視化)| AWS" 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://aws.amazon.com/jp/aws-cost-management/aws-cost-explorer/">aws.amazon.com</a></cite></p> <h4 id="3AWS-Security-Hub">3. AWS Security Hub</h4> <p>複数のサービスで発生したアラートの集約や整理、優先順位付けなどダッシュボードで可視化します。膨大なリソースの障害情報やセキュリティ設定を集約して、一つの管理画面から確認することで、運用者目線の負荷軽減につながります。</p> <p>弊社でもサービスのセキュリティ情報をまとめて、クライアントに提供する業務がありますが、セキュリティチェック項目が多くなりがちです。AWS Security Hubでは、クラウドサービスにおいて、ベストプラクティスに反する設定を自動検知します。検知した設定を管理者へ通知したり、一部の項目において、自動修復するなど、セキュリティインシデントの是正にも活用できます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fsecurity-hub%2F" title="AWS Security Hub(統合されたセキュリティ &amp; コンプライアンスセンター)| AWS" 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://aws.amazon.com/jp/security-hub/">aws.amazon.com</a></cite></p> <p>ここで挙げた三つのサービスの共通点は、運用負荷を軽減するには、潜在的かつ可視化しづらい情報を、いかに運用者の目が届く位置まで持っていくことに長けていると感じました。</p> <p>企業単位で運用するサービスは、リソースの数が多くなりがちで、運用専門の部署がまとめて管理するケースが多いと思います。しかし、数人のチームで、数多くのサービスのインフラ管理をするにはどうしても限界があります。潜在的な情報の取得も楽になり、事象が発生する前に、トラブルに気づくことができることで、運用の好循環に乗っていけるのかな、と感じました。</p> <h4 id="学んだこと感想">学んだこと・感想</h4> <p>AWSのスタッフの方々は、パートナー様が抱いている課題に対して、AWS側が「こういうサービスがあるよ」と適切なアドバイスをして、それを形にして何らかの効用が得られるまで、手厚い支援をしている点が印象的でした。参加した両セッションは、システムの開発プロセスこそ、対照的でしたが、中身は同じな気がします。</p> <p>清水建設さんのセッションのように、「ミニマムな機能から一つのサービスを作り上げていくケース」でも、デジタル庁とガバメントクラウドのセッションのように、「大枠の開発基盤から細部をカスタマイズしていくシステム」において、AWSを利用したい側がそれぞれの課題があり、AWS側が課題に沿う提案をしています。本イベントで言及されたAWSの各サービスを、そのまま業務で活用するかといえば、そうではありません。しかし、私が普段携わる弊社のサービスにおいても、「このサービスはクライアント側から見て、どういうサービスであるべきか」というポイントは大切にしたいと感じました。</p> <h4 id="おまけ-戦利品の紹介">おまけ 〜戦利品の紹介〜</h4> <p>RedHatさんから、真っ赤なRedHatのロゴ付きUSBハブをいただきました。<br> わーい!ありがとうございます!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230518/20230518095553.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こちらも障害アラートでお世話になっているPagerDutyさん。<br> なんと私、レアなマスコットキャラクターのぬいぐるみが当選しました!やった〜!!(赤印)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230518/20230518095544.jpg" width="1600" height="1200" 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/o/optim-tech/20230518/20230518095601.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="おわりに">おわりに</h4> <p>これまで連載してきたAWS Summit 2023 TOKYOシリーズも次回でいよいよ最後になります。</p> <p>AWS Summit 2023 TOKYO参加レポートのpart.4では、2日目で開催されたセッションについて、より技術的な内容をご紹介します!</p> <p>OPTiMではクラウドサービスを活用したインフラ構築・運用に関する活動に興味のある方を募集しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-masaya-inoue 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.2 hatenablog://entry/4207575160647618810 2023-05-18T17:00:00+09:00 2023-05-19T17:45:13+09:00 はじめに こんにちは。OPTiM Store インフラを担当している岡田です。普段の業務では、AWSを活用したインフラの構築・運用をしております。過去には EKSのワーカーノードで使用できるPodの制限緩和を行いました を執筆しました! 今回はAWS Summit TOKYOに参加したレポートの第2弾として、執筆して参ります。 part.1 part.3 part.4はこちら 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.1 - OPTiM TECH BLOG 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.… <h2 id="はじめに">はじめに</h2> <p>こんにちは。OPTiM Store インフラを担当している岡田です。普段の業務では、AWSを活用したインフラの構築・運用をしております。過去には <a href="https://tech-blog.optim.co.jp/entry/2021/11/10/100000">EKSのワーカーノードで使用できるPodの制限緩和を行いました</a> を執筆しました!</p> <p>今回は<a href="https://aws.amazon.com/jp/summits/tokyo/">AWS Summit TOKYO</a>に参加したレポートの第2弾として、執筆して参ります。</p> <p>part.1 part.3 part.4はこちら</p> <ul> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/18/100000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.1 - OPTiM TECH BLOG</a></li> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/19/100000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.3 - OPTiM TECH BLOG</a></li> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/19/170000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.4 - OPTiM TECH BLOG</a></li> </ul> <h2 id="当日の様子">当日の様子</h2> <p>僕は今回Day1に参加し、基調講演の聴講といくつかのブースを回ってきました。コロナ禍の2021年度入社であったため、このようなオフラインイベントに参加するのは初めてでした。コンセプトである「見て、触れて、楽しんで、学ぶ」の通り、体験型ブースが多く並ぶ会場に圧倒されました...!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510110559.jpg" width="1600" height="1201" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>「今踏み出す、変革への一歩」と題した基調講演では、主に機械学習に関連する以下の内容が印象的でした。</p> <ul> <li>Amazon Bedrock <ul> <li>自社業務にあった大規模基盤モデルを探すのが大変、巨大なインフラは作りたくないけれどシームレスに社内情報をリンクしたい、自社データを使ってセキュアな基盤モデルを作りたいというような業務課題を解決できる。</li> <li>URL: <a href="https://aws.amazon.com/jp/bedrock/">https://aws.amazon.com/jp/bedrock/</a></li> </ul> </li> <li>Amazon Titan <ul> <li>自然言語処理のタスクに特化したモデル。</li> <li>URL: <a href="https://aws.amazon.com/jp/bedrock/titan/">https://aws.amazon.com/jp/bedrock/titan/</a></li> </ul> </li> <li>Amazon DataZone <ul> <li>データアクセスの簡単な管理・制御や、タイムリーな利用を可能にする。</li> <li>URL: <a href="https://aws.amazon.com/jp/datazone/">https://aws.amazon.com/jp/datazone/</a></li> </ul> </li> <li>Amazon Clean Rooms <ul> <li>データを保護しながら、社内外関係者での分析可能な環境を短時間で提供したり、自社と他社のデータセットを統合、分析が可能になるサービス。</li> <li>URL: <a href="https://aws.amazon.com/jp/clean-rooms/">https://aws.amazon.com/jp/clean-rooms/</a></li> </ul> </li> </ul> <p>自然言語処理やデータ処理・管理・共有など、データを元にビジネスを推進していくうえで重要なサービスたちにとても興味が惹かれました。特に、Amazon Clean Roomsを活用すれば、社内外問わずインフラエンジニア↔️開発エンジニア間のコミュニケーションや、エンジニア↔️企画・営業間のコミュニケーションをより効率的にできるのではと感じました。プロダクションミーティングでも採用できそうです!</p> <p>基調講演を聴講した後には、各企業のブースを回りました。インフラ業務で普段お世話になっている<a href="https://www.hashicorp.co.jp/">Hashicorp</a>や<a href="https://www.datadoghq.com/ja/">Datadog</a>をはじめ多くのブースを回り、話を伺えました。</p> <p>Hashicorpのブースでは箸をいただきました🥢</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510110605.jpg" width="1600" height="627" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Datadogブースでは、<a href="https://www.datadoghq.com/ja/about/latest-news/press-releases/datadog-launches-new-data-center-in-japan/">日本に新しいデータセンターを開設する</a>ニュースがタイムリーで発表されていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510110611.jpg" width="1200" height="1600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>どのブースでも、サービス利用のデモ操作を観覧しました。ここでもやはり、機械学習を活用した未来予測や、自動判別がメイン機能として押し出されていました。また、普段当たり前のように使っているサービスでしたが、いざ第3者視点で見るともっと色々な使い方があることに気づきました。開発チームに説明するときはこのダッシュボードがよさそうだなあと思ったり、企画の方に共有するときはこういう見せ方がわかりやすそうなどと考えながら、楽しく回ることができました。</p> <h2 id="参加した感想">参加した感想</h2> <p>冒頭でも述べたように初めての外部イベント参加となりましたが、自分が普段使っているサービスをユーザーとして改めて見つめ直す、非常に良い機会となりました。AWSに限らず、これから様々なサービスで機械学習・生成系AIの流れが押し寄せてくると思います。その中で、ビジネスやタスクの目的と照らし合わせつつ、柔軟に使うサービスを選択・適用していきたいと強く考えました。 5/22より、今回の<a href="https://aws.amazon.com/jp/summits/tokyo/">オンデマンド配信</a>が順次公開されるそうです。こちらでまた見直してみようと思います!</p> <h2 id="おわりに">おわりに</h2> <p>次のAWS Summit TOKYO参加レポートでは、2日目の基調講演やブースの様子を執筆予定です。ぜひお楽しみに!</p> <p>OPTiMでは、AWSを活用したインフラ構築・運用に興味のある方を募集しております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-genki-okada 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.1 hatenablog://entry/4207575160647626688 2023-05-18T10:00:00+09:00 2023-05-19T17:46:05+09:00 はじめに はじめまして。インフラユニットへ配属されました、23新卒の中川です。 4月20日、21日に幕張メッセで開催された、AWS Summit Tokyo 2023の参加レポートのpart.1です。 自分は配属からまだ1週間程度のタイミングでしたが、業務でAWSを多く利用することもあり、「是非行って来てほしい」と先輩方に背中を押していただき、2日間の全日程に意気揚々と参加させていただきました。 part.2 以降はこちら 若手エンジニアがAWS Summit TOKYO 2023に参加してきました! part.2 - OPTiM TECH BLOG 若手エンジニアがAWS Summit TO… <h2 id="はじめに">はじめに</h2> <p>はじめまして。インフラユニットへ配属されました、23新卒の中川です。</p> <p>4月20日、21日に幕張メッセで開催された、<a href="https://aws.amazon.com/jp/summits/tokyo/">AWS Summit Tokyo 2023</a>の参加レポートのpart.1です。</p> <p>自分は配属からまだ1週間程度のタイミングでしたが、業務でAWSを多く利用することもあり、「是非行って来てほしい」と先輩方に背中を押していただき、2日間の全日程に意気揚々と参加させていただきました。</p> <p>part.2 以降はこちら</p> <ul> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/18/170000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.2 - OPTiM TECH BLOG</a></li> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/19/100000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.3 - OPTiM TECH BLOG</a></li> <li><a href="https://tech-blog.optim.co.jp/entry/2023/05/19/170000">&#x82E5;&#x624B;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x304C;AWS Summit TOKYO 2023&#x306B;&#x53C2;&#x52A0;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F;&#xFF01; part.4 - OPTiM TECH BLOG</a></li> </ul> <h2 id="1日目">1日目</h2> <p>AWS Summitのようなイベントは初めてだったこともあり、引率してくださった先輩の後ろで小さくなりながら入場しました。</p> <p>会場入口付近: <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510140125.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>AWS Summitの午前には、基調講演がありました。 1日目はメイン会場から溢れたため、サテライト会場から視聴しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510150343.jpg" width="1600" height="974" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>1日目の基調講演で機械学習がメインのテーマでした。 機械学習特化のEC2インスタンスや、新しい機械学習に関するサービスの紹介がありました。 機械学習のデータ共有時のガバナンスを意識したサービスの紹介が多い印象でした。</p> <p>ブースの様子: <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510140133.jpg" width="1600" height="1200" 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/o/optim-tech/20230510/20230510140141.jpg" width="1224" height="1600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>その後は、予約無しでセッションに潜り込みました。 当日の参加枠の行列があり、その行列に列んでおくことで、セッション開始5分前から順に入っていくことができました。 ただし、当日の参加枠は基本的に立ち見席になるため、足腰への負担は大きかったです。</p> <p>セッションが開始してからは、静止画の撮影はOKということで、写真を撮っている参加者が多かったのが印象的でした。 スライドが切り替わると同時に、複数のスマホが頭上に現れるので、自分も釣られるように撮影していました。</p> <h2 id="2日目">2日目</h2> <p>AWS Summit 2日目は、前日の反省を生かし、なるべくセッションの予約をしてから臨みました。</p> <p>基調講演開始の約30分前に入場しましたが、メイン会場へ滑り込む事ができました。</p> <p>開始約30分前のメイン会場の様子: <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510140118.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>2日目の基調講演では、データレイクがメインのテーマでした。 データレイクの話題の部分は英語だったため、日本語のスライドと断片的に聞き取れた単語でなんとか乗り切りました。 メイン会場の前方には同時通訳のレシーバーがありましたが、案内されたかなり後方の座席にはレシーバーがありませんでした。(前列に座れた方は何分前に会場入りしたのだろう)</p> <p>遠いため結局投影されたものを見ていた: <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510140110.jpg" width="1600" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>午後からは、予約できたセッションを周りました。 椅子も確保できたうえ、クッション(先着2800名のハズだったが、余り物を貰えた)も入手できたため、セッションの内容に集中することができました。</p> <p>Oktaのセッションでは、特に印象に残ったスライドがありました。 デモセッションでしたが、デモの前段で、しばしばSNSで目にする「認証・認可を自作するな」を補強するようなスライドでした。 自分が認証・認可を組み込む意思決定をする際には、自作はしないと誓いました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230510/20230510150350.jpg" width="1430" height="870" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="おわりに">おわりに</h2> <p>AWS Summitのようなイベントへ参加したのは初でしたが、配属直後で既にモチベーションが高いところから更にモチベーションが上がりました。</p> <p>AWS Summit TOKYO参加レポートのpart.2では、1日目に焦点を絞ったより充実した内容で執筆予定です。お楽しみに!</p> <p>OPTiMでは、AWSを利用したインフラ構築や、開発・運用体験向上のための取り組みに興味のある方を募集しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-shota-nakagawa RubyKaigi 2023直前、RubyKaigi 2022 YJITの話題を振り返る hatenablog://entry/4207112889985353904 2023-05-10T10:00:00+09:00 2023-05-10T13:19:59+09:00 概要 YJITは「Versioning of basic blocks」と「Lazy code generation」をキーコンセプトに据えて設計されていて、古典的なJITコンパイラがメソッドベースでのみ判断するのとは対照的に、Basic Blockのような細かい単位で判断を行う。 YJITがRubyに入ったことで、YJITユーザーはrustcのライフサイクルを気にする必要が出てきた。 はじめに Optimal Biz開発チームの戸舘です。私は業務では主にRubyを使ってWebアプリケーションの開発をしつつ、一方では C++ と COM を用いてWindows Serviceアプリケーションを… <h2 id="概要">概要</h2> <p>YJITは「Versioning of basic blocks」と「Lazy code generation」をキーコンセプトに据えて設計されていて、古典的なJITコンパイラがメソッドベースでのみ判断するのとは対照的に、Basic Blockのような細かい単位で判断を行う。</p> <p>YJITがRubyに入ったことで、YJITユーザーはrustcのライフサイクルを気にする必要が出てきた。</p> <h2 id="はじめに">はじめに</h2> <p>Optimal Biz開発チームの戸舘です。私は業務では主にRubyを使ってWebアプリケーションの開発をしつつ、一方では C++ と COM を用いてWindows Serviceアプリケーションを開発しています。そんなC++erです。</p> <p>2022年9月8~10日に開催されたRubyKaigi 2022がつい昨日のことのように思う今日このごろですが、まもなくRubyKaigi 2023が松本で5月11~13日にかけて開催されます。</p> <p><a href="https://rubykaigi.org/2023/">RubyKaigi 2023 - RubyKaigi 2023</a></p> <p>OPTiMではRubyKaigi 2022についての記事を執筆したわけですが、そのときはYJITについては単体の記事で出したいという思いから、概略だけにとどめていました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech-blog.optim.co.jp%2Fentry%2F2022%2F09%2F13%2F110000" title="RubyKaigi 2022 参加レポートとRuby 3.2.0の注目機能 - OPTiM TECH 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://tech-blog.optim.co.jp/entry/2022/09/13/110000">tech-blog.optim.co.jp</a></cite></p> <p>この記事ではRubyKaigi 2022において、YJITがどのように紹介されたのかを改めて振り返っていこうと思います。</p> <p>もちろんあれから1年近くが経過していますから、この記事が公開される時点ではすでに古い情報となっているでしょうが、なるべく変動の激しくないであろうところを焦点に、解説を試みています。</p> <h2 id="YJITの概略前回の記事より">YJITの概略(前回の記事より)</h2> <p>そもそもJIT(just-in-time)コンパイルとは、ネイティブコードにコンパイルを行う速度的ハンデを背負った上で、生成されるネイティブコードの速さを持って結果的に実行速度を上げることを目指すものです。</p> <p>RubyでJITをする試みは今回が初めてではありません。Ruby 2.6で追加されたMJITでは、Ruby VMのコードを利用しつつ、gcc/clangの恩恵を受けて最適化されたネイティブコードを得てそれを実行することができます。</p> <p>YJITはRuby 3.1.0で追加されたの新しいJITで、MJITより優れた遅延コード生成を行います。Ruby 3.2.0ではARM64アーキテクチャに対応します。ARM64を捨てることはApple対応しないのと同じことであると断言しており、意欲的なARM64対応が進んでいます。</p> <h2 id="basic-block">basic block</h2> <p>プログラムの最適化という文脈において、basic block(基本ブロック)という単語が出てきます。</p> <p>Wikipediaの説明を引用してくると</p> <blockquote><p><a href="https://ja.wikipedia.org/wiki/%E5%9F%BA%E6%9C%AC%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF">基本ブロック - Wikipedia</a></p> <p>基本ブロック(きほんブロック、英: Basic Block)は、コンピュータにおいて、一つの入り口(すなわち、内部のコードが他のコードの分岐先になっていない)と一つの出口を持ち、内部に分岐を含まないコードを指す。</p></blockquote> <p>とあります。</p> <p>例えば次のコードを見てみます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">def</span> <span class="synIdentifier">foo</span>(x) p x x = (x + <span class="synConstant">1</span>) * (x + <span class="synConstant">4</span>) / <span class="synConstant">2</span> p x ret = <span class="synConstant">0</span>; <span class="synStatement">if</span> x % <span class="synConstant">2</span> <span class="synStatement">then</span> ret = x * <span class="synConstant">4</span> + <span class="synConstant">7</span> <span class="synStatement">else</span> ret = x + <span class="synConstant">1</span> <span class="synStatement">end</span> p ret <span class="synStatement">return</span> ret <span class="synPreProc">end</span> </pre> <p>これを基本ブロックごとに色分けすると概ね次のA~Dのようになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230428/20230428212358.png" width="411" height="365" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="tail-duplication">tail duplication</h3> <p>tail duplicationとは、末尾のブロックを複製して、前に処理されるブロックに結合することを言います。</p> <p>先のコードを例にすると、Dの部分を複製してD'を作り、BにDを、CにD'を統合することになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230428/20230428212401.png" width="1238" height="435" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>tail duplicationは一般にsuper-blockの形成の過程で行われます。</p> <h3 id="super-block">super-block</h3> <p>スーパーブロック(super-block)とは、頻繁に実行されるパスを表現するために用いられる、基本ブロック(basic block)の直線的なシーケンスであり、次のような性質を持ちます。</p> <ul> <li>シーケンスの先頭ブロックからのみたどれる <ul> <li>super-blockに向かってjmpしてこない</li> </ul> </li> <li>どの基本ブロックからも離脱できる <ul> <li>jmpしてsuper-blockから離脱できる</li> </ul> </li> <li>(スーパーブロック内の基本ブロックは、コード上で連続している必要はない)</li> </ul> <p>結果として、実行がスーパーブロックに到達した場合、そのスーパーブロック内のすべての基本ブロックが実行される可能性が非常に高い、と期待できます。これによってsuper-block以外のパスの実行時間が増大する可能性を増加させる可能性がある代わりに、実行頻度の高いsuper-blockに対してより積極的な最適化を可能とします。</p> <p>まずFigure2(下記論文より引用)に示しているのは、basic blockのシーケンス図です。図中の矢印に添えられた数字は任意の方法でプログラムを実際に実行してプロファイリングして得た、basic block間の遷移回数です。この中で最も頻繁に実行されるパスは<code>{A, B , E , F}</code>であることは明々白々でしょう。</p> <p>点線の枠はトレースとよばれ、順番に実行される傾向にある基本ブロックをグループ化したものです。super-blockとは異なり、先頭以外のbasic blockにジャンプしてくる可能性があります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230428/20230428212716.png" width="501" height="663" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ここからsuper-blockを形成するためにtail duplicationを行います。</p> <p><code>{A, B , E , F}</code> のトレースに入ってくる経路はFに対して入ってくる2箇所なので、D,Cの参照先をF'にすることで、Fに入る経路を無くします。その結果が下のFigure5(下記論文より引用)です。これによって<code>{A, B , E , F}</code>はsuper-blockになります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230428/20230428212718.png" width="452" height="693" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ref: - <a href="http://impact.crhc.illinois.edu/shared/journal/spe.profile-classic.91.pdf">P. P. Chang, S. A. Mahlke, W. W. Hwu, "Using Profile Information to Assist Classic Code Optimizations", Software Practice and Experience, Dec. 1991, Vol.21, No. 12</a></p> <h4 id="この論文への疑問">この論文への疑問</h4> <p>ところで読んでもよくわからなかったのですが、super-blockからjump-outするのはいいのにjump-inするのはだめとしているのはなぜなんでしょうね?一般にjmp命令は、CPUの命令パイプラインを壊しキャッシュミスを起こすために遅いことが知られていると思います。一方でjump-inしてくる場合、それ自体は同様にして遅いわけですが、その可能性があるからと言って、jumpせずに一連のコードとして実行されるときに対して最適化を阻害すると言うのは、いまいちしっくり来ません。</p> <p>もしかしてbasic blockを並べ替えてre-orderするとかそういうことを念頭においているのでしょうか・・・?</p> <h3 id="参考-CCにおけるプロファイリングとは">参考: C/C++におけるプロファイリングとは</h3> <p>先程、「任意の方法でプログラムを実際に実行してプロファイリングして得た、basic blockが実行された頻度」と言いましたが、C/C++においてこれはどのように行われるのでしょうか?</p> <p>C/C++の一般的な処理系は、プログラムをコンパイルしてから実行するタイプです。つまりそのままでは実行時の情報をコンパイル時にフィードバックできません。</p> <p>これに対する解決策がProfile-Guided Optimization (PGO)です。</p> <ol> <li>プロファイル生成機能を埋め込んでプログラムをビルドする</li> <li>それをビルド者が一般的なユースケースになるように実行してプロファイルを生成する</li> <li>プロファイル結果を含めて再度ビルドして完成</li> </ol> <p>PGOビルドはこんにちにおいて、別に目新しいものではなくずいぶんと昔から利用できるものでした。</p> <p>2014年頃からMicrosoftがVisual StudioのCommunity版をリリースしたことでますます一般的に利用できる最適化となっています。</p> <p><a href="https://rigaya34589.blog.fc2.com/blog-entry-540.html">rigayaの日記兼メモ帳 x265 ビルド ~ Visual Studio PGOビルド</a></p> <p>2回のコンパイルと試験実行という、実質的なビルド工程の増大を犠牲に、JITに負けない、実行速度の最大化が得られます。</p> <h2 id="YJITの特徴">YJITの特徴</h2> <p>まず前提として、動的型付け言語において、あるコードの任意の変数がどのような型を取り得るかを知るには、プログラム全体の解析が必要であり、極めてコストが大きいという問題があります。ネイティブコードを生成するには当然型情報が必要ですが、この型の評価はなるべく最小化したいわけです。</p> <p>これを満たすためのYJITの主要な概念は「Versioning of basic blocks」と「Lazy code generation」です。</p> <h3 id="Versioning-of-basic-blocks">Versioning of basic blocks</h3> <p>basic blockの中がどういう型で評価されたかは文脈によって変化します。この文脈ごとに、basic blockを特殊化します。</p> <p>・・・C++erは特殊化と言われるとすぐにtemplateを思い浮かべてしまうので、つまりこういう感じですかね・・・?依存する変数の型ごとにネイティブコードを吐き出せるみたいなイメージなのかなと解釈しました。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">namespace</span> flip_flop { <span class="synType">struct</span> <span class="synType">source_location</span> { <span class="synType">uint_least32_t</span> line; <span class="synType">uint_least32_t</span> column; <span class="synConstant">std</span>::<span class="synType">string_view</span> file_name; <span class="synConstant">std</span>::<span class="synType">string_view</span> function_name; <span class="synType">constexpr</span> <span class="synType">source_location</span>(<span class="synType">const</span> <span class="synConstant">std</span>::<span class="synType">source_location</span>&amp; loc) : <span class="synIdentifier">line</span>(loc.<span class="synIdentifier">line</span>()), <span class="synIdentifier">column</span>(loc.<span class="synIdentifier">column</span>()), <span class="synIdentifier">file_name</span>(loc.<span class="synIdentifier">file_name</span>()), <span class="synIdentifier">function_name</span>(loc.<span class="synIdentifier">function_name</span>()) {} <span class="synType">auto</span> <span class="synStatement">operator</span>&lt;=&gt;(<span class="synType">const</span> <span class="synType">source_location</span>&amp;) <span class="synType">const</span> = <span class="synStatement">default</span>; }; } <span class="synType">namespace</span> <span class="synConstant">std</span> { <span class="synComment">// std::hashの特殊化</span> <span class="synType">template</span>&lt;&gt; <span class="synType">struct</span> <span class="synType">hash</span>&lt;flip_flop::<span class="synType">source_location</span>&gt; { <span class="synType">size_t</span> <span class="synStatement">operator</span>()(<span class="synType">const</span> flip_flop::<span class="synType">source_location</span>&amp; l) <span class="synType">const</span> { <span class="synStatement">using</span> flip_flop::detail::hash_combine; <span class="synType">size_t</span> ret{}; <span class="synIdentifier">hash_combine</span>(ret, l.line); <span class="synIdentifier">hash_combine</span>(ret, l.column); <span class="synIdentifier">hash_combine</span>(ret, l.file_name); <span class="synIdentifier">hash_combine</span>(ret, l.function_name); <span class="synStatement">return</span> ret; } }; } </pre> <p>文脈ごとに、basic blockを特殊化することで、複数バージョンのbasic blockが作られうることになります。これによって型情報の蓄積と伝播が達成できるようになります。</p> <p>特殊化にあたってコンパイルの単位は大きい方がいいので、必要に応じてtail duplicationを実施します。</p> <h3 id="Lazy-code-generation">Lazy code generation</h3> <p>YJITは古典的なJITコンパイラとは異なり、メソッド毎のJITではなくより細かい単位であるbasic block単位でJITを行えます。</p> <p>実行中のプロファイリングの結果、同じメソッド内でも実際にはほとんど通過しないbasic blockが存在すると、そこはJITコンパイルを行いません。</p> <p>JITコンパイルしないということは、コストがかかる型情報の評価をYJITでやらなくていいので、高速化に繋がります。つまりJITコンパイルが本当に必要になるまで、型の評価を遅延できるわけです。</p> <p>同様にして、tail duplicationも遅延されます。つまり上で述べていたsuper-blockの形成とは少し異なって、必要なところだけ、必要なときにtail duplicationするということでしょう。</p> <h2 id="YJITがARM64をサポートするために">YJITがARM64をサポートするために</h2> <p>ARM64を捨てることはApple対応しないのと同じことであると断言しており、意欲的なARM64対応が進んでいます。</p> <p>Cで書かれていたYJITの初期実装を改めて、Rustで書き直されました。</p> <p>また、複数のプラットフォームに対応するために、IR層を導入しました。これはbasic block単位のIRであることが想定されています。また生成したIRはコンパイルが終わると破棄するのでメモリー消費を抑えています。</p> <p>ref: - <a href="https://rubykaigi.org/2022/presentations/maximecb.html#day1">Building a Lightweight IR and Backend for YJIT - RubyKaigi 2022</a></p> <h2 id="YJITの全体像">YJITの全体像</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230508/20230508205403.png" width="1072" height="794" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>maximecb. <a href="https://docs.google.com/presentation/d/1xJ4CwS13Tu2inO2-YxtSNhYmi2y8VDyW4QRpan0fKV0/edit#slide=id.g14a05e20d54_0_44">YJIT RubyKaigi 2022 slides - Google スライド</a>, 2023-05-08閲覧.</p> <p>YJITが動き出すトリガーはメソッドがしきい値を超える回数呼び出されることです。</p> <p>ここから上述したbasic blockごとの解析を始めるために、メソッドをStubします。このStubの呼び出しがあると、ネイティブコードの生成が開始されていきます。</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/BbLGqTxTRp0?start=625" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> <h2 id="RubyにおけるYJIT">RubyにおけるYJIT</h2> <p>RubyはずっとCで頑張ると思っていたところ、Rustで書きたいと来たのがYJITだったようです。</p> <p>YJITはoptionalな存在であること、YJITチームの生産性がRuby全体に対して重要であることからRustで書いていいといったところ、恐るべき速度で開発が行われた、とMatzは語っています。</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/m_LW5WIYJ9Q?start=2396" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> <p>ref: - <a href="https://rubykaigi.org/2022/presentations/yukihiro_matz.html#day2">Contribute to Ruby - RubyKaigi 2022</a></p> <h2 id="rubybuildからみたYJIT">rubybuildからみたYJIT</h2> <p>YJITを使うためには<code>--enable-yjit</code>オプションを付けた上で、rustcが必要となります。</p> <p>開発環境はもちろんとして、アセットコンパイルする環境にもrustcが必要となるわけです。</p> <p>CRubyを動かすためにgccのライフサイクルをこれまで気にしてきたわけですが、これに加えてrustcのライフサイクルも気にする必要が出てきたということです。</p> <iframe width="560" height="315" src="https://www.youtube.com/embed/J5c-3HY7uH0?start=1401" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> <p>ref: - <a href="https://rubykaigi.org/2022/presentations/hsbt.html#day3">Why is building the Ruby environment hard? - RubyKaigi 2022</a></p> <h2 id="という話だったのさ">という話だったのさ</h2> <p>他にもYJITの話はありましたが、一番気になるところはここまでに述べたような話がRubyKaigi 2022では展開されました。</p> <p>発表の概ね全てはYoutubeで公開されています。</p> <p><a href="https://www.youtube.com/playlist?list=PLbFmgWm555yYwwmwMvpC-RaqnmUTKB2EO">(318) RubyKaigi 2022 - YouTube</a></p> <h2 id="おわりに">おわりに</h2> <p>まもなくRubykaigi2023が開催されます。そして今年もまたYJITに関する発表が行われるようです。そんなときにこの記事を見て予習/復習しておくと、きっと皆様の理解の助けとなるでしょう。</p> <ul> <li><a href="https://rubykaigi.org/2023/presentations/alanwusx.html#may12">Fitting Rust YJIT into CRuby - RubyKaigi 2023</a></li> <li><a href="https://rubykaigi.org/2023/presentations/maximecb.html#may12">Optimizing YJIT’s Performance, from Inception to Production - RubyKaigi 2023</a></li> <li><a href="https://rubykaigi.org/2023/presentations/k0kubun.html#may13">Ruby JIT Hacking Guide - RubyKaigi 2023</a></li> </ul> <p>OPTiMでは、YJITのようなRubyの低レイヤーな話に興味がある人材も募集しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-yumehiko-todate 点群の重ね合わせに色情報も活用しよう!(Colored Point Cloud Registration の解説) hatenablog://entry/4207112889949434887 2023-04-27T10:00:00+09:00 2023-04-27T10:00:01+09:00 はじめに ICP(Iterative Closest Point)とは 点群の位置合わせ(Registration) ローカルとグローバル 基本のICP 方針 point-to-point point-to-plane 色情報を使ったICP(Colored Point Cloud Registration) 目的関数 概観 色情報の項の詳細 目的関数の最適化 ICPを動かそう まとめ はじめに こんにちは、R&Dユニットの葉山です。最近は測量アプリ Geo Scan の開発に取り組んでおり、その関係で点群を扱っております。今回は点群の位置合わせ(重ね合わせ)をご紹介させていただきます。基本的な… <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#ICPIterative-Closest-Pointとは">ICP(Iterative Closest Point)とは</a><ul> <li><a href="#点群の位置合わせRegistration">点群の位置合わせ(Registration)</a></li> <li><a href="#ローカルとグローバル">ローカルとグローバル</a></li> </ul> </li> <li><a href="#基本のICP">基本のICP</a><ul> <li><a href="#方針">方針</a></li> <li><a href="#point-to-point">point-to-point</a></li> <li><a href="#point-to-plane">point-to-plane</a></li> </ul> </li> <li><a href="#色情報を使ったICPColored-Point-Cloud-Registration">色情報を使ったICP(Colored Point Cloud Registration)</a><ul> <li><a href="#目的関数">目的関数</a><ul> <li><a href="#概観">概観</a></li> <li><a href="#色情報の項の詳細">色情報の項の詳細</a></li> </ul> </li> <li><a href="#目的関数の最適化">目的関数の最適化</a></li> </ul> </li> <li><a href="#ICPを動かそう">ICPを動かそう</a></li> <li><a href="#まとめ">まとめ</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>こんにちは、R&amp;Dユニットの葉山です。最近は測量アプリ <a href="https://www.optim.co.jp/construction/optim-geo-scan/">Geo Scan</a> の開発に取り組んでおり、その関係で点群を扱っております。今回は点群の位置合わせ(重ね合わせ)をご紹介させていただきます。基本的なICP(Iterative Closest Point)を説明したのち、その応用である色情報を活用したICP(Colored Point Cloud Registration)に関して解説を行います。</p> <h1 id="ICPIterative-Closest-Pointとは">ICP(Iterative Closest Point)とは</h1> <h2 id="点群の位置合わせRegistration">点群の位置合わせ(Registration)</h2> <p>異なる位置から取得した(別々の座標系上で取得された)2つの点群に関して、その位置関係を推定して合成することは応用上重要になります。具体的には、三次元モデルの構築やロボティクス(空間認識等)などの場面で活躍します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230419/20230419133557.png" width="1600" height="926" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>上図のように位置関係がわかっていない2つの点群を重ね合わせたい</strong></p> <p>点群の位置合わせ手法の中で代表的なものがICP(Iterative Closest Point)です。ICPでは、2つの点群を最適に重ね合わせることができる剛体変換(回転と並進)を反復計算によって求めます。<a href="#f-19b88aac" name="fn-19b88aac" title="お察しの通り非剛体(Non-rigid)な手法も存在します。本記事で紹介するICPはrigidな手法になります。">*1</a></p> <h2 id="ローカルとグローバル">ローカルとグローバル</h2> <p>点群の位置合わせ手法にはローカル(Local Registration)とグローバル(Global Registration)の2種類があります。</p> <p>ローカル手法は、初めから2つの点群同士がある程度重ね合わせられている(最適な重ね合わせ位置に近い)ことを前提としています。逆に、グローバル手法は初期位置に対する前提を必要としません。</p> <p>本記事で紹介しているICPはローカルな位置合わせ手法になります。</p> <h1 id="基本のICP">基本のICP</h1> <p>まずは基本的なICPに関して概説します。</p> <h2 id="方針">方針</h2> <p>source の点群(点の集合)<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BQ%7D" alt=" \mathbf{Q}"/>を、target の点群<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BP%7D" alt=" \mathbf{P}"/>に重ね合わせることを考えます。<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BQ%7D" alt=" \mathbf{Q}"/>の各点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bq%7D" alt=" \mathbf{q}"/>に対して変換行列<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BT%7D" alt=" \mathbf{T}"/>を作用させ、その結果である<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BTq%7D" alt=" \mathbf{Tq}"/>がどの程度<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BP%7D" alt=" \mathbf{P}"/>に重なっているかを、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BT%7D" alt=" \mathbf{T}"/>に対する目的関数<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E%28%5Cmathbf%7BT%7D%29" alt=" E(\mathbf{T})"/>を設定して評価します。あとは、目的関数を最小化することで重ね合わせの変換行列である<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BT%7D" alt=" \mathbf{T}"/>を求めます。</p> <p>最小化手法に何を使用するかは手法によって異なりますが、基本の ICP (point-to-point)の元論文<a href="#f-0c8155c9" name="fn-0c8155c9" title="Paul J. Besl and Neil D. McKay, A Method for Registration of 3D Shapes, PAMI, 1992.">*2</a>では単純なニュートン法を使用して解くアプローチが取られています。本記事で紹介しますが、色付き ICP (Colored Point Cloud Registration) の場合はガウス・ニュートン法によって解くアプローチが取られています。<a href="#f-a084a781" name="fn-a084a781" title="J. Park, Q.-Y. Zhou, and V. Koltun, Colored Point Cloud Registration Revisited, ICCV, 2017.">*3</a></p> <p>また、問題設定として<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BQ%7D" alt=" \mathbf{Q}"/>と<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BP%7D" alt=" \mathbf{P}"/>のどの点同士が対応するかはわかっていませんので、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E%28%5Cmathbf%7BT%7D%29" alt=" E(\mathbf{T})"/>の計算を行う上でも何らかの対応を与える必要があります。初期状態は各種手法で最近傍の点を求めそれを対応点とします。各繰り返しのステップでは変換後の<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BTq%7D" alt=" \mathbf{Tq}"/>に対してまた最近傍の点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>と対応を取れば良いでしょう。最近傍点の探索はk近傍法やOctree等適切なアルゴリズムを使用することができます。</p> <h2 id="point-to-point">point-to-point</h2> <p>point-to-point と呼ばれる ICP は、名前の通り対応点同士の距離(の二乗和)を最小化するように目的関数を設定します。従って、目的関数<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E%28%5Cmathbf%7BT%7D%29" alt=" E(\mathbf{T})"/>は以下のようになります。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AE%28%5Cmathbf%7BT%7D%29%3D%7B%5Csum%7D%28%5Cmathbf%7Bp%7D-%5Cmathbf%7BTq%7D%29%5E2%0A" alt=" \displaystyle E(\mathbf{T})={\sum}(\mathbf{p}-\mathbf{Tq})^2 "/> </div> <p>(以降でも省略していますが、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%7B%5Csum%7D" alt=" {\sum}"/> は<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/> と <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bq%7D" alt=" \mathbf{q}"/> のペアに対して総和をとっているものとお考えください。)</p> <h2 id="point-to-plane">point-to-plane</h2> <p>point-to-plane と呼ばれる ICP は、点と接平面に関して目的関数を設定します。「変換後の source の点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BTq%7D" alt=" \mathbf{Tq}"/>から target の点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>へのベクトル」と「点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>の法線ベクトル」の内積が小さくなるように、目的関数<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E%28%5Cmathbf%7BT%7D%29" alt=" E(\mathbf{T})"/>は以下のようになります。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AE%28%5Cmathbf%7BT%7D%29%3D%7B%5Csum%7D%7B%28%20%28%5Cmathbf%7Bp%7D-%5Cmathbf%7BTq%7D%29%7B%5Ccdot%7D%5Cmathbf%7Bn_p%7D%29%7D%5E2%0A" alt=" \displaystyle E(\mathbf{T})={\sum}{( (\mathbf{p}-\mathbf{Tq}){\cdot}\mathbf{n_p})}^2 "/> </div> <p>なぜこの二つのベクトルの内積を取るかは以下の図を参照ください。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230418/20230418204314.png" width="1600" height="817" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>point-to-point より point-to-plane の方が収束までの時間は早くなる傾向にありますが、target の点群の法線が必要になります。情報がない場合は法線推定の手法を用いて事前に計算が必要になりますので、そこでの計算量と合わせてどちらの手法が優れるかは考慮が必要でしょう。<a href="#f-2af85f81" name="fn-2af85f81" title="http://www.open3d.org/docs/release/tutorial/pipelines/icp_registration.html">*4</a></p> <h1 id="色情報を使ったICPColored-Point-Cloud-Registration">色情報を使ったICP(Colored Point Cloud Registration)</h1> <p>点群が位置情報の他に色情報を持っている場合、それらを活用することでより最適にかつ安定して点群の重ね合わせができる可能性があります。色情報を使ったICPはpoint-to-planeのICPの拡張版といえます。</p> <h2 id="目的関数">目的関数</h2> <h3 id="概観">概観</h3> <p>目的関数は以下のように表されます。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AE%28%5Cmathbf%7BT%7D%29%3D%281-%7B%5Csigma%7D%29E_c%28%5Cmathbf%7BT%7D%29%2B%7B%5Csigma%7DE_g%28%5Cmathbf%7BT%7D%29%0A" alt=" \displaystyle E(\mathbf{T})=(1-{\sigma})E_c(\mathbf{T})+{\sigma}E_g(\mathbf{T}) "/> </div> <p>ここで、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%7B%5Csigma%7D" alt=" {\sigma}"/>はヒューリスティックな重み付けの定数であり、値は実験的に決定されます。<a href="#f-ec9d9d0a" name="fn-ec9d9d0a" title="Open3Dでのデフォルト値は0.968のようです [https://github.com/isl-org/Open3D/blob/aebdb9a4396808edece4c24156e0231dc47a5edd/cpp/open3d/pipelines/registration/ColoredICP.h#L74]">*5</a></p> <p>また、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E_c%28%5Cmathbf%7BT%7D%29" alt=" E_c(\mathbf{T})"/>は色情報を使用した項であり、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E_g%28%5Cmathbf%7BT%7D%29" alt=" E_g(\mathbf{T})"/>は位置情報を使用した項になります。各項の中身をそれぞれ見てみると、</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AE_c%28%5Cmathbf%7BT%7D%29%3D%7B%5Csum%7D%7B%28C_p%28%5Cmathbf%7Bf%7D%28%5Cmathbf%7BT%7D%5Cmathbf%7Bq%7D%29%29-C%28%5Cmathbf%7Bq%7D%29%29%7D%5E2%0A" alt=" \displaystyle E_c(\mathbf{T})={\sum}{(C_p(\mathbf{f}(\mathbf{T}\mathbf{q}))-C(\mathbf{q}))}^2 "/> </div> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AE_g%28%5Cmathbf%7BT%7D%29%3D%7B%5Csum%7D%7B%28%20%28%5Cmathbf%7Bp%7D-%5Cmathbf%7BTq%7D%29%7B%5Ccdot%7D%5Cmathbf%7Bn_p%7D%29%7D%5E2%0A" alt=" \displaystyle E_g(\mathbf{T})={\sum}{( (\mathbf{p}-\mathbf{Tq}){\cdot}\mathbf{n_p})}^2 "/> </div> <p>となっており、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E_g%28%5Cmathbf%7BT%7D%29" alt=" E_g(\mathbf{T})"/>はそのままpoint-to-planeの場合の目的関数と一致します。従って、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%7B%5Csigma%7D%3D1" alt=" {\sigma}=1"/>の場合はpoint-to-planeのICPと一致することがわかります。</p> <p>後の最適化の説明で使用するので、ここでは <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Csum" alt=" \sum"/> の二乗和の中身をそれぞれ以下のように置いておきます。後の説明のために <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bs%28q%2C%20T%29%7D%20%3D%20%5Cmathbf%7BTq%7D" alt=" \mathbf{s(q, T)} = \mathbf{Tq}"/> と関数を置いていることに注意ください。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0Ar_c%28%5Cmathbf%7BT%7D%29%3DC_p%28%5Cmathbf%7Bf%7D%28%5Cmathbf%7Bs%28q%2C%20T%29%7D%29%29-C%28%5Cmathbf%7Bq%7D%29%0A" alt=" \displaystyle r_c(\mathbf{T})=C_p(\mathbf{f}(\mathbf{s(q, T)}))-C(\mathbf{q}) "/> </div> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0Ar_g%28%5Cmathbf%7BT%7D%29%3D%28%5Cmathbf%7Bp%7D-%5Cmathbf%7Bs%28q%2C%20T%29%7D%29%7B%5Ccdot%7D%5Cmathbf%7Bn_p%7D%0A" alt=" \displaystyle r_g(\mathbf{T})=(\mathbf{p}-\mathbf{s(q, T)}){\cdot}\mathbf{n_p} "/> </div> <h3 id="色情報の項の詳細">色情報の項の詳細</h3> <p>さて、次は色情報を使った項を細かく見ていきます。</p> <p>まず、関数<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bf%7D%28%5Cmathbf%7Bs%7D%29" alt=" \mathbf{f}(\mathbf{s})"/>は、ある3次元空間上の点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bs%7D" alt=" \mathbf{s}"/>に対して、点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>の接平面に対する投影点を得る関数です。定義は以下のようになります。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cmathbf%7Bf%7D%28%5Cmathbf%7Bs%7D%29%3D%5Cmathbf%7Bs%7D-%5C%7B%28%5Cmathbf%7Bs%7D-%5Cmathbf%7Bp%7D%29%7B%5Ccdot%7D%5Cmathbf%7Bn_p%7D%5C%7D%5Cmathbf%7Bn_p%7D%0A" alt=" \displaystyle \mathbf{f}(\mathbf{s})=\mathbf{s}-\{(\mathbf{s}-\mathbf{p}){\cdot}\mathbf{n_p}\}\mathbf{n_p} "/> </div> <p>この関数に関しては、以下の図に示すように点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bs%7D" alt=" \mathbf{s}"/>を接平面に対して投影していることがわかります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230418/20230418205057.png" width="1600" height="1164" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>次に、関数<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20C%28%5Cmathbf%7Bq%7D%29" alt=" C(\mathbf{q})"/>は、点群中の点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bq%7D" alt=" \mathbf{q}"/>に関する色情報を出力する関数です。</p> <p>最後に、関数<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20C_p%28%5Cmathbf%7Bu%7D%29" alt=" C_p(\mathbf{u})"/>は接平面上で点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>から<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bu%7D" alt=" \mathbf{u}"/>ずれた位置(つまり、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bu%7D%7B%5Ccdot%7D%5Cmathbf%7Bn_p%7D%3D0" alt=" \mathbf{u}{\cdot}\mathbf{n_p}=0"/>の条件がある)の色を返す関数ですが、近似によって以下のように表すことができます。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0AC_p%28%5Cmathbf%7Bu%7D%29%7B%5Capprox%7DC%28%5Cmathbf%7Bp%7D%29%2B%5Cmathbf%7Bd_p%7D%7B%5Ccdot%7D%5Cmathbf%7Bu%7D%0A" alt=" \displaystyle C_p(\mathbf{u}){\approx}C(\mathbf{p})+\mathbf{d_p}{\cdot}\mathbf{u} "/> </div> <p>連続関数として考えている<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20C_p%28%5Cmathbf%7Bu%7D%29" alt=" C_p(\mathbf{u})"/>に関して、点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>まわりの展開を一階微分で切った形になります。<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bd_p%7D" alt=" \mathbf{d_p}"/>は<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20C_p%28%5Cmathbf%7Bu%7D%29" alt=" C_p(\mathbf{u})"/>のgradientであり、実際には点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>の近傍点を使って最小二乗法によって計算されます。</p> <p>以上より、色情報を使った項<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E_c%28%5Cmathbf%7BT%7D%29" alt=" E_c(\mathbf{T})"/>は、「点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>」と「点<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BTq%7D" alt=" \mathbf{Tq}"/>を接平面に投影した点」の「色情報ベクトルの差(の2乗)」の総和であることがわかります。</p> <h2 id="目的関数の最適化">目的関数の最適化</h2> <p>色情報を使った ICP の元論文で紹介されている最適化のプロセスを、ここでは簡単にご紹介させていただきます。<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20E%28%5Cmathbf%7BT%7D%29" alt=" E(\mathbf{T})"/> をガウス・ニュートン法で最適化する方式です。</p> <p>ガウス・ニュートン法の詳細はここでは省きますが、ヤコビアン<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BJ%7D" alt=" \mathbf{J}"/>と残差(ここでは<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bp%7D" alt=" \mathbf{p}"/>と<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BTq%7D" alt=" \mathbf{Tq}"/>の差)<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Br%7D" alt=" \mathbf{r}"/>があるとき、</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%20%5Cmathbf%7BJ%7D%5ET%20%5Cmathbf%7BJ%7D%5CDelta%5Cmathbf%7B%5Clambda_k%7D%20%3D%20%20%5Cmathbf%7BJ%7D%5ET%5Cmathbf%7Br%7D%0A" alt=" \displaystyle \mathbf{J}^T \mathbf{J}\Delta\mathbf{\lambda_k} = \mathbf{J}^T\mathbf{r} "/> </div> <p>となるような <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5CDelta%5Cmathbf%7B%5Clambda_k%7D" alt=" \Delta\mathbf{\lambda_k}"/>が分かれば、次のステップの値 <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7B%5Clambda_%7Bk%2B1%7D%7D" alt=" \mathbf{\lambda_{k+1}}"/> は以下の式で求めることができます。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cmathbf%7B%5Clambda_%7Bk%2B1%7D%7D%20%3D%5Cmathbf%7B%5Clambda_k%7D%20%2B%20%20%5CDelta%5Cmathbf%7B%5Clambda_k%7D%0A" alt=" \displaystyle \mathbf{\lambda_{k+1}} =\mathbf{\lambda_k} + \Delta\mathbf{\lambda_k} "/> </div> <p>さて、色情報を使った ICP に関して、あるステップ k+1 の変換行列 <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BT_%7Bk%2B1%7D%7D" alt=" \mathbf{T_{k+1}}"/> は、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BT_k%7D" alt=" \mathbf{T_k}"/> を用いて以下のように線形に近似します。<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BT_k%7D" alt=" \mathbf{T_k}"/> から少しだけ回転 &amp; 並行移動したイメージかと思われます。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cmathbf%7BT_%7Bk%2B1%7D%7D%20%5Capprox%20%5Cleft%28%0A%5Cbegin%7Barray%7D%7Brr%7D%0A1%20%26%20-%5Cgamma%20%26%20%5Cbeta%20%26%20a%20%5C%5C%0A%5Cgamma%20%26%201%20%26%20-%5Calpha%20%26%20b%20%5C%5C%0A-%5Cbeta%20%26%20%5Calpha%20%26%201%20%26%20c%20%5C%5C%0A0%20%26%200%20%26%200%20%26%201%20%5C%5C%0A%5Cend%7Barray%7D%0A%5Cright%29%0A%5Cmathbf%7BT_k%7D%0A" alt=" \displaystyle \mathbf{T_{k+1}} \approx \left( \begin{array}{rr} 1 &amp; -\gamma &amp; \beta &amp; a \\ \gamma &amp; 1 &amp; -\alpha &amp; b \\ -\beta &amp; \alpha &amp; 1 &amp; c \\ 0 &amp; 0 &amp; 0 &amp; 1 \\ \end{array} \right) \mathbf{T_k} "/> </div> <p>ここで <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7B%5Cxi%7D%20%3D%20%28%5Calpha%2C%20%5Cbeta%2C%20%5Cgamma%2C%20a%2C%20b%2C%20c%29" alt=" \mathbf{\xi} = (\alpha, \beta, \gamma, a, b, c)"/> という6要素のベクトルを置くと、ヤコビアンと残差を用いて以下の式が成り立ちます。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%20%5Cmathbf%7BJ%7D%5ET%20%5Cmathbf%7BJ%7D%5Cmathbf%7B%5Cxi%7D%20%3D%20%20%5Cmathbf%7BJ%7D%5ET%5Cmathbf%7Br%7D%0A" alt=" \displaystyle \mathbf{J}^T \mathbf{J}\mathbf{\xi} = \mathbf{J}^T\mathbf{r} "/> </div> <p>この線型方程式を解いて <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7B%5Cxi%7D" alt=" \mathbf{\xi}"/> を求め、次のステップの変換行列の値を求めます。</p> <p>残差とヤコビアンを位置情報の項と色情報の項に分けて考えると、以下のようになります。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%20%5Cmathbf%7Br%7D%20%3D%20%5B%5Csqrt%7B1-%7B%5Csigma%7D%7D%5Cmathbf%7Br%7D_c%3B%5Csqrt%7B%5Csigma%7D%5Cmathbf%7Br%7D_g" alt=" \displaystyle \mathbf{r} = [\sqrt{1-{\sigma}}\mathbf{r}_c;\sqrt{\sigma}\mathbf{r}_g"/> ] </div> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%20%5Cmathbf%7Br%7D_c%20%3D%20r_c%28%5Cmathbf%7BT%7D%29%7C_%7B%5Cmathbf%7BT%7D%3D%5Cmathbf%7BT_k%7D%7D%20%0A" alt=" \displaystyle \mathbf{r}_c = r_c(\mathbf{T})|_{\mathbf{T}=\mathbf{T_k}} "/> </div> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%20%5Cmathbf%7Br%7D_g%20%3D%20r_g%28%5Cmathbf%7BT%7D%29%7C_%7B%5Cmathbf%7BT%7D%3D%5Cmathbf%7BT_k%7D%7D%20%0A" alt=" \displaystyle \mathbf{r}_g = r_g(\mathbf{T})|_{\mathbf{T}=\mathbf{T_k}} "/> </div> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%20%5Cmathbf%7BJ%7D%20%3D%20%5B%5Csqrt%7B1-%7B%5Csigma%7D%7D%5Cmathbf%7BJ%7D_c%3B%5Csqrt%7B%5Csigma%7D%5Cmathbf%7BJ%7D_g" alt=" \displaystyle \mathbf{J} = [\sqrt{1-{\sigma}}\mathbf{J}_c;\sqrt{\sigma}\mathbf{J}_g"/> ] </div> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%20%5Cmathbf%7BJ%7D_c%20%3D%20%20%5Cnabla%20r_c%28%5Cmathbf%7BT%7D%29%7C_%7B%5Cmathbf%7BT%7D%3D%5Cmathbf%7BT_k%7D%7D%20%0A" alt=" \displaystyle \mathbf{J}_c = \nabla r_c(\mathbf{T})|_{\mathbf{T}=\mathbf{T_k}} "/> </div> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%20%5Cmathbf%7BJ%7D_g%20%3D%20%20%5Cnabla%20r_g%28%5Cmathbf%7BT%7D%29%7C_%7B%5Cmathbf%7BT%7D%3D%5Cmathbf%7BT_k%7D%7D%20%0A" alt=" \displaystyle \mathbf{J}_g = \nabla r_g(\mathbf{T})|_{\mathbf{T}=\mathbf{T_k}} "/> </div> <p>ヤコビアンに出てくる <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cnabla%20r_c" alt=" \nabla r_c"/> と <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cnabla%20r_g" alt=" \nabla r_g"/> ですが、微分の chain rule を用いて以下のように変形できます。</p> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cnabla%20r_c%28%5Cmathbf%7BT%7D%29%20%3D%20%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%5Cmathbf%7B%5Cxi%7D%7D%28C_p%20%5Ccirc%20%5Cmathbf%7Bf%7D%20%5Ccirc%20%5Cmathbf%7Bs%7D%29%20%3D%20%5Cnabla%20C_p%28%5Cmathbf%7Bf%7D%29%5Cmathbf%7BJ_f%7D%28%5Cmathbf%7Bs%7D%29%5Cmathbf%7BJ_s%7D%28%5Cmathbf%7B%5Cxi%7D%29%0A" alt=" \displaystyle \nabla r_c(\mathbf{T}) = \frac{\partial}{\partial\mathbf{\xi}}(C_p \circ \mathbf{f} \circ \mathbf{s}) = \nabla C_p(\mathbf{f})\mathbf{J_f}(\mathbf{s})\mathbf{J_s}(\mathbf{\xi}) "/> </div> <div align='center' class='scroll'> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cdisplaystyle%0A%5Cnabla%20r_g%28%5Cmathbf%7BT%7D%29%20%3D%20%5Cmathbf%7Bn_p%7D%5ET%5Cmathbf%7BJ_s%7D%28%5Cmathbf%7B%5Cxi%7D%29%0A" alt=" \displaystyle \nabla r_g(\mathbf{T}) = \mathbf{n_p}^T\mathbf{J_s}(\mathbf{\xi}) "/> </div> <p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cnabla%20C_p" alt=" \nabla C_p"/> は <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20C_p" alt=" C_p"/> の gradient なので、色情報の項で出てきた <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bd_p%7D" alt=" \mathbf{d_p}"/> ですね! <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BJ_f%7D" alt=" \mathbf{J_f}"/> は <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bf%28s%29%7D" alt=" \mathbf{f(s)}"/> に関するヤコビアン、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7BJ_s%7D" alt=" \mathbf{J_s}"/> は <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cmathbf%7Bs%28%5Cmathbf%7B%5Cxi%7D%29%7D" alt=" \mathbf{s(\mathbf{\xi})}"/>に関するヤコビアンであり、それぞれの式から導出が可能です。</p> <h1 id="ICPを動かそう">ICPを動かそう</h1> <p>本記事で解説している ICP は、 Open3D を用いて簡単に動かすことができます。興味がある方はぜひお試しください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fwww.open3d.org%2Fdocs%2Frelease%2Ftutorial%2Fpipelines%2Ficp_registration.html" title="ICP registration — Open3D 0.17.0 documentation" 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="http://www.open3d.org/docs/release/tutorial/pipelines/icp_registration.html">www.open3d.org</a></cite></p> <p>参考までに、 point-to-plane を動かす場合の Python スクリプトは以下のようになります。点群は X, Y, Z, R, G, B のような順序でスペース区切りで格納しておけば読み込めます(RGBは[0-1]に正規化が必要)。入出力の詳細は Open3D のリファレンスをご確認ください。以下のスクリプトを見るとわかる通り、 point-to-plane の場合 target の点群に対して(法線の情報がないなら)法線推定が必要になります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> argparse <span class="synPreProc">from</span> pathlib <span class="synPreProc">import</span> Path <span class="synPreProc">import</span> open3d <span class="synStatement">as</span> o3d <span class="synStatement">def</span> <span class="synIdentifier">parse_args</span>(): parser = argparse.ArgumentParser() <span class="synComment"># 各パラメータは点群に応じて調整</span> parser.add_argument(<span class="synConstant">'--max_correspondence_distance'</span>, <span class="synIdentifier">type</span>=<span class="synIdentifier">float</span>, default=<span class="synConstant">0.02</span>) parser.add_argument(<span class="synConstant">'--max_iteration'</span>, <span class="synIdentifier">type</span>=<span class="synIdentifier">int</span>, default=<span class="synConstant">500</span>) parser.add_argument(<span class="synConstant">'--estimate_normals_max_nn'</span>, <span class="synIdentifier">type</span>=<span class="synIdentifier">int</span>, default=<span class="synConstant">30</span>) parser.add_argument(<span class="synConstant">'--estimate_normals_radius'</span>, <span class="synIdentifier">type</span>=<span class="synIdentifier">float</span>, default=<span class="synConstant">0.1</span>) parser.add_argument(<span class="synConstant">'--pcd_source'</span>, <span class="synIdentifier">type</span>=Path) parser.add_argument(<span class="synConstant">'--pcd_target'</span>, <span class="synIdentifier">type</span>=Path) <span class="synStatement">return</span> parser.parse_args() <span class="synStatement">def</span> <span class="synIdentifier">main</span>(args): pcd_source = o3d.io.read_point_cloud(<span class="synIdentifier">str</span>(args.pcd_source)) pcd_target = o3d.io.read_point_cloud(<span class="synIdentifier">str</span>(args.pcd_target)) <span class="synStatement">assert</span> pcd_source.has_points() <span class="synStatement">assert</span> pcd_source.has_colors() <span class="synStatement">assert</span> pcd_source.dimension() == <span class="synConstant">3</span> <span class="synStatement">assert</span> pcd_target.has_points() <span class="synStatement">assert</span> pcd_target.has_colors() <span class="synStatement">assert</span> pcd_target.dimension() == <span class="synConstant">3</span> <span class="synComment"># 事前に点群の重なり具合の評価値を確認しておくと、ICP の有効性が確認しやすい</span> evaluation = o3d.pipelines.registration.evaluate_registration( source = pcd_source, target = pcd_target, max_correspondence_distance = args.max_correspondence_distance) <span class="synComment"># target の点群の法線推定</span> pcd_target.estimate_normals( search_param=o3d.geometry.KDTreeSearchParamHybrid( radius=args.estimate_normals_radius, max_nn=args.estimate_normals_max_nn ) ) <span class="synComment"># point-to-plane ICP を適用</span> reg = o3d.pipelines.registration.registration_icp( source = pcd_source, target = pcd_target, max_correspondence_distance = args.max_correspondence_distance, estimation_method = o3d.pipelines.registration.TransformationEstimationPointToPlane(), criteria = o3d.pipelines.registration.ICPConvergenceCriteria(max_iteration=args.max_iteration) ) <span class="synComment"># reg.transformation が結果の変換行列</span> pcd_source.transform(reg.transformation) o3d.io.write_point_cloud(<span class="synIdentifier">str</span>(args.pcd_source.parent / <span class="synConstant">'icp.ply'</span>), pcd_source) <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: main(parse_args()) </pre> <h1 id="まとめ">まとめ</h1> <p>本記事では基本のICPから、色情報を使ったICPまでを概説しました。色情報を使ったICPも、数式を紐解いていけば考え方は非常に単純であることがわかります。色情報を持っている点群を扱う場合は、ケースに応じてColored Point Cloud Registrationもぜひ活用していきたいところです。</p> <p>オプティムではAI・IoT技術によりビジネス課題・社会的課題の解決を目指しています。ぜひ以下の採用情報もご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-19b88aac" name="f-19b88aac" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">お察しの通り非剛体(Non-rigid)な手法も存在します。本記事で紹介するICPはrigidな手法になります。</span></p> <p class="footnote"><a href="#fn-0c8155c9" name="f-0c8155c9" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">Paul J. Besl and Neil D. McKay, A Method for Registration of 3D Shapes, PAMI, 1992.</span></p> <p class="footnote"><a href="#fn-a084a781" name="f-a084a781" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text">J. Park, Q.-Y. Zhou, and V. Koltun, Colored Point Cloud Registration Revisited, ICCV, 2017.</span></p> <p class="footnote"><a href="#fn-2af85f81" name="f-2af85f81" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="http://www.open3d.org/docs/release/tutorial/pipelines/icp_registration.html">http://www.open3d.org/docs/release/tutorial/pipelines/icp_registration.html</a></span></p> <p class="footnote"><a href="#fn-ec9d9d0a" name="f-ec9d9d0a" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text">Open3Dでのデフォルト値は0.968のようです <a href="https://github.com/isl-org/Open3D/blob/aebdb9a4396808edece4c24156e0231dc47a5edd/cpp/open3d/pipelines/registration/ColoredICP.h#L74">https://github.com/isl-org/Open3D/blob/aebdb9a4396808edece4c24156e0231dc47a5edd/cpp/open3d/pipelines/registration/ColoredICP.h#L74</a></span></p> </div> optim-yukihayama OpenAPIのschemasで JSONデータを検証する方法 hatenablog://entry/4207112889972141853 2023-03-20T10:00:00+09:00 2023-03-20T10:00:02+09:00 はじめに ビジネス統括本部のイチノです。最近は、ChatGPTにシェルスクリプトの書き方を教えてもらっています。 今回は、OpenAPIのschemasで JSONデータを検証する方法のメモです。 検証方法 openapi-schema-validator というライブラリを利用して JSON データがOpenAPIの schemas に合致するかを検証します。 pypi.org サンプルは以下のとおりです。説明用に OpenAPI のドキュメントや JSON データを直接書いています。利用する際は、データの直書きではなくファイル読み込みに修正すると使い勝手が良くなります。 #!/usr/bi… <h2 id="はじめに">はじめに</h2> <p>ビジネス統括本部のイチノです。最近は、ChatGPTにシェルスクリプトの書き方を教えてもらっています。</p> <p>今回は、OpenAPIのschemasで JSONデータを検証する方法のメモです。</p> <h2 id="検証方法">検証方法</h2> <p>openapi-schema-validator というライブラリを利用して JSON データがOpenAPIの schemas に合致するかを検証します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fpypi.org%2Fproject%2Fopenapi-schema-validator%2F" title="openapi-schema-validator" 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://pypi.org/project/openapi-schema-validator/">pypi.org</a></cite></p> <p>サンプルは以下のとおりです。説明用に OpenAPI のドキュメントや JSON データを直接書いています。利用する際は、データの直書きではなくファイル読み込みに修正すると使い勝手が良くなります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment">#!/usr/bin/env python3</span> <span class="synPreProc">import</span> json <span class="synPreProc">import</span> yaml <span class="synPreProc">from</span> openapi_schema_validator <span class="synPreProc">import</span> validate <span class="synPreProc">from</span> jsonschema.validators <span class="synPreProc">import</span> RefResolver OPENAPI_SPEC = <span class="synConstant">'''</span> <span class="synConstant">openapi: 3.0.3</span> <span class="synConstant">info:</span> <span class="synConstant"> title: OpenAPI ドキュメント</span> <span class="synConstant"> version: 1.0.0</span> <span class="synConstant">paths:</span> <span class="synConstant"> # 省略</span> <span class="synConstant">components:</span> <span class="synConstant"> schemas:</span> <span class="synConstant"> Server:</span> <span class="synConstant"> type: object</span> <span class="synConstant"> properties:</span> <span class="synConstant"> os:</span> <span class="synConstant"> type: string</span> <span class="synConstant"> admin:</span> <span class="synConstant"> $ref: '#/components/schemas/User'</span> <span class="synConstant"> User:</span> <span class="synConstant"> type: object</span> <span class="synConstant"> properties:</span> <span class="synConstant"> id:</span> <span class="synConstant"> type: integer</span> <span class="synConstant"> name:</span> <span class="synConstant"> type: string</span> <span class="synConstant"> password:</span> <span class="synConstant"> type: string</span> <span class="synConstant">'''</span> TEST_JSON = <span class="synConstant">'''</span> <span class="synConstant">{</span> <span class="synConstant"> &quot;os&quot;: &quot;Linux&quot;,</span> <span class="synConstant"> &quot;admin&quot;: {</span> <span class="synConstant"> &quot;id&quot;: 1000,</span> <span class="synConstant"> &quot;name&quot;: &quot;optim&quot;,</span> <span class="synConstant"> &quot;password&quot;: &quot;1234567890&quot;</span> <span class="synConstant"> }</span> <span class="synConstant">}</span> <span class="synConstant">'''</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): spec = yaml.safe_load(OPENAPI_SPEC) schemas = spec[<span class="synConstant">'components'</span>][<span class="synConstant">'schemas'</span>] ref_resolver = RefResolver.from_schema(spec) example = json.loads(TEST_JSON) validate(example, schemas[<span class="synConstant">'Server'</span>], resolver=ref_resolver) <span class="synIdentifier">print</span>(<span class="synConstant">'Succeeded!'</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: main() </pre> <p>実行に必要なライブラリを pip でインストール。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ pip install openapi-schema-validator pyyaml </pre> <p>実行して検証に成功すると <code>Succeeded!</code> と出力されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ python3 <span class="synStatement">test</span>.py Succeeded! </pre> <p>試しにJSON_DATAの "os" を <code>1</code> に書き換えると、不正なJSONデータとして検知してくれます。</p> <pre class="code lang-python" data-lang="python" data-unlink>Traceback (most recent call last): File <span class="synConstant">&quot;/app/test.py&quot;</span>, line <span class="synConstant">58</span>, <span class="synStatement">in</span> &lt;module&gt; main() File <span class="synConstant">&quot;/app/test.py&quot;</span>, line <span class="synConstant">54</span>, <span class="synStatement">in</span> main validate(example, schemas[<span class="synConstant">'Server'</span>], resolver=ref_resolver) File <span class="synConstant">&quot;/usr/local/lib/python3.11/site-packages/openapi_schema_validator/shortcuts.py&quot;</span>, line <span class="synConstant">23</span>, <span class="synStatement">in</span> validate <span class="synStatement">raise</span> error jsonschema.exceptions.ValidationError: <span class="synConstant">1</span> <span class="synStatement">is</span> <span class="synStatement">not</span> of <span class="synIdentifier">type</span> <span class="synConstant">'string'</span> Failed validating <span class="synConstant">'type'</span> <span class="synStatement">in</span> schema[<span class="synConstant">'properties'</span>][<span class="synConstant">'os'</span>]: {<span class="synConstant">'type'</span>: <span class="synConstant">'string'</span>} On instance[<span class="synConstant">'os'</span>]: <span class="synConstant">1</span> </pre> <h2 id="おわりに">おわりに</h2> <p>OpenAPI のschemas でJSONデータを検証する方法を紹介しました。エンジニアに限らず募集中ですので、ぜひご覧ください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-ichino WebSocketで送信できているように見えるデータが欠損している件を調査した件 hatenablog://entry/4207112889969394373 2023-03-08T10:00:00+09:00 2023-03-08T10:00:02+09:00 こんにちは。プラットフォーム事業部の中村です。 以前の記事を執筆した際はR&Dチームに所属していたのですが、異動の機会があったので他サービスも経験して幅を広げたいなぁという思いで、それに乗っかって異動してはや2年くらい経ちました。 OPTiMでは言語だとかフレームワークだとか開発に関する裁量がチームにあるため、社内異動しただけでもかなり新鮮味があって刺激的な毎日を過ごしています。提供するサービスの広さがこういった世界観を生み出してるんだと思います。 閑話休題。 タイトルが某テレビ番組っぽくなりましたが、先日私が所属しているチーム内で開発しているAPIサーバーへ、別チームが開発しているクライアン… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230307/20230307195047.png" width="1600" height="553" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。プラットフォーム事業部の中村です。</p> <p><a href="https://tech-blog.optim.co.jp/entry/2019/05/31/163000">以前の記事</a>を執筆した際はR&amp;Dチームに所属していたのですが、異動の機会があったので他サービスも経験して幅を広げたいなぁという思いで、それに乗っかって異動してはや2年くらい経ちました。 OPTiMでは言語だとかフレームワークだとか開発に関する裁量がチームにあるため、社内異動しただけでもかなり新鮮味があって刺激的な毎日を過ごしています。提供するサービスの広さがこういった世界観を生み出してるんだと思います。</p> <p>閑話休題。</p> <p>タイトルが某テレビ番組っぽくなりましたが、先日私が所属しているチーム内で開発しているAPIサーバーへ、別チームが開発しているクライアントから送信しているデータの一部が欠損するという事象が発生していました。これに対して調査を行っていたのですが、無事にクローズへと持っていけそうになったので残しておこうと思います。大それた内容ではないですが、バグやこういった事象に対してどういうアプローチをしていったかという点をお伝えできればと思います。</p> <h2 id="発端">発端</h2> <p>とあるクライアントからサーバーに対してデータを定期的に送信するシステムがあり、細かいデータを連続して送る必要があるという特性を考慮して、WebSocketでデータをサーバー側へ送るという実装になっていました。ところがある日データを確認すると、いくつか送信したはずなのにサーバー側で格納されていないデータが存在するということが分かりました。この時クライアント上のログにはエラーがなく、クライアントから見るとサーバー側へのデータ送信は成功しているように見えていたので、私のチームへ確認の依頼が来ました。</p> <h2 id="調査過程">調査過程</h2> <h3 id="1ログとデータを見る再現環境を作るそして祈る">1.ログとデータを見る、再現環境を作る、そして祈る</h3> <p>まずは範囲を絞りたいので <strong>(1)クライアント側の問題</strong>なのか、<strong>(2)経路上の問題</strong>なのか、<strong>(3)サーバー側の問題</strong>なのかに目星をつけるために、それぞれのログを確認しました。クライアント側にもネットワーク起因で再接続したログなどはありましたが、欠損時間帯に近い時刻でのエラーログのようなものはこの時見つかりませんでした。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230307/20230307191906.png" width="1600" height="531" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>欠損したデータを確認していくと、<strong>常に発生するというわけではなく稀にしか発生しない現象</strong>だということがわかりました。再現条件を整えることが困難だと判断したため、再現する可能性のある環境を整える方向に舵を切り、クライアント側の開発チームと連携して開発環境でも本番と似たような状態を作り、再現するのをお祈りできる状態を作りました。併せてサーバー側はより詳細なログを取れるように、WebSocketで到着したデータの内部情報までログに出力する変更を行い、より詳細な検証ができる状態にしたものを開発環境へデプロイしました。</p> <h3 id="2データ欠損のパターンを見る">2.データ欠損のパターンを見る</h3> <p>上記対応を入れて放置すること数週間、同様にデータ欠損が開発環境でも無事に再現しました。この段階で環境差分による要因ではないということが併せて確認できました。同じようにログを確認すると、<strong>クライアント側のログにはデータ欠損のあったほぼ同時刻にはエラーは記録されておらず</strong>、継続してデータを送信しているログとなっていました。一方でサーバー側のログでは、そもそもに<strong>欠損したデータが到着した形跡がない</strong>ことが分かりました。</p> <p>ログの出力内容が正しいという前提で、この結果から(1)クライアントと(3)サーバーの問題ではなく、<strong>(2)経路の問題である可能性が高く</strong>なりました。現在OPTiMでは週3出社を原則としたレギュレーションになっており<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>、この時の情報の整理や洗い出しは出社日にメンバーでホワイトボードを囲みながら行っていました。オンラインホワイトボードサービスを利用してはいますが、こういった時は顔を合わせて「あれでもない」「これでもない」とディスカッションする方がスピードも、アイデアの幅も広いなと感じました。あくまで当人比です。</p> <p>再現した際のログをより読み込んで欠損パターンを見ると、ポツポツと穴抜けに欠損するのではなく、<strong>一度欠損が始まるとしばらくの間(数分間)データ到着した形跡がサーバーのログから消え</strong>、しばらくすると届くようになるという事象になっていることが分かりました。以降、経路が不安定になっていたりするのではなく、<strong>何かのイベントを契機としてデータの欠損が起きているという</strong> 仮説をおいて確認を進めることにしました。</p> <h3 id="3-ログの範囲拡大して確認する">3. ログの範囲拡大して確認する</h3> <p>サーバー側はk8s(EKS)上に構築されており、ログ収集にDatadogを用いていました。Datadogより欠損が始まった同時刻に何かしらのイベントがないかを経路やEKSを中心に確認していくと、<strong>欠損が始まった時間帯にサーバーのPodのうちの1つがRestartしている</strong>ことが分かりました。WebSocketの性質上ロードバランシングが難しく、対象のPodが落ちた場合に通信が切れてしまいますが、すぐに再接続すれば別の生きているPodに繋がるため、数分間欠損するということは考えにくいです。またPodのRestart自体も数十秒で完了するため、数分間の欠損とは時間が合いませんでした。</p> <p>クライアント側では同時刻に切断イベントのエラーは出ていないという点、ネットワークアダプタをOffにして切断してみるとコネクションエラーは発生しており、再接続のロジックは機能しているという点など不可解な点は残りつつも、ここでは欠損は<strong>接続先のサーバー側のPodがRestartなどで落ちるイベントがトリガーになっている</strong>ということが分かり、再現条件が整ってきました。</p> <h3 id="4-仮定をもとに再現確認を行う">4. 仮定をもとに再現確認を行う</h3> <p>条件が整ったため、これらをもとに再度クライアント側のチームと連携して再現確認を行うと、確かにPodを強制的に落とすと数分間のデータ欠損が発生していることが分かり、確実に再現することがわかりました。調査を進めていると、PodがRestartして通信が切れているはずの時刻から <strong>ほぼ3分後に</strong> クライアント側でWebSocketの切断ログが出ているということが判明しました。この後、再接続ロジックが機能して再度サーバー側に接続された後に関しては再度データが正常に送信され、サーバー側でも受信できているという状態になっていることが確認できました。つまり、<strong>実際の切断タイミングとクライアント側でそれを認識するタイミングがほぼ3分遅れている</strong> 状態になっていました。</p> <p>ここで発生している事象の全容がほぼほぼ把握でき、以下のように整理されました。</p> <ol> <li>サーバー側のPodがRestartするなど、Podが落ちるタイミングで欠損が起きる</li> <li>クライアント側で切断を検知するタイミングに必ず3分前後の遅延がある</li> <li>3分間の遅延中に送られたデータが欠損扱いとなる</li> <li>切断検知から再接続した後は正常な挙動に戻る</li> </ol> <p>実際にWebSocketの接続が切れているタイミングから、クライアント側がコード上でそれを検知するまでの約3分間に送信したデータが欠損扱いとなっているというのが事象のようでした。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230307/20230307191902.png" width="1600" height="614" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="5-原因を詰める">5. 原因を詰める</h3> <p>必ず約3分の遅延が生じるというみるからにキリの良い数字が出てきたので、次はこの数字がどこからきているのかを確認しました。試しに手元でPythonスクリプトを書いて同じような挙動をさせてみましたが、このスクリプトだとPodのRestartから20秒ほどで切断検知していました。WebSocket通信周りは以下のライブラリを使用しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwebsockets.readthedocs.io%2Fen%2Fstable%2Findex.html" title="websockets" 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></p> <p>ライブラリのドキュメントを見ていると内部でPing-Pongを自動的にやってくれており、このインターバルのデフォルト値が20秒だったので切断検知までの遅延と一致していました。(<a href="https://websockets.readthedocs.io/en/stable/reference/client.html#opening-a-connection">ドキュメント</a>の引数の<code>ping_interval</code>を参照)</p> <p>この結果をもとにクライアント側の実装を確認するとPing-Pongの機構は入っておらず、かつライブラリ側も自動で的に裏でPing-Pongをしてくれるわけではなかったため、コネクションの死活監視ができていなかったという点がわかりました。しかし遅延はするもののCloseイベントは呼び出されていたことから、TCPにおいてサーバー側が切断したときに流すRSTパケットの到着が3分遅れている可能性がありそうだということになりました。<a href="https://www.rfc-editor.org/rfc/rfc6455#section-1.4">RFC6455 - 1.4. Closing Handshake参照のこと</a></p> <p>この時にサーバー側のメンバーがインフラ周りの設定を確認してくださり、その結果として経路内のロードバランサー(Envoy)に以下の設定がされていることが確認されました。</p> <pre class="code" data-lang="" data-unlink>httpConnectionManagerConfig: server_header_transformation: PASS_THROUGH use_remote_address: true # .... delayed_close_timeout: 180s </pre> <p>この<code>delayed_close_timeout: 180s</code>に関して<a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto">ドキュメント</a>を見ると、まさに今回の事象の通りとなっており、この設定値が引き金になってサーバー側が流したRSTパケットをLBが3分遅延させているという状態になっていました。デフォルトからの変更に関しては、過去に別問題に対しての解消案として変更が入っていたという経緯があったため、これをデフォルト値に戻せるかどうかは要検証という形でした。(デフォルトは1秒です)</p> <p>対応策に関しては短期的にはクライアント側にPing-Pongの機構を入れ、現状よりは遅延を短くして検知できるように進める形になりそうです。これは欠損したデータに関してクライアント側で再送処理が行われるため、少々の欠損であれば再送処理で対応できるためです。中長期的には<code>delayed_close_timeout</code>の設定値の見直しなど、いくつかインフラ周りで取れる策はありそうなので、継続課題となりそうです。</p> <h2 id="感想など">感想など</h2> <p>今回はバグではなく、過去に問題に対応するための設定値の変更が別の箇所で意図しない挙動をするという形で現れました。バグや不具合系に関して、よほど今年の運勢が良い(引き当てる確率という意味で)場合でなければ、調査して原因を絞り特定することはできると思います。原因を絞り込んでいったり、あたりをつけたりという点においては経験則が働く面もありますが、基本はログやコード、ドキュメントと真摯に向き合うということは大切だと思います。あとはいろいろな観点を持っているチームメンバーが集まることで、見えてくるものはあると思うので、めげずに頑張っていきましょう!</p> <p>そしてOPTiMではバグや不具合調査が楽しめるメンバーも募集しています、採用ページもご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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></p> <h4 id="謝辞">謝辞</h4> <p>記事内いらすと素材: いらすとや 様</p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> 出社ルールは都度見直されているため、このブログを読んでいる現在変更されている可能性があります。<a href="#fnref:1" rev="footnote">&#8617;</a></li> </ol> </div> optim-nakamura-kentaro Amazon CorrettoイメージでJava11+Gradle+Spring Bootプロジェクトを動かす: Java SE脆弱性対応 hatenablog://entry/4207112889963120734 2023-02-16T10:00:00+09:00 2023-02-16T10:00:00+09:00 こんにちは、AI・IoTサービス開発部のがんがんこと岩丸です。 最近2023年になったと思っていましたが既に1 ヶ月以上経過しておりました。時の流れは早いものです。 2023年1月、IPAから「Oracle Javaの脆弱性対策について(CVE-2023-21835等)」という記事が投稿されました。こちらの記事は記憶に新しい人が多いかと思います。 そこで、今回はJavaイメージについての調査を行い、Amazon Correttoイメージを用いたGradle + Spring Bootプロジェクトを動かしてみました。 <h1 id="はじめに">はじめに</h1> <p>こんにちは、AI・IoTサービス開発部の<a href="https://twitter.com/gangan_nikki">がんがん</a>こと岩丸です。 最近2023年になったと思っていましたが既に1 ヶ月以上経過しておりました。時の流れは早いものです。</p> <p>地元福岡には「三社参り」という文化があり小さい頃はよく近くの神社や太宰府天満宮などを巡っていました。2023年は1月1日→1月2日に<strong>深夜の大お散歩会</strong>を実施し、東京タワー・虎ノ門周辺の4社近くを巡りました。今後も都内オススメのお散歩スポットを巡っていきたいです。</p> <p>今回はJavaイメージに関する調査を実施したので、そちらの調査結果をまとめていきます。</p> <h1 id="やりたいこと">やりたいこと</h1> <p>2023年1月、IPAから<a href="https://www.ipa.go.jp/security/ciadr/vul/20230118-jre.html">Oracle Javaの脆弱性対策について(CVE-2023-21835等)</a>という記事が投稿されました。こちらの記事は記憶に新しい人が多いかと思います。 CVSSスコア(CVSSv3.1):5.3と緊急レベルのスコアではない(5.3は警告レベルに該当)ものの、セキュリティチェックシート対応などで影響を受けた/受けているJava/Kotlinアプリエンジニアの方も多いのではないでしょうか。</p> <p>弊プロダクトでも一部Javaを利用しており、上記影響を調査する必要がありました。そこで、今回はJavaイメージについての調査を行い、Amazon Correttoイメージを用いたGradle + Spring Bootプロジェクトを動かしてみました。</p> <h1 id="目次">目次</h1> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#やりたいこと">やりたいこと</a></li> <li><a href="#目次">目次</a></li> <li><a href="#Java用Dockerイメージについて調べてみた">Java用Dockerイメージについて調べてみた</a></li> <li><a href="#なぜamazoncorrettoイメージを採用したのか">なぜamazoncorrettoイメージを採用したのか</a></li> <li><a href="#amazoncorretto11--Gradleイメージを作る">amazoncorretto:11 + Gradleイメージを作る</a><ul> <li><a href="#gradlewを用いるケース">gradlewを用いるケース</a></li> <li><a href="#gradlewを用いないケース">gradlewを用いないケース</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> </ul> <h1 id="Java用Dockerイメージについて調べてみた">Java用Dockerイメージについて調べてみた</h1> <p>今回調査を行う上での前提条件は以下の通りです。</p> <ul> <li>Java11</li> <li>Gradleプロジェクトである</li> <li>内部に入って shell実行したい</li> <li>AWSで利用するコンテナイメージである</li> </ul> <p>前提条件に該当しない<a href="https://hub.docker.com/_/maven">maven</a>や<a href="https://github.com/GoogleContainerTools/distroless">distroless</a>などは調査対象から外しています。 Javaイメージの詳細調査については2021年時点の記事ですが以下の記事が参考になります。そちらを参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffuture-architect.github.io%2Farticles%2F20211220a%2F" title="JavaのDockerイメージ何選ぶ? | フューチャー技術ブログ" 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://future-architect.github.io/articles/20211220a/">future-architect.github.io</a></cite></p> <p>また、今回はAWSで利用することを前提にしているため<a href="https://hub.docker.com/_/microsoft-openjdk-jdk">Microsoft Build of OpenJDK</a> についても調査対象から外しています。 Microsoft Azure環境でよく利用されていたAzul Zulu for Azureは現在(2023年2月)時点で既にサポートが終了しておりMicrosoft Azureでの利用は別途調査が必要になるかと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdevblogs.microsoft.com%2Fjava%2Fend-of-updates-support-and-availability-of-zulu-for-azure%2F" title="End of Updates, Support and Availability of the Zulu for Azure builds of OpenJDK" 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://devblogs.microsoft.com/java/end-of-updates-support-and-availability-of-zulu-for-azure/">devblogs.microsoft.com</a></cite></p> <p><br></p> <p>さて本題に戻りますが、Javaアプリをコンテナ化したい場合、最初に考えられるのは<a href="https://hub.docker.com/_/openjdk">openjdk</a>イメージかと思います。 しかし、openjdkイメージは2022年7月以降は非推奨となっています。</p> <blockquote><p>This image is officially deprecated and all users are recommended to find and use suitable replacements ASAP. Some examples of other Official Image alternatives (listed in alphabetical order with no intentional or implied preference):</p> <ul> <li><a href="https://hub.docker.com/_/amazoncorretto">amazoncorretto</a></li> <li><a href="https://hub.docker.com/_/eclipse-temurin">eclipse-temurin</a></li> <li><a href="https://hub.docker.com/_/ibm-semeru-runtimes">ibm-semeru-runtimes</a></li> <li><a href="https://hub.docker.com/_/ibmjava">ibmjava</a></li> <li><a href="https://hub.docker.com/_/sapmachine">sapmachine</a></li> </ul> </blockquote> <p>そこで次の選択肢として以下2つが上がるかと思います。</p> <ul> <li><a href="https://hub.docker.com/_/gradle">gradle</a></li> <li><a href="https://hub.docker.com/_/amazoncorretto">amazoncorretto</a></li> </ul> <p>Gradleプロジェクトをコンテナ化したいとき、最初に考えられるのはやはり <strong>gradle</strong> イメージです。gradleイメージはopenjdkが代替先として列挙していた<a href="https://hub.docker.com/_/eclipse-temurin">eclipse-temurin</a>をベースに作られています。 gradleイメージの詳細は<a href="https://github.com/keeganwitt/docker-gradle/blob/24b8697d05b77df7eaacdbb20b8f9cc023d659fc/jdk11/Dockerfile">Dockerfile</a>を参照ください。</p> <p><a href="https://aws.amazon.com/corretto/">Amazon Corretto</a> はAmazonが管理するOpenJDK(Open Java Development Kit)ディストリビューションです。amazoncorrettoイメージはAWS環境で利用する場合は1番最初に選択肢に上がるかと思います。</p> <p><br></p> <p>今回の実験では<strong>amazoncorretto</strong>イメージを採用します。理由は後述致します。</p> <h1 id="なぜamazoncorrettoイメージを採用したのか">なぜamazoncorrettoイメージを採用したのか</h1> <p>amazoncorrettoを採用した理由は脆弱性発表後の対応の速さ、対応完了までのSTEP数が少ない点です。</p> <p>amazoncorrettoは<a href="https://openjdk.org/groups/vulnerability/advisories/2023-01-17">OpenJDK Vulnerability Advisory: 2023/01/17</a>が公表された翌日に Pull Requestがマージされており対応速度に圧倒されました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fcorretto%2Fcorretto-docker%2Fpull%2F132" title="Updates for 8.362.08.1, 11.0.18.10.1, 17.0.6.10.1, 19.0.2.7.1 by Rudometov · Pull Request #132 · corretto/corretto-docker" 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://github.com/corretto/corretto-docker/pull/132">github.com</a></cite></p> <p><br></p> <p>gradleの方はベースイメージの参照元である<a href="https://github.com/adoptium/containers">adoptium/containers</a>側で2023年1月24日に対応がマージされていました。こちらもさすがの対応速度です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fadoptium%2Fcontainers%2Fpull%2F347" title="Update Dockerfiles by eclipse-temurin-bot · Pull Request #347 · adoptium/containers" 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://github.com/adoptium/containers/pull/347">github.com</a></cite></p> <p>ただ、gradleイメージの作成者とベースイメージの作成者が異なるため対応完了までに2STEP要することになります。amazoncorrettoの方は1STEPで対応完了しています。</p> <p>以上の2点の理由を踏まえ今回は<strong>amazoncorretto</strong>イメージを採用して実験を進めました。</p> <h1 id="amazoncorretto11--Gradleイメージを作る">amazoncorretto:11 + Gradleイメージを作る</h1> <p>前半の調査でかなりお腹いっぱいかと思いますので後半はサクッと執筆していきます。</p> <p><br></p> <p>今回は<strong>gradlewを用いるケース</strong>、<strong>gradlewを用いないケース</strong>の 2 パターンで作成してみます。 <strong>gradlew</strong>(Windows なら gradlew.bat)はGradle Wrapperで生成されたシェルスクリプトです。gradlew の説明は今回の主題から外れるため割愛致します。公式ドキュメントを参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.gradle.org%2Fcurrent%2Fuserguide%2Fgradle_wrapper.html" title="The Gradle Wrapper" 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://docs.gradle.org/current/userguide/gradle_wrapper.html">docs.gradle.org</a></cite></p> <h2 id="gradlewを用いるケース">gradlewを用いるケース</h2> <pre class="code lang-dockerfile" data-lang="dockerfile" data-unlink><span class="synStatement">FROM </span>amazoncorretto:11.0.18-alpine3.17 as builder <span class="synStatement">ENV </span>JAVA_HOME /usr/lib/jvm/java-11-amazon-corretto <span class="synStatement">WORKDIR </span>/app <span class="synStatement">COPY </span>. . <span class="synStatement">RUN </span>./gradlew bootjar -x test </pre> <h2 id="gradlewを用いないケース">gradlewを用いないケース</h2> <p>gradleイメージ公式の<a href="https://github.com/keeganwitt/docker-gradle/blob/24b8697d05b77df7eaacdbb20b8f9cc023d659fc/jdk11/Dockerfile">Dockerfile</a>を参考にしています。</p> <pre class="code lang-dockerfile" data-lang="dockerfile" data-unlink><span class="synStatement">FROM </span>amazoncorretto:11.0.18-alpine3.17 as builder <span class="synStatement">WORKDIR </span>/home/gradle <span class="synStatement">ENV </span>GRADLE_HOME /opt/gradle <span class="synStatement">ENV </span>GRADLE_OUTPUT gradle.zip <span class="synStatement">ENV </span>GRADLE_VERSION 7.6 <span class="synStatement">ARG </span>GRADLE_DOWNLOAD_SHA256=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b <span class="synComment"># Set up Gradle v7.6</span> <span class="synStatement">RUN </span>set -o errexit -o nounset \ &amp;&amp; echo <span class="synConstant">&quot;Downloading Gradle&quot;</span> \ &amp;&amp; wget --no-verbose --output-document=${GRADLE_OUTPUT} <span class="synConstant">&quot;https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip&quot;</span> \ \ &amp;&amp; echo <span class="synConstant">&quot;Checking download hash&quot;</span> \ &amp;&amp; echo <span class="synConstant">&quot;${GRADLE_DOWNLOAD_SHA256} *gradle.zip&quot;</span> | sha256sum -c - \ \ &amp;&amp; echo <span class="synConstant">&quot;Installing Gradle&quot;</span> \ &amp;&amp; unzip ${GRADLE_OUTPUT} \ &amp;&amp; rm ${GRADLE_OUTPUT} \ &amp;&amp; mv <span class="synConstant">&quot;gradle-${GRADLE_VERSION}&quot;</span> <span class="synConstant">&quot;${GRADLE_HOME}/&quot;</span> \ &amp;&amp; ln -s <span class="synConstant">&quot;${GRADLE_HOME}/bin/gradle&quot;</span> /usr/bin/gradle \ \ &amp;&amp; echo <span class="synConstant">&quot;Testing Gradle installation&quot;</span> \ &amp;&amp; gradle --version <span class="synStatement">ENV </span>JAVA_HOME /usr/lib/jvm/java-11-amazon-corretto <span class="synStatement">WORKDIR </span>/app <span class="synStatement">COPY </span>. . <span class="synStatement">RUN </span>gradle build --no-daemon -x test </pre> <h1 id="おわりに">おわりに</h1> <p>今回は<strong>Amazon Corretto</strong>イメージを用いて<strong>Java11 + Gradle + Spring Boot</strong>イメージを作成してみました。gradlewが用意されている環境であればgradlewを用いた方が便利ですね。</p> <p>やはり有識者が作成したDockerfileを覗くのは楽しいですね。機会あれば調査レポートをまとめます。</p> <hr /> <p>OPTiMではサーバーサイド/インフラに興味があるエンジニア、新卒からどんどんチャレンジしていきたいエンジニア学生を随時募集しております。少しでもご興味のある方はこちらも合わせてご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-shimpei-iwamaru 【Java】作成したライブラリをGitLab Package Registry で管理する hatenablog://entry/4207112889927112953 2023-01-18T10:00:00+09:00 2023-04-13T09:22:32+09:00 はじめに こんにちは!アルバイトスタッフの吉田です。 OPTiM Geo Scan のバックエンドを担当しています。 この度、プロダクト間での共通ライブラリを作成する方法についての調査と開発を行ったので、それについて書いていこうと思います。 目次 はじめに 目次 直面していた課題 GitLab Package Registryについて 環境 大まかな手順 トークンの発行 build.gradle に maven-publish の内容を書く gitlab-ci.yml を書く pushする 公開したパッケージの利用方法 参考にした記事など 感想 直面していた課題 プロダクト間で共通する部分がそ… <h2 id="はじめに">はじめに</h2> <p>こんにちは!アルバイトスタッフの吉田です。 OPTiM Geo Scan のバックエンドを担当しています。<br> この度、プロダクト間での共通ライブラリを作成する方法についての調査と開発を行ったので、それについて書いていこうと思います。</p> <h2 id="目次">目次</h2> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#目次">目次</a></li> <li><a href="#直面していた課題">直面していた課題</a></li> <li><a href="#GitLab-Package-Registryについて">GitLab Package Registryについて</a></li> <li><a href="#環境">環境</a></li> <li><a href="#大まかな手順">大まかな手順</a><ul> <li><a href="#トークンの発行">トークンの発行</a></li> <li><a href="#buildgradle-に-maven-publish-の内容を書く">build.gradle に maven-publish の内容を書く</a></li> <li><a href="#gitlab-ciyml-を書く">gitlab-ci.yml を書く</a></li> <li><a href="#pushする">pushする</a></li> </ul> </li> <li><a href="#公開したパッケージの利用方法">公開したパッケージの利用方法</a></li> <li><a href="#参考にした記事など">参考にした記事など</a></li> <li><a href="#感想">感想</a></li> </ul> <h2 id="直面していた課題">直面していた課題</h2> <p>プロダクト間で共通する部分がそれぞれ個別で開発されていました。 このままにしていると同じことを何度もするといった、無駄な作業が出てきてしまうので、共通部分をライブラリとして切り出してプロダクト間で流用できるようにすることとなりました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20230116/20230116134221.png" width="361" height="411" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="GitLab-Package-Registryについて">GitLab Package Registryについて</h2> <p>いろいろ調べていると、<a href="https://docs.gitlab.com/ee/user/packages/package_registry/">GitLab Package Registry</a> というものに辿り着きました。<br> <a href="https://docs.gitlab.com/ee/user/packages/package_registry/">GitLab Package Registry</a> とは、GitLabを様々なパッケージマネージャーのレジストリとして利用できる機能で、Maven、npm、NuGet、PyPIなどがサポートされています。(<a href="https://docs.gitlab.com/ee/user/packages/package_registry/#supported-package-managers">サポートされているパッケージマネージャー</a>)<br></p> <p>ここでは Maven Repository を利用する手順について説明していきます。</p> <h2 id="環境">環境</h2> <ul> <li>Gradle 7.5.1</li> <li>Java 11</li> </ul> <h2 id="大まかな手順">大まかな手順</h2> <p>※前提としてJavaライブラリは作成済みで、GitLabリポジトリがある状態とします。</p> <ul> <li>トークンの発行</li> <li>build.gradle に maven-publish の内容を書く</li> <li>gitlab-ci.yml を書く</li> <li>pushする</li> </ul> <h3 id="トークンの発行">トークンの発行</h3> <p>認証方法は下記の3つがあります。</p> <ul> <li>プライベートトークン</li> <li>デプロイトークン</li> <li>CIトークン</li> </ul> <p>今回はデプロイトークンを利用した認証方法を使います。</p> <p><strong>発行画面</strong><br></p> <p>GitLabのリポジトリ画面左側にあるSettingsからRepositoryを選択します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20221226/20221226181950.png" width="243" height="290" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Deploy tokensという項目のExpandをクリック。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20221226/20221226181945.png" width="699" height="606" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>トークンの名前を入力し、read_package_registry と write_package_registry にチェックを入れてトークンを作成。<br> 作成されたトークンはメモしておいてください。</p> <h3 id="buildgradle-に-maven-publish-の内容を書く">build.gradle に maven-publish の内容を書く</h3> <p>build.gradle の plugins に maven-publish を追加</p> <pre class="code gradle" data-lang="gradle" data-unlink>// build.gradle plugins { ... id &#39;maven-publish&#39; }</pre> <p>group 名と version を書いておく</p> <pre class="code gradle" data-lang="gradle" data-unlink>//build.gradle group = &#39;jp.co.optim.~&#39; version = &#39;0.0.1&#39; // この場合、使用の際は &#39;jp.co.optim.~:ライブラリ名:0.0.1&#39; となる</pre> <p>publishing の設定を書く</p> <pre class="code gradle" data-lang="gradle" data-unlink>// build.gradle publishing { publications { library(MavenPublication) { from components.java } } repositories { maven { url &#39;https://GitLabのFQDN/api/v4/projects/プロジェクトID/packages/maven&#39; name &#39;GitLab&#39; credentials(HttpHeaderCredentials) { name = &#39;Deploy-Token&#39; value = &#39;発行したデプロイトークン&#39; } authentication { header(HttpHeaderAuthentication) } } } }</pre> <p>いじる必要のある部分は url と value になります。</p> <p>注意点:</p> <ul> <li>name は 発行した際のトークン名ではなく 'Deploy-Token' です。</li> <li>セキュリティ面を考慮すると、デプロイトークンは別ファイルに記述したほうが良いです。</li> </ul> <h3 id="gitlab-ciyml-を書く">gitlab-ci.yml を書く</h3> <p>.gitlab-ci.yml に以下を追加します。以下は一例で、imageなどは環境に合わせてください。</p> <pre class="code gradle" data-lang="gradle" data-unlink>gradle-publish: stage: publish image: amazoncorretto:11 script: ./gradlew publish</pre> <p>※参考:<a href="https://scrapbox.io/tsuchinaga/GitLab%E3%81%AEMaven_Repository%E6%A9%9F%E8%83%BD%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">Scrapbox GitLabのMaven Repository機能を使ってみる</a> <br></p> <p>書く部分は以上になります。</p> <h3 id="pushする">pushする</h3> <p>push すると CIが実行されます。うまく行けば Package Registry にパッケージが表示されます。(画面左のダンボール箱のマークから飛べる。)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20221226/20221226181948.png" width="236" height="146" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="公開したパッケージの利用方法">公開したパッケージの利用方法</h2> <p>公開したパッケージの個別ページに使用方法が書いてありますが、プライベートリポジトリの場合は使用の際も認証が必要になるので、こちらの手順も書いておきます。(Gradle)</p> <p>まず、デプロイトークンを発行する画面で、read_package_registry のみにチェックを入れてトークンを発行してください。 次に、ライブラリを使うプロジェクトのbuild.gradleを以下のように書いてください。</p> <pre class="code gradle" data-lang="gradle" data-unlink>// build.gradle repositories { ... maven { url &#39;https://GitLabのFQDN/api/v4/projects/プロジェクトID/packages/maven&#39; name &#34;GitLab&#34; credentials(HttpHeaderCredentials) { name = &#39;Deploy-Token&#39; value = &#39;発行したデプロイトークン&#39; } authentication { header(HttpHeaderAuthentication) } } } dependencies{ ... implementation &#39;グループ名:ライブラリ名:バージョン&#39; }</pre> <p>これで利用することが可能になります。</p> <h2 id="参考にした記事など">参考にした記事など</h2> <ul> <li><a href="https://qiita.com/ssc-ksaitou/items/b0e4a155a6c828041f5d">Qiita プロジェクト間共通ライブラリを GitLab Package Registry に登録して使う</a></li> <li><a href="https://scrapbox.io/tsuchinaga/GitLab%E3%81%AEMaven_Repository%E6%A9%9F%E8%83%BD%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">Scrapbox GitLabのMaven Repository機能を使ってみる</a></li> <li><a href="https://gitlab-docs.creationline.com/ee/user/packages/">GitLab Package Registry</a></li> </ul> <h2 id="感想">感想</h2> <p>Java も Gradle も CI もほとんど触ったことがなかったので結構しょうもないところで詰まりましたが、おかげで力がついた気がします。</p> <p>OPTiM ではアルバイトスタッフでも活躍できる環境があります。興味のある方はぜひ採用情報をご覧ください。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.optim.co.jp%2Frecruit%2F" title="株式会社オプティムの採用情報 | OPTiM" 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.optim.co.jp/recruit/">www.optim.co.jp</a></cite></p> optim-kyosuke_yoshida