FLINTERS Engineer's Blog FLINTERSのエンジニアによる技術ブログ 2024-01-19T14:30:00+09:00 septeni-original Hatena::Blog hatenablog://blog/8454420450097812022 ブログリレー最終日。 hatenablog://entry/6801883189076070449 2024-01-19T14:30:00+09:00 2024-01-19T15:07:04+09:00 FLINTERS10周年記念ブログリレーの最終日の記事となります。 <p>こんにちは、FLINTERS CEOの武藤です。</p> <p>本ブログは、FLINTERS10周年記念ブログリレーとして、133回目の記事として執筆しております。 そして、133日連続ブログ投稿するという企画も本日が最終日になります。</p> <p>まずはこの途方もない企画/運営をしてくれた事務局の皆さん、執筆にご協力していただいた皆さん、そして何かのきっかけでこのブログを読んで頂いた皆さんに御礼申し上げます。</p> <p>中にはすでにFLINTERSを退職して他社様や独立して活躍されている方々、業務委託や出向中の方々までとても多くの尊敬する皆さんにご協力いただき本当に感謝です。いつもありがとうございます!</p> <p>133日分のブログはこちらのまとめから飛べますのでご興味あるものを是非探してみてください! <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.flinters.co.jp%2Frecruit%2F10thanniversary_blog" title="FLINTERS10周年ブログリレー|採用情報|株式会社FLINTERS" 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.flinters.co.jp/recruit/10thanniversary_blog">www.flinters.co.jp</a></cite></p> <p>改めてになりますがFLINTERSはおかげさまで10周年を迎えることが出来ました。</p> <p>前半5年はコストセンターとして、後半5年はプロフィットセンターとしても進化を続け現在<strong>283</strong>名の仲間たちと働くことができています。</p> <p><strong>「未来につながる火を灯そう」</strong>をミッションに、<strong>「データ活用でユーザー体験を豊かにする」</strong>というビジョンを掲げ、共感してくれた仲間たちとともにクライアントの素晴らしいプロダクトやサービスを通じて社会を前に進めるチャレンジを日々続けさせていただいております。</p> <p>夢中になり走り続けてきた10年間も振り返ってみればあっという間で、一度きりの人生、戻ることのない尊い時間をFLINTERSの仲間たちと共に歩んでこれた事は僕の人生にとってかけがえのない事であり誇りとなっています。本当にありがたいですね。</p> <p>ここで10年間を振り返り始めると永遠と続いてしまいそうですが、この10年間の沿革については親会社であるセプテーニ・ホールディングス グループCEOの佐藤光紀さんとの対談記事が2024年1月9日にアップされていますので詳細についてはこちらを是非、読んで頂けると嬉しいです! <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fsepteni_group%2Fn%2Fn872de8f8027d" title="“プロフィットセンター”としての自立。開発組織としての覚悟と歩んだ10年間<FLINTERS設立10周年記念対談>|Septeni Group" 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://note.com/septeni_group/n/n872de8f8027d">note.com</a></cite></p> <p>さて、実はFLINTERS10周年記念ブログリレー133回目の最終日である本日は半期に1回実施している全社キックオフ当日なんです。</p> <p>全社キックオフとは年2回実施される半期レビューと次の半期の全社方針、部門方針、戦略共有などを行う一部、半期の表彰や懇親を目的とした二部で構成される一大イベントでテレワークが基本となっているFLINTERSにとって非常に貴重な機会になっています。</p> <p>一部はPC見ながら自宅でオンライン、二部は移動してオフラインで表彰と懇親というハイブリット方式で実に快適なスタイルで実施しています。 僕はこの日が大好きなんです。普段は合理的に仕事をしたいタイプですが仲間と会うのはやっぱり嬉しいんですよね。</p> <p>ちなみに、二部に関しては自由参加としていますが出席率はかなり高く参加してくれるのも嬉しいポイントです。 表彰に関しては事前に全社投票を行いその中から役員陣で3名のノミネートをします。</p> <p>そして当日その3名の中から僕が毎回MVPの発表、ノミネート者の表彰(前回は特別賞として追加1名)をするのですが、喜んでいる人、照れてる人、恐縮している人、緊張して手が震えている人、涙を流す人など実に様々な感情と間近で触れ合うことができて半年に一度のこの日はいつも心が揺さぶられる経験をさせてもらってます。 <figure class="figure-image figure-image-fotolife" title="表彰の様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/saaaara/20240118/20240118181703.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>表彰の様子</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="MVPノミネート者"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/saaaara/20240118/20240118181707.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>MVPノミネート者</figcaption></figure></p> <p>本当はみんな頑張ってるんで全員表彰したいくらいなんですけどね(笑) 一方で、懇親会中にみんなと話していると「次こそは!」とか「〇〇さんにとってもらいたい!」とかいろいろ話を聞いてると目指すべき目標があるというのは良いものだなと感じます。</p> <p>ということで、とても楽しみにしているキックオフ会場にそろそろ向かわなくてはならない時間になってきました。</p> <p>改めてになりますが10周年ということでFLINTERSに関わっていただいた皆さま、そしてこれを見ている未来の仲間になるかもしれない皆さま、FLINTERSは11年目も走り続けますので本年もどうぞよろしくお願いします!</p> septeni-original 日付の豆知識とJavaの話 hatenablog://entry/6801883189075341440 2024-01-18T12:00:00+09:00 2024-01-18T12:00:09+09:00 日付の豆知識とJavaの話 <p>こんにちは。 株式会社FLINTERSの高嶋です。</p> <p>この記事は10周年記念として133日間ブログを書き続けるチャレンジの132日目の記事となります。</p> <h2 id="なくなった12月31日">なくなった12月31日</h2> <p>ここで少し日付に関する豆知識です。 フィリピンでは1521年から1844年まで日付変更線の”東側”にありました。 しかし1844年12月30日からフィリピンは日付変更線の"西側"に異動することになり、その際に12月31日を削除して翌日を1845年1月1日とすることで周囲の国と日付を調整しました。</p> <p>フィリピンでかつてこんなことがありましたよという話では豆知識として面白いだけですがエンジニアにとっては少し怖い話ですよね。 フィリピンの1844年12月31日を扱う可能性は低いかもしれませんが、時間というのは政治的な事情に左右され直感的ではない変化をする可能性があるので今後同様のことが起こりえるかも?と思うと不安ですよね。 今回はJavaがどういった対応をしているのかを紹介したいと思います。</p> <h2 id="Javaでの対応状況">Javaでの対応状況</h2> <p>Javaの現在の日付関連の標準ライブラリであるDate Time APIでは過去に起きたこういったタイムゾーンの変更などにしっかりと対応されており、普段使う分にはあまり意識せずにこの問題に対応できるようになっています。</p> <p>ここではみんなもう導入しているであろうJava21で試します。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">import</span> java.time.ZoneId; <span class="synPreProc">import</span> java.time.ZonedDateTime; <span class="synType">public</span> <span class="synType">class</span> Main { <span class="synType">public</span> <span class="synType">static</span> <span class="synType">void</span> main(String[] args) { <span class="synStatement">var</span> PHT = ZoneId.of(<span class="synConstant">&quot;Asia/Manila&quot;</span>); <span class="synStatement">var</span> dayOf12_30 = ZonedDateTime.of(<span class="synConstant">1844</span>, <span class="synConstant">12</span>, <span class="synConstant">30</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>,PHT); <span class="synStatement">var</span> nextDayOf12_30 = dayOf12_30.plusDays(<span class="synConstant">1</span>); System.out.println(dayOf12_30); System.out.println(nextDayOf12_30); } } </pre> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takashima0411/20240115/20240115201039.png" width="292" height="48" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ちゃんと日付が一つ飛んでますよね。 ちなみにこれをJSTにするとちゃんと1844年12月31日は現れますよ!</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">import</span> java.time.ZoneId; <span class="synPreProc">import</span> java.time.ZonedDateTime; <span class="synType">public</span> <span class="synType">class</span> Main { <span class="synType">public</span> <span class="synType">static</span> <span class="synType">void</span> main(String[] args) { <span class="synStatement">var</span> JST = ZoneId.of(<span class="synConstant">&quot;Asia/Tokyo&quot;</span>); <span class="synStatement">var</span> dayOf12_30 = ZonedDateTime.of(<span class="synConstant">1844</span>, <span class="synConstant">12</span>, <span class="synConstant">30</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>, <span class="synConstant">0</span>,JST); <span class="synStatement">var</span> nextDayOf12_30 = dayOf12_30.plusDays(<span class="synConstant">1</span>); System.out.println(dayOf12_30); System.out.println(nextDayOf12_30); } } </pre> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takashima0411/20240115/20240115201028.png" width="309" height="50" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="どうやってるの">どうやってるの?</h2> <p>ちゃんと日付飛んでる!っていうのはいいのですが、後で自分で対応せざるを得ない時になった時のためにどうやって実現されているかは気になるところですよね! こういう時はデバッガが便利なのでコードにブレークポイントをはってステップインを連打して日付を飛ばしそうなところを見つけてみましょう</p> <p><code>plusDays(1)</code> の処理のどこかでそれが行われているに違いない気がするのでそこにブレークポイントを張ってみます。 (標準ライブラリはステップインの対象から外されている場合があるので、ステップインできなかったら設定の確認をしてみましょう)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/takashima0411/20240115/20240115202443.png" width="1158" height="235" 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/t/takashima0411/20240115/20240115201012.png" width="970" height="537" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>このコードからはそこはかとなく、時間の調整をしていそうな匂いを感じます。 特にこの<code>ZoneRule</code>なんてもう絶対これが関係しているに違いないって気がする名前をしています。 JavaDocを確認すると、 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.oracle.com%2Fjavase%2Fjp%2F21%2Fdocs%2Fapi%2Fjava.base%2Fjava%2Ftime%2Fzone%2FZoneRules.html" title="ZoneRules (Java SE 21 &amp; JDK 21)" 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.oracle.com/javase/jp/21/docs/api/java.base/java/time/zone/ZoneRules.html">docs.oracle.com</a></cite></p> <blockquote><p>単一タイムゾーンのゾーンオフセットがどのように変化するかを定義するルール。 このルールはタイムゾーンの履歴および将来のすべての遷移をモデル化します。 ZoneOffsetTransitionは既知の遷移(通常は履歴)に使用されます。 ZoneOffsetTransitionRuleは、アルゴリズムの結果に基づく将来の遷移に使用されます。</p> <p>ルールはZoneIdを使用してZoneRulesProvider経由でロードされます。 同じルールが複数のゾーンID間で内部的に共有されることがあります。</p></blockquote> <p>もうほぼこれにより過去の日付変更などの管理がされている感じがしてきましたね。 さらに<code>ZoneRulesProvider</code>というものがいて、そちらから時間の遷移のルールについて提供されていそうですね。</p> <p><code>ZoneRulesProvider</code>のJavaDocでは、</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.oracle.com%2Fjavase%2Fjp%2F21%2Fdocs%2Fapi%2Fjava.base%2Fjava%2Ftime%2Fzone%2FZoneRulesProvider.html" title="ZoneRulesProvider (Java SE 21 &amp; JDK 21)" 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.oracle.com/javase/jp/21/docs/api/java.base/java/time/zone/ZoneRulesProvider.html">docs.oracle.com</a></cite></p> <blockquote><p>システムへのタイムゾーン・ルールのプロバイダ。 このクラスはタイムゾーン・ルールの構成を管理します。staticメソッドは、プロバイダの管理に使用できるpublic APIを提供します。抽象メソッドは、ルールが提供されることを許可するSPIを提供します。</p> <p>ZoneRulesProviderは、拡張クラス(通常の拡張ディレクトリに配置されるjarファイル)として、Javaプラットフォームのインスタンスにインストールできます。インストールされたプロバイダは、ServiceLoaderクラスで定義されているサービス・プロバイダのロード機能を使用してロードされます。ZoneRulesProviderは、リソース・ディレクトリMETA-INF/services内のjava.time.zone.ZoneRulesProviderというプロバイダ構成ファイルを使用して自身を識別します。このファイルは、完全修飾された具象ゾーン・ルール・プロバイダ・クラス名を指定する行を含んでいるはずです。プロバイダは、クラスパスにそれらを追加するか、registerProvider(java.time.zone.ZoneRulesProvider)メソッド経由で自身を登録することによって、使用可能にすることもできます。</p> <p>Java仮想マシンは、IANAタイムゾーン・データベース(TZDB)で定義されるタイムゾーンにゾーン・ルールを提供するデフォルト・プロバイダを持っています。システム・プロパティjava.time.zone.DefaultZoneRulesProviderが定義されている場合、具象ZoneRulesProviderクラスの完全修飾名と見なされ、システム・クラス・ローダーを使用してデフォルト・プロバイダとしてロードされます。このシステム・プロパティが定義されていない場合、システム・デフォルト・プロバイダがロードされ、デフォルト・プロバイダとして機能します。</p> <p>ルールは、ZoneIdで使用されるゾーンIDによって主に参照されます。ゾーン地域IDのみが使用できます。ここではゾーン・オフセットIDは使用されません。</p> <p>タイムゾーン・ルールは政治的であるため、データはいつでも変わる可能性があります。各プロバイダはゾーンIDごとに最新ルールを提供しますが、ルールがどのように変更されたかの履歴を提供することもできます。</p></blockquote> <p>これを見ると基本デフォルトで提供されているもの以外に、必要であれば自分でZoneRulesProviderを定義して利用することもできそうです。</p> <p>とはいえ、多くの場合は突然日付に関する調整が行われることはなく、事前に決めておいて行われるものなので適切にバージョンアップしていれば問題になることはあまりないかもしれませんが、こういう機能があるんだと知っておくと何かの役に立つかもしれませんね。</p> <h2 id="まとめ">まとめ</h2> <p>というわけで、今回は日付の話のちょっとしたトリビアとJavaの日付ライブラリを見てみました。 Javaに限らず普段日付については標準ライブラリでサクッと済ませる場合が多いかもしれませんが、利用している言語・ライブラリにてこの辺の挙動がサポートされているかな?などと今後気にしてみると面白いかもしれませんね。</p> takashima0411 仕事のパフォーマンスを向上させる運動習慣の重要性 hatenablog://entry/6801883189075176264 2024-01-17T12:00:00+09:00 2024-01-17T14:45:48+09:00 運動習慣が仕事のパフォーマンスに与える影響 <p>こんにちは、細川です。<br/> 全133回の10周年記念ブログリレーも、この記事を含めて残り3本となりました。<br/> 131日目のテーマは「仕事と運動習慣の関係」です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki0102/20240115/20240115082028.jpg" width="1200" height="800" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><br/> 画像は<a href="https://www.pakutaso.com">フリー素材ぱくたそ</a>を使用しました</p> <h1 id="背景">背景</h1> <p>最近、仕事で体力が追いつかないことが増えてきました。<br/> 具体的には個人作業を先延ばしにしたり、集中力が続かなかったりといった状況です。</p> <p>このため仕事において</p> <p>作業成果=作業量*効率</p> <p>と分解したときに、少ない作業量なりに成果を出そうと作業効率を上げることに意識が行きがちになっています。ツールの設定を見直したり、なんとなく情報収集を始めたり、休憩を必要以上に多くとったり。<br/> しかし、効率が良くても作業量が少なければ成果は低止まりし、逆に効率がそこそこでも量を増やせたら成果がもっと向上するのではないかと自分に対して批判的な見方も持っています。</p> <p>また別の視点では、</p> <p>作業成果=個人的要因*環境的要因</p> <p>と分解しても、組織的な優先順位や協力関係などの環境的要因が整っていて自分に言い訳ができない状況まで来たら、最終的には自分自身の問題と向き合う必要があると感じています。</p> <p>そこで自分自身としっかり向き合い、変えるべき点を整理しようと思います。</p> <h1 id="仕事の高いパフォーマンスを発揮する人の共通点">仕事の高いパフォーマンスを発揮する人の共通点</h1> <p>普段働いていて、「この人はすごいな」と思う瞬間がありますよね。アウトプットが多い、明るく接しやすい、体調が安定しており大事な時に頼りになるなど。<br/> そういった人の自己紹介プロフィールを見たり雑談したりしていると、共通の特徴があることに気づきます。 それは、<strong>日頃から習慣的に体を動かしていること</strong>です。</p> <p>たとえば、以下のような習慣が見受けられます。</p> <ul> <li>昼休みにストレッチやトレーニングをしている</li> <li>散歩をする</li> <li>ジムに通っている</li> <li>自転車やサッカーを楽しんでいる</li> <li>育児で子供と一緒に運動している</li> </ul> <p>負荷の差こそあれど、何らかの形で体を動かしていることが分かります。</p> <h1 id="自分はどうか">自分はどうか</h1> <p>最近は年単位でほぼ運動していません。<br/> リモートワークになり通勤がなくなったことで、体を動かす必要のある機会が週2~3回の徒歩5分圏内の買い物くらいに減りました。<br/> また、以前は遊んでいた運動系のゲームを他の趣味に時間を割いて休止しています。</p> <p>数年前まで積極的に体を動かしていた時期と現在を比較すると、前のほうが精力的に仕事ができていたと感じます。<br/> 体力と仕事のパフォーマンスには関連があるという実感があります。</p> <h1 id="運動習慣と仕事の科学的な関係">運動習慣と仕事の科学的な関係</h1> <p>科学的には、運動習慣と仕事には以下のような関係があるそうです。</p> <ol> <li><p><strong>集中力とクリエイティビティの向上</strong><br/> 運動は脳に良い影響を与えます。血流が促進され、脳に酸素や栄養が適切に供給されるようになります。<br/> これにより、集中力やクリエイティビティが向上し、集中を持続させ問題解決能力やアイディアの生成にプラスの影響が現れます。</p></li> <li><p><strong>健康な体は健全な精神を育む</strong><br/> 運動は身体だけでなく、精神的な健康にも大きな影響を与えます。仕事のストレスやプレッシャーに対抗するためには、健康な体が基盤となります。<br/> 運動によって分泌されるエンドルフィンは、ストレスを和らげ気分を改善させる効果があります。<br/> これが積み重なり、仕事に対するポジティブなアプローチをサポートします。</p></li> <li><p><strong>時間の効率的な使い方</strong><br/> 定期的な運動は生活リズムを整え、時間の使い方を効率的にする作用があります。</p></li> <li><p><strong>目標の達成と達成感</strong><br/> 運動は目標を設定し、それを達成するプロセスを経験する良い機会となります。この経験は仕事においても目標を設定し、それに向かって努力する習慣となります。</p></li> </ol> <p>また個人作業の視点からは少し外れますが、グループ活動での運動は協調とリーダーシップのスキル向上効果があるとされています。</p> <p>上記の出典はChatGPTですが、最もらしい内容だと思います。</p> <h1 id="まとめ">まとめ</h1> <p>今回の結論は「仕事の高いパフォーマンスを発揮する人は運動習慣がある」ということでした。<br/> 運動の機会を意識的に作り出す必要性を自分ごと化できました。まわりの人を見習い、この記事を書きながらストレッチをしています。休止していた運動ゲームも先週末に再開しました。</p> <p>10周年ブログリレーも残り2回です。最後までお楽しみください。</p> tsubaki0102 2023年購入書籍から読み取る、FLINTERSの興味関心 hatenablog://entry/6801883189075162009 2024-01-16T12:00:00+09:00 2024-01-16T12:00:28+09:00 2023年購入書籍から読み取る、FLINTERSの興味関心 <p>こんにちは。FLINTERSでバックオフィス周りの業務に従事しております横山と申します。</p> <p>本ブログは、FLINTERS10周年記念ブログリレーの一環として、130回目の記事として執筆しております。</p> <h3 id="書籍購入制度">書籍購入制度</h3> <p>唐突ですが、FLINTERS社 及び FLINTERS BASE社 には、書籍購入制度なるものがございます。</p> <p>業務に必要な書籍については、会社に承認をもらった上で会社が購入し、必要な社員に貸与する制度です。</p> <p>購入依頼については特段上限数や金額を設けておらず、会社が承認すればどのような書籍でも<strong>際限無く</strong>購入が可能であり、日々社員のスキル向上・知見習得のために様々な書籍が購入されています。</p> <p>購入申請方法は、社内の有志エンジニア達が作成した、Slackと連携した書籍管理システムからオーダーすることで簡単に申請が可能です。</p> <p><figure class="figure-image figure-image-fotolife" title="書籍購入申請"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokoyamountain/20240115/20240115042734.png" width="742" height="240" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>書籍購入申請</figcaption></figure></p> <p>もちろん、貸出が終わった書籍はオフィスの本棚に格納され、いつでも同システム上から貸出予約をすることが可能です。予約した書籍自体はオフィスに借りに行く必要があります。(将来エンジニアリングの力で自動発送などできると良いのですが。。。)</p> <p><figure class="figure-image figure-image-fotolife" title="FLINTERS書籍管理システム"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokoyamountain/20240115/20240115043150.png" width="1200" height="652" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>FLINTERS書籍管理システム</figcaption></figure></p> <h3 id="2023年に購入された書籍達">2023年に購入された書籍達</h3> <p>さて、ここからが本題ですが、昨年2023年1年間に書籍購入制度を利用して購入された書籍は、29冊ありました。(同一タイトルの複数購入除く) 一昨年、一昨々年は年間6〜70冊程度だったので、例年に比べると控えめですが、2023年はどのような書籍が購入されたのかを見てみます。</p> <p>結果は以下でした。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yokoyamountain/20240115/20240115044901.png" width="813" height="573" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>カテゴリは独断で適当に分けましたのでご容赦いただきたいですが、どのような書籍が多かったのかみていきましょう♪</p> <h4 id="2票獲得カテゴリPython--ピープルマネジメント">2票獲得カテゴリ Python / ピープルマネジメント</h4> <h5 id="Python">Python</h5> <p>FLINTERSは以前はプログラミング言語としてScalaがメインでしたが、このところデータ領域の仕事も急激に増えており、Pythonの書籍も増えている気がします。書籍管理システム内で検索したところ、他に10冊程度Python関連の書籍がありました。</p> <ul> <li><a href="https://www.amazon.co.jp/%E3%83%AD%E3%83%90%E3%82%B9%E3%83%88Python-%E2%80%95%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%81%A7%E4%BF%9D%E5%AE%88%E3%81%97%E3%82%84%E3%81%99%E3%81%84%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F-Patrick-Viafore/dp/4814400179/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=14Z184HNEF2B7&amp;keywords=%E3%83%AD%E3%83%90%E3%82%B9%E3%83%88Python+%E2%80%95%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%81%A7%E4%BF%9D%E5%AE%88%E3%81%97%E3%82%84%E3%81%99%E3%81%84%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F&amp;qid=1705262863&amp;s=books&amp;sprefix=%E3%83%AD%E3%83%90%E3%82%B9%E3%83%88python+%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%81%A7%E4%BF%9D%E5%AE%88%E3%81%97%E3%82%84%E3%81%99%E3%81%84%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F%2Cstripbooks%2C134&amp;sr=1-1">ロバストPython ―クリーンで保守しやすいコードを書く</a></li> <li><a href="https://www.amazon.co.jp/%E3%82%A8%E3%82%AD%E3%82%B9%E3%83%91%E3%83%BC%E3%83%88Python%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E6%94%B9%E8%A8%824%E7%89%88-%E3%82%A2%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%89%E3%83%AF%E3%83%B3%E3%82%B4-%EF%BC%B4%EF%BD%81%EF%BD%92%EF%BD%85%EF%BD%8B-%EF%BC%BA%EF%BD%89%EF%BD%81%EF%BD%84%EF%BD%85-ebook/dp/B0CC4TK94R/ref=sr_1_1?crid=2PUPKQHXITVLS&amp;keywords=%E3%82%A8%E3%82%AD%E3%82%B9%E3%83%91%E3%83%BC%E3%83%88python%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0+%E6%94%B9%E8%A8%824%E7%89%88&amp;qid=1705262901&amp;s=books&amp;sprefix=%E3%82%A8%E3%82%AD%E3%82%B9%E3%83%91%E3%83%BC%E3%83%88Python%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0+%E6%94%B9%E8%A8%824%E7%89%88%2Cstripbooks%2C136&amp;sr=1-1">エキスパートPythonプログラミング 改訂4版</a></li> </ul> <h5 id="ピープルマネジメント">ピープルマネジメント</h5> <p>管理職のメンバー評価やキャリア支援などへの興味が伺えますね。 2023年は2冊だったものの、マネジメント、経営関連の書籍も本棚には相当数置いてあります。</p> <ul> <li><a href="https://www.amazon.co.jp/%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A3%E3%83%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E4%BA%BA%E4%BA%8B%E8%A9%95%E4%BE%A1%E3%81%A7%E6%9C%80%E9%AB%98%E3%81%AE%E3%83%81%E3%83%BC%E3%83%A0%E3%82%92%E3%81%A4%E3%81%8F%E3%82%8B%E6%96%B9%E6%B3%95-%E3%80%8C%E6%9F%BB%E5%AE%9A%E3%81%99%E3%82%8B%E5%A0%B4%E3%80%8D%E3%81%8B%E3%82%89%E3%80%8C%E5%85%B1%E3%81%AB%E6%88%90%E9%95%B7%E3%81%99%E3%82%8B%E5%A0%B4%E3%80%8D%E3%81%B8-%E5%B7%9D%E5%86%85-%E6%AD%A3%E7%9B%B4-ebook/dp/B0BTGYM9VM/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=TPQM2HMRUZWF&amp;keywords=%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A3%E3%83%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E4%BA%BA%E4%BA%8B%E8%A9%95%E4%BE%A1%E3%81%A7%E6%9C%80%E9%AB%98%E3%81%AE%E3%83%81%E3%83%BC%E3%83%A0%E3%82%92%E3%81%A4%E3%81%8F%E3%82%8B%E6%96%B9%E6%B3%95+%E3%80%8C%E6%9F%BB%E5%AE%9A%E3%81%99%E3%82%8B%E5%A0%B4%E3%80%8D%E3%81%8B%E3%82%89%E3%80%8C%E5%85%B1%E3%81%AB%E6%88%90%E9%95%B7%E3%81%99%E3%82%8B%E5%A0%B4%E3%80%8D%E3%81%B8&amp;qid=1705262966&amp;s=books&amp;sprefix=%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A3%E3%83%BC%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E4%BA%BA%E4%BA%8B%E8%A9%95%E4%BE%A1%E3%81%A7%E6%9C%80%E9%AB%98%E3%81%AE%E3%83%81%E3%83%BC%E3%83%A0%E3%82%92%E3%81%A4%E3%81%8F%E3%82%8B%E6%96%B9%E6%B3%95+%E6%9F%BB%E5%AE%9A%E3%81%99%E3%82%8B%E5%A0%B4+%E3%81%8B%E3%82%89+%E5%85%B1%E3%81%AB%E6%88%90%E9%95%B7%E3%81%99%E3%82%8B%E5%A0%B4+%E3%81%B8%2Cstripbooks%2C141&amp;sr=1-1">マネジャーのための人事評価で最高のチームをつくる方法 「査定する場」から「共に成長する場」へ</a></li> <li><a href="https://www.amazon.co.jp/s?k=%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88%E5%85%A5%E9%96%80&amp;i=stripbooks&amp;__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=1JD8LSOXYRBPJ&amp;sprefix=%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88%E5%85%A5%E9%96%80%2Cstripbooks%2C137&amp;ref=nb_sb_noss_1">エンジニアのためのマネジメント入門</a></li> </ul> <h4 id="3票獲得カテゴリGoogleアナリティクス--システム設計--プロジェクトマネジメント">3票獲得カテゴリ Googleアナリティクス / システム設計 / プロジェクトマネジメント</h4> <h5 id="Googleアナリティクス">Googleアナリティクス</h5> <p>デジタルマーケティングデータに関わるプロジェクトに新たに配属された社員がまとめて申請をしているようでした。新たなプロジェクト立ち上げや所属の際に、関連書籍の申請がくることも多いです。</p> <ul> <li><p><a href="https://www.amazon.co.jp/Google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94%E3%81%AE%E3%82%84%E3%81%95%E3%81%97%E3%81%84%E6%95%99%E7%A7%91%E6%9B%B8%E3%80%82-%E6%89%8B%E3%82%92%E5%8B%95%E3%81%8B%E3%81%97%E3%81%AA%E3%81%8C%E3%82%89%E5%AD%A6%E3%81%B6%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E8%A7%A3%E6%9E%90%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E5%AE%9F%E8%B7%B5%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88-%E5%B1%B1%E9%87%8E-%E5%8B%89/dp/4295202509/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=1VGGQ6ATDQK2H&amp;keywords=Google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94%E3%81%AE%E3%82%84%E3%81%95%E3%81%97%E3%81%84%E6%95%99%E7%A7%91%E6%9B%B8&amp;qid=1705264207&amp;s=books&amp;sprefix=google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94%E3%81%AE%E3%82%84%E3%81%95%E3%81%97%E3%81%84%E6%95%99%E7%A7%91%E6%9B%B8%2Cstripbooks%2C199&amp;sr=1-1">Googleアナリティクス4のやさしい教科書。 手を動かしながら学ぶアクセス解析の基本と実践ポイント</a></p></li> <li><p><a href="https://www.amazon.co.jp/%E3%80%8C%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8%E3%80%8D%E3%81%8B%E3%82%89%E3%83%91%E3%83%83%E3%81%A8%E5%BC%95%E3%81%91%E3%82%8B-Google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94-%E8%A8%AD%E5%AE%9A%E3%83%BB%E5%88%86%E6%9E%90%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%8C%E3%82%8F%E3%81%8B%E3%82%8B%E6%9C%AC-%E5%B0%8F%E5%B7%9D-%E5%8D%93/dp/4800713080/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=3SENT95N3ZK22&amp;keywords=%E3%80%8C%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8%E3%80%8D%E3%81%8B%E3%82%89%E3%83%91%E3%83%83%E3%81%A8%E5%BC%95%E3%81%91%E3%82%8B+Google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94+%E8%A8%AD%E5%AE%9A&amp;qid=1705264270&amp;s=books&amp;sprefix=%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8+%E3%81%8B%E3%82%89%E3%83%91%E3%83%83%E3%81%A8%E5%BC%95%E3%81%91%E3%82%8B+google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94+%E8%A8%AD%E5%AE%9A%2Cstripbooks%2C140&amp;sr=1-1">「やりたいこと」からパッと引ける Googleアナリティクス4 設定・分析のすべてがわかる本</a></p></li> <li><p><a href="https://www.amazon.co.jp/%E3%81%A7%E3%81%8D%E3%82%8B%E9%80%86%E5%BC%95%E3%81%8D-Google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94-%E6%88%90%E6%9E%9C%E3%82%92%E7%94%9F%E3%81%BF%E5%87%BA%E3%81%99%E5%88%86%E6%9E%90%E3%83%BB%E6%94%B9%E5%96%84%E3%83%AF%E3%82%B6-192-%E6%9C%A8%E7%94%B0/dp/4295015644/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=YVRSDNU6JXWR&amp;keywords=%E3%81%A7%E3%81%8D%E3%82%8B%E9%80%86%E5%BC%95%E3%81%8D+Google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94+%E6%88%90%E6%9E%9C%E3%82%92%E7%94%9F%E3%81%BF%E5%87%BA%E3%81%99%E5%88%86%E6%9E%90%E3%83%BB%E6%94%B9%E5%96%84%E3%83%AF%E3%82%B6+192&amp;qid=1705264305&amp;s=books&amp;sprefix=%E3%81%A7%E3%81%8D%E3%82%8B%E9%80%86%E5%BC%95%E3%81%8D+google%E3%82%A2%E3%83%8A%E3%83%AA%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B94+%E6%88%90%E6%9E%9C%E3%82%92%E7%94%9F%E3%81%BF%E5%87%BA%E3%81%99%E5%88%86%E6%9E%90+%E6%94%B9%E5%96%84%E3%83%AF%E3%82%B6+192%2Cstripbooks%2C135&amp;sr=1-1">できる逆引き Googleアナリティクス4 成果を生み出す分析・改善ワザ 192</a></p></li> </ul> <h5 id="システム設計">システム設計</h5> <p>設計関連の書籍数も非常に数が多く、26冊も社内にありました。</p> <ul> <li><p><a href="https://www.amazon.co.jp/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%83%BB%E3%83%8F%E3%83%BC%E3%83%89%E3%83%91%E3%83%BC%E3%83%84-%E2%80%95%E5%88%86%E6%95%A3%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%83%88%E3%83%AC%E3%83%BC%E3%83%89%E3%82%AA%E3%83%95%E5%88%86%E6%9E%90-Neal-Ford/dp/4814400063/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=2J3CUBL76OEB2&amp;keywords=%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%83%BB%E3%83%8F%E3%83%BC%E3%83%89%E3%83%91%E3%83%BC%E3%83%84&amp;qid=1705264327&amp;s=books&amp;sprefix=%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3+%E3%83%8F%E3%83%BC%E3%83%89%E3%83%91%E3%83%BC%E3%83%84+%2Cstripbooks%2C139&amp;sr=1-1">ソフトウェアアーキテクチャ・ハードパーツ ―分散アーキテクチャのためのトレードオフ分析</a></p></li> <li><p><a href="https://www.amazon.co.jp/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E8%A8%AD%E8%A8%88%E3%81%AE%E3%83%88%E3%83%AC%E3%83%BC%E3%83%89%E3%82%AA%E3%83%95%E3%81%A8%E8%AA%A4%E3%82%8A-%E2%80%95%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E9%9A%9B%E3%81%AB%E3%82%88%E3%82%8A%E8%89%AF%E3%81%84%E9%81%B8%E6%8A%9E%E3%82%92%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF-Tomasz-Lelek/dp/4814400314/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=2XRJWJB2B34ZJ&amp;keywords=%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E8%A8%AD%E8%A8%88%E3%81%AE%E3%83%88%E3%83%AC%E3%83%BC%E3%83%89%E3%82%AA%E3%83%95%E3%81%A8%E8%AA%A4%E3%82%8A&amp;qid=1705264348&amp;s=books&amp;sprefix=%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E8%A8%AD%E8%A8%88%E3%81%AE%E3%83%88%E3%83%AC%E3%83%BC%E3%83%89%E3%82%AA%E3%83%95%E3%81%A8%E8%AA%A4%E3%82%8A%2Cstripbooks%2C142&amp;sr=1-1">ソフトウェア設計のトレードオフと誤り ―プログラミングの際により良い選択をするには</a></p></li> <li><p><a href="https://www.amazon.co.jp/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E8%A8%AD%E8%A8%88%E3%81%AE%E9%9D%A2%E6%8E%A5%E8%A9%A6%E9%A8%93-%E3%82%A2%E3%83%AC%E3%83%83%E3%82%AF%E3%82%B9%E3%83%BB%E3%82%B7%E3%83%A5%E3%82%A6-ebook/dp/B0C61BNTW9/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=1FAZCH12NC38W&amp;keywords=%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E8%A8%AD%E8%A8%88%E3%81%AE%E9%9D%A2%E6%8E%A5%E8%A9%A6%E9%A8%93&amp;qid=1705264367&amp;s=books&amp;sprefix=%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E8%A8%AD%E8%A8%88%E3%81%AE%E9%9D%A2%E6%8E%A5%E8%A9%A6%E9%A8%93%2Cstripbooks%2C138&amp;sr=1-1">システム設計の面接試験</a></p></li> </ul> <h5 id="プロジェクトマネジメント">プロジェクトマネジメント</h5> <p>そして最後はプロジェクトマネジメント。<a href="https://blog.flinters.co.jp/entry/2022/12/12/190902">以前のブログ</a>でも書きましたが、プロジェクトマネージャーの採用、育成にも非常に力を入れており、プロジェクトマネジメント関連の購入申請も多く、社内で活発に読書会や勉強会が行われています。</p> <ul> <li><p><a href="https://www.amazon.co.jp/%E5%9B%B3%E8%A7%A3%E5%8D%B3%E6%88%A6%E5%8A%9B-PMBOK%E7%AC%AC6%E7%89%88%E3%81%AE%E7%9F%A5%E8%AD%98%E3%81%A8%E6%89%8B%E6%B3%95%E3%81%8C%E3%81%93%E3%82%8C1%E5%86%8A%E3%81%A7%E3%81%97%E3%81%A3%E3%81%8B%E3%82%8A%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8-%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BETRADECREATE/dp/4297108879/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=3MHAQCLS5M3CV&amp;keywords=%E5%9B%B3%E8%A7%A3%E5%8D%B3%E6%88%A6%E5%8A%9B+PMBOK%E7%AC%AC6%E7%89%88%E3%81%AE&amp;qid=1705264548&amp;sprefix=%E5%9B%B3%E8%A7%A3%E5%8D%B3%E6%88%A6%E5%8A%9B+pmbok%E7%AC%AC6%E7%89%88%E3%81%AE%2Caps%2C149&amp;sr=8-1">図解即戦力 PMBOK第6版の知識と手法がこれ1冊でしっかりわかる教科書</a></p></li> <li><p><a href="https://www.amazon.co.jp/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%92%E4%BD%9C%E3%82%89%E3%81%9B%E3%82%8B%E6%8A%80%E8%A1%93-%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%84%E3%81%82%E3%81%AA%E3%81%9F%E3%81%B8-%E7%99%BD%E5%B7%9D-%E5%85%8B/dp/4532323991/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=YWWCIXE554T6&amp;keywords=%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%92%E4%BD%9C%E3%82%89%E3%81%9B%E3%82%8B%E6%8A%80%E8%A1%93+%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%84%E3%81%82%E3%81%AA%E3%81%9F%E3%81%B8&amp;qid=1705264965&amp;sprefix=%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%92%E4%BD%9C%E3%82%89%E3%81%9B%E3%82%8B%E6%8A%80%E8%A1%93+%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%84%E3%81%82%E3%81%AA%E3%81%9F%E3%81%B8%2Caps%2C387&amp;sr=8-1">システムを作らせる技術 エンジニアではないあなたへ</a></p></li> <li><p><a href="https://www.amazon.co.jp/This-Lean-%E3%80%8C%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%80%8D%E3%81%AB%E3%81%A8%E3%82%89%E3%82%8F%E3%82%8C%E3%81%9A%E3%83%81%E3%83%BC%E3%83%A0%E3%82%92%E5%A4%89%E3%81%88%E3%82%8B%E6%96%B0%E6%99%82%E4%BB%A3%E3%81%AE%E3%83%AA%E3%83%BC%E3%83%B3%E3%83%BB%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88-%E4%BA%8C%E3%82%AF%E3%83%A9%E3%82%B9%E3%83%BB%E3%83%A2%E3%83%BC%E3%83%87%E3%82%A3%E3%82%B0-ebook/dp/B08W52R8LD/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=1OOM94VJKNL25&amp;keywords=This+is+Lean+%E3%80%8C%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%80%8D%E3%81%AB%E3%81%A8%E3%82%89%E3%82%8F%E3%82%8C%E3%81%9A%E3%83%81%E3%83%BC%E3%83%A0%E3%82%92%E5%A4%89%E3%81%88%E3%82%8B%E6%96%B0%E6%99%82%E4%BB%A3%E3%81%AE%E3%83%AA%E3%83%BC%E3%83%B3%E3%83%BB%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88&amp;qid=1705264990&amp;sprefix=this+is+lean+%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9+%E3%81%AB%E3%81%A8%E3%82%89%E3%82%8F%E3%82%8C%E3%81%9A%E3%83%81%E3%83%BC%E3%83%A0%E3%82%92%E5%A4%89%E3%81%88%E3%82%8B%E6%96%B0%E6%99%82%E4%BB%A3%E3%81%AE%E3%83%AA%E3%83%BC%E3%83%B3+%E3%83%9E%E3%83%8D%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88%2Caps%2C141&amp;sr=8-1">This is Lean 「リソース」にとらわれずチームを変える新時代のリーン・マネジメント</a></p></li> </ul> <h5 id="その他ピックアップ">その他ピックアップ</h5> <ul> <li><a href="https://www.amazon.co.jp/GitLab%E3%81%AB%E5%AD%A6%E3%81%B6-%E4%B8%96%E7%95%8C%E6%9C%80%E5%85%88%E7%AB%AF%E3%81%AE%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E7%B5%84%E7%B9%94%E3%81%AE%E3%81%A4%E3%81%8F%E3%82%8A%E3%81%8B%E3%81%9F-%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AE%E6%B4%BB%E7%94%A8%E3%81%A7%E3%82%AA%E3%83%95%E3%82%A3%E3%82%B9%E3%81%AA%E3%81%97%E3%81%A7%E3%82%82%E6%9C%80%E5%A4%A7%E3%81%AE%E6%88%90%E6%9E%9C%E3%82%92%E5%87%BA%E3%81%99%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E4%BC%81%E6%A5%AD%E3%81%AE%E3%81%97%E3%81%8F%E3%81%BF-%E5%8D%83%E7%94%B0-%E5%92%8C%E5%A4%AE-ebook/dp/B0CBR9GYF6/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=1Q12EUDB6CEG4&amp;keywords=*+GitLab%E3%81%AB%E5%AD%A6%E3%81%B6+%E4%B8%96%E7%95%8C%E6%9C%80%E5%85%88%E7%AB%AF%E3%81%AE%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E7%B5%84%E7%B9%94%E3%81%AE%E3%81%A4%E3%81%8F%E3%82%8A%E3%81%8B%E3%81%9F+%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AE%E6%B4%BB%E7%94%A8%E3%81%A7%E3%82%AA%E3%83%95&amp;qid=1705265049&amp;sprefix=gitlab%E3%81%AB%E5%AD%A6%E3%81%B6+%E4%B8%96%E7%95%8C%E6%9C%80%E5%85%88%E7%AB%AF%E3%81%AE%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E7%B5%84%E7%B9%94%E3%81%AE%E3%81%A4%E3%81%8F%E3%82%8A%E3%81%8B%E3%81%9F+%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AE%E6%B4%BB%E7%94%A8%E3%81%A7%E3%82%AA%E3%83%95%2Caps%2C138&amp;sr=8-1">GitLabに学ぶ 世界最先端のリモート組織のつくりかた ドキュメントの活用でオフ</a></li> </ul> <p>言わずと知れたグローバル規模でのリモートワーク実践企業の書籍。そういえばコロナが始まり弊社もリモートワークへ移行した際に、GitLab社が公開しているリモートワークのノウハウ集を、有志で翻訳して読書会を実施したこともありました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fabout.gitlab.com%2Fcompany%2Fall-remote%2F" title="All Remote" 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://about.gitlab.com/company/all-remote/">about.gitlab.com</a></cite></p> <ul> <li><a href="https://www.amazon.co.jp/%E6%B1%BA%E5%AE%9A%E7%89%88-%E3%83%99%E3%83%88%E3%83%8A%E3%83%A0%E3%81%AE%E3%81%93%E3%81%A8%E3%81%8C%E3%83%9E%E3%83%B3%E3%82%AC%E3%81%A73%E6%99%82%E9%96%93%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B%E6%9C%AC-%E7%A6%8F%E6%A3%AE-%E5%93%B2%E4%B9%9F/dp/4756921299/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;crid=B62AAUKGZI4Z&amp;keywords=%E3%83%99%E3%83%88%E3%83%8A%E3%83%A0%E3%81%AE%E3%81%93%E3%81%A8%E3%81%8C%E3%83%9E%E3%83%B3%E3%82%AC%E3%81%A73%E6%99%82%E9%96%93%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B%E6%9C%AC&amp;qid=1705265250&amp;sprefix=%E3%83%99%E3%83%88%E3%83%8A%E3%83%A0%E3%81%AE%E3%81%93%E3%81%A8%E3%81%8C%E3%83%9E%E3%83%B3%E3%82%AC%E3%81%A73%E6%99%82%E9%96%93%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B%E6%9C%AC+%2Caps%2C141&amp;sr=8-1"> &lt;決定版>ベトナムのことがマンガで3時間でわかる本 </a></li> </ul> <p>弊社のオフシェア開発拠点であるFLINTERS VIETNAM社がベトナムのハノイにあり、日本側と連携して開発を行うことも多くあります。コロナ以前はお互いに出張で行き来することも多かったです。</p> <p>新たにFLINTERS VIETNAM社とのプロジェクトに参加する社員がベトナムの文化を学ぶために購入申請をしたようですが、国境を超えたプロジェクトをうまく進めていくためには、何よりもまずお互いの文化を理解することが重要ですよね。</p> <p>以上2023年に会社で購入された書籍に注目してみました。</p> <p>2024年はどのような書籍が購入されるのでしょうか??? 弊社に限らず、書籍購入制度がある会社については、社内で最近購入されている書籍を知ることで、その会社内での最近の興味関心や、プロジェクトの状況を垣間見ることができるかもしれないですね。</p> <p>それではまた〜</p> yokoyamountain DependabotとGitHub Actionsを使い、低コストでパッケージやライブラリをアップデートする hatenablog://entry/6801883189075157260 2024-01-15T12:30:19+09:00 2024-01-15T15:07:13+09:00 ソフトウェア開発で重要なセキュリティについて、ソフトウェアが利用しているパッケージやライブラリをソフトウェアのコードを管理しているGitHub上でDependabotやGitHub Actionsを利用することによって低コストで定期的にバージョンの更新を行い、セキュリティリスク下げる取り組みをしたので、それについて書きたいと思います。 <p>こんにちは、株式会社FLINTERSでエンジニアをやっている丸山です。</p> <p>この記事は2024年1月にFLINTERSが10周年を迎えることを記念して、133日連続でブログを書き続けるチャレンジの一環として書かれました。本記事は129日目の記事となります。</p> <p>以前<a href="https://blog.flinters.co.jp/entry/2023/11/20/121147">73日目の記事</a>も執筆しましたので、よければそれもご覧になってください。</p> <p>今回の記事は、ソフトウェア開発で重要なセキュリティについて、ソフトウェアが利用しているパッケージやライブラリをソフトウェアのコードを管理しているGitHub上でDependabotやGitHub Actionsを利用することによって低コストで定期的にバージョンの更新を行い、セキュリティリスク下げる取り組みをしたので、それについて書きたいと思います。</p> <h1 id="はじめに">はじめに</h1> <p> ビッグデータという言葉がもてはやされ、研究者などの専門家以外でもOpenAIのChatGPTなどを通してAI技術に簡単に触れられるようになった現在、情報はますます重要なものとなっています。 またこれらの情報をソーシャル・ネットワーキング・サービス(SNS)などを使いインターネットを通して多くの人に配信をするような通信技術も同様に重要です。 この2つはまとめて情報通信技術(Information and Communication Technology: ICT)と呼ばれますが、このICTが発展・普及した経済と社会をデジタル経済と呼ぶ<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>ほど今日ではICTは重要視されています。</p> <p> このICTを支える技術の1つにソフトウェアがあり、パソコンやスマートフォンのOSからブラウザ、ブラウザで閲覧するWebページを配信するWebアプリケーションなど、さまざまなところにソフトウェアは存在しています。デジタル経済と呼ばれる現在ではこのソフトウェアの信頼性は社会基盤の安定性を左右するほどとなり、ほとんどのビジネスにおいてもソフトウェアは重要であると言えるでしょう。</p> <p> そんな社会基盤を担い、ビジネスにおいても重要なソフトウェアですが、近年はそのソフトウェアを取り扱うものに対して被害を与えるサイバー攻撃による被害が拡大しています<sup id="fnref:2"><a href="#fn:2" rel="footnote">2</a></sup>。 サイバー攻撃による被害を受けると、金銭の詐取等の直接的な被害だけでなく、株価や純利益が下落・減少するといった間接的な被害が発生します<sup id="fnref:3"><a href="#fn:3" rel="footnote">3</a></sup>。他にも業務停止といった被害の事例もあり、その被害は無視できないものとなっています。 このような被害を防ぐソフトウェア開発者ができる対策の1つにソフトウェアが利用しているライブラリ・パッケージを更新するという方法があります。</p> <p> 今日の複雑化し、かつ高速に開発することを求められるソフトウェア開発では、世界中の開発者によって開発され公開されているOSS(Open Source Software)やFOSS(Free and Open Source Software)と呼ばれるソフトウェアをライブラリーやパッケージという形で利用するということは珍しくなく、2022年時点で開発されている商用ソフトウェアを対象にした調査では、ソフトウェアの96%にOSSが含まれている<sup id="fnref:4"><a href="#fn:4" rel="footnote">4</a></sup>という調査結果が報告されるほど一般的となっています。 このようにOSSのライブラリやパッケージの利用による恩恵は大きく、それを利用することも一般的となっている現在ですが、それを起因とするセキュリティの懸念というのも存在します。 この懸念には大きく2つのパターンが存在しており、OSSの開発者が悪意を持って利用者に被害を与えるプログラムを仕込むマルウェアと、開発者が意図せずにコードの欠陥を仕込ませてしまう脆弱性が存在します<sup id="fnref:5"><a href="#fn:5" rel="footnote">5</a></sup>。</p> <p> コードの欠陥によって生じた脆弱性で近年最も有名な脆弱性は2021年にJavaベースのロギング用ライブラリであるApache Log4jで発見された任意コード実行が行える通称Log4Shellと呼ばれる脆弱性(CVE-2021-44228<sup id="fnref:6"><a href="#fn:6" rel="footnote">6</a></sup>)です。 この脆弱性は攻撃者が送信した文字列をLog4jがログとして記録すると、その文字列に記述された通信先やサーバー内部のファイルを読み込んで実行するもので、インターネット上に公開されたサーバーからマルウェアをダウンロードさせWebサーバーにマルウェアを感染させることが可能となる内容であり<sup id="fnref:7"><a href="#fn:7" rel="footnote">7</a></sup>、実際にLog4jを利用したソフトウェアを動かしているサーバーがランサムウェアのマルウェアに感染させられたり、暗号資産のマイニングを行うマイナーを設置されたりする被害が報告されています<sup id="fnref:8"><a href="#fn:8" rel="footnote">8</a></sup>。 この脆弱性に対する対策はソフトウェアが利用しているLog4jのバージョンを脆弱性が修正されたバージョンに更新するという方法です。</p> <p> このようにメンテナンスが行われているOSSでは脆弱性が発見されるとその脆弱性を修正したバージョンが公開されるのが一般的であり、OSSの利用者はその修正したバージョンにライブラリやパッケージを更新することによって脆弱性を防ぐことができます。 しかし実際にはライブラリやパッケージに脆弱性が発見され、それを修正するバージョンが公開されても、修正したバージョンを利用せずに脆弱性が存在するバージョンを利用し続けられていることが少なくないことが確認されています。 公開されているJavaのライブラリの配布を行っているMaven Centralを対象にした調査では、ダウンロードされている脆弱性が確認されているライブラリの96%はアップデートすることによってその脆弱性を防げることがわかっています[^5]。また、最悪の脆弱性とまで言われたLog4Shellからほぼ2年が経過した現在でもLog4jのダウンロードの約4分の1がLog4Shell対処前の脆弱性があるバージョンであることもわかっています[^5]。</p> <p> 脆弱性が含まれるライブラリやパッケージのほとんどがアップデートを行うことによってその脆弱性が防ぐことが可能であるにもかかわらず、アップデートされない理由はなぜなのでしょうか? これは多くの開発者にとってセキュリティが最優先事項でないことが原因の1つにあります。 開発者はセキュリティに関心がないというわけではありませんが、セキュリティを優先することによって納期が守れないことを回避したり、セキュリティについて学習する時間が不十分などの理由により、セキュリティについて積極的に取り組めておらず<sup id="fnref:9"><a href="#fn:9" rel="footnote">9</a></sup>、ライブラリやパッケージを最新に保つことを苦痛と考えるような開発者も52%いると調査されています<sup id="fnref:10"><a href="#fn:10" rel="footnote">10</a></sup>。</p> <p> このような課題を解決する方法の1つにDependabot<sup id="fnref:11"><a href="#fn:11" rel="footnote">11</a></sup>やRenovate<sup id="fnref:12"><a href="#fn:12" rel="footnote">12</a></sup>など、ソフトウェア開発を行っているリポジトリで利用しているライブラリやパッケージなどの依存関係の更新を行う支援をしてくる仕組みを導入する方法があります。 DependabotやRenovateはGitHub<sup id="fnref:13"><a href="#fn:13" rel="footnote">13</a></sup>やGitLab<sup id="fnref:14"><a href="#fn:14" rel="footnote">14</a></sup>などのソフトウェアのコードを管理するサービスに導入が可能であり、導入すると管理を行っているソフトウェアのコードが利用している依存関係を管理しているPackage managerを利用して、利用している依存関係に最新バージョンがリリースされていないか定期的にチェックをしてくれるようになります。 もしチェックした時に利用している依存関係に最新バージョンがリリースされていれば、Dependabotなどはそのソフトウェアが最新のバージョンを利用するように変更を行うPull RequestやMerge Requestを作成してくれます。</p> <p>そこで我々の開発チームでは、開発物を管理しているGitHub上のリポジトリに対してDependabotを導入し、依存関係の更新にかかるコストを抑えつつ更新することによってセキュリティリスクを削減する仕組みを導入しています。 この記事では、チームで導入しているDependabot運用ルールを説明しつつ、その設定方法について書きたいと思います。</p> <h1 id="Dependabotについて">Dependabotについて</h1> <p> Dependabotは2017年にスタートしたプロジェクト<sup id="fnref:15"><a href="#fn:15" rel="footnote">15</a></sup>で、2019年にGitHubに買収され<sup id="fnref:16"><a href="#fn:16" rel="footnote">16</a></sup>、2021年にGitHubから正式にサービス提供され始めました<sup id="fnref:17"><a href="#fn:17" rel="footnote">17</a></sup>。 現在Dependabotは、使用している依存関係に存在する脆弱性を通知するDependabot alerts<sup id="fnref:18"><a href="#fn:18" rel="footnote">18</a></sup>、既知の脆弱性を更新するDependabot security updates<sup id="fnref:19"><a href="#fn:19" rel="footnote">19</a></sup>、依存関係を最新に保つDependabot version updates<sup id="fnref:20"><a href="#fn:20" rel="footnote">20</a></sup>が正式に提供されており、アラートを管理するDependabot auto-triage rules<sup id="fnref:21"><a href="#fn:21" rel="footnote">21</a></sup>がベータ版<sup id="fnref:22"><a href="#fn:22" rel="footnote">22</a></sup>で提供されています。</p> <p> ベータ版であるDependabot auto-triage rules以外について、それぞれについて説明すると次のようになります。</p> <h2 id="Dependabot-alerts">Dependabot alerts</h2> <p> リポジトリ内のコードが使用している依存関係をスキャンし、安全でない依存関係を検出したときに通知を行います。 スキャンを行うタイミングはリポジトリの依存関係が更新された時と、既知のセキュリティの脆弱性とマルウェアを記録しているGitHub Advisory Databaseに新しい脆弱性またはマルウェアが登録されたときです<sup id="fnref:23"><a href="#fn:23" rel="footnote">23</a></sup>.<sup id="fnref:24"><a href="#fn:24" rel="footnote">24</a></sup>。</p> <p>もし、脆弱性やマルウェアが検出されたときは、リポジトリのSecurityタブにアラートが表示され、リポジトリのメンテナーに対してはメールで通知を行います。 このほかにも、mainなど特定のブランチに対して行われたPull Requestで変更される依存関係を確認し、既知の脆弱性があるか確認することもできます。</p> <h2 id="Dependabot-security-updates">Dependabot security updates</h2> <p> Dependabot alertsが安全でない依存関係を検出した時に、依存関係の更新が最小限で済むような脆弱性を解決するPull Requestを作成します。 これによって作成されたPull RequestをマージをするとDependabot alertsの警告が解決済みとして扱われます。</p> <h2 id="Dependabot-version-updates">Dependabot version updates</h2> <p> リポジトリ内のコードが使用している依存関係のバージョンを最新に保つためのPull Requestsを作成します。 リポジトリに<code>.github/dependabot.yml</code>を設置し、更新を行いたいパッケージシステムや更新頻度などの設定を記述する必要があります。</p> <h1 id="開発チームについて">開発チームについて</h1> <p> 現在所属しているチームの開発者は7~8名で、開発言語にはTypeScript, Rust, Python, Terraform, Scalaなどが使用されています。 チームでは複数のリポジトリで開発を行っていますが、全員が全てのリポジトリ開発に関わっているわけではなく、得意分野や関心、プロジェクトのドメイン領域などによって複数の担当リポジトリを受け持っています。 Pull RequestsをMergeする際はレビューを行うルールもあり、GitHub Actionsを利用したCIによるチェックも行っています。 リポジトリの設定ではCIによるチェックとレビューを強制するためにBranch protection ruelesを設定しており、リポジトリの担当者を明示的に示す<code>.github/CODEOWNERS</code>に記載された担当者からのレビューが必要です。</p> <h1 id="チームのDependabotのルール">チームのDependabotのルール</h1> <p> チームではどのリポジトリに対しても最低月1回は依存関係のアップデートを行うルールがあります。 しかしDependabotが同時に開けるPull Requestsの数には上限があり最大で10個までであるため<sup id="fnref:25"><a href="#fn:25" rel="footnote">25</a></sup>、1月で10個以上Pull Requestsが作成されると次のチェックを行うタイミングまで無視された依存関係の更新ができないという理由から、必要に応じて週1回や毎日にDependabotが更新を確認するタイミングを増やします。</p> <p> また、Dependabotが作成したPull Requestsのレビューを行う担当者は<code>.github/CODEOWNERS</code>に記載されている人が行います。 ただし、<code>npm</code>を扱うリポジトリなどは依存関係の数も多く、更新頻度も高いため、全てのPull Requestsに対して全員がレビューを行うのは開発者の負荷が高くなるという理由から、 依存関係がアプリケーションに直接利用されるものか、開発時にのみ使われるものか、加えて更新の内容のバージョンがMajor Update, Minor Update, Patch Updateなのかに応じて、自動でマージを行う、もしくはレビューを行う人数を減らすなどの工夫を行なっています。 これを整理すると次のようなルールでレビューとマージを行なっています。</p> <table> <thead> <tr> <th> 依存の種類 </th> <th> 更新のバージョン</th> <th> 自動マージ </th> <th> 人間のレビュー </th> </tr> </thead> <tbody> <tr> <td> アプリが使う </td> <td> Major </td> <td> しない </td> <td> 全員 </td> </tr> <tr> <td> アプリが使う </td> <td> Minor </td> <td> しない </td> <td> 抽選で選ばれた2人 </td> </tr> <tr> <td> アプリが使う </td> <td> Patch </td> <td> する </td> <td> しない </td> </tr> <tr> <td> 開発用 </td> <td> Major </td> <td> しない </td> <td> 全員 </td> </tr> <tr> <td> 開発用 </td> <td> Minor </td> <td> する </td> <td> しない </td> </tr> <tr> <td> 開発用 </td> <td> Patch </td> <td> する </td> <td> しない </td> </tr> </tbody> </table> <h1 id="DependabotとGitHub-Actionsの設定">DependabotとGitHub Actionsの設定</h1> <p> 前章ではチームでのDependabotの運用のルールを示しました。本章ではこれを実際に動かすためにGitHubの設定とDependabotやGitHub Actionsのymlの設定を解説していきます。</p> <h2 id="CODEOWNERS">CODEOWNERS</h2> <p> まず、リポジトリのレビューの担当者を示すCODEOWNERSについて設定していきます。 CODEOWNERS<sup id="fnref:26"><a href="#fn:26" rel="footnote">26</a></sup>は<code>.github/CODEOWNERS</code>に配置し、行ごとにファイルやディレクトリとその担当者を記述することによってその担当箇所の変更が含まれるPull Requestのレビューを強制することができます。</p> <p>例えば次のように設定を行うとリポジトリ内の全てのファイルに対してuser1, user2, user3のレビューを強制することができます。</p> <pre class="code CODEOWNERS" data-lang="CODEOWNERS" data-unlink>* @user1 @user2 @user3</pre> <p> しかし、これに記述されるとdependabotが作成したPull RequestsでもCODEOWNERSに記載されたレビュワーにレビュー依頼が飛んでしまうため、もしPull Requestsの内容に応じてレビュー担当者を変えたい場合は、Dependabotが変更を行うファイルの担当者がいないことを記載してあげる必要があります。</p> <pre class="code CODEOWNERS" data-lang="CODEOWNERS" data-unlink>* @user1 @user2 @user3 # Dependabotが変更を行うファイルを記載し、後ろにユーザーIDを記載しないことによってDependabotが作成したPull Requestsに強制的にレビュワーが割り当てられない /package.json /package-lock.json</pre> <h2 id="dependabotyml">dependabot.yml</h2> <p> 実際にDependabotで依存関係の更新を行うPull Requestsを作成する設定を書きます。 Dependabotの設定は<code>.github/dependabot.yml</code>に記述します。 例えば、リポジトリのルートに置かれている<code>./package.json</code>と<code>./package-lock.json</code>を使用して利用しているnpmパッケージの更新がないかを月1回確認する設定は次のようになります。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">2</span> <span class="synIdentifier">updates</span><span class="synSpecial">:</span> <span class="synComment"> # npmのバージョン更新を有効にする</span> <span class="synStatement">- </span><span class="synIdentifier">package-ecosystem</span><span class="synSpecial">:</span> <span class="synConstant">&quot;npm&quot;</span> <span class="synComment"> # rootディレクトリに置かれているpackage.jsonとlockファイルを使用する</span> <span class="synIdentifier">directory</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/&quot;</span> <span class="synComment"> # 毎月1日にnpmレジストリを更新のチェックをする</span> <span class="synIdentifier">schedule</span><span class="synSpecial">:</span> <span class="synIdentifier">interval</span><span class="synSpecial">:</span> <span class="synConstant">&quot;monthly&quot;</span> </pre> <p> これが基本的な設定ですが、dependabotが作成するPull Requestsにレビュワーを割り当てたり、チェックを行う時間の指定を行うようにすると次のようになります。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">2</span> <span class="synIdentifier">updates</span><span class="synSpecial">:</span> <span class="synComment"> # npmのバージョン更新を有効にする</span> <span class="synStatement">- </span><span class="synIdentifier">package-ecosystem</span><span class="synSpecial">:</span> <span class="synConstant">&quot;npm&quot;</span> <span class="synComment"> # rootディレクトリに置かれているpackage.jsonとlockファイルを使用する</span> <span class="synIdentifier">directory</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/&quot;</span> <span class="synComment"> # 毎月1日に日本時間の10時にnpmレジストリを更新のチェックをする</span> <span class="synIdentifier">schedule</span><span class="synSpecial">:</span> <span class="synIdentifier">interval</span><span class="synSpecial">:</span> <span class="synConstant">&quot;monthly&quot;</span> <span class="synIdentifier">timezone</span><span class="synSpecial">:</span> <span class="synConstant">&quot;Asia/Tokyo&quot;</span> <span class="synIdentifier">time</span><span class="synSpecial">:</span> <span class="synConstant">&quot;10:00&quot;</span> <span class="synComment"> # 作成されたPull Requestsにuser1, user2, user3のレビュワーを割り当てる。</span> <span class="synIdentifier">reviewers</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synConstant">&quot;user1&quot;</span> <span class="synStatement">- </span><span class="synConstant">&quot;user2&quot;</span> <span class="synStatement">- </span><span class="synConstant">&quot;user3&quot;</span> </pre> <p> ただしチームでは、レビュワーは更新の内容に応じて動的に割り当てるようにしてあるため、reviewersは設定せず、また、デフォルトでは同時に作成されるPull Requestsの数が5つまでで、それ以上は次のチェックのタイミングまで作成されないため数を緩和する設定も行い次のようになっています</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">2</span> <span class="synIdentifier">updates</span><span class="synSpecial">:</span> <span class="synComment"> # npmのバージョン更新を有効にする</span> <span class="synStatement">- </span><span class="synIdentifier">package-ecosystem</span><span class="synSpecial">:</span> <span class="synConstant">&quot;npm&quot;</span> <span class="synComment"> # rootディレクトリに置かれているpackage.jsonとlockファイルを使用する</span> <span class="synIdentifier">directory</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/&quot;</span> <span class="synComment"> # 毎日に日本時間の10時にnpmレジストリを更新のチェックをする</span> <span class="synIdentifier">schedule</span><span class="synSpecial">:</span> <span class="synIdentifier">interval</span><span class="synSpecial">:</span> <span class="synConstant">&quot;daily&quot;</span> <span class="synIdentifier">timezone</span><span class="synSpecial">:</span> <span class="synConstant">&quot;Asia/Tokyo&quot;</span> <span class="synIdentifier">time</span><span class="synSpecial">:</span> <span class="synConstant">&quot;10:00&quot;</span> <span class="synComment"> # デフォルトで作成できるPull Requestsの数は5個なので10個まで作成できるようにする</span> <span class="synIdentifier">open-pull-requests-limit</span><span class="synSpecial">:</span> <span class="synConstant">10</span> <span class="synComment"> # GitHub Actionsが利用しているActionsのバージョンは月1回確認を行う</span> <span class="synStatement">- </span><span class="synIdentifier">package-ecosystem</span><span class="synSpecial">:</span> <span class="synConstant">&quot;github-actions&quot;</span> <span class="synComment"> # github-actionsかつdirectoryが/の場合.github/workflowsが参照される</span> <span class="synIdentifier">directory</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/&quot;</span> <span class="synIdentifier">schedule</span><span class="synSpecial">:</span> <span class="synIdentifier">interval</span><span class="synSpecial">:</span> <span class="synConstant">&quot;monthly&quot;</span> <span class="synIdentifier">timezone</span><span class="synSpecial">:</span> <span class="synConstant">&quot;Asia/Tokyo&quot;</span> <span class="synIdentifier">time</span><span class="synSpecial">:</span> <span class="synConstant">&quot;10:00&quot;</span> <span class="synIdentifier">open-pull-requests-limit</span><span class="synSpecial">:</span> <span class="synConstant">10</span> </pre> <p> また、更新がある依存関係に関して通常はそれぞれPull Requestsが作成されますが、groupesを使うことによって、複数のライブラリの更新をまとめて1つのPull Requestsで更新を行うことができます。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">2</span> <span class="synIdentifier">updates</span><span class="synSpecial">:</span> <span class="synComment"> # npmのバージョン更新を有効にする</span> <span class="synStatement">- </span><span class="synIdentifier">package-ecosystem</span><span class="synSpecial">:</span> <span class="synConstant">&quot;npm&quot;</span> <span class="synComment"> # rootディレクトリに置かれているpackage.jsonとlockファイルを使用する</span> <span class="synIdentifier">directory</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/&quot;</span> <span class="synComment"> # 毎日に日本時間の10時にnpmレジストリを更新のチェックをする</span> <span class="synIdentifier">schedule</span><span class="synSpecial">:</span> <span class="synIdentifier">interval</span><span class="synSpecial">:</span> <span class="synConstant">&quot;daily&quot;</span> <span class="synIdentifier">timezone</span><span class="synSpecial">:</span> <span class="synConstant">&quot;Asia/Tokyo&quot;</span> <span class="synIdentifier">time</span><span class="synSpecial">:</span> <span class="synConstant">&quot;10:00&quot;</span> <span class="synComment"> # デフォルトで作成できるPull Requestsの数は5個なので10個まで作成できるようにする</span> <span class="synIdentifier">open-pull-requests-limit</span><span class="synSpecial">:</span> <span class="synConstant">10</span> <span class="synComment"> # reactに関する依存関係の更新を1つのPull Requestsで行う</span> <span class="synIdentifier">groups</span><span class="synSpecial">:</span> <span class="synIdentifier">react</span><span class="synSpecial">:</span> <span class="synIdentifier">patterns</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synConstant">&quot;react&quot;</span> <span class="synStatement">- </span><span class="synConstant">&quot;react-dom&quot;</span> <span class="synStatement">- </span><span class="synConstant">&quot;@types/react&quot;</span> <span class="synStatement">- </span><span class="synConstant">&quot;@types/react-dom&quot;</span> </pre> <h2 id="GitHub-Actionsを使い自動マージや動的にレビュワーを割り当てられるようにする">GitHub Actionsを使い自動マージや動的にレビュワーを割り当てられるようにする</h2> <p> dependabot.ymlを設定することによってDependabotが依存関係を更新するPull Requestsを作成するようになりましたが、作成されるPull Requestsの量が多いと開発者はそのレビューが負担となり普段の開発の速度が遅くなる、もしくはDependabotが作成したPull Requestsを無視するようになります。 そこでレビューの負担を下げるために自動マージや影響度が低そうな更新についてはレビューを行う人数を減らすなどの対応が取れます。 これを実行するにはGitHubが提供しているCI/CD環境であるGitHub Actionsを使用する方法があります。</p> <p> ここではGitHub Actionsを利用してDependabotが作成するPull Requestsに自動マージと動的にレビュワーを割り当てる機能を実装します。</p> <h3 id="リポジトリに対する設定">リポジトリに対する設定</h3> <p> まずは、自動マージが行えるようにリポジトリの設定を行います。 GitHubで設定を行いたいリポジトリのSettingsを開きます(<code>https://github.com/[user-name]/[repository-name]/settings</code>)。GeneralからPull Requestsの項目にあるAllow auto-mergeにチェックを入れます。</p> <p><figure class="figure-image figure-image-fotolife" title="GitHub リポジトリのSettingsから自動マージを許可できるようになるAllow auto-mergteの設定項目"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m_maruyama/20240115/20240115082926.png" width="703" height="148" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>GitHub リポジトリのSettingsから自動マージを許可できるようになるAllow auto-mergteの設定項目</figcaption></figure></p> <p> これを入れることによって、各Pull Requestsで自動マージを行う許可を出せ、許可を出すとBranch protections rulesで定めたルールを満たした時に自動でマージを行うようになります。 これは各Pull Requestsごとに自動マージを行う許可を出すため、通常の開発ではPull Requestsのページから自動マージの許可を出さなければ影響はありません。</p> <p> 前章では自動マージを行う条件としてPatch Updateか、開発のみに使用されるパッケージのMinor Updateかという条件が示されていましたが、そのほかにもCIが問題なく通った場合にマージをするという条件もあります。このため、CIが通らなかった場合はマージされないようにする必要があるため、Branch protection ruleでしっかり条件を示してあげる必要があります。</p> <p> このBranch protection ruleは通常の開発で人間が作成したPull RequestsとDependabotが作成したPull Requestsでルールを分けることができないため、両方を満たすように条件を設定してあげることが必要です。</p> <p> 例えば通常の開発でレビューを必須とした場合 Require a pull request before merging をチェックし、ブランチの内容が必ずPull Requestから更新されるようにし、Require approvalsをチェックすることによって最低n人からのapproveがないとマージできないようにするという設定が行えます。また、Require review from Code Ownersをチェックすることによって<code>.github/CODEOWNERS</code>に記載されたユーザーがapproveを行わないとマージができないようにすることもできます。</p> <p><figure class="figure-image figure-image-fotolife" title="Branch protection ruleでレビューを必須とする設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m_maruyama/20240115/20240115084111.png" width="703" height="357" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Branch protection ruleでレビューを必須とする設定</figcaption></figure></p> <p> 加えて、CIによるチェックを必須とした場合は、Require status check to pass before mergingにチェックを入れることによって、CIが失敗した時にマージができないようにすることが可能です。CIによるチェックを必要とする場合は同時に Status checks that are requiredにチェックを行うCIの名前も記載しておくと、記載されたCIが正常に終了するまでマージができないようになります。 もし、自動マージを有効にする場合は、Status checks that are requiredにCIの名前を記載しておかないと、CIが動く前に正常と判断されてマージされてしまう場合があります。 <figure class="figure-image figure-image-fotolife" title="Branch protection ruleでCIによるチェックを必須とする設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m_maruyama/20240115/20240115084722.png" width="703" height="331" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Branch protection ruleでCIによるチェックを必須とする設定</figcaption></figure></p> <p> また、自動マージを行う際は自動マージされたPull Requestsか区別がつきやすいようにlabelをつけるようにしておくことがおすすめです。 IssueやPull RequestsからLabelsを選択することによって<code>https://github.com/[user-name]/[repository-name]/labels</code> に移動するので、そこからNew labelをクリックして自動マージかどうか識別しやすいラベルをあらかじめ作っておきます。 Dependabotが作成したPull Requestsにはdependenciesと対応する言語のラベルがつくので、Dependabotが作成したPull Requestsの識別は簡単です。</p> <p><figure class="figure-image figure-image-fotolife" title="Dependabotによって作成されたPull Requestsを識別しやすくするために自動マージについてのauto mergeラベルを作成する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/m_maruyama/20240115/20240115085836.png" width="592" height="313" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Dependabotによって作成されたPull Requestsを識別しやすくするために自動マージについてのauto mergeラベルを作成する</figcaption></figure></p> <p>以上でリポジトリに対する設定は完了です。以降は自動マージと動的にレビュワーを割り当てるGitHub Actionsを設定します。</p> <h3 id="GitHub-Actionsの設定">GitHub Actionsの設定</h3> <p> GitHub ActionsはGitHubが提供しているCI/CD環境です。これを利用することによってDependabotが作成したPull Requestsにのみ自動マージを行うなどのタスクを実行させることができます。</p> <p>まず、GitHubが自動マージについて簡単な例を示している<sup id="fnref:27"><a href="#fn:27" rel="footnote">27</a></sup>のでそれを解説します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">name</span><span class="synSpecial">:</span> Dependabot auto-merge <span class="synIdentifier">on</span><span class="synSpecial">:</span> pull_request <span class="synIdentifier">permissions</span><span class="synSpecial">:</span> <span class="synIdentifier">contents</span><span class="synSpecial">:</span> write <span class="synIdentifier">pull-requests</span><span class="synSpecial">:</span> write <span class="synIdentifier">jobs</span><span class="synSpecial">:</span> <span class="synIdentifier">dependabot</span><span class="synSpecial">:</span> <span class="synIdentifier">runs-on</span><span class="synSpecial">:</span> ubuntu-latest <span class="synComment"> # Pull Requestを開いたユーザーがdependabotの場合のみ実行する</span> <span class="synIdentifier">if</span><span class="synSpecial">:</span> github.actor == <span class="synConstant">'dependabot[bot]'</span> <span class="synIdentifier">steps</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Dependabot metadata <span class="synIdentifier">id</span><span class="synSpecial">:</span> metadata <span class="synIdentifier">uses</span><span class="synSpecial">:</span> dependabot/fetch-metadata@v1 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">github-token</span><span class="synSpecial">:</span> <span class="synConstant">&quot;${{ secrets.GITHUB_TOKEN }}&quot;</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Enable auto-merge for Dependabot PRs <span class="synIdentifier">if</span><span class="synSpecial">:</span> contains(steps.metadata.outputs.dependency-names, <span class="synConstant">'my-dependency'</span>) <span class="synType">&amp;&amp;</span> steps.metadata.outputs.update-type == <span class="synConstant">'version-update:semver-patch'</span> <span class="synIdentifier">run</span><span class="synSpecial">:</span> gh pr merge --auto --merge <span class="synConstant">&quot;$PR_URL&quot;</span> <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synIdentifier">PR_URL</span><span class="synSpecial">:</span> ${{github.event.pull_request.html_url}} <span class="synIdentifier">GH_TOKEN</span><span class="synSpecial">:</span> ${{secrets.GITHUB_TOKEN}} </pre> <p> このworkflowはPull Requestsが開かれた時、同期された時、再度開かれた時に実行されます<sup id="fnref:28"><a href="#fn:28" rel="footnote">28</a></sup>。 実行されるとこのPull Requestsが開いたユーザーを確認し、それがDependabotだった場合は処理を続行します。 stepsには実行したい内容を記述しますが、1つ目のstepでは<code>dependabot/fetch-metadata</code><sup id="fnref:29"><a href="#fn:29" rel="footnote">29</a></sup>を使用してDependabotが作成した更新の内容を取得しています。2つ目のstepでは取得した更新の内容を確認し、更新する依存関係の名前が<code>my-dependency</code>かつ、アップデートのバージョンがPatchバージョンの時に自動マージを行う許可を出しています。 この条件を満たして自動マージを行う許可が出た場合は、Branch protection ruleを満たした時に自動でマージされるという流れになります。 また、<code>permissions</code>はdependabotが作成した更新内容の取得や、自動マージを許可するのに必要なものとなっています。</p> <p> では、これについてマージする条件をPatchバージョンの更新もしくは開発依存かつMinorバージョンの更新であるというものに更新し、Pull Requestsに対してApproveを出し、自動マージに関するPull Requestであることを表すauto mergeのラベルをつけるようにします。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">name</span><span class="synSpecial">:</span> Dependabot auto-merge <span class="synIdentifier">on</span><span class="synSpecial">:</span> pull_request <span class="synIdentifier">permissions</span><span class="synSpecial">:</span> <span class="synIdentifier">contents</span><span class="synSpecial">:</span> write <span class="synIdentifier">pull-requests</span><span class="synSpecial">:</span> write <span class="synIdentifier">issues</span><span class="synSpecial">:</span> write <span class="synIdentifier">repository-projects</span><span class="synSpecial">:</span> write <span class="synIdentifier">jobs</span><span class="synSpecial">:</span> <span class="synIdentifier">dependabot</span><span class="synSpecial">:</span> <span class="synIdentifier">runs-on</span><span class="synSpecial">:</span> ubuntu-latest <span class="synComment"> # Pull Requestを開いたユーザーがdependabotの場合のみ実行する</span> <span class="synIdentifier">if</span><span class="synSpecial">:</span> github.actor == <span class="synConstant">'dependabot[bot]'</span> <span class="synIdentifier">steps</span><span class="synSpecial">:</span> <span class="synComment"> # 依存関係の更新内容の取得</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Dependabot metadata <span class="synIdentifier">id</span><span class="synSpecial">:</span> metadata <span class="synIdentifier">uses</span><span class="synSpecial">:</span> dependabot/fetch-metadata@v1 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">github-token</span><span class="synSpecial">:</span> ${{ secrets.GITHUB_TOKEN }} <span class="synComment"> # 自動マージ対象の場合はPull Requestにauto mergeのラベルを貼り、自動マージの許可をする</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Enable auto-merge for Dependabot Pull Requests <span class="synComment"> # メジャーバージョンは自動マージしない</span> <span class="synComment"> # パッチバージョンもしくはdevDependenciesのとき自動マージする</span> <span class="synIdentifier">if</span><span class="synSpecial">:</span> | steps.metadata.outputs.update-type <span class="synType">!=</span> <span class="synConstant">'version-update:semver-major'</span> <span class="synType">&amp;&amp;</span> (steps.metadata.outputs.update-type == <span class="synConstant">'version-update:semver-patch'</span> || steps.metadata.outputs.dependency-type == <span class="synConstant">'direct:development'</span>) <span class="synIdentifier">run</span><span class="synSpecial">:</span> | gh pr review --approve <span class="synConstant">&quot;$PR_URL&quot;</span> gh pr edit <span class="synConstant">&quot;$PR_URL&quot;</span> --add-label <span class="synConstant">&quot;auto merge&quot;</span> gh pr merge --auto --merge <span class="synConstant">&quot;$PR_URL&quot;</span> <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synIdentifier">PR_URL</span><span class="synSpecial">:</span> ${{github.event.pull_request.html_url}} <span class="synIdentifier">GITHUB_TOKEN</span><span class="synSpecial">:</span> ${{ secrets.GITHUB_TOKEN }} </pre> <p>ラベルをつけるために<code>permissions</code>が増えたりしましたが、ここまでは問題ないと思います。</p> <p> 次は自動マージ対象じゃないPull Requestsに対して、レビュワーを割り当てるようにします。 レビュワーは、Majorバージョンの更新の時はリポジトリの担当者全員に、そうでない時は担当者からランダムに2名を抽出して割り当てることになります。</p> <p> この時、リポジトリの担当者は<code>.github/CODEOWNERS</code>に記載されているので、管理の箇所を複数に増やさないためにそれを利用します。</p> <p> レビュワーを割り当てる処理の内容としては次のようになります。<br>  まず、レビューを割り当てようとしているPull Requestsにすでにレビュワーが割り当たっていないか確認をします。これは、workflowsの発火条件がPull Requestsが作成されたとき以外のも、同期した時、再度開いた時にもあるため、重複で割り当てないようにするためです。 また、この時レビューに割り当てられたユーザーを確認するとともにレビュー済みのユーザーも確認します。これはレビューに割り当てられたユーザがレビューを行うと、レビューに割り当てられたユーザを取得するAPIから取得できなくなるためです。 すでにレビューしたユーザやレビューに割り当てられているユーザが存在しないことを確認すれば、次はリポジトリの担当者が記載されているCODEOWNERSから担当者を取得し担当者についてのArrayに変換します。 そしたら条件に応じてレビューを割り当てるユーザーを選択し、レビュワーを割り当てます。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">name</span><span class="synSpecial">:</span> Dependabot automation <span class="synIdentifier">on</span><span class="synSpecial">:</span> pull_request <span class="synIdentifier">permissions</span><span class="synSpecial">:</span> <span class="synIdentifier">contents</span><span class="synSpecial">:</span> write <span class="synIdentifier">pull-requests</span><span class="synSpecial">:</span> write <span class="synIdentifier">issues</span><span class="synSpecial">:</span> write <span class="synIdentifier">repository-projects</span><span class="synSpecial">:</span> write <span class="synIdentifier">jobs</span><span class="synSpecial">:</span> <span class="synIdentifier">dependabot</span><span class="synSpecial">:</span> <span class="synIdentifier">runs-on</span><span class="synSpecial">:</span> ubuntu-latest <span class="synIdentifier">if</span><span class="synSpecial">:</span> ${{ github.actor == <span class="synConstant">'dependabot[bot]'</span> }} <span class="synIdentifier">steps</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Dependabot metadata <span class="synIdentifier">id</span><span class="synSpecial">:</span> metadata <span class="synIdentifier">uses</span><span class="synSpecial">:</span> dependabot/fetch-metadata@v1 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">github-token</span><span class="synSpecial">:</span> ${{ secrets.GITHUB_TOKEN }} <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Enable auto-merge for Dependabot Pull Requests <span class="synComment"> # メジャーバージョンは自動マージしない</span> <span class="synComment"> # パッチバージョンもしくはdevDependenciesのとき自動マージする</span> <span class="synIdentifier">if</span><span class="synSpecial">:</span> | steps.metadata.outputs.update-type <span class="synType">!=</span> <span class="synConstant">'version-update:semver-major'</span> <span class="synType">&amp;&amp;</span> (steps.metadata.outputs.update-type == <span class="synConstant">'version-update:semver-patch'</span> || steps.metadata.outputs.dependency-type == <span class="synConstant">'direct:development'</span>) <span class="synIdentifier">run</span><span class="synSpecial">:</span> | gh pr review --approve <span class="synConstant">&quot;$PR_URL&quot;</span> gh pr edit <span class="synConstant">&quot;$PR_URL&quot;</span> --add-label <span class="synConstant">&quot;auto merge&quot;</span> gh pr merge --auto --merge <span class="synConstant">&quot;$PR_URL&quot;</span> <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synIdentifier">PR_URL</span><span class="synSpecial">:</span> ${{github.event.pull_request.html_url}} <span class="synIdentifier">GITHUB_TOKEN</span><span class="synSpecial">:</span> ${{ secrets.GITHUB_TOKEN }} <span class="synComment"> # 自動マージ対象じゃないPull Requestsに対してレビュワーを割り当てる</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Set Reviewers in Dependabot Pull Requests <span class="synIdentifier">if</span><span class="synSpecial">:</span> | steps.metadata.outputs.update-type == <span class="synConstant">'version-update:semver-major'</span> || (steps.metadata.outputs.update-type <span class="synType">!=</span> <span class="synConstant">'version-update:semver-patch'</span> <span class="synType">&amp;&amp;</span> steps.metadata.outputs.dependency-type <span class="synType">!=</span> <span class="synConstant">'direct:development'</span>) <span class="synIdentifier">uses</span><span class="synSpecial">:</span> actions/github-script@v7 <span class="synIdentifier">env</span><span class="synSpecial">:</span> <span class="synIdentifier">UPDATE_TYPE</span><span class="synSpecial">:</span> ${{ steps.metadata.outputs.update-type }} <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">script</span><span class="synSpecial">:</span> | // レビューに割り当てられ、未レビューのユーザーを取得 const assignedUsersResponse = await github.rest.pulls.listRequestedReviewers({ <span class="synIdentifier">owner</span><span class="synSpecial">:</span> context.repo.owner, <span class="synIdentifier">repo</span><span class="synSpecial">:</span> context.repo.repo, <span class="synIdentifier">pull_number</span><span class="synSpecial">:</span> context.payload.pull_request.number, }); const assignedUsers = assignedUsersResponse.data.users.map(user =&gt; user.login); // レビュー済みのユーザーを取得 const reviewedUsersResponse = await github.rest.pulls.listReviews({ <span class="synIdentifier">owner</span><span class="synSpecial">:</span> context.repo.owner, <span class="synIdentifier">repo</span><span class="synSpecial">:</span> context.repo.repo, <span class="synIdentifier">pull_number</span><span class="synSpecial">:</span> context.payload.pull_request.number, }); const reviewedUsers = reviewedUsersResponse.data.map(review =&gt; review.user.login); // すでに割り当て済み、もしくはレビュー済みの場合は終了 if (assignedUsers.length <span class="synType">!==</span> <span class="synConstant">0</span> || reviewedUsers.length <span class="synType">!==</span> 0) return; // CODEOWNERSからユーザーを取得 const codeownersContent = await github.rest.repos.getContent({ <span class="synIdentifier">owner</span><span class="synSpecial">:</span> context.repo.owner, <span class="synIdentifier">repo</span><span class="synSpecial">:</span> context.repo.repo, <span class="synIdentifier">path</span><span class="synSpecial">:</span> <span class="synConstant">'.github/CODEOWNERS'</span>, }); const codeownersText = Buffer.from(codeownersContent.data.content, <span class="synConstant">'base64'</span>).toString(); const users = codeownersText.split('\n')[<span class="synConstant">0</span>].split(' <span class="synConstant">').slice(1).map(user =&gt; user.slice(1));</span> <span class="synConstant"> </span> <span class="synConstant"> // レビューするユーザーをCODEOWNERSからMajor-updateの場合は全員、それ以外はランダムに2人割り当てる</span> <span class="synConstant"> const updateType = process.env[&quot;UPDATE_TYPE&quot;];</span> <span class="synConstant"> const copy = users.slice();</span> <span class="synConstant"> const reviewers = updateType === '</span>version-update:semver-major' <span class="synSpecial">?</span> users : <span class="synSpecial">[</span>...Array(2)<span class="synSpecial">]</span>.map(() =&gt; copy.splice(Math.floor(Math.random() * copy.length), 1)[<span class="synConstant">0</span>]); // レビュワーを割り当てる await github.rest.pulls.requestReviewers({ <span class="synIdentifier">owner</span><span class="synSpecial">:</span> context.repo.owner, <span class="synIdentifier">repo</span><span class="synSpecial">:</span> context.repo.repo, <span class="synIdentifier">pull_number</span><span class="synSpecial">:</span> context.payload.pull_request.number, <span class="synIdentifier">reviewers</span><span class="synSpecial">:</span> reviewers, }); </pre> <p> 以上で、Dependabotが作成したPull Requestsについて自動マージとレビュワーを動的に割り当てられるようになりました。</p> <p> 余談ですが、レビュワーを割り当てる時はgithubのapiを利用することになると思いますが、ghコマンドを使って<code>gh api</code>で割り当てようとする場合はDependabotが作成したPull Requestsで動くGitHub Actionsが持てる権限では権限不足で割り当てることができません<sup id="fnref:30"><a href="#fn:30" rel="footnote">30</a></sup>。これを動かそうとする場合は個人のPATを利用する方法もありますが、ghコマンドが使うようなGraphQL APIではなくREST APIを使用すると権限不足で怒られることがないので、もしGitHub Actionsを使ってレビュワーを割り当てる時はREST APIを使ってください</p> <h1 id="おわりに">おわりに</h1> <p> GitHub上でDependabotとGitHub Actionsを利用することによってソフトウェアの依存関係の更新を開発者の負担をなるべく減らしてできるようになりました。</p> <p> しかしこれは更新によって防げる場合のみなので、ライブラリ・パッケージの開発の更新が止まっている場合は対策できません。そのため、GitHub Alertsなどによって開発が止まった依存関係の脆弱性を検知できるようにする必要もあります。</p> <p> また、最新のパッケージに積極的に更新していくことが必ずしもセキュリティ的に一番良いという策にはならず、新しいバグや脆弱性が埋め込まれるという可能性もあります。バグについてはCIを充実させることによってある程度防ぐことが可能ですが、両者とも開発コミュニティなどで情報収集をして日々注意して開発する必要があります。</p> <p> そのほかにScalaついて、DependabotはScalaについて対応していません。そのため、Dependabotのみを使用している場合はScalaの依存関係を更新することができません。ですがScala Steward<sup id="fnref:31"><a href="#fn:31" rel="footnote">31</a></sup>などを利用することによってScalaでも依存関係を自動更新することができます。しかしGitHubでScala Stewardを利用するとGitHub Appを使用する必要があるため、もし使用できない場合は、Sbt Dependency Submission<sup id="fnref:32"><a href="#fn:32" rel="footnote">32</a></sup>とDependabot Alertsを組み合わせることによって脆弱性の検知ができるようになりますので、Scalaでも依存関係のセキュリティを健全に保ちたい場合はこれらのツールを検討してみてください。</p> <h1 id="参考">参考</h1> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> 総務省|令和元年版 情報通信白書|ICTにより、新しい経済・社会の仕組みが生まれている, <a href="https://www.soumu.go.jp/johotsusintokei/whitepaper/ja/r01/html/nd101100.html">https://www.soumu.go.jp/johotsusintokei/whitepaper/ja/r01/html/nd101100.html</a><a href="#fnref:1" rev="footnote">&#8617;</a></li> <li id="fn:2"> ICT サイバーセキュリティ総合対策 2022, <a href="https://www.soumu.go.jp/main_content/000830903.pdf">https://www.soumu.go.jp/main_content/000830903.pdf</a><a href="#fnref:2" rev="footnote">&#8617;</a></li> <li id="fn:3"> プラクティス・ナビ IPA 情報処理推進機構, <a href="https://www.ipa.go.jp/security/economics/practice/practices/Practice101">https://www.ipa.go.jp/security/economics/practice/practices/Practice101</a><a href="#fnref:3" rev="footnote">&#8617;</a></li> <li id="fn:4"> Open Source Security and Risk Analysis 2023, <a href="https://www.synopsys.com/content/dam/synopsys/sig-assets/reports/rep-ossra-2023.pdf">https://www.synopsys.com/content/dam/synopsys/sig-assets/reports/rep-ossra-2023.pdf</a><a href="#fnref:4" rev="footnote">&#8617;</a></li> <li id="fn:5"> Understanding Open Source Adoption: Insights from the 9th State of the Software Supply Chain Report., <a href="https://www.sonatype.com/state-of-the-software-supply-chain/Introduction">https://www.sonatype.com/state-of-the-software-supply-chain/Introduction</a><a href="#fnref:5" rev="footnote">&#8617;</a></li> <li id="fn:6"> NVD - CVE-2021-44228, <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-44228">https://nvd.nist.gov/vuln/detail/CVE-2021-44228</a><a href="#fnref:6" rev="footnote">&#8617;</a></li> <li id="fn:7"> Apache Log4jに見つかった脆弱性「Log4Shell」とは | ドコモビジネス | NTTコミュニケーションズ 法人のお客さま, <a href="https://www.ntt.com/business/services/xmanaged/lp/column/apache-log4j.html">https://www.ntt.com/business/services/xmanaged/lp/column/apache-log4j.html</a><a href="#fnref:7" rev="footnote">&#8617;</a></li> <li id="fn:8"> Apache Log4j に関する解説 1.6版, <a href="https://www.intellilink.co.jp/-/media/ndil/ndil-jp/column/vulner/2021/121500/ApacheLog4j.pdf">https://www.intellilink.co.jp/-/media/ndil/ndil-jp/column/vulner/2021/121500/ApacheLog4j.pdf</a><a href="#fnref:8" rev="footnote">&#8617;</a></li> <li id="fn:9"> Secure Code Warrior Survey Finds 86% of Developers Do Not View Application Security As a Top Priority, <a href="https://www.securecodewarrior.com/press-releases/secure-code-warrior-survey-finds-86-of-developers-do-not-view-application-security-as-a-top-priority">https://www.securecodewarrior.com/press-releases/secure-code-warrior-survey-finds-86-of-developers-do-not-view-application-security-as-a-top-priority</a><a href="#fnref:9" rev="footnote">&#8617;</a></li> <li id="fn:10"> 2019 State of the Software Supply Chain, <a href="https://www.sonatype.com/hubfs/SSC/2019%20SSC/SON_SSSC-Report-2019_jun16-DRAFT.pdf">https://www.sonatype.com/hubfs/SSC/2019%20SSC/SON_SSSC-Report-2019_jun16-DRAFT.pdf</a><a href="#fnref:10" rev="footnote">&#8617;</a></li> <li id="fn:11"> Keeping your supply chain secure with Dependabot - GitHub Docs, <a href="https://docs.github.com/en/code-security/dependabot">https://docs.github.com/en/code-security/dependabot</a><a href="#fnref:11" rev="footnote">&#8617;</a></li> <li id="fn:12"> Mend Renovate Automated Dependency Updates | Mend.io, <a href="https://www.mend.io/renovate/">https://www.mend.io/renovate/</a><a href="#fnref:12" rev="footnote">&#8617;</a></li> <li id="fn:13"> GitHub, <a href="https://github.com/">https://github.com/</a><a href="#fnref:13" rev="footnote">&#8617;</a></li> <li id="fn:14"> The DevSecOps Platform | GitLab, <a href="https://about.gitlab.com/">https://about.gitlab.com/</a><a href="#fnref:14" rev="footnote">&#8617;</a></li> <li id="fn:15"> Living Off Our Savings and Growing Our SaaS to $740/mo - Indie Hackers, <a href="https://www.indiehackers.com/interview/living-off-our-savings-and-growing-our-saas-to-740-mo-696f9b110f">https://www.indiehackers.com/interview/living-off-our-savings-and-growing-our-saas-to-740-mo-696f9b110f</a><a href="#fnref:15" rev="footnote">&#8617;</a></li> <li id="fn:16"> Acquired by GitHub!, <a href="https://www.indiehackers.com/product/dependabot/acquired-by-github--LgT7DN1rGEZM2O4srhF">https://www.indiehackers.com/product/dependabot/acquired-by-github--LgT7DN1rGEZM2O4srhF</a><a href="#fnref:16" rev="footnote">&#8617;</a></li> <li id="fn:17"> Goodbye Dependabot Preview, hello Dependabot! - The GitHub Blog, <a href="https://github.blog/2021-04-29-goodbye-dependabot-preview-hello-dependabot/">https://github.blog/2021-04-29-goodbye-dependabot-preview-hello-dependabot/</a><a href="#fnref:17" rev="footnote">&#8617;</a></li> <li id="fn:18"> Identifying vulnerabilities in your project's dependencies with Dependabot alerts - GitHub Docs, <a href="https://docs.github.com/en/code-security/dependabot/dependabot-alerts">https://docs.github.com/en/code-security/dependabot/dependabot-alerts</a><a href="#fnref:18" rev="footnote">&#8617;</a></li> <li id="fn:19"> Automatically updating dependencies with known vulnerabilities with Dependabot security updates - GitHub Docs, <a href="https://docs.github.com/en/code-security/dependabot/dependabot-security-updates">https://docs.github.com/en/code-security/dependabot/dependabot-security-updates</a><a href="#fnref:19" rev="footnote">&#8617;</a></li> <li id="fn:20"> Keeping your dependencies updated automatically with Dependabot version updates - GitHub Docs, <a href="https://docs.github.com/en/code-security/dependabot/dependabot-version-updates">https://docs.github.com/en/code-security/dependabot/dependabot-version-updates</a><a href="#fnref:20" rev="footnote">&#8617;</a></li> <li id="fn:21"> Prioritizing Dependabot alerts with Dependabot auto-triage rules - GitHub Docs, <a href="https://docs.github.com/en/code-security/dependabot/dependabot-auto-triage-rules">https://docs.github.com/en/code-security/dependabot/dependabot-auto-triage-rules</a><a href="#fnref:21" rev="footnote">&#8617;</a></li> <li id="fn:22"> Introducing auto-triage rules for Dependabot - The GitHub Blog, <a href="https://github.blog/2023-09-14-introducing-auto-triage-rules-for-dependabot/">https://github.blog/2023-09-14-introducing-auto-triage-rules-for-dependabot/</a><a href="#fnref:22" rev="footnote">&#8617;</a></li> <li id="fn:23"> About Dependabot alerts - GitHub Docs, <a href="https://docs.github.com/en/code-security/dependabot/dependabot-alerts/about-dependabot-alerts">https://docs.github.com/en/code-security/dependabot/dependabot-alerts/about-dependabot-alerts</a><a href="#fnref:23" rev="footnote">&#8617;</a></li> <li id="fn:24"> About the GitHub Advisory database - GitHub Docs, <a href="https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database">https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database</a><a href="#fnref:24" rev="footnote">&#8617;</a></li> <li id="fn:25"> Configuration options for the dependabot.yml file - GitHub Docs, <a href="https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#open-pull-requests-limit">https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#open-pull-requests-limit</a><a href="#fnref:25" rev="footnote">&#8617;</a></li> <li id="fn:26"> About code owners - GitHub Docs, <a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners">https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners</a><a href="#fnref:26" rev="footnote">&#8617;</a></li> <li id="fn:27"> Automating Dependabot with GitHub Actions - GitHub Docs, <a href="https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request">https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request</a><a href="#fnref:27" rev="footnote">&#8617;</a></li> <li id="fn:28"> Events that trigger workflows - GitHub Docs, <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request">https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request</a><a href="#fnref:28" rev="footnote">&#8617;</a></li> <li id="fn:29"> dependabot/fetch-metadata: Extract information about the dependencies being updated by a Dependabot-generated PR., <a href="https://github.com/dependabot/fetch-metadata">https://github.com/dependabot/fetch-metadata</a><a href="#fnref:29" rev="footnote">&#8617;</a></li> <li id="fn:30"> <code>gh pr edit --add-reviewer</code> Don't acquire organizational teams if it's not necessary · Issue #4844 · cli/cli, <a href="https://github.com/cli/cli/issues/4844">https://github.com/cli/cli/issues/4844</a><a href="#fnref:30" rev="footnote">&#8617;</a></li> <li id="fn:31"> scala-steward-org/scala-steward: :robot: A bot that helps you keep your projects up-to-date, <a href="https://github.com/scala-steward-org/scala-steward">https://github.com/scala-steward-org/scala-steward</a><a href="#fnref:31" rev="footnote">&#8617;</a></li> <li id="fn:32"> scalacenter/sbt-dependency-submission: A Github Action to submit the dependency graph of an sbt build to the Dependency Submission API, <a href="https://github.com/scalacenter/sbt-dependency-submission">https://github.com/scalacenter/sbt-dependency-submission</a><a href="#fnref:32" rev="footnote">&#8617;</a></li> </ol> </div> m_maruyama Terraformを使ったAmazon OpenSearch Serverlessの構成管理 hatenablog://entry/6801883189073952673 2024-01-14T12:00:00+09:00 2024-01-14T12:00:20+09:00 Amazon OpenSearch Serverless コレクションと関連するリソースを作成 作成したコレクションに対して、インデックス(OpenSearch index)を作成 について <p>こんにちは、永倉です。<br/> この記事はFLINTERS設立10周年ブログリレーの128日目の記事になります。</p> <p>Amazon OpenSearch Serverlessが<a href="https://aws.amazon.com/about-aws/whats-new/2023/01/amazon-opensearch-serverless-available/">一般利用可能</a>になってから、1年ぐらい経ちました。</p> <p>今回はAmazon OpenSearch Serverlessに関連して、Terraformを使い2つのことをやってみようと思います。</p> <ul> <li>Amazon OpenSearch Serverless コレクションと関連するリソースを作成</li> <li>作成したコレクションに対して、インデックス(OpenSearch index)を作成</li> </ul> <p>OpenSearch Serverless コレクションやそれに関連するリソースをTerraformを使って作成する話は、<a href="https://aws.amazon.com/blogs/big-data/deploy-amazon-opensearch-serverless-with-terraform/">AWSの公式のブログ</a>を含めていくつかあります。 しかし、その作成したコレクションに対して、Terraformを使いインデックスを作成する話はほぼ見たことがなかったため書いてみることにしました。</p> <p>なお、ここではAmazon OpenSearch ServerlessやOpenSearchがどういったものなのかについては、ほとんど書いていません。Amazon OpenSearch Serverlessについては、<a href="https://docs.aws.amazon.com/opensearch-service/">公式ドキュメント</a>や AWS Black Belt オンラインセミナーの<a href="https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_AmazonOpenSearchServerless_0131_v1.pdf">Amazon OpenSearch Serverlessの資料</a>を参照してください。 また、OpenSearchについては、<a href="https://opensearch.org/">公式サイト</a>を参照してください。</p> <h2 id="使用するTerraformやTerraform-Providerのバージョン">使用するTerraformやTerraform Providerのバージョン</h2> <ul> <li>Terraform 1.6.6</li> <li><a href="https://github.com/hashicorp/terraform-provider-aws">terraform-provider-aws</a> v5.31.0</li> <li><a href="https://github.com/opensearch-project/terraform-provider-opensearch">terraform-provider-opensearch</a> v2.2.0</li> </ul> <h2 id="Amazon-OpenSearch-Serverless-コレクションと関連するリソースの作成">Amazon OpenSearch Serverless コレクションと関連するリソースの作成</h2> <p>Amazon OpenSearch Serverless コレクションと関連するリソースを作成します。</p> <p>作成するのは以下の4つのリソースです。</p> <ul> <li><a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-manage.html">コレクション</a> <ul> <li>論理的なインデックスの集合を表します</li> <li>今回は<a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-overview.html#serverless-usecase">コレクションタイプ</a>として Search を設定します</li> </ul> </li> <li><a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-encryption.html">暗号化ポリシー</a> <ul> <li>コレクションにデータを保管する際のデータの暗号化についてのポリシーです</li> <li>今回は作成するコレクションのデータの暗号化について、AWS所有キーを使用するポリシーを設定します</li> </ul> </li> <li><a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-network.html">ネットワークポリシー</a> <ul> <li>コレクションのcollection endpoint(コレクションのOpenSearch APIにアクセスする際のエンドポイント)やdashboard endpoint(コレクションのダッシュボードにアクセスする際のエンドポイント)へのアクセス経路のポリシーです</li> <li>今回は作成するコレクションのcollection endpointに対して、パブリックネットワークからインターネット経由でアクセス可能とするポリシーを設定します(Access typeとしてはpublicのことです)</li> </ul> </li> <li><a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-data-access.html">データアクセスポリシー</a> <ul> <li>IAM UserやIAM Roleなどに対して、コレクションやインデックスに対する操作を許可するポリシーです</li> <li>今回は作成するコレクションに対して、Terraformを実行しているIAM Userがインデックスの作成や更新する許可を与えるポリシーを設定します</li> </ul> </li> </ul> <h3 id="Terraformのコード">Terraformのコード</h3> <p><a href="https://github.com/hashicorp/terraform-provider-aws">terraform-provider-aws</a> を使い対象のリソースを作成します。</p> <p>terraform-provider-aws では、AWSのリソースを操作するために認証情報が必要です。<br/> 今回の例では、AWS IAM Userを作成し、そのAWS IAM Userのアクセスキーとシークレットアクセスキーをそれぞれ環境変数 <code>AWS_ACCESS_KEY_ID</code>,<code>AWS_SECRET_ACCESS_KEY</code>として設定しているものとします。</p> <pre class="code lang-hcl" data-lang="hcl" data-unlink> <span class="synType">terraform</span> <span class="synSpecial">{</span> <span class="synType">required_providers</span> <span class="synSpecial">{</span> <span class="synIdentifier">opensearch</span> = <span class="synSpecial">{</span> <span class="synIdentifier">source</span> = <span class="synConstant">&quot;opensearch-project/opensearch&quot;</span> <span class="synIdentifier">version</span> = <span class="synConstant">&quot;2.2.0&quot;</span> <span class="synSpecial">}</span> <span class="synIdentifier">aws</span> = <span class="synSpecial">{</span> <span class="synIdentifier">source</span> = <span class="synConstant">&quot;hashicorp/aws&quot;</span> <span class="synIdentifier">version</span> = <span class="synConstant">&quot;5.31.0&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synType">provider</span> <span class="synConstant">&quot;aws&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">region</span> = <span class="synConstant">&quot;ap-northeast-1&quot;</span> <span class="synSpecial">}</span> <span class="synType">variable</span> <span class="synConstant">&quot;collection_name&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">type</span> = string <span class="synIdentifier">default</span> = <span class="synConstant">&quot;example-collection&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;作成するOpenSearch Serverless コレクションの名前&quot;</span> <span class="synSpecial">}</span> <span class="synType">variable</span> <span class="synConstant">&quot;index_name&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">type</span> = string <span class="synIdentifier">default</span> = <span class="synConstant">&quot;example-index&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;OpenSearch Serverless コレクション上に作製するindexの名前&quot;</span> <span class="synSpecial">}</span> <span class="synComment"># 暗号化ポリシーを作成します</span> <span class="synType">resource</span> <span class="synConstant">&quot;aws_opensearchserverless_security_policy&quot;</span> <span class="synConstant">&quot;example_encryption&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">name</span> = <span class="synConstant">&quot;example&quot;</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;encryption&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;encryption security policy&quot;</span> <span class="synIdentifier">policy</span> = <span class="synIdentifier">jsonencode</span>(<span class="synSpecial">{</span> <span class="synIdentifier">Rules</span> = <span class="synSpecial">[</span> <span class="synSpecial">{</span> <span class="synIdentifier">Resource</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;collection/$</span><span class="synSpecial">{</span>var.collection_name<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synSpecial">]</span>, <span class="synIdentifier">ResourceType</span> = <span class="synConstant">&quot;collection&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">]</span>, <span class="synIdentifier">AWSOwnedKey</span> = <span class="synConstant">true</span> <span class="synSpecial">}</span>) <span class="synSpecial">}</span> <span class="synComment"># ネットワークポリシーを作成します</span> <span class="synType">resource</span> <span class="synConstant">&quot;aws_opensearchserverless_security_policy&quot;</span> <span class="synConstant">&quot;example_network&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">name</span> = <span class="synConstant">&quot;example&quot;</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;network&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;network security policy&quot;</span> <span class="synIdentifier">policy</span> = <span class="synIdentifier">jsonencode</span>(<span class="synSpecial">[</span> <span class="synSpecial">{</span> <span class="synIdentifier">Rules</span> = <span class="synSpecial">[</span> <span class="synSpecial">{</span> <span class="synIdentifier">ResourceType</span> = <span class="synConstant">&quot;collection&quot;</span>, <span class="synIdentifier">Resource</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;collection/$</span><span class="synSpecial">{</span>var.collection_name<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">]</span>, <span class="synIdentifier">AllowFromPublic</span> = <span class="synConstant">true</span> <span class="synSpecial">}</span> <span class="synSpecial">]</span> ) <span class="synSpecial">}</span> <span class="synComment"># データアクセスポリシーでTerraformを実行しているIAMユーザーのARNを使用するために使います</span> <span class="synType">data</span> <span class="synConstant">&quot;aws_caller_identity&quot;</span> <span class="synConstant">&quot;current&quot;</span> <span class="synSpecial">{}</span> <span class="synComment"># データアクセスポリシーを作成します</span> <span class="synType">resource</span> <span class="synConstant">&quot;aws_opensearchserverless_access_policy&quot;</span> <span class="synConstant">&quot;example&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">name</span> = <span class="synConstant">&quot;example&quot;</span> <span class="synIdentifier">description</span> = <span class="synConstant">&quot;example&quot;</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;data&quot;</span> <span class="synIdentifier">policy</span> = <span class="synIdentifier">jsonencode</span>(<span class="synSpecial">[</span> <span class="synSpecial">{</span> <span class="synIdentifier">Rules</span> = <span class="synSpecial">[</span> <span class="synSpecial">{</span> <span class="synIdentifier">ResourceType</span> = <span class="synConstant">&quot;index&quot;</span>, <span class="synIdentifier">Resource</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;index/$</span><span class="synSpecial">{</span>var.collection_name<span class="synSpecial">}</span><span class="synConstant">/$</span><span class="synSpecial">{</span>var.index_name<span class="synSpecial">}</span><span class="synConstant">&quot;</span> <span class="synSpecial">]</span>, <span class="synIdentifier">Permission</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;aoss:CreateIndex&quot;</span>, <span class="synConstant">&quot;aoss:DeleteIndex&quot;</span>, <span class="synConstant">&quot;aoss:UpdateIndex&quot;</span>, <span class="synConstant">&quot;aoss:DescribeIndex&quot;</span>, <span class="synConstant">&quot;aoss:ReadDocument&quot;</span>, <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">]</span>, <span class="synIdentifier">Principal</span> = <span class="synSpecial">[</span> data.aws_caller_identity.current.arn <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">]</span>) <span class="synSpecial">}</span> <span class="synComment"># AWS OpenSearch Serverless コレクション を作成します</span> <span class="synType">resource</span> <span class="synConstant">&quot;aws_opensearchserverless_collection&quot;</span> <span class="synConstant">&quot;example&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">name</span> = var.collection_name <span class="synIdentifier">description</span> = <span class="synConstant">&quot;example collection&quot;</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;SEARCH&quot;</span> <span class="synComment"># `aws_opensearchserverless_collection` resourceのドキュメント(https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/opensearchserverless_collection)にも書いてありますが、</span> <span class="synComment"># AWS OpenSearch Serverless コレクション を作成する前に暗号化ポリシーを作成しておく必要があります。</span> <span class="synComment"># 作成していない場合、terraform applyを実行すると以下のようなエラーが出ます。</span> <span class="synComment"># &gt; ValidationException: No matching security policy of encryption type found for collection name: example_collection. Please create security policy of encryption type for this collection.</span> <span class="synIdentifier">depends_on</span> = <span class="synSpecial">[</span>aws_opensearchserverless_security_policy.example_encryption<span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synType">output</span> <span class="synConstant">&quot;collection_endpoint&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">value</span> = aws_opensearchserverless_collection.example.collection_endpoint <span class="synIdentifier">description</span> = <span class="synConstant">&quot;作成されたAWS OpenSearch Serverless コレクション のcollection endpoint&quot;</span> <span class="synSpecial">}</span> <span class="synType">output</span> <span class="synConstant">&quot;collection_name&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">value</span> = var.collection_name <span class="synIdentifier">description</span> = <span class="synConstant">&quot;作成されたAmazon OpenSearch Serverless コレクションの名前&quot;</span> <span class="synSpecial">}</span> <span class="synType">output</span> <span class="synConstant">&quot;index_name&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">value</span> = var.index_name <span class="synIdentifier">description</span> = <span class="synConstant">&quot;作成されたインデックスの名前&quot;</span> <span class="synSpecial">}</span> </pre> <h2 id="Amazon-OpenSearch-Serverless-コレクションに対してインデックスを作成">Amazon OpenSearch Serverless コレクションに対してインデックスを作成</h2> <p>作成されたAmazon OpenSearch Serverless コレクションに対してインデックスを作成します。</p> <p>インデックスの設定例として、今回は日本語検索を扱うものを使います。 これは <a href="https://www.elastic.co/blog/how-to-implement-japanese-full-text-search-in-elasticsearch">How to implement Japanese full-text search in Elasticsearch | Elastic Blog</a>を参考にしています。</p> <p>インデックスの作成にあたり、Terraform Providerとして <a href="https://github.com/opensearch-project/terraform-provider-opensearch">opensearch-project/terraform-provider-opensearch</a> を使用します。このProviderはOpenSearchのバージョン1.xと2.xどちらにも対応しており、Amazon OpenSearch Serverlessもサポートしています。</p> <p>terraform-provider-opensearchでは、<a href="https://registry.terraform.io/providers/opensearch-project/opensearch/2.2.0/docs/resources/index">opensearch_index</a> resourceでインデックスを作成します。 インデックス以外にもこのProviderで作成できるリソースはいくつもあります。詳細は<a href="https://registry.terraform.io/providers/opensearch-project/opensearch/2.2.0/docs">ドキュメント</a>を確認してください。</p> <p>なお、OpenSearchやElasticSearchのインデックス等を管理するTerraform providerとしては、他にも<a href="https://github.com/elastic/terraform-provider-elasticstack">elastic/terraform-provider-elasticstack</a> や<a href="https://github.com/phillbaker/terraform-provider-elasticsearch">phillbaker/terraform-provider-elasticsearch</a>があります。 しかし、前者はOpenSearchをサポートしておらず、後者はOpenSearchのバージョン2.xには<a href="https://github.com/phillbaker/terraform-provider-elasticsearch/issues/298">対応していません</a>。</p> <h3 id="Terraformのコード-1">Terraformのコード</h3> <p>terraform-provider-opensearch では、Amazon OpenSearch Serverless コレクションに対してインデックスを作成するなどの操作には認証情報が必要です。</p> <p>今回の例では、terraform-provider-aws と同じように、 AWS IAM Userのアクセスキーとシークレットアクセスキーをそれぞれ環境変数<code>AWS_ACCESS_KEY_ID</code>,<code>AWS_SECRET_ACCESS_KEY</code>として設定しているものとします。<br/> その他の認証方法については、ドキュメント(<a href="https://registry.terraform.io/providers/opensearch-project/opensearch/2.2.0/docs">https://registry.terraform.io/providers/opensearch-project/opensearch/2.2.0/docs</a>)を参照してください。</p> <pre class="code lang-hcl" data-lang="hcl" data-unlink><span class="synType">provider</span> <span class="synConstant">&quot;opensearch&quot;</span> <span class="synSpecial">{</span> <span class="synComment"># 作成されたコレクションのcollection endpointを指定します </span> <span class="synIdentifier">url</span> = aws_opensearchserverless_collection.example.collection_endpoint <span class="synIdentifier">aws_region</span> = <span class="synConstant">&quot;ap-northeast-1&quot;</span> <span class="synComment"># terraformの実行時に以下のようなエラーが出て失敗することがあるため false にしています</span> <span class="synComment"># &gt; Error: HEAD healthcheck failed: This is usually due to network or permission issues. The underlying error isn't accessible, please debug by disabling healthchecks.</span> <span class="synIdentifier">healthcheck</span> = <span class="synConstant">false</span> } <span class="synComment"># インデックスを作成します</span> <span class="synType">resource</span> <span class="synConstant">&quot;opensearch_index&quot;</span> <span class="synConstant">&quot;example&quot;</span> <span class="synSpecial">{</span> <span class="synIdentifier">name</span> = var.index_name <span class="synComment"># アナライザーの設定</span> <span class="synIdentifier">analysis_analyzer</span> = <span class="synIdentifier">jsonencode</span>(<span class="synSpecial">{</span> <span class="synComment"># kuromoji tokenizerを使ったアナライザーの設定</span> <span class="synIdentifier">example_kuromoji_analyzer</span> = <span class="synSpecial">{</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;custom&quot;</span> <span class="synIdentifier">char_filter</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;icu_normalizer&quot;</span> <span class="synSpecial">]</span> <span class="synIdentifier">tokenizer</span> = <span class="synConstant">&quot;kuromoji_tokenizer&quot;</span> <span class="synIdentifier">filter</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;kuromoji_baseform&quot;</span>, <span class="synConstant">&quot;kuromoji_part_of_speech&quot;</span>, <span class="synConstant">&quot;ja_stop&quot;</span>, <span class="synConstant">&quot;kuromoji_stemmer&quot;</span>, <span class="synConstant">&quot;lowercase&quot;</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synComment"># N-gram tokenizerを使ったアナライザーの設定</span> <span class="synIdentifier">example_ngram_analyzer</span> = <span class="synSpecial">{</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;custom&quot;</span> <span class="synIdentifier">char_filter</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;icu_normalizer&quot;</span> <span class="synSpecial">]</span> <span class="synIdentifier">tokenizer</span> = <span class="synConstant">&quot;example_ngram_tokenizer&quot;</span> <span class="synIdentifier">filter</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;lowercase&quot;</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span>) <span class="synIdentifier">analysis_tokenizer</span> = <span class="synIdentifier">jsonencode</span>(<span class="synSpecial">{</span> <span class="synIdentifier">example_ngram_tokenizer</span> = <span class="synSpecial">{</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;ngram&quot;</span> <span class="synIdentifier">min_gram</span> = <span class="synConstant">2</span> <span class="synIdentifier">max_gram</span> = <span class="synConstant">3</span> <span class="synIdentifier">token_chars</span> = <span class="synSpecial">[</span> <span class="synConstant">&quot;letter&quot;</span>, <span class="synConstant">&quot;digit&quot;</span> <span class="synSpecial">]</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span>) <span class="synComment"># マッピングの設定</span> <span class="synIdentifier">mappings</span> = <span class="synIdentifier">jsonencode</span>(<span class="synSpecial">{</span> <span class="synIdentifier">dynamic</span> = <span class="synConstant">&quot;strict&quot;</span> <span class="synIdentifier">properties</span> = <span class="synSpecial">{</span> <span class="synIdentifier">example_field</span> = <span class="synSpecial">{</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;text&quot;</span> <span class="synIdentifier">analyzer</span> = <span class="synConstant">&quot;example_kuromoji_analyzer&quot;</span> <span class="synIdentifier">fields</span> = <span class="synSpecial">{</span> <span class="synIdentifier">ngram</span> = <span class="synSpecial">{</span> <span class="synIdentifier">type</span> = <span class="synConstant">&quot;text&quot;</span> <span class="synIdentifier">analyzer</span> = <span class="synConstant">&quot;example_ngram_analyzer&quot;</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span>) <span class="synIdentifier">number_of_shards</span> = <span class="synConstant">2</span> <span class="synIdentifier">number_of_replicas</span> = <span class="synConstant">0</span> } </pre> <h3 id="実際にインデックスが作成されたのかを確認する">実際にインデックスが作成されたのかを確認する</h3> <p>実際にインデックスが作成されたのかを確認します。 この確認は、作成したコレクションのcollection endpointに対して<a href="https://opensearch.org/docs/2.11/api-reference/index-apis/get-index/">Get index API</a>を呼び出すことで実現します。</p> <p>ここではAPIを呼び出す際にcurlコマンドを使います。 collection endpointでAPIを呼び出す際に、AWSの署名付きリクエストを送る必要があります。 curlコマンドの <code>--aws-sigv4</code> オプションと <code>--user</code> オプションを使うことで署名付きリクエストが送れます。</p> <p>以下のコマンドで、インデックスが作成されたのかを確認します。</p> <pre class="code lang-sh" data-lang="sh" data-unlink> <span class="synComment"># 作成したAmazon OpenSearch Serverless コレクションのcollection endpointをTerraformのoutputを使い取得します</span> <span class="synIdentifier">COLLECTION_ENDPOINT</span>=<span class="synStatement">&quot;</span><span class="synPreProc">$(</span><span class="synSpecial">terraform output -raw collection_endpoint</span><span class="synPreProc">)</span><span class="synStatement">&quot;</span> <span class="synComment"># 作成されたインデックスの名前をTerraformのoutputを使い取得します</span> <span class="synIdentifier">INDEX_NAME</span>=<span class="synStatement">&quot;</span><span class="synPreProc">$(</span><span class="synSpecial">terraform output -raw index_name</span><span class="synPreProc">)</span><span class="synStatement">&quot;</span> <span class="synComment"># Get index API を呼び出します</span> <span class="synComment"># --aws-sigv4 オプションに設定する値は https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-clients.html#serverless-signing を参考にしています</span> <span class="synComment"># --user オプションに設定している値はTerraformの実行時に渡していたのと同じAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY 環境変数です</span> curl <span class="synStatement">\</span> --aws-sigv4 <span class="synStatement">&quot;</span><span class="synConstant">aws:amz:ap-northeast-1:aoss</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> --user <span class="synStatement">&quot;</span><span class="synPreProc">${AWS_ACCESS_KEY_ID}</span><span class="synConstant">:</span><span class="synPreProc">${AWS_SECRET_ACCESS_KEY}</span><span class="synStatement">&quot;</span> <span class="synStatement">\</span> <span class="synStatement">&quot;</span><span class="synPreProc">${COLLECTION_ENDPOINT}</span><span class="synConstant">/</span><span class="synPreProc">${INDEX_NAME}</span><span class="synStatement">&quot;</span> </pre> <p>得られたレスポンスは以下のようになります。(見やすいようにレスポンスを<a href="https://jqlang.github.io/jq/">jq</a>コマンドで整形しています。)</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">{</span> &quot;<span class="synStatement">example-index</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">aliases</span>&quot;: <span class="synSpecial">{}</span>, &quot;<span class="synStatement">mappings</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">dynamic</span>&quot;: &quot;<span class="synConstant">strict</span>&quot;, &quot;<span class="synStatement">properties</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">example_field</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">type</span>&quot;: &quot;<span class="synConstant">text</span>&quot;, &quot;<span class="synStatement">fields</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">ngram</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">type</span>&quot;: &quot;<span class="synConstant">text</span>&quot;, &quot;<span class="synStatement">analyzer</span>&quot;: &quot;<span class="synConstant">example_ngram_analyzer</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">analyzer</span>&quot;: &quot;<span class="synConstant">example_kuromoji_analyzer</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">settings</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">index</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">number_of_shards</span>&quot;: &quot;<span class="synConstant">2</span>&quot;, &quot;<span class="synStatement">provided_name</span>&quot;: &quot;<span class="synConstant">example-index</span>&quot;, &quot;<span class="synStatement">creation_date</span>&quot;: &quot;<span class="synConstant">1704868738849</span>&quot;, &quot;<span class="synStatement">analysis</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">analyzer</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">example_ngram_analyzer</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">filter</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">lowercase</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">char_filter</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">icu_normalizer</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">type</span>&quot;: &quot;<span class="synConstant">custom</span>&quot;, &quot;<span class="synStatement">tokenizer</span>&quot;: &quot;<span class="synConstant">example_ngram_tokenizer</span>&quot; <span class="synSpecial">}</span>, &quot;<span class="synStatement">example_kuromoji_analyzer</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">filter</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">kuromoji_baseform</span>&quot;, &quot;<span class="synConstant">kuromoji_part_of_speech</span>&quot;, &quot;<span class="synConstant">ja_stop</span>&quot;, &quot;<span class="synConstant">kuromoji_stemmer</span>&quot;, &quot;<span class="synConstant">lowercase</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">char_filter</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">icu_normalizer</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">type</span>&quot;: &quot;<span class="synConstant">custom</span>&quot;, &quot;<span class="synStatement">tokenizer</span>&quot;: &quot;<span class="synConstant">kuromoji_tokenizer</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">tokenizer</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">example_ngram_tokenizer</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">token_chars</span>&quot;: <span class="synSpecial">[</span> &quot;<span class="synConstant">letter</span>&quot;, &quot;<span class="synConstant">digit</span>&quot; <span class="synSpecial">]</span>, &quot;<span class="synStatement">min_gram</span>&quot;: &quot;<span class="synConstant">2</span>&quot;, &quot;<span class="synStatement">type</span>&quot;: &quot;<span class="synConstant">ngram</span>&quot;, &quot;<span class="synStatement">max_gram</span>&quot;: &quot;<span class="synConstant">3</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span>, &quot;<span class="synStatement">number_of_replicas</span>&quot;: &quot;<span class="synConstant">0</span>&quot;, &quot;<span class="synStatement">uuid</span>&quot;: &quot;<span class="synConstant">ay4Y8owBZS7FqaybduCp</span>&quot;, &quot;<span class="synStatement">version</span>&quot;: <span class="synSpecial">{</span> &quot;<span class="synStatement">created</span>&quot;: &quot;<span class="synConstant">135217827</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> <span class="synSpecial">}</span> </pre> <p>インデックスが作成されていることが確認できました。</p> <h2 id="おわりに">おわりに</h2> <p>Terraformを使ったAmazon OpenSearch Serverless コレクションとインデックスの作成方法について紹介しました。</p> r_nagakura 「プログラミングかじってる」からエンジニアになってみて、独学だと身につけられなかったなーと思うこと hatenablog://entry/6801883189073769546 2024-01-12T12:00:00+09:00 2024-01-12T12:41:04+09:00 プログラミングを独学の趣味から仕事にしてみて、何が得られたかという話 <p>この記事は、株式会社FLINTERS10周年記念ブログリレーの126日目の投稿です。果てしないなと思っていた133日のブログリレーも残り1週間ということで、感慨深いです。継続は力なり。</p> <h3 id="何の記事">何の記事?</h3> <h5 id="プログラミングを独学の趣味から仕事にしてみて何が得られたかという話">プログラミングを独学の趣味から仕事にしてみて、何が得られたかという話</h5> <p>こんにちは。株式会社FLINTERSに新卒入社して2年目のエンジニアです。普段の業務では、データベース周りのワークフローの開発や運用を担当しています。</p> <p>入社前の学生時代にはプログラミングは趣味として軽く独学していただけで、情報系の専攻でもなく、プログラミング関係のインターンなどをしていた経験もありません。</p> <p>そのため、私がFLINTERSに入社を決めた理由の一つが「独学ではなく仕事としてプログラミングを学んでみたかった」ことでした(もちろん理由は他にもたくさんあります!)。独学だと得にくい知識があるんじゃないかなー、ということをうっすら感じていて、それを探しにエンジニアとしてのキャリアに飛び込んでみました。入社から2年弱経ち、現時点での感想として、その結果を書いてみたいと思います。</p> <p>「プログラミングを仕事にしようか、趣味のままにしておこうか」と悩んでいる方に参考になったら嬉しいです。</p> <h3 id="学生時代何をやっていたか">学生時代何をやっていたか</h3> <h5 id="工業デザイン専攻でプロトタイプ制作でプログラミングしてました">工業デザイン専攻で、プロトタイプ制作でプログラミングしてました</h5> <p>この記事では、「プログラミングを独学の趣味から仕事にしてみて、何が得られたか」「得られたものによって、そのまま趣味にしていた場合と何が変わったか」ということを書きます。なので、趣味にしていた学生時代は何をしていたかについて少し触れたいと思います。デザイン専攻だったのでデザインの話が多くなりますが、今はちゃんと心から張り切ってピカピカエンジニア目指しているのでご安心ください!!!</p> <p>デザインにもいろいろ種類がありますが、私は工業デザインを専攻していました。工業デザインでは、プロトタイプ作成のため軽くプログラミングをすることがあります(必修ではありませんが、カリキュラムにも入っています)。それをきっかけに動くものが作れる楽しさにハマり、ちょっとだけ自主的に勉強して、ごく小さい規模の作品を作って遊ぶ日々を過ごしました。</p> <p>同じ専攻でエンジニア職を選ぶ人は稀ですが、プログラミングに触れる機会は多いのと、作りたがりな人間が多いため、プログラミング学習に意欲的な人は多いと思います。私のようにエンジニアになる道を選ばなかった人でも、「プログラミングつよつよになったら楽しいだろうな!」と考えてたデザイン学生はけっこういるのではないでしょうか。</p> <p>私の学生時代の「プログラミングかじってた」度合いのイメージがついたところで、本題に移ります。</p> <h3 id="本題独学ではなく仕事にして何が得られたか">本題 独学ではなく仕事にして何が得られたか</h3> <h5 id="動くかどうか以外の観点と組織で開発するためのノウハウ">「動くかどうか」以外の観点と、組織で開発するためのノウハウ</h5> <p>今のところ、主にこの二つかなと思っています。</p> <ul> <li>「動くかどうか」以外の観点</li> </ul> <p>1人で好きなものを作って楽しんでいた頃は、プログラミングのゴールは「動くかどうか」しかありませんでした。したがって、独学で集める知識も「動かすため」に必要なものに限られます。</p> <p>もちろんエンジニアになってからはそうはいかず、読みやすいか、保守しやすいか、システム全体のセキュリティはどうか、コストはどうか、、、と、「動くかどうか」以外のことも考慮に入るようになりました。</p> <p>デザインで行うプロトタイピングでは、とにかく動くものを早く作るのが正義なので、これらを考える必要はほぼないです。ただプロトタイピングの枠を超えて、自分の作ったものをたくさんの人に使ってほしくなった時、絶対に必要になる領域だなと思っています。例えば、自分で作ってみたアプリを公開してたくさんの人に遊んでもらいたい!とか、そういうことにチャレンジできるようになるのではないでしょうか。</p> <ul> <li>組織で開発するためのノウハウ</li> </ul> <p>趣味の作品制作では、もちろんひとりで私による私のための私のコードを書いていました。たまにグループワークのプロトタイピングでコードを書きたい場面も訪れるのですが、分担作業するのが難しく、結局得意もしくはプログラミングを苦としないメンバーが1人で書き上げることが多かったです。(多分デザイン学生あるある)</p> <p>エンジニアになってからは、組織の一員としてチームでコードを書きます。コミットしてプルリクエスト作ってレビューをもらって…という流れだったり、自分以外の人にも理解してもらいやすい書き方だったり、自分1人で作業していた時には考えなかった概念を学びました。</p> <p>これに関しても、1人で開発するなら極論なくてもなんとかなるスキルだと思います。しかし、1人で作っていたもののスケールを大きくしたくなったとき、誰かの手を借りる場合にはとても役に立つのではないでしょうか。</p> <p>--</p> <p>以下、その他に私がエンジニアになってよかったなと思うことを書きました。</p> <ul> <li><p>実際に運用されているシステムのコードを見られる。</p> <ul> <li>チュートリアルのサンプルコードを見るより、実際にたくさんの方に利用されてるシステムのコードを見る方が楽しいし勉強になるなと感じています。</li> </ul> </li> <li><p>でっかいデータが入ったデータベースも見られる。</p> <ul> <li>データベースも、やっぱりサンプルデータよりたくさんデータが入っている方が楽しいです。</li> </ul> </li> <li><p>当たり前だけどアンテナ張ってる人が周りにいる方が勉強になる</p></li> <li><p>これも当たり前だけど、平日の一日の中で一番長いのは仕事をしている時間なので、強制的に勉強時間が確保される</p></li> </ul> <h3 id="まとめ">まとめ</h3> <p>こうして振り返ってみると、エンジニアになる選択をしてよかったなと感じます。こう思えるのも、日々FLINTERSの皆さんに支えていただいているおかげです。FLINTERSのどんなところが素敵なのかについては、他の方のブログで書かれているのでぜひ読んでみてください。</p> <p>実は新年一日目の初仕事がこのブログ執筆で身が引き締まりました!今年も頑張ります!よろしくお願いしまーす!</p> n_mizumoto VyOSとDockerで仮想ルータをつくってみる。 hatenablog://entry/6801883189069797788 2024-01-08T12:00:00+09:00 2024-01-08T12:00:03+09:00 業務上はAWSやGoogle Cloudといったクラウド環境なのですが、ネットワークの基礎とかをちゃんと押さえるためにも、ねすぺリベンジすべきか...?と重い腰をあげました。 <p>こんにちは。 株式会社FLINTERSの阿部です。10周年記念として133日間ブログリレー122日目の投稿です。</p> <h2 id="1-きっかけ">1. きっかけ</h2> <p>FLINTERSに転職して、インフラまわりをやることになりました。 業務上はAWSやGoogle Cloudといったクラウド環境なのですが、ネットワークの基礎とかをちゃんと押さえるためにも、これは10年前に挫折した<a href="https://www.ipa.go.jp/shiken/kubun/nw.html">ねすぺ</a>にリベンジすべきか...?と重い腰を9mm Parabellum Bulletほどあげました。<br/> 久々にぱらぱらと参考書とか見てみて、やっぱり実機やってないとわからんなーとなり、ローカルで気軽にネットワークで遊べるものを探してたら見つけたのが、今回の<a href="https://vyos.io/">VyOS</a>です。 VyOSはOSSの仮想ネットワークツールのようです。</p> <h2 id="2-つくったもの">2. つくったもの</h2> <p><a href="https://github.com/abejjj/vyos_tutorial">GitHub - abejjj/vyos_tutorial</a> にあげています。</p> <h2 id="3-やってみよう">3. やってみよう〜</h2> <p>VyOSをDockerでたちあげ、双方のルータ配下にあるサブネットワーク間でpingが通るところを目指します。 図はこんなかんじです。</p> <p><figure class="figure-image figure-image-fotolife" title="vyos-tutorial01"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/abejjj/20231226/20231226054416.png" width="418" height="210" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>vyos-tutorial01</figcaption></figure></p> <p>この図でいう、debian-a-1からdebian-b-1へpingを飛ばして、応答が返れば成功です。</p> <h3 id="3-1-Dockerイメージのビルド">3-1. Dockerイメージのビルド</h3> <p>以下を参考にして、手順を上記Githubの <code>build/build_docker_image.sh</code> にまとめました。 ざっくりの流れとしては、VyOSが公開しているISOビルド用ツールを使ってISOファイルを吐き出して、そのISOとその中にあるSquashFSを展開し、最終的にそれをDockerイメージとしてimportしています。</p> <ul> <li><a href="https://docs.vyos.io/en/equuleus/contributing/build-vyos.html">https://docs.vyos.io/en/equuleus/contributing/build-vyos.html</a></li> <li><a href="https://docs.vyos.io/en/latest/installation/virtual/docker.html">https://docs.vyos.io/en/latest/installation/virtual/docker.html</a></li> </ul> <h3 id="3-2-docker-composeから起動">3-2. docker composeから起動</h3> <p>ビルド毎にイメージ名を識別するのと、GOSU関係の環境変数が必要な都合上、簡単なシェルスクリプトにしています。 ここでの学びは、docker-composeのnetworksは、一つのサービスに対して複数指定できるんだー、ってことです。 (これできなかったらルータたてられない)</p> <h3 id="3-3-初期状態の確認">3-3. 初期状態の確認</h3> <p>起動直後のネットワークまわりの状態を確認しておきます。</p> <p>(vrouterA)</p> <pre class="code" data-lang="" data-unlink>vyos@vyos:~$ show interfaces Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down Interface IP Address S/L Description --------- ---------- --- ----------- eth0 192.168.21.3/28 u/u eth1 192.168.100.3/28 u/u vyos@vyos:~$show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, f - OpenFabric, &gt; - selected route, * - FIB route, q - queued, r - rejected, b - backup K&gt;* 0.0.0.0/0 [0/0] via 192.168.21.1, eth0, 00:03:25 C&gt;* 192.168.21.0/28 is directly connected, eth0, 00:03:25 C&gt;* 192.168.100.0/28 is directly connected, eth1, 00:03:25 </pre> <p>(vrouterB)</p> <pre class="code :routerB" data-lang=":routerB" data-unlink>vyos@vyos:~$ show interfaces Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down Interface IP Address S/L Description --------- ---------- --- ----------- eth0 192.168.22.3/28 u/u eth1 192.168.100.2/28 u/u lo 127.0.0.1/8 u/u vyos@vyos:~$ show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, f - OpenFabric, &gt; - selected route, * - FIB route, q - queued, r - rejected, b - backup K&gt;* 0.0.0.0/0 [0/0] via 192.168.22.1, eth0, 00:05:52 C&gt;* 192.168.22.0/28 is directly connected, eth0, 00:05:52 C&gt;* 192.168.100.0/28 is directly connected, eth1, 00:05:52 </pre> <h3 id="3-4-デフォルトゲートウェイの変更">3-4. デフォルトゲートウェイの変更</h3> <p>疎通したいdebian側ですが、起動直後の状態だと、デフォルトゲートウェイがDockerのホストに向いてるため、 この先なにやっても通らなくなっています。。 なので、デフォルトゲートウェイを今回作ったVyOSのルータに向けるように変更します。 なお、docker-compose.ymlで以下のオプションを指定しないとそもそも変更ができません。。(これで時間が溶けました)</p> <pre class="code" data-lang="" data-unlink>cap_add: - NET_ADMIN</pre> <p>かつこれは起動時のオプションらしいので、Dockerfileでビルド中によしなにやることができなかったです、、(もっといい方法しりたい</p> <p>(debian-a-1)</p> <pre class="code" data-lang="" data-unlink>root@debian-a-1:/# ip route default via 192.168.21.1 dev eth0 192.168.21.0/28 dev eth0 proto kernel scope link src 192.168.21.2 root@debian-a-1:/# ip route add 192.168.21.1 via 192.168.21.1 root@debian-a-1:/# ip route del default via 192.168.21.1 root@debian-a-1:/# ip route add default via 192.168.21.3 root@debian-a-1:/# ip route default via 192.168.21.3 dev eth0 192.168.21.0/28 dev eth0 proto kernel scope link src 192.168.21.2 192.168.21.1 via 192.168.21.1 dev eth0 </pre> <p>(debian-b-1)</p> <pre class="code" data-lang="" data-unlink>root@debian-b-1:/# ip route default via 192.168.22.1 dev eth0 192.168.22.0/28 dev eth0 proto kernel scope link src 192.168.22.2 root@debian-b-1:/# ip route add 192.168.22.1 via 192.168.22.1 root@debian-b-1:/# ip route del default via 192.168.22.1 root@debian-b-1:/# ip route add default via 192.168.22.3 root@debian-b-1:/# ip route default via 192.168.22.3 dev eth0 192.168.22.0/28 dev eth0 proto kernel scope link src 192.168.22.2 192.168.22.1 via 192.168.22.1 dev eth0 </pre> <p>ping打ってこのままだと通らないことを確認します。</p> <pre class="code" data-lang="" data-unlink>root@debian-a-1:/# ping -c 4 192.168.22.2 PING 192.168.22.2 (192.168.22.2) 56(84) bytes of data. --- 192.168.22.2 ping statistics --- 4 packets transmitted, 0 received, 100% packet loss, time 3064ms root@debian-b-1:/# ping -c 4 192.168.21.2 PING 192.168.21.2 (192.168.21.2) 56(84) bytes of data. --- 192.168.21.2 ping statistics --- 4 packets transmitted, 0 received, 100% packet loss, time 3060ms</pre> <h3 id="3-5-ルーティングの設定">3-5. ルーティングの設定</h3> <p>通るように設定していきます。 今回は単純にstaticで(このサブネットだったらこっちだよー)設定します。</p> <p>routerA/往路</p> <pre class="code" data-lang="" data-unlink>$ configure $ set protocols static route 192.168.22.0/28 next-hop 192.168.100.2 $ commit $ save $ exit #設定の反映を確認 $ show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, f - OpenFabric, &gt; - selected route, * - FIB route, q - queued, r - rejected, b - backup K&gt;* 0.0.0.0/0 [0/0] via 192.168.21.1, eth0, 00:33:18 C&gt;* 192.168.21.0/28 is directly connected, eth0, 00:33:18 S&gt;* 192.168.22.0/28 [1/0] via 192.168.100.2, eth1, weight 1, 00:00:09 ★ココ C&gt;* 192.168.100.0/28 is directly connected, eth1, 00:33:18 </pre> <p>routerB/復路</p> <pre class="code" data-lang="" data-unlink>$ configure $ set protocols static route 192.168.21.0/28 next-hop 192.168.100.3 $ commit $ save $ exit #設定の反映を確認 $ show ip route Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, f - OpenFabric, &gt; - selected route, * - FIB route, q - queued, r - rejected, b - backup K&gt;* 0.0.0.0/0 [0/0] via 192.168.22.1, eth0, 00:35:36 S&gt;* 192.168.21.0/28 [1/0] via 192.168.100.3, eth1, weight 1, 00:00:27 C&gt;* 192.168.22.0/28 is directly connected, eth0, 00:35:36 C&gt;* 192.168.100.0/28 is directly connected, eth1, 00:35:36 </pre> <h3 id="3-6-ping通るかな">3-6. ping通るかな</h3> <p>通りましたーーーー!!!</p> <pre class="code" data-lang="" data-unlink>root@debian-a-1:/# ping -c 4 192.168.22.2 PING 192.168.22.2 (192.168.22.2) 56(84) bytes of data. 64 bytes from 192.168.22.2: icmp_seq=1 ttl=62 time=0.157 ms 64 bytes from 192.168.22.2: icmp_seq=2 ttl=62 time=0.169 ms 64 bytes from 192.168.22.2: icmp_seq=3 ttl=62 time=0.168 ms 64 bytes from 192.168.22.2: icmp_seq=4 ttl=62 time=0.169 ms --- 192.168.22.2 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3068ms rtt min/avg/max/mdev = 0.157/0.165/0.169/0.005 ms </pre> <h2 id="おわりに">おわりに</h2> <p>VyOS自体よりもDockerのネットワーク設定まわりでのハマりどころが多くて泣きそうでしたが、ひとまずそれらしく動いてよかったです。<br/> 変なところあればコメントください。<br/> ねすぺがんばります〜</p> <h2 id="参考">参考</h2> <p><a href="https://vyos.io/">公式サイト</a>の他、以下を参考にさせて頂きました。<br/> 感謝です。</p> <ul> <li><a href="https://changineer.info/network/vyatta/vyatta_routing_static.html">https://changineer.info/network/vyatta/vyatta_routing_static.html</a></li> </ul> abejjj ZIOを使ったプログラムの魅力とは?Scopeの活用方法を紹介します hatenablog://entry/6801883189069121182 2024-01-06T12:00:00+09:00 2024-01-06T12:00:02+09:00 ZIOを使ったプログラムの魅力とは?Scopeの活用方法を紹介します <p>こんにちは河内です。</p> <p>この記事は10周年記念として133日間ブログを書き続けるチャレンジの120日目の記事となります。 そして本日2024年1月6日がFLINTERS創立10周年当日です! 10年を無事に迎えられたのは、取引先のお客様、応援してくださる皆様、そして社員の皆のおかげです。ありがとうございます!</p> <p>さて、最近は ZIO を使ってプログラムを書くことが多いのですが、先日会社の人と ZIO のいいところ、いろいろあるよね、という話をしていまして、今回は Scope について書こうと思います。 (Scala 2.13.4, ZIO 2.0.13 です。)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fzio.dev%2Freference%2Fresource%2Fscope%2F" title="Scope | ZIO" 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://zio.dev/reference/resource/scope/">zio.dev</a></cite></p> <p>Scope はリソース管理のための機能です。 リソース管理というのは Java でいうところの try-with-resources文がやっているようなものです。確保したものを使い終わったら解放するための仕組みです。</p> <p>ZIO は Scala のライブラリなので、Scala で例を書きます。 Scala では try-with-resources の代わりに <code>scala.util.Using</code> が使えます。</p> <pre class="code lang-scala" data-lang="scala" data-unlink>Using.resource(<span class="synStatement">new</span> FileInputStream(<span class="synConstant">&quot;foo.txt&quot;</span>)) { is =&gt; ... <span class="synComment">// is を使う</span> } <span class="synComment">// is はここで close される</span> </pre> <p>そんなに話すことのない機能のように思えますが、実行タイミングが異なる要素が入ると少し難しくなります。 例えば前述のコードの中で <code>is</code> を使う部分が <code>ZIO.attempt()</code> になったとします。 <code>ZIO.attempt()</code> はその場で実行されるのではなく、ワークフロー(ZIO型のインスタンス)の構築のみを行い、実行は後で行われます。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synType">val</span> io = Using.resource(<span class="synStatement">new</span> FileInputStream(<span class="synConstant">&quot;foo.txt&quot;</span>)) { is =&gt; <span class="synComment">// is を使う</span> ZIO.attempt { <span class="synComment">// is を使う</span> } } <span class="synComment">// is はここで close される</span> <span class="synComment">// io の実行</span> </pre> <p>この場合、io の実行時には <code>is</code> は close されてしまっているので、実行時にエラーになってしまいます。 この例ですと <code>Using.resource()</code> を <code>ZIO.attempt()</code> の中に入れてしまえばいいのですが、複数の <code>ZIO.attempt()</code> から <code>is</code> を使いたい場合などはそうもいきません。</p> <p>これが ZIO のやり方ではこうなります。</p> <pre class="code lang-scala" data-lang="scala" data-unlink>ZIO.scoped { <span class="synStatement">for</span> { is &lt;- ZIO.fromAutoCloseable(ZIO.attempt(<span class="synStatement">new</span> FileInputStream(<span class="synConstant">&quot;foo&quot;</span>))) _ &lt;- ZIO.attempt { ... <span class="synComment">// is を使う</span> } } <span class="synType">yield</span> () } <span class="synComment">// is はここで close される</span> </pre> <p><code>ZIO.fromAutoCloseable()</code> は <code>ZIO[R with Scope, E, A]</code> 型の値を返します。 <code>Scope</code> はリソースの解放方法を知っている値です。 また <code>ZIO.scoped</code> はスコープを閉じる関数で、 <code>ZIO[R with Scope, E, A]</code> を <code>ZIO[R, E, A]</code> に変換します。</p> <p><code>ZIO.fromAutoCloseable(ZIO.attempt(new FileInputStream("foo")))</code> を説明のために分解すると、次のようになります。</p> <pre class="code lang-scala" data-lang="scala" data-unlink> <span class="synComment">// &quot;foo&quot; を開く方法を記述したワークフロー</span> <span class="synType">val</span> howToOpenFoo: ZIO[Any, Throwable, FileInputStream] = ZIO.attempt(<span class="synStatement">new</span> FileInputStream(<span class="synConstant">&quot;foo&quot;</span>)) <span class="synComment">// &quot;foo&quot; を開き、 Scope が閉じられる際に close する方法を記述したワークフロー</span> <span class="synType">val</span> howToOpenAndCloseFoo: ZIO[Scope, Throwable, FileInputStream] = ZIO.fromAutoCloseable(howToOpenFoo) </pre> <p>Java の try-with-resources や <code>Using.resource()</code> がコードブロックでリソースの利用範囲を表現するのに対して、ZIO では <code>Scope</code> が <code>R</code> に含まれていることによってリソースの利用範囲を表現しています。</p> <p><code>FileInputStream</code> のように <code>java.lang.AutoCloseable</code> を継承している値には <code>ZIO.fromAutoCloseable()</code> を使えます。 それ以外には <code>ZIO.acquireRelease()()</code> が使えるので、AutoCloseable でないリソースも扱えます。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synComment">// ZIO.fromAutoCloseable(ZIO.attempt(new FileInputStream(&quot;foo&quot;))) を書き換えたもの</span> ZIO.acquireRelease(ZIO.attempt(<span class="synStatement">new</span> FileInputStream(<span class="synConstant">&quot;foo&quot;</span>)))(is =&gt; ZIO.succeed(is.close())) </pre> <h2 id="Scope-は複数のリソースを扱える">Scope は複数のリソースを扱える</h2> <p><code>Scope</code> は複数のリソースを扱うことができます。 例を示します。</p> <pre class="code lang-scala" data-lang="scala" data-unlink>ZIO.scoped { <span class="synStatement">for</span> { is1 &lt;- ZIO.fromAutoCloseable(ZIO.attempt(<span class="synStatement">new</span> FileInputStream(<span class="synConstant">&quot;foo&quot;</span>))) is2 &lt;- ZIO.fromAutoCloseable(ZIO.attempt(<span class="synStatement">new</span> FileInputStream(<span class="synConstant">&quot;bar&quot;</span>))) _ &lt;- ZIO.attempt(???) } <span class="synType">yield</span> () } <span class="synComment">// is2 → is1 の順で、ここで close される</span> </pre> <p>リソースは確保した順と逆の順でシリアルに解放されます。</p> <h2 id="bracket-と比べて何が嬉しいの">bracket と比べて何が嬉しいの?</h2> <p>確保、利用、解放を表現するときに使われるイディオムとして、それぞれを関数として引数に取る方法があります。 bracket pattern と呼ばれるものです。 該当するものとして ZIO 2.x には <code>ZIO.acquireReleaseWith()()()</code> という関数があるので、比べてみましょう。</p> <p>一時ファイルを作ってそれを利用する例を考えます。</p> <p>まずは Scope を使って書いたものです。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> zio.{Scope, ZIO, ZIOAppArgs, ZIOAppDefault} <span class="synPreProc">import</span> java.nio.file.{Files, Path} <span class="synType">object</span> ScopeExample <span class="synType">extends</span> ZIOAppDefault { <span class="synIdentifier"> def</span> createTmpFile: ZIO[Scope, Nothing, Path] = ZIO.acquireRelease( ZIO.succeed(Files.createTempFile(<span class="synConstant">&quot;foo&quot;</span>, <span class="synConstant">&quot;.tmp&quot;</span>)) )(f =&gt; ZIO.succeed(Files.deleteIfExists(f))) <span class="synType">override</span><span class="synIdentifier"> def</span> run: ZIO[ZIOAppArgs <span class="synType">with</span> Scope, Any, Any] = <span class="synStatement">for</span> { tmp1 &lt;- createTmpFile tmp2 &lt;- createTmpFile _ &lt;- ZIO.logInfo(s<span class="synConstant">&quot;tmp1: $tmp1, tmp2: $tmp2&quot;</span>) } <span class="synType">yield</span> () } </pre> <p>次に bracket を使って書いたものです。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> zio.{Scope, ZIO, ZIOAppArgs, ZIOAppDefault} <span class="synPreProc">import</span> java.nio.file.{Files, Path} <span class="synType">object</span> BracketExample <span class="synType">extends</span> ZIOAppDefault { <span class="synIdentifier"> def</span> createTmpFile[R, E, A](use: Path =&gt; ZIO[R, E, A]): ZIO[R, E, A] = ZIO.acquireReleaseWith( ZIO.succeed(Files.createTempFile(<span class="synConstant">&quot;foo&quot;</span>, <span class="synConstant">&quot;.tmp&quot;</span>)) )(f =&gt; ZIO.succeed(Files.deleteIfExists(f)))(use) <span class="synType">override</span><span class="synIdentifier"> def</span> run: ZIO[ZIOAppArgs <span class="synType">with</span> Scope, Any, Any] = <span class="synStatement">for</span> { _ &lt;- createTmpFile { tmp1 =&gt; createTmpFile { tmp2 =&gt; ZIO.logInfo(s<span class="synConstant">&quot;tmp1: $tmp1, tmp2: $tmp2&quot;</span>) } } } <span class="synType">yield</span> () } </pre> <p>ポイントは for 式の中です。 Scope は flatMap で合成できるので、平坦に for 式がかけますが、 bracket の場合は利用部を関数として渡すことになるため、ネストが深くなります。</p> <h2 id="一部の-Scope-を延長する">一部の Scope を延長する</h2> <p>Scope はリソースの閉じ方を知っている値で、複数のリソースを扱えることは説明しました。 <code>ZIO.scoped</code> はそれらすべてを閉じる関数です。</p> <p>一方で、一部のリソースを延長したい場合もあります。 そんなときは <code>ZIO.scoped</code> の外で <code>ZIO.service[Scope]</code> で Scope を取得し、 <code>extend()</code> でスコープを延長します。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synComment">// tmp1 はワークフロー内で close され、tmp2 は Scope が閉じられるときに close される</span> <span class="synIdentifier">def</span> workflowWithHowToCloseTmp2: ZIO[Scope, Nothing, Unit] = <span class="synStatement">for</span> { s &lt;- ZIO.service[Scope] _ &lt;- ZIO.scoped { <span class="synStatement">for</span> { tmp1 &lt;- createTmpFile tmp2 &lt;- s.extend(createTmpFile) _ &lt;- ZIO.logInfo(s<span class="synConstant">&quot;tmp1: $tmp1, tmp2: $tmp2&quot;</span>) } <span class="synType">yield</span> () } } <span class="synType">yield</span> () </pre> <h2 id="まれにあるケース">まれにあるケース</h2> <p>Scope はリソースの管理を確実にするためのものですが、たまにはわざと管理をすり抜けたいこともあります。</p> <p>そんなときは <code>Scope.global</code> で extend します。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synIdentifier">def</span> workflow: ZIO[Any, Nothing, Unit] = ZIO.scoped { <span class="synStatement">for</span> { tmp1 &lt;- createTmpFile tmp2 &lt;- Scope.global.extend(createTmpFile) <span class="synComment">// 注意! close されない!</span> _ &lt;- ZIO.logInfo(s<span class="synConstant">&quot;tmp1: $tmp1, tmp2: $tmp2&quot;</span>) } <span class="synType">yield</span> () } </pre> <p>危ないのでよく考えて使いましょう。</p> <h2 id="まとめ">まとめ</h2> <p>ZIO の Scope はリソースの管理を確実にするための機能です。 for 式で平坦にかけ、複数のリソースを扱えるのが特徴です。 いざというときの抜け道もありますが、危険なのでよく考えて使いましょう。</p> t_kawachi ChatGPTとLangChainの活用を新卒エンジニアが考えてみる hatenablog://entry/6801883189069577133 2024-01-02T12:00:00+09:00 2024-01-02T12:00:03+09:00 OpenAI APIとLangChainで長文データを扱う SQLDatabaseChainでSQL問い合わせツールの検証 <p>皆さま、明けましておめでとうございます! <br> 株式会社FLINTERSのDataチームに所属しています、西垣です。<br> 年明け三が日のど真ん中をいかがお過ごしでしょうか? <br></p> <p>10周年記念ブログリレーの第116回目を担当させていただき、いよいよゴールが近付いてまいりました。 位置的には抑え投手(?)かと思われますので、安定感ありつつも、閲覧される方々にとって新鮮味のあるブログとなるように努めさせていただきました! お手隙の際にでもご観覧いただけますと幸いです🙇‍♂️ <br></p> <h3 id="プログの内容について">プログの内容について</h3> <p>2022年、<strong>生成系AI技術</strong>が爆発的に成長し、利用頻度は一気に拡大したかと思います。私自身、コーディングの不明確な点やリファクタリングの観点からChatGPTを利用して業務に活用する機会も多く、利便性の高いツールであることは自明であり、エンジニアの方々もかなり扱い慣れている頃合いではないでしょうか?<br><br> <strong>ChatGPT</strong>が普及したタイミングが私の入社と同時期であり、<strong>生成系AIネイティブ</strong>として何か会社内に取り込めないかとの意欲もあり、OpenAIが提供するサービスをもうワンランク上で扱えるようにこちらのブログで情報共有させていただく次第となりました!</p> <h3 id="ブログの構成">ブログの構成</h3> <p>全てを伝えきるにはブログでは少々短すぎるため、かなり省略気味ではあります💦 <br> また順を追って技術共有ができればと思います! <br><br> 今回の進行は以下となります。</p> <ul> <li>OpenAI APIとLangChainで長文データを扱う</li> <li>SQLDatabaseChainでSQL問い合わせツールの検証</li> </ul> <h3 id="OpenAI-APIとLangChainで長文データを扱う">OpenAI APIとLangChainで長文データを扱う</h3> <p>普段触れるChatGPTはチャット会話文の範囲内に埋め込まれた文脈を利用して回答を導くものです。しかし、プロンプトに埋め込むことのできるサイズは限定されており長い文章を扱うことが難しいという欠点をはらんでいます。 <br> 今回はLLM(Large Language Model)と連携ができるベクトルデータ構築によって長文を要約したり、それらを基に質疑応答可能な方法を実装してみたいと思います。もっと応用ができれば社内文書や、技術文書などをまとめるツールが作れるはずです!<br><br></p> <h5 id="LangChainとloader">LangChainとloader</h5> <p>LangChainはLLMと外部データ連携やLLMタスクの自動化のためのライブラリです。<br> 詳細については<a href="https://python.langchain.com/docs/get_started/introduction">公式ドキュメント</a>を参照してください。<br><br> LangChainの基本はテキストデータからベクトルデータを生成することですが、実際の現場ではWeb、PDF、Wordなどテキスト以外の文書がほとんどです。これをloaderを使うことで様々な入力ソースからインデックスを作成することが可能となります。<br> 各種loaderは<a href="https://python.langchain.com/docs/modules/data_connection/document_loaders/">公式ドキュメント</a>を参照してください。<br><br></p> <h5 id="実践">実践</h5> <p>開発はGoogle Colaboratoryで、Webから情報を取得しインデックス化を行いました。<br> <font color='gray'>※ OpenAIのAPI Keyは西垣個人のものを使用しています。</font> <br></p> <p><strong>OpenAIへ接続</strong></p> <pre class="code" data-lang="" data-unlink>!pip install openai</pre> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> os os.environ[<span class="synConstant">'OPENAI_API_KEY'</span>]=<span class="synConstant">&quot;&lt;&lt;自身のAPI Key&gt;&gt;&quot;</span> </pre> <p><br> <strong>ライブラリのインストールとインデックスの作成 </strong></p> <pre class="code" data-lang="" data-unlink>!pip install langchain !pip install chromadb !pip install tiktoken !pip install unstructured !pip install pdf2image</pre> <p>これによりLangChainのデータローダーである<code>UnstructuredURLLoader</code>が使用可能となります。このローダーはURLを複数指定できる点がポイント高いです。<br> 次はWikipediaから『夏目漱石』と『森鴎外』を読込インデックスDBとして作成します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> langchain.text_splitter <span class="synPreProc">import</span> CharacterTextSplitter, RecursiveCharacterTextSplitter <span class="synPreProc">from</span> langchain.chat_models <span class="synPreProc">import</span> ChatOpenAI <span class="synPreProc">from</span> langchain.embeddings <span class="synPreProc">import</span> OpenAIEmbeddings <span class="synPreProc">from</span> langchain.vectorstores <span class="synPreProc">import</span> Chroma <span class="synPreProc">from</span> langchain.chains <span class="synPreProc">import</span> RetrievalQA <span class="synPreProc">from</span> langchain.document_loaders <span class="synPreProc">import</span> UnstructuredURLLoader urls = [ <span class="synConstant">'https://ja.wikipedia.org/wiki/%E5%A4%8F%E7%9B%AE%E6%BC%B1%E7%9F%B3'</span>, <span class="synConstant">'https://ja.wikipedia.org/wiki/%E6%A3%AE%E9%B7%97%E5%A4%96'</span> ] loader = UnstructuredURLLoader(urls = urls) documents = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size = <span class="synConstant">500</span>, chunk_overlap = <span class="synConstant">0</span>) texts = text_splitter.split_documents(documents) embeddings = OpenAIEmbeddings() db = Chroma.from_documents(texts, embeddings) retriever = db.as_retriever() qa = RetrievalQA.from_chain_type(llm = ChatOpenAI(model_name = <span class="synConstant">'gpt-3.5-turbo'</span>), chain_type=<span class="synConstant">&quot;stuff&quot;</span>, retriever=retriever) </pre> <p>検索は<code>qa.run("テキスト")</code>によって以下のように実行されました。 <br> <figure class="figure-image figure-image-fotolife" title="LangChainTest"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t_nishigaki/20231225/20231225101557.gif" width="1200" height="636" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure> 夏目漱石から『吾輩は猫である』を検索し、同時に森鴎外からその時期に何を執筆していたかを検索しています。複数の文書を指定できると、それだけ検索する対象が広がるのでドキュメントの参照や比較的大きくURLが項目ごとに分かれている構造的な文章も検索対象にできます。 <br></p> <h3 id="SQLDatabaseChainでSQL問い合わせツールの検証">SQLDatabaseChainでSQL問い合わせツールの検証</h3> <p>私はDataチームに所属しているため、DBへのアクセスは日常茶飯事です。ただ、確認したいことがある度に毎回SQLをたたくのが億劫になることもあり、作業効率化として何か自動化する手立てはないかと探っていた中で<code>SQLDatabaseChain</code>を使ったSQL問い合わせツールに糸口を感じ、簡易な設計を行ってみました。<br><br></p> <h5 id="DBの作成">DBの作成</h5> <p>まず、下記のER図をもとにSQLDatabaseオブジェクトをsqliteで定義します。<br> <font color='gray'>※ DBの作成は少々長くなるため割愛します</font>🙏 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t_nishigaki/20231225/20231225104802.png" width="1200" height="961" 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/t/t_nishigaki/20231225/20231225110015.png" width="1200" height="504" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h5 id="チャットボットの作成">チャットボットの作成</h5> <p>作成するチャットボットは以下3点のタスクを追加します。<br></p> <ol> <li>ユーザーを特定する</li> <li>そのユーザーに関する情報の全てを取得する</li> <li>サポートを行う</li> </ol> <h5 id="-ユーザーを特定する">① ユーザーを特定する</h5> <p>まずはライブラリの追加から行います。 <br></p> <pre class="code" data-lang="" data-unlink>!pip install -U langchain langchain_experimental</pre> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> json sql_uri = <span class="synConstant">&quot;sqlite:///user_support.db&quot;</span> db= SQLDatabase.from_uri(sql_uri) llm = ChatOpenAI(temperature=<span class="synConstant">0.2</span>) <span class="synStatement">def</span> <span class="synIdentifier">find_user</span>(user_text): template= <span class="synSpecial">\</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">ユーザテーブルのみを対象とします。次の要求文に対しユーザを一意に特定したい。</span> <span class="synConstant">回答は例にしめすようにJSON形式で表示してください:要求文[{question}]</span> <span class="synConstant">例:</span> <span class="synConstant">[{{</span> <span class="synConstant"> &quot;user_id&quot;: ,</span> <span class="synConstant"> &quot;last_mame&quot;: &quot;&quot;,</span> <span class="synConstant"> &quot;first_name&quot;: &quot;&quot;,</span> <span class="synConstant"> &quot;phone&quot;: &quot;&quot;,</span> <span class="synConstant"> &quot;email&quot;: &quot;&quot;</span> <span class="synConstant">}}]</span> <span class="synConstant">&quot;&quot;&quot;</span> prompt = PromptTemplate(template=template, input_variables=[<span class="synConstant">&quot;question&quot;</span>]) db_chain = SQLDatabaseChain.from_llm(llm,db,verbose=<span class="synIdentifier">True</span>,output_key=<span class="synConstant">&quot;Answer&quot;</span>) <span class="synStatement">return</span> db_chain.run(prompt.format(question=user_text)) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synIdentifier">print</span>(<span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">お客様の情報を確認します。お名前、電話番号、ユーザID、メールなどお客様を特定できるデータを入力してください。&quot;</span>) <span class="synStatement">while</span> <span class="synIdentifier">True</span>: user_text=<span class="synIdentifier">input</span>(<span class="synConstant">&quot;&gt;&quot;</span>) user_json = find_user(user_text) <span class="synStatement">try</span>: users = json.loads(user_json) <span class="synStatement">except</span>: <span class="synIdentifier">print</span>(<span class="synConstant">&quot;お客様の情報が確認できませんでした。もう一度入力してください。&quot;</span>) <span class="synStatement">continue</span> <span class="synStatement">if</span> users <span class="synStatement">is</span> <span class="synIdentifier">None</span>: <span class="synIdentifier">print</span>(<span class="synConstant">&quot;お客様の情報が確認できませんでした。もう一度入力してください。&quot;</span>) <span class="synStatement">continue</span> <span class="synStatement">if</span> <span class="synIdentifier">len</span>(users) &gt; <span class="synConstant">1</span> : <span class="synIdentifier">print</span>(<span class="synConstant">&quot;お客様の情報が特定できませんでした。もう一度入力してください。&quot;</span>) <span class="synStatement">continue</span> <span class="synIdentifier">print</span>(<span class="synConstant">&quot;お客様の情報が確認できました。&quot;</span>) <span class="synStatement">break</span> user = <span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>.join(f<span class="synConstant">'{key}: {value}'</span> <span class="synStatement">for</span> key, value <span class="synStatement">in</span> users[<span class="synConstant">0</span>].items()) <span class="synComment">#print(user)</span> </pre> <p>実行結果は以下のようになります。 <br> IDや名称からユーザーを特定することができました。 <br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t_nishigaki/20231225/20231225111801.gif" width="1200" height="636" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h5 id="-ユーザーに関する情報の全てを取得する">② ユーザーに関する情報の全てを取得する</h5> <p>次に確定したユーザー情報をもとに、購入履歴と対応履歴を検索します。 <br></p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> langchain_experimental.sql <span class="synPreProc">import</span> SQLDatabaseSequentialChain <span class="synStatement">def</span> <span class="synIdentifier">get_user_info</span>(user): template= <span class="synSpecial">\</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> 次の要求文のユーザIDの広告名や価格も含めた購買履歴(テーブル)の内容を知りたい。</span> <span class="synConstant"> なお回答はヘッダー付のCSV形式で出力してください。:</span> <span class="synConstant"> 要求文[{user}]</span> <span class="synConstant"> &quot;&quot;&quot;</span> prompt = PromptTemplate(template=template, input_variables=[<span class="synConstant">&quot;user&quot;</span>]) chain = SQLDatabaseSequentialChain.from_llm(llm, db, verbose=<span class="synIdentifier">True</span>) order_history = chain.run(prompt.format(user=user)) template= <span class="synSpecial">\</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> 次の要求文のユーザIDの対応履歴(テーブル)の内容を知りたい。</span> <span class="synConstant"> なお回答はヘッダー付のCSV形式で出力してください。:</span> <span class="synConstant"> 要求文[{user}]</span> <span class="synConstant"> &quot;&quot;&quot;</span> prompt = PromptTemplate(template=template, input_variables=[<span class="synConstant">&quot;user&quot;</span>]) chain = SQLDatabaseSequentialChain.from_llm(llm, db, verbose=<span class="synIdentifier">True</span>) support_history = chain.run(prompt.format(user=user)) <span class="synStatement">return</span> f<span class="synConstant">&quot;購買履歴:</span><span class="synSpecial">\n</span><span class="synConstant">{order_history}</span><span class="synSpecial">\n\n</span><span class="synConstant">対応履歴:</span><span class="synSpecial">\n</span><span class="synConstant">{support_history}</span><span class="synSpecial">\n\n</span><span class="synConstant">&quot;</span> <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: user_info = get_user_info(user) </pre> <p>実行結果は以下のようになります。 <br> <code>user_info</code>に購買履歴と対応履歴が代入されていることが確認できました。 <br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t_nishigaki/20231225/20231225113324.png" width="1200" height="629" 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/t/t_nishigaki/20231225/20231225113338.png" width="1200" height="192" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h5 id="-サポートを行う">③ サポートを行う</h5> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> langchain.prompts <span class="synPreProc">import</span> ( ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, HumanMessagePromptTemplate ) <span class="synPreProc">from</span> langchain.chains <span class="synPreProc">import</span> ConversationChain <span class="synPreProc">from</span> langchain.chat_models <span class="synPreProc">import</span> ChatOpenAI <span class="synPreProc">from</span> langchain.memory <span class="synPreProc">import</span> ConversationBufferMemory template=<span class="synSpecial">\</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> ## ユーザ情報</span> <span class="synConstant"> {user}</span> <span class="synConstant"> {user_info}</span> <span class="synConstant"> ## 処理</span> <span class="synConstant"> あなたは接客のエキスパートです。お客様の要求文にたいして的確に答えてください。</span> <span class="synConstant"> 接客のさい、上記のユーザ情報を参照し親切丁寧にお客様サポートをしてください。</span> <span class="synConstant"> ただし、わからないものに関してはわからないと答えてください。</span> <span class="synConstant">&quot;&quot;&quot;</span> system_template = template.format(user=user,user_info=user_info) prompt = ChatPromptTemplate.from_messages([ SystemMessagePromptTemplate.from_template(system_template), MessagesPlaceholder(variable_name=<span class="synConstant">&quot;history&quot;</span>), HumanMessagePromptTemplate.from_template(<span class="synConstant">&quot;{input}&quot;</span>) ]) llm = ChatOpenAI(temperature=<span class="synConstant">0.5</span>) memory = ConversationBufferMemory(return_messages=<span class="synIdentifier">True</span>) conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm) <span class="synStatement">while</span> <span class="synIdentifier">True</span>: command = <span class="synIdentifier">input</span>(<span class="synConstant">&quot;質問をどうぞ(qで終了)&gt;&quot;</span>) <span class="synStatement">if</span> command == <span class="synConstant">&quot;q&quot;</span>: <span class="synStatement">break</span> response = conversation.predict(<span class="synIdentifier">input</span>=command) <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;{response}</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>) </pre> <p>実行結果は以下のようになります。 <br> ユーザーや購入履歴も把握することができているようです。 <br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t_nishigaki/20231225/20231225122523.png" width="1200" height="602" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="まとめ">まとめ</h3> <p>本ブログにてLangChainで長文データを扱う技術、またSQLDatabaseChainでSQL問い合わせツールの検証を行いました。非常に単純な実装ではありましたが、今後もっと機能を拡充することさえできれば、DBでの自然言語での問い合わせやタスク間の値の受け渡しなどを自動化することができ、業務改善に新たな一手を予感させるほど魅力的な技術だと感じています。 <br> FLINTERSの行動規範である『学びを結集し、新たな付加価値を見つけよう。』があるように、知識を昇華させ社内外に影響を与えられるようなエンジニアになれることを目標にまた今年からも精進して参りたいと思います!ブログをご覧いただき誠にありがとうございました!</p> t_nishigaki エンジニアの悩みと成長について考える hatenablog://entry/6801883189068737521 2024-01-01T12:00:00+09:00 2024-01-01T12:00:35+09:00 本記事では多くのエンジニアが抱えているであろう初歩的だが重要な6つの悩みについて私の経験を基に簡単に書いていきます。 <h2 id="自己紹介">自己紹介</h2> <p>明けましておめでとうございます!エンジニアの服部です。FLINTERS 10周年記念ブログリレー、今回で115日目です。133日目までもう少しですね。2024年もよろしくお願いします!</p> <h2 id="導入">導入</h2> <p>『つよつよエンジニア』をご存知ですか?技術力や問題解決能力が高く、コミュニケーション能力や学習意欲の高いエンジニアのことを指す言葉です。</p> <p>周りからつよつよと言われる人達は情報発信も盛んなため、目にする機会も多く、周りが皆優秀に見えて自信をなくすこともあると思います。私は『よわよわエンジニア』だ......と思い、悩んでいる人も多いのではないでしょうか?</p> <p>そこで、本記事では多くのエンジニアが抱えているであろう初歩的だが重要な6つの悩みについて私の経験を基に簡単に書いていきます。ほんの少しでも役に立てば幸いです。</p> <h2 id="悩み1ドキュメントの読み方が分からない">悩み1 ドキュメントの読み方が分からない</h2> <p>まずは以下の4つを行います。</p> <ul> <li><p>解決したい問題と求めている情報を明確にする</p></li> <li><p>何のために書かれたドキュメントなのか目的を理解する</p></li> <li><p>先に目次や見出しで全体像を把握する</p></li> <li><p>専門用語は逐一調べながら読む</p></li> </ul> <p>分かっていない事を言語化してまとめると要点が整理しやすいです。生成AI を活用するのもオススメです。</p> <h2 id="悩み2泥沼にハマって何をすれば良いのか分からなくなる">悩み2 泥沼にハマって何をすれば良いのか分からなくなる</h2> <p>「問題に行き詰まった。多分これをああしてこうすれば良いのかな?あれ、別の問題が出たぞ。こっちを変えると別の問題が発生するなぁ、また新しい問題。うーん、今何やってるんだっけ?」</p> <p>優先順位の低いものに時間を浪費してしまうことは避けるべきです。担当しているタスクの位置や重要度、期限や完了条件は常に意識する方が良いです。定期的にタスクを整理することで優先順位や今やるべきことが明確になります。一人で無理な場合はチームに共有して指摘をもらうのも良いかもしれません。</p> <h2 id="悩み3勉強すべきことが多過ぎる">悩み3 勉強すべきことが多過ぎる</h2> <p>知っておいた方が良いこと、やった方が良いことはたくさんあります。しかし、自分が本当に今やらなくてはならないことは限られています。もちろん費やせる時間や労力も限られています。</p> <p>まず、自分がチームで求められていることを把握しましょう。それによって効率的に学習することができます。少しずつで良いので着実に知識を増やしていきましょう。</p> <h2 id="悩み4先延ばしにしてしまう">悩み4 先延ばしにしてしまう</h2> <p>後で読もうと思いブックマークに入れた記事を後で読むことはほとんどありません。溜まる一方です。面倒なタスクを後回しにして後で時間に追われることもあるあるです。</p> <p>先延ばしは脳の認知リソースを消耗するため集中力が低下します。なるべく先延ばしをしない、先延ばしをする場合はいつやるのか先に決めておくと頭からもやもやを追い出せます。やりたくないことはタスクを分解して最初のハードルを下げましょう。短めの締め切りを設けるのも効果的です。また、やらなくて良いことは頭から捨てた方が楽です。不要なブックマークも消しましょう。</p> <p>先延ばしについては <a href="https://www.amazon.co.jp/%E3%81%84%E3%81%A4%E3%82%82%E3%80%8C%E6%99%82%E9%96%93%E3%81%8C%E3%81%AA%E3%81%84%E3%80%8D%E3%81%82%E3%81%AA%E3%81%9F%E3%81%AB-%E6%AC%A0%E4%B9%8F%E3%81%AE%E8%A1%8C%E5%8B%95%E7%B5%8C%E6%B8%88%E5%AD%A6-%E3%83%8F%E3%83%A4%E3%82%AB%E3%83%AF%E3%83%BB%E3%83%8E%E3%83%B3%E3%83%95%E3%82%A3%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E6%96%87%E5%BA%AB-%E3%82%BB%E3%83%B3%E3%83%87%E3%82%A3%E3%83%AB-%E3%83%A0%E3%83%83%E3%83%A9%E3%82%A4%E3%83%8A%E3%82%BF%E3%83%B3/dp/4150504830">いつも「時間がない」あなたに:欠乏の行動経済学</a> という本に詳しく書いてあります。オススメです。</p> <h2 id="悩み5自発的にコミュニケーションが取れない">悩み5 自発的にコミュニケーションが取れない</h2> <p>「人と話すのが怖い!無知を責められるのが怖い!相手の迷惑になるかも......」</p> <p>一番怖いのは問題を先送りにして状況が悪化してから発覚することです。周りはあなたが思うほど怖くありません。頭に浮かぶネガティブなイメージは全て被害妄想です。</p> <p>先に自分で調べることは大前提ですが、分からないことは質問しましょう。一人で無理ならペアプロをお願いしましょう。タスクに行き詰まっていることを報告するのも仕事の内です。自分からコミュニケーションを取るようにしましょう。もちろん感謝は忘れずに。</p> <h2 id="悩み6これからもエンジニアとして働けるか不安">悩み6 これからもエンジニアとして働けるか不安</h2> <p>「周りは凄い人ばかり。年下で自分より優秀な人もたくさんいる。私はどうだ?これからもエンジニアとしてやっていけるのか?生成AI で自分程度のエンジニアは淘汰されるのでは?」</p> <p>まず、周りと比べて落ち込む必要はありません。誰しも得意不得意があります。自分の強みを見つけて伸ばす方が賢明です。変化の激しい世界なので勉強は必須ですが、IT技術に日々触れるエンジニアはAI時代に適した職種です。</p> <p>また、生成AI は人間の能力を拡張するツールに過ぎません。むしろ使いこなすことでエンジニアの可能性は広がると思います。人間の強みは責任を持てることです。日々スキルや経験を磨くことで、エンジニアとして働き続けることができると思います。</p> <h2 id="終わりに">終わりに</h2> <p>頭では分かっていても行動に移すのはとても難しいことです。仕事の悩みは業務のパフォーマンスに影響するため、少しでも悩みを取り除けたら良いなと思っています。</p> <p>楽しいエンジニアライフを!</p> k_hattori 情シスとして大事にしていること hatenablog://entry/6801883189070281799 2023-12-31T12:00:00+09:00 2023-12-31T12:00:02+09:00 情シスとして大事にしていることを振り返ってみようかなと思います。 <p>FLINTERS社内情シスの山本です。<br/> 株式会社FLINTER10周年記念 133日間ブログリレーの114日目を担当します。</p> <p>早いもので、もう大晦日ですね。</p> <p>大晦日なので、今年を振り返ってという感じで、ちょっと自分として大事にしていることを振り返ってみようかなと思います。</p> <h3 id="正確で分かりやすい対応をする">正確で分かりやすい対応をする</h3> <p>情シスは、基本的には社内のシステム全般やPCに関する社内横断的な管理やヘルプデスク、セキュリティ的なことなど、担当する範囲が広いことが多いかと思います。 そのため、社内から色々な相談や依頼を受けることがたくさんあります。</p> <p>依頼者からの相談・依頼に応えるのはもちろんですが、より正確で分かりやすく、手間や手戻りがない対応ができた方が、お互いやり取りも早く終わって良い結果を得られます。</p> <p>全て完璧・・・とは言えなくて何度もごめんなさいしていることもありますが、お互いの時間と体力と気力を浪費しないで、その分を他の有意義な事に使いたいですよね。</p> <h3 id="社内のスペシャリストになる">社内のスペシャリストになる</h3> <p>管理しているシステムや担当している業務について、社内で一番詳しい、もしくは必要に応じて詳しくなることができる、と言うことも大事にしています。</p> <p>全部覚えたり理解することは到底無理ですが、どの情報は一次情報がどこにあるか、どう調べれば良いかが分かっていれば(調べる時間は少し必要ですが)、正確な情報の入手ができます。<br/> 知らなかったことも調べたことで知って知識になり、対応したことで経験になり、積み重ねて実力になります。</p> <p>技術的なことも、そうでないことも、自分が関与する業務の知識や理解は可能な限り貪欲に得ていきたいと思っています。</p> <h3 id="背景と真意を気にする">背景と真意を気にする</h3> <p>依頼や相談の内容や対象に関する理解だけでなく、依頼者が置かれている状況や携わっている業務、関連するシステムや関係者など、その背景についても気にすることを心がけています。</p> <p>単純に依頼されたそのままをやる、相談へのマニュアル回答のみでは、依頼や相談が全てクリアできるとは限りません。 (もちろん、それが求められる場合もあるとは思います)</p> <p>依頼や相談するに至った背景や状況をちゃんと考慮しないと、依頼者が本当に求めている回答や対応に辿り着けなかったり、最悪間違えたりと言うことになりかねません。</p> <p>正確で分かりやすい対応のためには絶対必要ですね。</p> <h3 id="普段から社内の状況を把握する">普段から社内の状況を把握する</h3> <p>どんな部署やプロジェクトがどんなことをどんな環境でやってて、どんなツールを使っているのかなど、システム的なところを知っておくことは情シス的にとっても大事です。<br/> もちろん、社内のルールや制度、文化的なところも把握していないといけない場面もあるかと思います。</p> <p>それだけでなく、他の部署の今後の計画や目標、懸念していることについても、なるべく気にしておくようにしています。</p> <p>FLINTERSにはオープンにして良い情報はなるべくオープンにして、全社で共有していこうという文化があります。<br/> 各部門から技術的な情報やプロジェクト状況の共有や、全社的な事業状況の共有などを定期的に行っているので、それらも見ておくようにしています。</p> <p>これは背景を把握したり、より求めている回答や対応をするための良い材料になります。</p> <h3 id="話しかけやすさを心がける">話しかけやすさを心がける</h3> <p>情シスは相談や依頼がいっぱい来る部署ではあるのですが、逆に相談や依頼が来てくれないと困る部署でもあると思います。<br/> 「新しいサービスを使いたい」とか「やらかしちゃったんだけど」と言う話など、情シスに相談や依頼無しに進められてしまうと大きな問題になる可能性があったりします。</p> <p>ムスッとして怖かったり、話を聞いてくれなかったり、反応が無かったり、オフィスでもテキストチャットでもあんまり話したくないな、と思われてしまうと、本来して欲しい依頼や相談もされなくなってしまいます。</p> <p>普段から、話しやすい相談しやすいと思ってもらえるように、なるべく柔らかい対応や親しみを持ってもらえるように心がけてます。</p> <h3 id="情シスとして">情シスとして</h3> <p>今の自分が情シスとして大事にしたい思うことを、思いつくままに挙げてみました。</p> <p>偉そうなこと言って、大したことできてないじゃないかと言われるかも知れません。<br/> 理想にはまだ道半ばですが、新しい年もこれらを大事にしていきたいと思います。</p> <p>FLINTERSで頑張りたい方が全力で頑張れるよう、情シスも頑張ります!</p> yuta_yamamoto_sep EitherTもういいや、時代はZIO hatenablog://entry/6801883189068361782 2023-12-30T12:00:00+09:00 2023-12-30T12:00:03+09:00 EitherTもういいや、時代はZIO <p>みやしーです。<br/> 弊社FLINTERSが2024年1月6日に会社設立10周年ということで、みんなでブログを毎日書いています。この記事は113日目の記事だそうです。すごいですね</p> <p>さっそく本題へ</p> <h2 id="対象読者">対象読者</h2> <ul> <li>Scalaをやっている</li> <li>失敗と成功のいずれかを表すには Either[E, A] を使うことを知っている</li> <li>非同期処理を表すには Future[A] を使うことを知っている</li> </ul> <h2 id="これまでのあらすじ">これまでのあらすじ</h2> <ul> <li>Either と Future は同時に使うことが多いじゃん</li> <li>Either と Future は同時に使う… <a href="https://typelevel.org/cats/datatypes/eithert.html">EitherT</a> の出番か!?</li> <li>いや、EitherTなんかちょっとまどろっこしいな</li> </ul> <p>わかるなあ</p> <p>いやー EitherT 便利だと思ってたんですけどね。4年前の私は <a href="https://github.com/KazuyaMiyashita/eithert">EitherT大好きって感じの記事</a>を書いてキャッキャしていたし。</p> <p>でも、非同期処理と失敗と成功を同時に表現できるすごい型があればそれで良くない?って思うわけですよ。</p> <p>そうです</p> <h2 id="時代はZIO">時代はZIO</h2> <p><span style="font-size: 200%"><a href="https://zio.dev/">ZIO</a> !!!</span></p> <p>ZIOっていうのは、大体 <code>Future[Either[E, A]]</code> のことです!</p> <p>便利ですね。現場からは以上です。</p> <h2 id="続き">続き</h2> <p>ちょっと嘘つきました。<br/> <a href="https://zio.dev/overview/summary">ドキュメント</a>には 「(この例えは正確とは言えないが)ZIOは <code>R =&gt; Either[E, A]</code> だ」と書いてますね。<br/> <code>Future[Either[E, A]]</code> とは色々違いますが、まあでも大体こんなもんです。</p> <h2 id="続き2">続き2</h2> <p>ZIO型には3つの型パラメータがあり、 <code>ZIO[R, E, A]</code> のようになっています。<br/> このうち <code>E</code> と <code>A</code> は <code>Either[E, A]</code> と同様に失敗と成功を表すものです。</p> <p>なお、 <code>R</code> は関数の引数として使うようなものですが、使わないなら <code>Any</code> を入れておけばOKです。</p> <p>Either を使ったコードは、ZIOでは次のように書きます。</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synType">val</span> a: Either[<span class="synConstant">String</span>, Int] = Right(<span class="synConstant">42</span>) <span class="synType">val</span> b: Either[<span class="synConstant">String</span>, Int] = Left(<span class="synConstant">&quot;failed!&quot;</span>) </pre> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> zio.ZIO <span class="synType">val</span> a: ZIO[Any, <span class="synConstant">String</span>, Int] = ZIO.succeed(<span class="synConstant">42</span>) <span class="synType">val</span> b: ZIO[Any, <span class="synConstant">String</span>, Int] = ZIO.fail(<span class="synConstant">&quot;failed!&quot;</span>) </pre> <p><code>.map</code>, <code>.flatMap</code> のようなおなじみの処理や、 <code>.mapError</code> によるエラーの値の変換ができます。<br/> その他もろもろ、Either で出来ることは ZIO でもできます。</p> <p>また、 Future に関してはというと、 Future と ZIO で非同期に関する考え方が違う点がいくつかあります。</p> <p>Future ではこのようなコードがあったとき</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synPreProc">import</span> scala.concurrent.Future <span class="synPreProc">import</span> scala.concurrent.ExecutionContext.Implicits.global <span class="synType">val</span> a: Furure[Int] = Future { <span class="synComment">// なんか色々通信したりする処理</span> <span class="synConstant">42</span> } </pre> <ul> <li>型は、同期的な処理は <code>A</code> で、非同期的な処理は <code>Future[A]</code> と区別する</li> <li>なんか <code>ExecutionContext</code> とかようわからんものを指定する必要がある</li> <li>Future型のインスタンスが作成されたタイミングで非同期処理が開始される</li> </ul> <p>ですよね。</p> <p>ZIO の場合は、なんかこんな感じのコードになるのですが</p> <pre class="code lang-scala" data-lang="scala" data-unlink><span class="synType">val</span> a: ZIO[Any, Throwable, Int] = ZIO.attemptBlocking { <span class="synComment">// なんか色々通信したりする処理</span> <span class="synConstant">42</span> } </pre> <ul> <li>型は、同期的だろうと非同期だろうと <code>ZIO[R, E, A]</code> である</li> <li><code>.attemptBlocking</code> や <code>.blocking</code> などのメソッドを使うことで、いい感じに実行してくれる</li> <li>ZIO型のインスタンスが作成されたタイミングではまだ処理は実行されない</li> </ul> <p>といった違いがあります。</p> <h2 id="まとめ">まとめ</h2> <p>まあ、そんなこんなで <code>Future[Either[E, A]]</code> と ZIO にはいくつかの違いはあるのですが、 ZIO っていう型一つで両方のことができます。</p> <p><code>Future[Either[E, A]]</code> を用意して <code>EitherT</code> でラップして〜みたいなこと、もうやらんでいいです。</p> <p>Either も Future もアプリケーション書いてたら絶対使うわけでして、 ということはみんな ZIO を使ったらいいよってことですよね。</p> <p>めでたしめでたし。</p> km_flinters おまえらの個人情報の認識は間違っている hatenablog://entry/6801883189069764226 2023-12-28T12:00:00+09:00 2023-12-28T12:00:02+09:00 システム開発では避けて通れない個人情報の取り扱い。 ですが、どういったものが個人情報なのでしょうか? <p>この記事はFLINTERS設立10周年ブログリレーの 111 日目の記事です。<br/> ゾロ目ですね。</p> <p>FLINTERSの森と申します。<br/> 普段はさまざまなログデータを整理・集計するチームにて、主にコードを眺める仕事をしております。</p> <h1 id="タイトルについて">タイトルについて</h1> <p>主語を大きくした方がよく火がつくと聞きました。<br/> <a href="https://www.flinters.co.jp/company/">FLINTERSのMISSION</a>は <strong>未来につながる火を灯そう</strong> となっております。</p> <p>この記事では、個人情報について理解するための第1歩である <em>個人情報の法律上の定義</em> について確認していきます。</p> <h1 id="注意事項">注意事項</h1> <ol> <li>この記事は2023-12-20時点での情報を元に記載されています。<br/> 閲覧時点でも役に立つ情報であるかはご自身でご確認ください。</li> <li>著者はプログラマーであり、法律についてはなんの資格も持っていない素人です。<br/> 裏付けが必要な事項については必ずご自身でご確認ください。</li> <li>この記事に登場するデータ上の個人は全て架空の人物です。<br/> 実在する人物とは一切関係ありません。</li> </ol> <h1 id="イメージの確認">イメージの確認</h1> <p>「個人情報を含むテーブル」という言葉を聞くと、どのようなものをイメージしますか?<br/> テーブルではなくオブジェクトやエンティティなどでも影響はないのですが、今回は表にしやすいテーブルを前提に話を進めます。</p> <p>まずはイメージを確認した上で、法律上どのように定義されているのかをみていきましょう。</p> <h2 id="個人情報を含むテーブルの間違ったイメージ">個人情報を含むテーブルの間違ったイメージ</h2> <p><figure class="figure-image figure-image-fotolife" title="個人情報を含むテーブルの間違ったイメージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/km_9f609fc/20231225/20231225221714.png" alt="&#x500B;&#x4EBA;&#x60C5;&#x5831;&#x304B;&#x3069;&#x3046;&#x304B;&#x306F;&#x5217;&#x5358;&#x4F4D;&#x306B;&#x5224;&#x5B9A;&#x3055;&#x308C;&#x308B;&#x3068;&#x3044;&#x3046;&#x9593;&#x9055;&#x3063;&#x305F;&#x30A4;&#x30E1;&#x30FC;&#x30B8;" width="791" height="273" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>個人情報を含むテーブルの間違ったイメージ</figcaption></figure></p> <h2 id="個人情報を含むテーブルのより正解に近いイメージ">個人情報を含むテーブルのより正解に近いイメージ</h2> <p><figure class="figure-image figure-image-fotolife" title="個人情報を含むテーブルのより正解に近いイメージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/km_9f609fc/20231225/20231225221919.png" alt="&#x60C5;&#x5831;&#x306E;&#x307E;&#x3068;&#x307E;&#x308A;&#x3054;&#x3068;&#x306B;&#x500B;&#x4EBA;&#x60C5;&#x5831;&#x304B;&#x305D;&#x3046;&#x3067;&#x306A;&#x3044;&#x304B;&#x304C;&#x5224;&#x5B9A;&#x3055;&#x308C;&#x308B;&#x30A4;&#x30E1;&#x30FC;&#x30B8;" width="793" height="830" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>個人情報を含むテーブルのより正解に近いイメージ</figcaption></figure></p> <p>イメージの共有ができたところで、なぜこのようなことが言えるのかを確認していきたいと思います。<br/> なお、テーブル設計については記事の都合上テーブル数を2個にすることを優先したため、残りの部分は脳内補完をお願いいたします。</p> <h1 id="法律文の確認">法律文の確認</h1> <p>プログラミングでは一次情報やソースコードを確認することを大事だと考えているプログラマーは多いと思っています。</p> <p>同様に法律の場合でも、原典を確認しに行くことは非常に重要ではないでしょうか。</p> <p>日本の法律は<a href="https://elaws.e-gov.go.jp/">e-Gov法令検索</a>というサイトで検索可能です。<br/> ドメインもgo.jpなので安心ですね。</p> <p>このサイトで「個人情報」などで検索すると、<a href="https://elaws.e-gov.go.jp/document?lawid=415AC0000000057">個人情報の保護に関する法律(平成十五年法律第五十七号)</a>を見つけることが可能です。<br/> なお、情報は検索時点で最新のものに更新されるため、この記事では引用してきた法律文を元に解説します。</p> <h2 id="法律文を読む前に">法律文を読む前に</h2> <p>普段情報処理に関わる人たちに向けて、法務関連のオブジェクトについて私の理解を情報処理関連の用語と比較して共有しておきます。</p> <h3 id="法律文">法律文</h3> <p>法務関係において法律文は情報処理でいうところのソースコードにあたります。<br/> このソースコードを裁判所で実際に実行させると、裁判の対象について判定が下されるということになります。</p> <p>なお、ソースコードの修正(法案作成)やレビュー(国会審議)・デプロイ(法律施行)には非常に長い時間がかかります。<br/> 参考: <a href="https://www.sangiin.go.jp/japanese/kids/html/shikumi/houritsu.html">国会のしくみと法律ができるまで!</a></p> <h3 id="ガイドライン">ガイドライン</h3> <p>法務関係においてガイドラインは情報処理でいうところの公式ドキュメントにあたります。<br/> 参考: <a href="https://www.ppc.go.jp/personalinfo/legal/">個人情報の保護に関する法律についてのガイドライン</a></p> <p>これらは法案を作成したグループが法案の意図や注意点を記載したものとなっており、実際の法律を読む上よりはかなり読みやすくなっています。</p> <p>注意点として覚えておくべきこととして、プログラムでいうところのバグの存在があります。<br/> ドキュメントとソースコードの挙動が違う場合にはソースコード側がドキュメントに合わせて修正されるべきですが、 当然新しい修正がデプロイされるまでの期間はソースコードに記載された通りの挙動を繰り返すことになります。</p> <p>なお、ソースコードの修正(法案作成)や(以下略。</p> <h3 id="裁判">裁判</h3> <p>法務関係において裁判は情報処理でいうところの実行環境やインタープリターにあたります。</p> <p>同じコードでも実行環境やパラメーター(裁判官や弁護士など?)が違うと違う結果になる可能性も存在するため、 控訴や上告などの「実行環境変えて再実行してくれ」リクエストが可能となっています。</p> <p>ただし実行(=裁判)するコストが非常に高いため、関係者による事前レビューで裁判が行われないまま終わることも少なくありません。</p> <p>なので世の中的には「多分実行すればこうなる」を前提に対応することがほとんどとなるようです。</p> <h2 id="個人情報の定義">個人情報の定義</h2> <p>今回の記事で対象とするのは、「平成十五年法律第五十七号 個人情報の保護に関する法律」の施行日 令和5年11月29日版より、 「第2条 第1項」の個人情報の定義についてです。</p> <p>条文を引用します。</p> <blockquote><p>第二条 この法律において「個人情報」とは、生存する個人に関する情報であって、次の各号のいずれかに該当するものをいう。<br/>   一 当該情報に含まれる氏名、生年月日その他の記述等(文書、図画若しくは電磁的記録(電磁的方式(電子的方式、磁気的方式その他人の知覚によっては認識することができない方式をいう。次項第二号において同じ。)で作られる記録をいう。以下同じ。)に記載され、若しくは記録され、又は音声、動作その他の方法を用いて表された一切の事項(個人識別符号を除く。)をいう。以下同じ。)により特定の個人を識別することができるもの(他の情報と容易に照合することができ、それにより特定の個人を識別することができることとなるものを含む。)<br/>   二 個人識別符号が含まれるもの</p></blockquote> <p>さて、パッと見るだけで難しそうな気がしてきますね。<br/> 難しいものは「分割統治」が基本なので、分解してみていきましょう。</p> <h1 id="法律分の解釈">法律分の解釈</h1> <h2 id="第1項の簡略化">第1項の簡略化</h2> <blockquote><p>この法律において「個人情報」とは、生存する個人に関する情報であって、次の各号のいずれかに該当するものをいう。</p></blockquote> <p>まずはこの部分だけを取り出してみましょう。</p> <p>「個人情報」とは、<ins><em><strong>A. </strong>生存する個人に関する情報</em></ins>であって、<ins><em><strong>B. </strong>次の各号のいずれかに該当するもの</em></ins>をいう。</p> <p>つまり「個人情報」とは 「A and (any of B)」を満たすものということですね。</p> <h3 id="A-生存する個人に関する情報">A. 生存する個人に関する情報</h3> <p>まずは「生存する個人に関する情報」について確認しましょう。</p> <h4 id="生存する">生存する</h4> <p>この法律上では死者は対象になりません。</p> <p>ただし、「○○さんの先祖は関ヶ原の合戦で亡くなった」というような情報は「○○さん」についての情報になるので、 生存していない人が出てきたから大丈夫と判断してしまうのは危険です。</p> <p>また、業務対象によってはデータの対象が生存しているかどうかが簡単にわかるものもありますが、 ほとんどのシステムでは確認が難しいものだと思われます。</p> <p>そのため、この項目については生存している前提で扱った方が無難なシステムが多いと思われます。</p> <h4 id="個人に関する情報">個人に関する情報</h4> <p>ここを押さえると今回の主題の8割ぐらいは理解できます。</p> <p><a href="https://www.ppc.go.jp/personalinfo/legal/guidelines_tsusoku/#a2-1">ガイドライン 通則編</a>を要約すると、 その人物が誰かがわかるかどうかに関わらず「ある1人の人間についての情報」がこの「個人に関する情報」になります。</p> <p>例えばWebでアクセスしてきたユーザーに対してセッションIDを振った場合、 そのユーザーの一連のアクセスは「ある1人の人間の行動」についての情報のため「個人に関する情報」となるわけです。<br/> 冒頭のイメージのログテーブルは全てのレコードが「個人に関する情報」ということになります。</p> <p>ここまでは「生存する個人に関する情報」についてでした。</p> <p>個人情報となるためにはもうひとつのアンド条件を満たす必要があります。</p> <h3 id="B-次の各号のいずれかに該当するもの">B. 次の各号のいずれかに該当するもの</h3> <p>この法律の項には号が2つあります。<br/> そのうちどちらかを満たせば個人情報ということになります。</p> <p>第1号は補足説明が長いため、シンプルにしたものをここに記載します。<br/> 元の文と適時見比べながらご確認ください。</p> <blockquote><p>一 当該情報に含まれる氏名、生年月日その他の記述等により特定の個人を識別することができるもの<br/> (他の情報と容易に照合することができ、それにより特定の個人を識別することができることとなるものを含む。)</p></blockquote> <p>第2号は元々短めですね。</p> <blockquote><p>二 個人識別符号が含まれるもの</p></blockquote> <p>第1号からみていきましょう。</p> <h4 id="当該情報に含まれる氏名生年月日その他の記述等により特定の個人を識別することができるもの">当該情報に含まれる氏名、生年月日その他の記述等により特定の個人を識別することができるもの</h4> <p>こちらの後半部分「氏名、生年月日その他の記述等により特定の個人を識別することができるもの」はイメージしやすいのではないでしょうか?</p> <p>冒頭の間違ったイメージの図はこの部分だけを色付けしたものです。<br/> では、何が間違っているのでしょうか?</p> <p>正解は前半部分の「当該情報に<ins><em><strong>含まれる</strong></em></ins>」の部分ですね。</p> <p>法律分の全体を見直すと以下のようにまとめ直せるものとなります。</p> <p>「個人情報」とは、「生存する個人に関する情報」であって、「当該情報に含まれる氏名、生年月日その他の記述等により特定の個人を識別することができるもの」をいう。 <br/> ※ この後触れるOR条件を忘れないように注意が必要です。</p> <p>追加でカッコ書きの以下の情報も、情報処理業務においては重要になります。</p> <h5 id="他の情報と容易に照合することができそれにより特定の個人を識別することができることとなるものを含む">(他の情報と容易に照合することができ、それにより特定の個人を識別することができることとなるものを含む。)</h5> <p>この制約があるため、他のテーブルと結合できるような「個人に関する情報」も「個人情報」の一部となります。</p> <p>「容易に」の基準については裁判で争うことは可能かと思われますが、一般的に普段の業務で行うような範囲が「容易」だと言われています。<br/> 我々のお仕事では2つのAPIから取ってきたデータを加工して結合などはよく行うと思うので、この辺りは「容易に」に入りそうです。</p> <h4 id="個人識別符号が含まれるもの">個人識別符号が含まれるもの</h4> <p>こちらは難しいものではないのですが、まとめると抜け漏れが多くなるタイプのものなので詳しくは原文をご覧ください。<br/> 個人識別符号についての定義が後続の法律文に書かれています。</p> <p>抜け漏れがあっても良いという人に適度にリストアップすると、以下のようなものです。 * 画像解析ソフトウェアなどの内部で使用される個人を判別するための特徴データ * マイナンバーや免許証番号など公的な証明書の記号・番号</p> <p>こちらも「含まれるもの」となっているので上記と同じですね。</p> <h1 id="終わりに">終わりに</h1> <p>冒頭のイメージ2つの違いについて、法律文を分解して確認を行いました。</p> <p>拙いブログをご覧いただきありがとうございました。<br/> 楽しんでいただけた方がひとりでも居れば幸いです。</p> <h1 id="この先について">この先について</h1> <p>今回は個人情報の定義についてだけみていきましたが、これらを扱う上で何をどう気をつけるのかなどには一切触れませんでした。<br/> 法律上では個人情報以外にもいろいろな言葉が定義されており、それぞれに対して取り扱い時の義務や注意点などが記載されています。<br/> システムを作る上でもこういった知識を持っていると、設計に活かせることは多いはずです。</p> <p>読者の皆様の良き開発体験に少しでもお役に立てていると嬉しく思います。</p> km_9f609fc 社内で実施しているアジャイル/スクラム研修のご紹介 hatenablog://entry/6801883189062359375 2023-12-26T12:00:00+09:00 2023-12-26T12:00:21+09:00 社員に向けて実施しているアジャイル、スクラム研修についてご紹介いたします。 <p>こんにちは、株式会社FLINTERSの小野です。</p> <p>この記事は10周年記念として133日間ブログを書き続けるチャレンジの109日目の記事になります。</p> <p>今回の記事ではFLINTERS、FLINTERS BASEの社員に向けて実施しているアジャイル、スクラム研修についてご紹介いたします。</p> <h2 id="イントロダクション">イントロダクション</h2> <p>アジャイル、スクラム研修はFLINTERSとFLINTERS BASEで協力して作成したもので、現在はFLINTERS BASEが中心となり研修を実施しています。</p> <p>VUCAな世の中において変化に柔軟に対応し価値を早期に提供して勝ち残っていくために、アジャイルのようなアプローチの重要性は日に日に高まっていると感じます。</p> <p>FLINTERSではエンジニアリング指針の一環として「変化に強い構造を作る」を掲げており、アジャイルな開発プロセスを採用しています。これにより、マーケットの要求に対して柔軟に対応し、顧客に対して価値の高いシステムを迅速に提供し続けることを目指しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.flinters.co.jp%2Fculture%2Fguidelines" title="エンジニアリング指針|環境|株式会社FLINTERS" 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.flinters.co.jp/culture/guidelines">www.flinters.co.jp</a></cite></p> <h2 id="アジャイル研修の紹介">アジャイル研修の紹介</h2> <p>アジャイル研修では、アジャイルの考え方や価値観を理解すること、そしてチーム開発で活用していくことを目的に実施しています。</p> <p>そのため、どのように活動するか(HOW)だけでなく、何が大事なのか、なぜ大事なのか(WHY)も含めてアジェンダとして取り扱っています。</p> <p>研修はIPAが作成したアジャイルソフトウェア開発宣言の読み解き方を参考にして作成しており、スクラム、カンバン、XPなどの具体的なフレームワークやプラクティスがどのように原則と関係しているのか、紐付けて理解できるような内容にしています。</p> <p><figure class="figure-image figure-image-fotolife" title="アジャイル研修_アジェンダ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/masahiro_ono/20231128/20231128133657.png" width="554" height="221" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アジャイル研修_アジェンダ</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="アジャイル原則一覧"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/masahiro_ono/20231128/20231128130028.png" width="570" height="321" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アジャイル原則一覧</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="アジャイル研修_原則07"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/masahiro_ono/20231128/20231128133840.png" width="566" height="309" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アジャイル研修_原則07</figcaption></figure></p> <h4 id="参考資料">参考資料</h4> <p><a href="https://www.ipa.go.jp/index.html">IPA &#x72EC;&#x7ACB;&#x884C;&#x653F;&#x6CD5;&#x4EBA; &#x60C5;&#x5831;&#x51E6;&#x7406;&#x63A8;&#x9032;&#x6A5F;&#x69CB;</a></p> <p><a href="https://www.ipa.go.jp/jinzai/skill-standard/plus-it-ui/itssplus/ps6vr70000001i7c-att/000065601.pdf">https://www.ipa.go.jp/jinzai/skill-standard/plus-it-ui/itssplus/ps6vr70000001i7c-att/000065601.pdf</a></p> <h2 id="スクラム研修の紹介">スクラム研修の紹介</h2> <p>FLINTERSではアジャイル原則に則った開発をする場合に、スクラムを採用するケースが多くアジャイル研修と合わせてスクラム研修を実施しています。</p> <p>スクラム研修では、チーム開発への参画をスムーズにすることを目的として実施しています。</p> <p>スクラム研修では前半に座学でスクラムの概要、各種セレモニーやロールなど基本的な概要を理解し、後半では学んだことをアウトプットするワークショップを設けて知識の定着を行います。</p> <p><figure class="figure-image figure-image-fotolife" title="スクラム研修_スクラムの説明"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/masahiro_ono/20231128/20231128134531.png" width="569" height="327" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>スクラム研修_スクラムの説明</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="スクラム研修_スクラム全体像"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/masahiro_ono/20231128/20231128134727.png" width="566" height="320" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>スクラム研修_スクラム全体像</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="スクラム研修_プランニングとリファイメント"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/masahiro_ono/20231128/20231128134723.png" width="574" height="322" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>スクラム研修_プランニングとリファイメント</figcaption></figure></p> <h2 id="研修の成果">研修の成果</h2> <p>各研修後には理解度チェックアンケートを実施していて、アンケート結果は5段階評価で平均が4.2、4.3(それぞれロールの理解、セレモニーの理解)となっていて良い評価をいただいてます。</p> <p>一方でより良い研修にするためにアンケートにコメントをいただいていることもあるので、受講者の声を聞きながら継続的なブラッシュアップもおこなっています。</p> <p>また、現在の研修は初学者向けとなっているため、ある程度アジャイルな開発を経験してきたメンバーに向けた学習コンテンツを用意することも検討しています。</p> <h2 id="最後に">最後に</h2> <p>ここまで読んでいただきありがとうございました。 FLINTERSで実施しているアジャイル、スクラム研修について少しでも知っていただけたら幸いです。</p> masahiro_ono 弊チームでのGraphQLの設計・実装を「Production Ready GraphQL」と照らし合わせて振り返る hatenablog://entry/6801883189067578991 2023-12-25T12:00:00+09:00 2023-12-27T18:44:00+09:00 弊チームでのスキーマ定義やサーバー実装について、著書「Production Ready GraphQL」に書かれている内容と照らし合わせて振り返る <p>こんにちは。FLINTERS エンジニアの山下です。この記事はFLINTERS設立10周年ブログリレーの108日目の記事になります。 今回は弊チームで利用しているGraphQLを「<a href="https://book.productionreadygraphql.com/">Production Ready GraphQL</a>」と照らし合わせて振り返っていこうと思います。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbook.productionreadygraphql.com%2F" title="Production Ready GraphQL" 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://book.productionreadygraphql.com/">book.productionreadygraphql.com</a></cite></p> <h2 id="はじめに">はじめに</h2> <p>弊チームではWebアプリに<a href="https://graphql.org/">GraphQL</a>を利用しています。 しかし自分は前のチームではGraphQLは一切触っておらず、GraphQLについての知識はありませんでした。 そこでGraphQLへの理解を深めるためにGraphQLの概要や設計について書かれている著書「Production Ready GraphQL」を読んで時間が経ったので 本の復習を兼ねて、本の内容の要点と弊チームでの設計・実装を振り返っていこうと思います。 今回はスキーマ設計・サーバー実装について掘り下げていこうと思います。</p> <h2 id="スキーマ設計">スキーマ設計</h2> <p>本では、スキーマ設計について抑えるべきポイントとして以下の4つが挙げられていました。 (引用: Production Ready GraphQL p.83)</p> <h3 id="デザインファースト">デザインファースト</h3> <blockquote><p>First, use a design-first approach to schema development. Discuss design with teammates that know the domain best and ignore implementation details.</p></blockquote> <p>(いきなり実装を始めるのではなく、早い段階で設計を検討することが大切である。 )</p> <p>「Design First」 (Production Ready GraphQL p. 27-28) の節で、実装を最初にやってしまうとスキーマの設計が実装に密接に結びついてしまう問題があるということが説明されていました。</p> <h3 id="クライアントファースト">クライアントファースト</h3> <blockquote><p>Second, design in terms of client use cases. Don’t think in terms of data, types, or fields.</p></blockquote> <p>(クライアントのユースケースを念頭に置いて設計することが大切である。データ・タイプ・フィールドで考えてはいけない。 )</p> <p>「Client First」(Production Ready GraphQL p. 28-29) の節で、クライアントがAPIから欲しいデータを取得する際に大量のドキュメントを読むのを避けることにつながること・そのためには設計の早い段階で「最初のクライアント」と協力することが重要であることが説明されていました。</p> <h3 id="表現豊かなスキーマ">表現豊かなスキーマ</h3> <blockquote><p>Third, make your schema as expressive as possible. The schema should guide clients towards good usage. Documentation should be the icing on the cake.</p></blockquote> <p>(スキーマはクライアントにとって使い方が分かりやすいものになっているべきで、ドキュメントは補足程度になるべきである。)</p> <h3 id="あらゆるユースケースに対応するようなスキーマを作らない">あらゆるユースケースに対応するようなスキーマを作らない</h3> <blockquote><p>Finally, avoid the temptation of a very generic and clever schema. Build specific fields and types that clearly answer client use cases.</p></blockquote> <p>( 最後に、非常に一般的で巧妙なスキーマの誘惑を避けよう。クライアントのユースケースに明確に答える特定のフィールドとタイプを構築しよう。)</p> <h2 id="振り返りスキーマ設計">振り返り(スキーマ設計)</h2> <p>上記の4つについて弊チームでの状況と照らし合わせていこうと思います。</p> <p>1つ目の「デザインファースト」についてですが、ドメインモデリングを行ったのちにバックエンドの実装を行い、コードを元にスキーマを生成しました。(ライブラリは <a href="https://ghostdogpr.github.io/caliban/">Caliban</a> を利用)<br/> ドメイン定義・Query/Mutation定義 -> usecase, repositoryをダミーで実装 -> 具体的な実装 の手順で実装しておりScalaコードでのドメインの定義の段階でスキーマを設計しているのに等しいため、デザインファーストと言えるかと思います。</p> <p>2つ目の「クライアントファースト」についてですが、 クライアントを弊チームで開発している都合上、クライアントのユースケースを考慮しながらドメインモデリングを進めていたためクライアントのユースケースを念頭に置くことは無意識にできていました。<br/> 設計の早い段階で「最初のクライアント」と協力することも弊チームで開発しているクライアントなので自然にそうなっていました。</p> <p>3つ目の「表現豊かなスキーマ」についてですが 挙げられる工夫としては、<br/> Query名に関しては「データ名(s)」の名前にして特定のデータが取得できることを示し、<br/> Mutation名に関しては「動詞 + データ名(s)」としています。<br/> そのため、Mutationに関してはユースケースごとにmutationが実装されており、Queryに関してはデータごとにqueryが実装されています。<br/> また、クライアント側で欲しい値については、バックエンドから得た値を元にクライアントで計算するのではなく、バックエンド側で予め計算するようにしています。<br/> そうすることで、スキーマの変更がなされても、フロントエンド側で誤ったデータの使い方をしないようなスキーマになっています。</p> <p>最後に4つ目の「あらゆるユースケースに対応するようなスキーマを作らない」ですが<br/> 前述したQueryやMutationに関する工夫に加えて<br/> スキーマのフィールド名や型については、バックエンドの実装時にレビューをするので<br/> 不自然なものになりにくくなっています。もし、後から不自然だと感じたものに関してはリファクタリングを行なって修正します。</p> <p>つぎにサーバー実装について振り返っていきます。</p> <h2 id="サーバー実装">サーバー実装</h2> <p>サーバー実装の抑えるべき点としては以下の6つの点が挙げられていました。(引用: Production Ready GraphQL p.103)</p> <h3 id="コードファーストのフレームワークを選択しよう">コードファーストのフレームワークを選択しよう</h3> <blockquote><p>Prefer code-first frameworks with high extendability (metadata, plugin and middlewares, etc)</p></blockquote> <p>(拡張性の高いコードファーストのフレームワーク (メタデータ、プラグイン、ミドルウェアなど) を選択しよう。)</p> <h3 id="GraphQL層は可能な限り薄く保つ">GraphQL層は可能な限り薄く保つ</h3> <blockquote><p>Keep your GraphQL layer as thin as possible, and refactor logic to its own domain layer if not already the case.</p></blockquote> <p>(GraphQL層は可能な限り薄く保ちましょう。そして、すでにそのようになっていない場合はドメイン層にひもづくロジックをリファクタリングしよう。)</p> <p>「Resolver Design」 (Production Ready GraphQL p. 93-94) の節では「GraphQLはAPIインターフェースであり、ドメインやビジネスロジックへのインターフェースでもあるため、GraphQL自身がビジネスロジックを持つべきではない。」と説明されていました。</p> <h3 id="リゾルバをシンプルに">リゾルバをシンプルに</h3> <blockquote><p>Keep resolvers as simple as possible, and don't rely on global mutable state.</p></blockquote> <p>(リゾルバを可能な限りシンプルに保ち、グローバルな変更可能な状態変数に依存しないようにしよう。)</p> <p>具体的な例ではcontextオブジェクトに依存しすぎないこと・contextオブジェクトを不変に保つことが説明されていました。</p> <h3 id="モジュール化を実現しよう">モジュール化を実現しよう</h3> <blockquote><p>Modularize when it starts hurting, you don't need a magical or specific framework. Use your programming language to achieve modularity.</p></blockquote> <p>(痛くなったらモジュール化しよう。魔法のようなあるいは特定のフレームワークは必要ない。プログラミング言語を用いてモジュール化を実現しよう。)</p> <h3 id="統合テストはGraphQLサーバーにとって最高のアプローチ">統合テストはGraphQLサーバーにとって最高のアプローチ</h3> <blockquote><p>Test most of the domain logic at the domain layer. Integration tests are the best "bang for buck" approach for GraphQL servers.</p></blockquote> <p>(ドメインロジックのほとんどをドメインレイヤーでテストしよう。統合テストはGraphQLサーバーにとって最高の「費用対効果」のあるアプローチです。)</p> <h3 id="実行時の条件に基づく小さなスキーマの変種には可視性フィルターを使おう">実行時の条件に基づく小さなスキーマの変種には可視性フィルターを使おう</h3> <blockquote><p>Use visibility filters for small schema variations based on runtime conditions, but don't hesitate to build completely different servers at build time when dealing with wildly different schemas.</p></blockquote> <p>(実行時の条件に基づく小さなスキーマの変種には、可視性フィルター<a href="#f-050b9f5c" id="fn-050b9f5c" name="fn-050b9f5c" title="特定条件下においてスキーマをマスキングするフィルター。特定のクライアントにのみ共有したいスキーマが存在したり、フィーチャーフラグによって新しい機能を公開する際に使うことが推奨される。">*1</a>を使用しよう。しかし、大きく異なるスキーマを扱う場合はビルド時に全く異なるサーバーを構築することをためらわないようにしましょう。)</p> <p>上記の6つについて弊チームでの状況と照らし合わせていこうと思います。</p> <h2 id="振り返りサーバー実装">振り返り(サーバー実装)</h2> <p>1つ目の「コードファーストのフレームワークを選択しよう」についてですが チームで使用している<a href="https://ghostdogpr.github.io/caliban/">Caliban</a>では<a href="https://ghostdogpr.github.io/caliban/docs/middleware.html#wrapper-types">Wrapper</a>というミドルウェアが使えて、クエリの処理に対してさまざまなレベルで追加アクションを実行することができ、この機能をプロジェクトでも利用しています。 GraphQLリクエストやレスポンスのエラーをログに出力したり、クエリの処理に時間がかかった場合、ログにかかった時間を出力するWrapperを自前で実装し利用しています。</p> <p>2つ目の「GraphQL層は可能な限り薄く保つ」についてですが usecase層・domain層がビジネスロジックを持っています。GraphQL層にはAPI用のケースクラスとリゾルバが存在する構成なので薄く保たれていると思います。</p> <p>3つ目の「リゾルバをシンプルに」についてですが リゾルバではusecase層のメソッドを呼び出してデータ取得を行っています。そしてAPI用のケースクラスに変換しています。それ以外の役割は担っていないのでシンプルな作りになっています。 また、contextオブジェクトは不変で利用しており、ほとんど認可の部分でのみ利用しています。</p> <p>4つ目の「モジュール化を実現しよう」についてですが コードを元にスキーマを生成するコードファーストなアプローチをとってスキーマを生成しておりコードとスキーマの対応関係の把握が容易なため、我々のプロジェクトでは気にしなくても良さそうです。</p> <p>5つ目の「統合テストはGraphQLサーバーにとって最高のアプローチ」についてですが ドメインロジックに関してはロジックが複雑なものはテストを用意しています。また、統合テストとして、PlayGroundで実行予定のクエリを実行しテストしたり、クライアントとセットで結合テストしています。</p> <p>最後に6つ目の「実行時の条件に基づく小さなスキーマの変種には可視性フィルターを使おう」についてですがユーザの認可に応じてアクセスできるデータやアプリの機能に制限を施しており、新機能についてはGraphQLではない部分でフィーチャーフラグによる制御を行っているため、現状はマスキングしていません。</p> <h2 id="まとめ">まとめ</h2> <p>以上が スキーマ定義・サーバー実装についての振り返りになります。書籍の一部分の内容との比較にはなりますがプロダクトのスキーマ定義・サーバー設計についての理解を深めることができました。</p> <div class="footnote"> <p class="footnote"><a href="#fn-050b9f5c" id="f-050b9f5c" name="f-050b9f5c" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">特定条件下においてスキーマをマスキングするフィルター。特定のクライアントにのみ共有したいスキーマが存在したり、フィーチャーフラグによって新しい機能を公開する際に使うことが推奨される。</span></p> </div> h_yamashita FLINTERSに入社して5年経ったお話 hatenablog://entry/6801883189068179271 2023-12-23T12:00:00+09:00 2023-12-23T15:27:47+09:00 FLINTERSに入社して5年経ったお話 <p>FLINTERSでデータエンジニアをしているやまがみです🐼💫<br/> この記事は10周年記念として133日間ブログを書き続けるチャレンジの106日目の記事となります!</p> <p>さて、タイトルの通り、気づけば新卒5年目、FLINTERSに入社して5年目になりました。<br/> 最近入社して5年目という話を友達とすると意外と長くいるねと言われることが増えてきました!<br/> 続けられている理由をよく聞かれるので、個人的に感じているFLINTERSの素敵ポイントを紹介したいと思います!✨<br/> ちなみに当たり前ですが、転職=悪いことだとは全く思ってないです!!</p> <p>まず、自分の中で働くにあたって大事なことが3つあるのですが、</p> <ol> <li>働きやすさ</li> <li>人間関係</li> <li>業務内容</li> </ol> <p>1 > 2 > 3の順番で重要だと考えています!(あくまで個人の考えです!!!)</p> <p>仕事以外の時間が一番大事だし、<br/> 人間関係で悩みたくないし、<br/> 楽しくない仕事は続けられない、、、そんな方も多いのではないでしょうか!</p> <h2 id="1-働きやすさ">1: 働きやすさ</h2> <h3 id="休みの多さと取りやすさ">休みの多さと取りやすさ</h3> <ul> <li>有給休暇に加えて夏休みが10日あります。また、単発の有給は(チームにもよりますが、私が所属するチームでは)前日までの連絡で取得できます。</li> <li>FLINTERSの夏休みについてはこちら↓のブログをご覧ください✨</li> <li><a href="https://www.wantedly.com/companies/flinters/post_articles/543030">https://www.wantedly.com/companies/flinters/post_articles/543030</a></li> </ul> <h3 id="在宅勤務">在宅勤務</h3> <ul> <li>2020/2から在宅勤務が可能になったのですが、2023/12時点で出社を強制されることはコロナ禍後1度もないです!</li> <li>ただ、任意参加のオフライン勉強会のような社内イベントはよく開催されていますし、気分転換のためにオフィスで仕事することももちろん可能です。</li> <li>ほとんどMTGがない作業日はお化粧しなくて良いのが個人的に嬉しいポイントです☝🏻</li> </ul> <h3 id="裁量労働">裁量労働</h3> <ul> <li>会社として決まった労働時間のコアタイムがないので、柔軟な働き方ができます</li> <li>チームや業務にもよりますが、午前中にMTGがなければゆっくり起床して午後から業務開始することも可能です</li> <li>一時期軽く体調を崩して毎週通院していた時期があったのですが、午前中不在でも有給が減らないのは嬉しいポイントです☝🏻</li> <li>(前チームではお昼スタートのメンバーが多かったので、平日午前中に美容クリニックなどによくある平日割引を利用できたのが良かったです😽)</li> <li>(最近は早く開始して早く終わる派になりました😽)</li> </ul> <h2 id="2-近すぎず遠すぎない距離感">2: 近すぎず遠すぎない距離感</h2> <ul> <li>働き方のところにも書きましたが、出社が強制されていないのであったことのないチームメンバーがいたりします</li> <li>歓迎会や送別会はオフラインで開催されることが多いですが、もちろん強制参加の雰囲気はないです(正直飲み会って得意不得意分かれますよね...)</li> <li>かといって社内の人たちと全く関わりがないわけではなく、先輩や同僚、グループ会社の同期とランチにいくために出社したり、オンラインで雑談したりもします🎶</li> </ul> <h2 id="3-やりたいことやりたくないことを尊重してもらえる">3: やりたいこと、やりたくないことを尊重してもらえる</h2> <ul> <li>マネージャーと毎月1on1があるのですが、プライベートのことや仕事のことキャリアのことなどなどいろんなことを話す機会があります</li> <li>特に、異動については詳細にやりたいことやりたくないことを聞いた上で配属を考えてくれます!</li> <li>異動後も、前マネージャーが嫌なことないか、元気に生きてるか定期的に連絡くれて気にしてくれるのが嬉しいです(お世話になったマネージャー全員あったかい🥲)</li> </ul> <h1 id="最後に">最後に</h1> <p>いかがだったでしょうか?<br/> 働き方について社外のお友達に羨ましがられることが多いので、存分に自慢させていただきました!w どなたかの参考になれば幸いです🌟<br/> あわよくばまだまだ少ないエンジニア女子が増えることを願っています🙏🏻💫</p> nokonoko2318 内製の業務支援ツール開発という道 hatenablog://entry/6801883189067881441 2023-12-21T12:00:00+09:00 2023-12-21T12:00:00+09:00 内製の業務支援ツール開発という道 <p>こんにちは。グループ会社から出向し株式会社FLINTERSでPMをしている kida です。</p> <p>この記事はFLINTERS設立10周年ブログリレーの 104 日目の記事です。</p> <p>FLINTERSに出向以来、データテクノロジーを活用したマーケティング業務支援プロダクトの開発をしています。 大規模なグループ会社の実業務で即活用されるツールをスモールチームで開発主導でつくれるような環境が、とても面白く心地よく、ありがたいです。</p> <h3 id="内製の独自ツール開発の価値">内製の独自ツール開発の価値</h3> <p>世の中に便利な市販ツールがある中で内製で業務支援ツールを開発する場合、意識したいことがあります。</p> <ul> <li>対象の業務はそもそも必要なのか?</li> <li>市販ツールがその業務に対応してこないか?</li> </ul> <p>※ 内製ツールと言っても、一人が数日でサッと作るような規模感でなく、それなりに人と時間をかけて作るものの想定です。</p> <p>ぐんぐん伸びてる市販ツールがある場合、それは注力事業として多くのリソースが投下されているはずで、同じコースに乗ってガチンコのスピード勝負となるとなかなか厳しそうです。</p> <p>弊社独自のオリジナル業務を対象にしたいです。</p> <p>また、そのオリジナル業務はそもそも必要なのかという話で、</p> <p>業務にツールを合わせるな、ツールに業務を合わせろ、という話もある中で、その業務が本当に会社にとって価値をうんでいるのか (できれば長い目で、少なくとも直近で) は確認しておきたいです。(なんなら、その業務をさらに価値ある形に変えることを目指せればなお良い)</p> <p>それをクリアできれば、内製ツール開発というのは、マネタイズを気にせずに純粋にユーザーファーストを追求できるという、とてもハッピー&amp;エモーショナルな仕事かもしれません。(投資対効果はもちろん気にしないとですが!)</p> <h3 id="内製にもある大変さ">内製にもある大変さ</h3> <p>内製ツールと言えど、何らかの強制力が働かない限りは、作ったけど使われない可能性は多分にあります。ちゃんと「選ばれるツール」を作り、社内GTM戦略も考えないとです。</p> <p>また、社内と言っても、開発チームが対象の業務に詳しいわけでなく、また、ユーザー部門の方が現場業務を全て把握しているわけでもなかったりするので、実務現場の反応を見て学びを得ながらアジャイルに開発できると望ましいです。</p> <p>ただ、ユーザーへのヒアリングやユーザーテストをするにも、忙しい中で工数を割いてもらうにはそれなりのハードルがあります。</p> <p>しかしそこは社内。がっつり協力いただける可能性は社外ユーザーと比べたらだいぶ高く、フィードバックのサイクルも早いはずです。</p> <p>ユーザー候補を見つけて、コミュニケーションしながら、期待をもってもらい協力を頂きながら、業務フィットするプロダクトを目指していきたいです。</p> <h3 id="まとめ">まとめ</h3> <p>内製ツール開発はユーザーファーストを追求できるハッピー&amp;エモーショナルな仕事という話をしましたが、</p> <p>ハッピー&amp;エモーショナルと言えば、先日、「新規オープンしたお店って、綺麗で、気持ちよくて、木工用ボンドの匂いがするなぁ」と、幸せな気持ちになることがありました。</p> <p>今後の人生にも、そんなふうに感情が揺れる瞬間が多々訪れるといいな、と、冬の夜空を見上げながら「ごつ盛り ソース焼きそば」を食べて涙する、そんな年末でした。</p> kida_a 循環するキャリア: FLINTERSへの帰還 hatenablog://entry/6801883189066694125 2023-12-19T12:00:00+09:00 2023-12-21T17:43:38+09:00 循環するキャリア: FLINTERSへの帰還 <p>株式会社FLINTER10周年ブログリレー 102日目です。</p> <h2 id="はじめに">はじめに</h2> <p>2023年11月、やれやれ、またFLINTERSの扉を叩いてみたよ、堀だ。なんか新人みたいにフレッシュな気持ちだけど、実情はちょっと複雑で。</p> <p>なんせ、こんな歩みを経てるからね:</p> <ul> <li>2019年4月、FLINTERSに初めて入社。</li> <li>2022年7月、さよならFLINTERS。大企業に転職。</li> <li>2023年11月、そしてまた、FLINTERSへのカムバック。</li> </ul> <p>これはただの入社記じゃねーよ、復職記だ。</p> <p>「俺ってね、7社経験してるんだ。で、7社目が5社目なんだよね」って言ってみんなをビックリさせるのが密かな楽しみだったりする。</p> <p>でかい企業での経験っていうのは、まあ痛いくらいに俺の力不足を痛感したわけ。でも今、FLINTERSに戻ったら、めっちゃ幸せに仕事してるって感じだね。</p> <p>転職ってのは人それぞれで色んな理由があるよね。俺の話を失敗例の一つとして読んでくれたら嬉しいな。刺激的になりすぎないように、でもリアルに伝えたいんだ。</p> <p>それに、言えることは改善しやすいけど、言いづらい問題ってのはずっとくすぶりがち(<a href="https://qiita.com/piyonakajima/items/ad3c44d1dc377e41d394">ふりかえり手法「象、死んだ魚、嘔吐」</a>の「象」みたいにね)。俺の場合、大企業に残った言いづらい問題がドカンと来ちゃってさ。そんな時、俺みたいな普通のヤツはどうやって折り合いつけるんだ?エモくなるしかねぇじゃん。</p> <p>だから、このエントリ、ちょっとエモすぎるかもしんないけど、そこは勘弁してくれよな。</p> <h2 id="そもそもなんで転職したんだ">そもそもなんで転職したんだ?</h2> <ul> <li>マンネリ感が否めなくてさ。同じことの繰り返しにちょっと疲れてたんだ。</li> <li>ちょうどその時、ある大企業がピカピカして見えてさ、つい飛び込んでみたんだ。</li> </ul> <h2 id="FLINTERSに戻ってきて良かったこと">FLINTERSに戻ってきて良かったこと</h2> <h3 id="どんよりムードはナシ">どんよりムードはナシ</h3> <p>前の職場はなんだかどんよりしてて、「みんな何でこんなビビりながら仕事してるんだ?」と思ってたよ。</p> <p>でもFLINTERSは違う。何か問題があっても、きっちりと解決しようって文化が根付いてるって信じてるからさ。</p> <h3 id="ちょうどいいサイズ感">ちょうどいいサイズ感</h3> <p>結局ね、一人ひとりにフィットするサイズっていうのがあるんだと思ってる。前の職場は、俺にとってはちょっと大きすぎたんだよ。みんなで同じボートを漕いでる感じがしなかったんだ。</p> <h3 id="えらいってのは仕事の中身で決まるべき">「えらい」ってのは仕事の中身で決まるべき</h3> <p>このエンジニアリングの世界ではな、ちゃんとしたやり方を選ぶヤツが、本当に「えらい」んだよ。そういうのがFLINTERSにはあって、俺にはそれがすげーよくってさ。</p> <p>あと、新しいやり方は古いやり方を更新していくもんで、エンジニアリングってのはそれがすげー楽しいんだわ。今だとAIが流行りかな?</p> <h2 id="おわりに">おわりに</h2> <p>まあ、言ってしまえば人生ってのは結局、延々と続く旅路のようなもんだ。FLINTERSへの戻り道は、俺にとって居心地のいい場所になりそうだし、ちょいと皮肉屋な俺でも、ここならガツガツ頑張れるんじゃないかって思わせてくれるんだ。</p> <p>こうして思いがけず戻ってきたこの場所で、新しくも懐かしい空気を吸いながら、俺はまたエンジニアとして、もっと上を目指そうと胸に誓ったんだ。終わることのない好奇心と、ときめきを忘れずにね。</p> <p>というわけで、この復職のページをめくりつつ、今宵は買っておいた特別なウイスキーでもあけてみるかな。キャリアの曲がり角を祝ってさ。それじゃ、また会社でな。</p> ho1yhero チームでアジャイル開発を学び直した話 hatenablog://entry/6801883189066007370 2023-12-17T12:00:00+09:00 2023-12-17T12:00:17+09:00 開発チームでアジャイル方法の見積もりについての効果的な手法について勉強をし直した。これにより、見積もりに費やす時間を減らす選択肢をチームが取れるようになった。 <p>こんにちは。FLINTERSでデータエンジニアをしている藤原です。 株式会社FLINTER10周年記念ブログリレー、なんと今回で100日目でございます。おめでたいですね。</p> <p>先日、開発チームでアジャイル開発における見積もりについて勉強会を実施しました。 今回はその概要と感想について書こうと思います。</p> <h3 id="背景">背景</h3> <p>私たちの開発チームでは開発手法としてスクラムを導入していて、こんな状態に陥っていました。</p> <h4 id="見積もりにものすごく時間がかかる">見積もりにものすごく時間がかかる</h4> <p>見積もりには、ストーリーポイントと呼ばれる規模を表す数値を利用しているのですが、1つの作業を見積もるのに20分も30分もかかってしまう状態になっていました。 それにより1週間の作業計画を行うイベントも半日近くかかってしまい、開発時間を圧迫するという悪循環に陥っていました。</p> <p>専任のスクラムマスターもいない状態で、なんとかせねばと思い勉強会の実施に至りました。</p> <h3 id="効果があった学び">効果があった学び</h3> <p>”<strong>収穫逓減の法則</strong>:一定の土地から得られる収穫は、投下された労働量、資本量に比例してある程度まで増加するが、ある限度を超えると次第に減っていくという法則” <a href="https://kotobank.jp/word/%E5%8F%8E%E7%A9%AB%E9%80%93%E6%B8%9B%E3%81%AE%E6%B3%95%E5%89%87-76789#:~:text=%E4%B8%80%E5%AE%9A%E3%81%AE%E5%9C%9F%E5%9C%B0%E3%81%8B%E3%82%89%E5%BE%97,%E5%8F%8E%E7%9B%8A%E9%80%93%E6%B8%9B%E3%81%AE%E6%B3%95%E5%89%87%E3%80%82">&#x53CE;&#x7A6B;&#x9013;&#x6E1B;&#x306E;&#x6CD5;&#x5247;(&#x3057;&#x3085;&#x3046;&#x304B;&#x304F;&#x3066;&#x3044;&#x3052;&#x3093;&#x306E;&#x307B;&#x3046;&#x305D;&#x304F;)&#x3068;&#x306F;&#xFF1F; &#x610F;&#x5473;&#x3084;&#x4F7F;&#x3044;&#x65B9; - &#x30B3;&#x30C8;&#x30D0;&#x30F3;&#x30AF;</a></p> <p>”時間と労力を費やして見積もりを出したからといって、必ずしも見積もりが正確になるとは限らない。見積もりに費やすべき労力は、見積もりの目的に応じて決めなくてはならない。”(MIKE COHN、「アジャイルな見積りと計画作り 価値あるソフトウェアを育てる概念と技法」、マイナビ出版、2009、84ページ)</p> <p>私たちのチームはなるべく考慮漏れの少ない精緻な見積もりをとにかく行わなければならないと必死になっていました。これは、アジャイル開発のような変化に適応しようとする開発手法とは相反するものであると再認識できました。</p> <p>他にも細かい見積もり手法についての学びがたくさんあったのですが、引用箇所が多くなりすぎるので割愛します。</p> <h3 id="終わりに">終わりに</h3> <p>勉強会を実施した結果、開発チームでのストーリーポイントの見積もりは、正確性は下がったかもしれませんが<u>早くはなりました</u>。他にも勉強会を開催して良かった点として、正しいアジャイル開発の手法を知れば知るほど「今の私たちの開発フローにアジャイル開発は向いているのだろうか」という意見が出るようになった点です。勉強会前では、そのような議論も出てこなかったので今のフローを見つめ直す良いきっかけにはなったかと思います。</p> y_fjwr VPSのLinuxサーバーを使って、低コストで安定したARK: Survival Ascendedの専用サーバーを建てよう! hatenablog://entry/6801883189065966375 2023-12-16T12:00:00+09:00 2023-12-16T12:00:33+09:00 VPSのLinux サーバーを使って低コストでARK: SAの専用サーバーを立ち上げる手順について解説します <h2 id="はじめに">はじめに</h2> <p>こんにちは。私はWebフロントエンド周りを触っていることの多い中途採用のエンジニアです。 2024年1月にFLINTERSが祝10周年!全社員でブログリレーをやらないか?と誘われて久しぶりにブログを書いております。私は99日目の担当です。</p> <p>私事ですが、最近は11月初旬に発売した<a href="https://store.steampowered.com/app/2399830/ARK_Survival_Ascended/?l=japanese">ARK: Survival Ascended</a>(以下、ARK: SA)という恐竜を育てたり建築したりサバイバルゲームを楽しんでいます。</p> <p>ARK: SAは現時点で開発途中の早期アクセスゲームであり、公式公認のレンタルゲームサーバーでさえまだ安定しているとは言えない状況です。 しかし自分でサーバーを建ててしまえば、努力次第で安定したサーバーを作ることができます。</p> <p>そこで本記事では、少々イレギュラーなサーバー環境となる、VPSのLinux サーバーを使って低コストで安定したARK: SAの専用サーバーを建てる手順について解説します。</p> <h2 id="代替手段との比較">代替手段との比較</h2> <ul> <li>Nitradoでゲームサーバーを借りる <ul> <li>$24.99/Month</li> <li>評判は𝕏などのSNSで確認してください</li> </ul> </li> <li>Windowsで専用サーバーを建てる <ul> <li>自宅サーバーだと電気代が高い(推奨スペックを元に雑にChatGPT 3.5に計算をさせて4,320円/Month)</li> <li>VPSだとWindows Serverのライセンスが高い(ContaboでUbuntuより+$17.99/Month)</li> <li>安定感はある</li> </ul> </li> <li>AWS、Google Cloudなどで建てる <ul> <li>一般的にVPSの方がかなり安いです</li> </ul> </li> </ul> <h2 id="必要な費用">必要な費用</h2> <ol> <li>Contabo CLOUD VPS M: $12.50/Month</li> <li>Contabo Region Japan: $6.00/Month</li> <li>Contabo One-Time Setup Fee: $8.50<br/> ※VPSの契約期間に応じて割引あります</li> </ol> <p>合計: $27.00($18.50/Month)</p> <h2 id="事前知識">事前知識</h2> <p>ARK: SA Dedicated ServerはSteamからダウンロードすることになるので、事前に<a href="https://store.steampowered.com/eula/2430930_eula_0">利用規約</a>に目を通してください。 商業目的で使用することができない点に注目ですね!</p> <p>また、本来ARK SA Dedicated ServerはWindows Serverでの稼働を期待していますが、ここではValveの開発したProtonを使ってLinux上で稼働させます。 Protonは一言で言えばLinux上でWindows用のゲームを実行するためのソフトウェアです。</p> <p>最後に、Steamのインストールパスがなぜかぶれがちです。 もしパスに関するエラーが出たら、正しいパスを確認後スクリプトを修正し、いったんまっさらにした状態から再インストールすることをオススメします。</p> <p>ここまで確認できたら次の項から実際の手順になります。</p> <h2 id="ContaboでVPSを購入する">ContaboでVPSを購入する</h2> <p>まず最初に、<a href="https://contabo.com/en/">Contaboの公式ウェブサイト</a>にアクセスし、VPSを購入します。 ARK公式から専用サーバーの推奨スペックは発表されていないように見えますが、私は <strong>CLOUD VPS M $12.50</strong> を選択しました。</p> <p>Google検索で出てくる有識者の情報と比較しても問題はなさそうですね。</p> <table> <thead> <tr> <th> </th> <th style="text-align:left;"> 有識者の情報 </th> <th style="text-align:left;"> CLOUD VPS M $12.50 </th> </tr> </thead> <tbody> <tr> <td> Cores </td> <td style="text-align:left;"> 4 CPU </td> <td style="text-align:left;"> 8 vCPU </td> </tr> <tr> <td> RAM </td> <td style="text-align:left;"> 16GB </td> <td style="text-align:left;"> 16GB </td> </tr> <tr> <td> SSD </td> <td style="text-align:left;"> 200GB </td> <td style="text-align:left;"> 400GB </td> </tr> <tr> <td> Bandwidth </td> <td style="text-align:left;"> 400 Mbps </td> <td style="text-align:left;"> 400 Mbps </td> </tr> <tr> <td> OS </td> <td style="text-align:left;"> Windows Server 2019 or newer version </td> <td style="text-align:left;"> Ubuntu 20.04 </td> </tr> </tbody> </table> <p>注意点として、必要スペックはサーバーに接続するプレイヤーの人数に大きく影響を受けます。 今回紹介する環境では、プレイヤーの同時接続数10人までは快適に遊べることを確認しています。</p> <p>以下、私の <strong>Configure your VPS</strong> です。</p> <table> <thead> <tr> <th> 項目 </th> <th style="text-align:left;"> Cloud VPS M </th> </tr> </thead> <tbody> <tr> <td> 1. Select your term length </td> <td style="text-align:left;"> 1 Month </td> </tr> <tr> <td> 2. Region </td> <td style="text-align:left;"> Asia (Japan) </td> </tr> <tr> <td> 3. Strage Type </td> <td style="text-align:left;"> 400 GB SSD </td> </tr> <tr> <td> 4. Image </td> <td style="text-align:left;"> Ubuntu 20.04 </td> </tr> <tr> <td> 5. Login &amp; password for your server </td> <td style="text-align:left;"> 任意のパスワード(記憶する) </td> </tr> <tr> <td> 6. Object Storage </td> <td style="text-align:left;"> None </td> </tr> <tr> <td> 7. Networking </td> <td style="text-align:left;"> Private Networking: No Private Networking<br />Bandwidth: 10 TB Out + Unlimited In<br />IPv4: 1 IP Address </td> </tr> <tr> <td> 8. Add-Ons </td> <td style="text-align:left;"> Backup Space: None<br />Server Management: Unmanaged<br />Monitoring: None<br />SSL: None </td> </tr> <tr> <td> Server Quantity </td> <td style="text-align:left;"> 1 </td> </tr> </tbody> </table> <p>注文完了後、1~3日後にサーバーにアクセスできるようになるとメールで案内がきますが、私の場合はさらにその後10分後にサーバー情報が送られてきました。早い!</p> <h2 id="ARK-SA-Dedicated-Serverをインストールする">ARK: SA Dedicated Serverをインストールする</h2> <p>メールで送られてきたサーバー情報を元にsshでVPSに接続します。Windows 10からはcmdにOpenSSHが標準搭載されるようになったのは嬉しいですね!なお、今回はLinuxサーバーにおけるSSHの設定やFirewallの設定は割愛させていただきます。</p> <pre class="code" data-lang="" data-unlink>ssh -lroot -p22 &lt;IP address&gt; root@&lt;IP address&gt;&#39;s password: as chosen by you during order process</pre> <p>gitをインストールして、専用サーバーをインストールするスクリプトをダウンロードして実行します。</p> <pre class="code" data-lang="" data-unlink>apt update # vimはお好みのエディタで apt install git vim mkdir git &amp;&amp; cd $_ git clone https://github.com/k-sukehiro/ARKSurvivalAscended-Linux.git cd ARKSurvivalAscended-Linux/ chmod 744 server-install-ubuntu20.sh sudo ./server-install-ubuntu20.sh</pre> <p>途中、 <strong>STEAM LICENSE AGREEMENT</strong> が表示されるのでしっかり読んでからAGREEしましょう!</p> <p>スクリプトの実行が終わったらプロセスを確認してみてください。 きっとARK: SA Dedicated Serverが起動しています。 それから数分後、ARKクライアントの非公式サーバーセッション一覧の中にあなたのサーバーが見つかるはずです。</p> <h2 id="ARK-SA-Dedicated-Serverを管理する">ARK: SA Dedicated Serverを管理する</h2> <ul> <li>サーバーをアップデートする</li> </ul> <pre class="code" data-lang="" data-unlink>sudo -u steam /usr/games/steamcmd +login anonymous +app_update 2430930 validate +quit</pre> <ul> <li>起動する</li> </ul> <pre class="code" data-lang="" data-unlink>sudo systemctl start ark-island</pre> <ul> <li>再起動する</li> </ul> <pre class="code" data-lang="" data-unlink>sudo systemctl restart ark-island</pre> <ul> <li>停止する</li> </ul> <pre class="code" data-lang="" data-unlink>sudo systemctl stop ark-island</pre> <ul> <li>サービスを変更する</li> </ul> <pre class="code" data-lang="" data-unlink>sudo vim /etc/systemd/system/ark-island.service sudo systemctl daemon-reload</pre> <ul> <li>サーバー設定を変更する</li> </ul> <pre class="code" data-lang="" data-unlink>vim /home/steam/island-Game.ini vim /home/steam/island-GameUserSettings.ini</pre> <ul> <li>サーバーの起動ログを確認する</li> </ul> <pre class="code" data-lang="" data-unlink>tail -F /home/steam/island-ShooterGame.log</pre> <h2 id="補足">補足</h2> <p>一日一回Linuxサーバーを再起動するとリソースが良い感じです! 再起動後、ARK: SA Dedicated Serverは自動で起動します。</p> <pre class="code" data-lang="" data-unlink>sudo vim /etc/crontab # 朝4時に再起動する(サーバーのTime zoneはEST-0500) 0 14 * * * root /sbin/shutdown -r now</pre> <h2 id="まとめ">まとめ</h2> <p>低コストでARK: SAの専用サーバーを建てるための手順を解説しました。</p> <p>GUIによって直感的にサーバーを管理でき、価格もお手頃なレンタルゲームサーバーは魅力的です。 しかしエンジニア視点ではSSH、CLIで管理できた方が問題が起こったときに自力で調査しやすく、レンタルサーバーのサポートとやりとりをせずに復旧できるのでこの不安定期には強みだと思います。 場合によってはサーバーバージョンアップをしない選択肢や、プログラムによる復旧の自動化など取れうる手段は無限大です。</p> <p>この機会に仲間内用に一台いかがでしょうか!</p> parallelto プロジェクトマネージャーとして大事にしている5つの考え hatenablog://entry/6801883189066276190 2023-12-14T12:00:00+09:00 2023-12-14T12:00:00+09:00 プロジェクトマネージャーとして働く上で私が大事にしている考えについて紹介します。 <p>こんにちは。株式会社FLINTER10周年ブログリレー 97日目の記事を担当します、菅です。</p> <p>今回はFLINTERSでプロジェクトマネージャー(以下PM)として働く上で私が大事にしている考えについて紹介します。</p> <p>このテーマを選んだのは、PMという役割は人によって働き方が千差万別なので、自分の考えを整理し直すことで自分がどういう観点を重要視しているのか整理したいと思ったのがきっかけです。</p> <h3 id="私のPMとしての役割">私のPMとしての役割</h3> <p>現在は特定のプロジェクトに属してPM業務を行うのではなく、プロジェクトを横断する施策の立案・実行、オフショア拠点のマネジメントや顧客の営業窓口としてリソース調達などがメインの業務になっています。世間一般でいうPMOに近いと思います。</p> <h3 id="PMとして大事にしている考え">PMとして大事にしている考え</h3> <p>さっそく本題にいきます。以下が私が大事にしている考えです。</p> <ul> <li>介在価値を明確にする</li> <li>とにかくスピード</li> <li>課題を構造化して考える</li> <li>意思を明確にする</li> <li>時間がかかるが重要な事に取り掛かる</li> </ul> <p>それぞれ説明していきます。</p> <h3 id="介在価値を明確にする">介在価値を明確にする</h3> <p>PMは顧客と開発者のコミュニケーションのハブになることが多いです。顧客からの要望を開発者に伝えたり、開発側からの提案を顧客に伝えたりといったようなことです。</p> <p>この際に重要になるのが、PMとしてコミュニケーションの間に入ることでどういった介在価値を提供するかです。</p> <p>ただ両者の意見を間に入って情報を伝えるだけでは介在価値はゼロです。むろしコミュニケーションパスが増えることでマイナスになることもあります。</p> <p>以下はあくまで一例ですが、PMとして介在することで以下のような介在価値を付けることを心掛けています。</p> <ul> <li>情報の整理</li> <li>新たな課題の発見</li> <li>異なる観点の解決策の提案</li> </ul> <h3 id="とにかくスピード">とにかくスピード</h3> <p>この考えは楽天グループの行動規範「<a href="https://corp.rakuten.co.jp/about/philosophy/principle/">スピード!!スピード!!スピード!</a>!」からインスパイアされたものです。</p> <p>ビジネスにおいてスピードというのは明確に競争優位になり、これはPMの行動でも同じです。</p> <p>他の人が1週間かかるものを1日でやり遂げることが出来れば、それだけで差別化出来ます(無理して働けということではないです。。)。</p> <p>PMが行うタスクは開発に落とし込んでリリースしたり、顧客に伝えるなどして初めて価値が生まれます。タスクというボールをスピード感をもって打ち返し価値に還元する必要があります。</p> <h3 id="課題を構造化して考える">課題を構造化して考える</h3> <p>いわゆるロジカルシンキングですね。</p> <p>よく開発計画を考えていると何を開発するかといった手段が議論の中心になりすぎてしまい、元々解決したかった課題や、この開発を行うとどう課題解決につながるのか迷子になることがあります。</p> <p>そういった際にあらかじめ課題から解決策を構造化しておくことで、当初解決したかった課題と解決策のつながりを明確にすることが出来ます。</p> <h3 id="意思を明確にする">意思を明確にする</h3> <p>私個人はプライベートでは意思を明確にするタイプではないですが、仕事ではそうはいきません。</p> <p>開発プロジェクトではどの課題にアプローチするのか、スケジュールを優先するのか、いくらコストをかけるのかといったように意思決定の連続です。</p> <p>その際にPMとしてどの選択肢を取るのか意思が明確になっていないとプロジェクト全体として意思決定が出来なくなります。</p> <p>また一度明確にした意思を簡単に変えないこと、変えるとしても理由を明確にすることも重要だと思います。</p> <h3 id="時間がかかるが重要な事に取り掛かる">時間がかかるが重要な事に取り掛かる</h3> <p>最後の考えはPMとしてだけでなく社会人として大事にしている考えです。</p> <p>プロジェクトマネジメントで言えば組織文化の構築だったり技術的負債の解消、個人で言えば語学や資格といったスキルの習得が該当します。</p> <p>こういったことへの取り組みは周囲も後回しにしてしまうことが多いと思いますが、積極的に取り組むことで未来への投資につながります。</p> <p>私自身も敬遠しがちですが、各タスクを時間×成果の4象限に分類して時間がかかるが得られる成果も大きいことに意識的に取り組むようにしています。</p> <h3 id="最後に">最後に</h3> <p>いかがだったでしょうか。</p> <p>今回はPMとして私個人が重要だと思うマインドにフォーカスして整理してみました。</p> <p>PMの考え方には普遍的な正確はなく、常に考えもアップデートする必要があります。</p> <p>少しでも皆さんの日々の業務の参考になれば幸いです。</p> h_kan55 新卒1年目エンジニア、インフラ知らんのにISUCONに出場する hatenablog://entry/6801883189065083704 2023-12-12T12:00:00+09:00 2023-12-12T12:00:39+09:00 新卒1年目エンジニアが第13回ISUCONに出場した体験談。 <p>こんにちは、株式会社FLINTERSのBN(新卒)重永です。</p> <p><strong>現在、FLINTERSが今年で10周年を迎えるということで、その記念に皆んなでブログリレーをしようという試み中です。</strong></p> <p>僕はそんなブログリレーの95日目を担当します。</p> <p>実は前に既に1本書いているので、2本目のブログになりますね。</p> <p><strong>そんな2本目のブログ、執筆するきっかけになったのは、僕が(第13回)ISUCONというWebパフォーマンスチューニングの大会に出場したからです。</strong></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fisucon.net%2Farchives%2F57566481.html" title="ISUCON13開催決定!今年は本選のみ開催!&参加チームとメンバーリスト #isucon : ISUCON公式Blog" 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://isucon.net/archives/57566481.html">isucon.net</a></cite></p> <p>他にも出場された方はいて、何名かはブログを執筆して、公開もされていると思うので、ぜひ探して見てみてください。</p> <p><strong>僕のブログでは、「新卒エンジニアでインフラとか全然知らんけど、勢いでISUCONに出場してみた結果こうでした!」という話をしていきます。</strong></p> <p>同じく新人の方には参考になる内容かと思うので、ぜひ最後まで見ていってください。</p> <p>新人じゃない方も、「新人ってこういうこと考えてるんだなぁ…」的な参考になるかも知れません。</p> <ul class="table-of-contents"> <li><a href="#なぜISUCONに出場した">なぜISUCONに出場した?</a></li> <li><a href="#ISUCON勉強法">ISUCON勉強法</a></li> <li><a href="#本番の結果は">本番の結果は?</a></li> <li><a href="#ISUCONやってみて">ISUCONやってみて</a></li> </ul> <h3 id="なぜISUCONに出場した">なぜISUCONに出場した?</h3> <p><strong>そもそもなぜ、インフラなんて全く分からないのにISUCONに出場したのかと言えば、単純に勢いとノリです(笑)。</strong></p> <p>社内で実施されていた『ISUCON勉強会』なるものに、先輩から誘われて出席していたら、「重永くんも出る?」という流れになって承諾しました。</p> <p>僕は断らない性格というか、「まあやってみれば何とかなるでしょ。それにちょっと面白いかも」と思ってしまうタイプなので、こういうことがよくあります。</p> <p>結果、良かったとは思っています。</p> <p><strong>用語を知ってる程度のインフラ知識だったのが、ISUCON出場にあたっての勉強でレベルが底上げされたからです。</strong></p> <p>こういう大会にでも出ないと勉強しないこと、体験できないことって、やっぱりありますよね。</p> <h3 id="ISUCON勉強法">ISUCON勉強法</h3> <p>僕のISUCON出場へ向けた勉強は、まず目的が『一緒に出る先輩の足を引っ張らない、且つちょっと役に立ちたい』でした。</p> <p>『足を引っ張らない』に関しては、とりあえず公式が出している書籍があるので、それをじっくりコトコト見ていきました。</p> <p><strong>それがこちらですね↓</strong><br> <a href="https://amzn.asia/d/hSiSux5">達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践</a></p> <p>正直言うと、この書籍を本気で読んだことで、まるで既に優勝したかのように、僕は満足していました。</p> <p>理由は、先にも述べた通り、『用語を知ってる程度のインフラ知識が底上げされたから』です。</p> <p><strong>先輩とも話しましたが、ISUCONとか関係なく、特に新人はエンジニア全員が見ておいた方が良い本だと思います。</strong></p> <p>『ちょっと役に立ちたい』に関しては、GO言語が読めるように、公式ドキュメントとISUCONの過去問のコードは読んでいました(当日はGO言語を選ぼうというチーム内での作戦のため)。</p> <p>インフラ側は一緒に出場した先輩に任せ切る気満々だったので、せめてアプリケーション側でスコアを伸ばせないかと考えたのです。</p> <p><strong>出場にあたってISUCON関連の記事を読んでいましたが、サーバー側とアプリ側で役割分担していたという話はよく聞きました。</strong></p> <p>僕たちのチームも、2人でしたが、当日は先輩がサーバーを分割したりしている間に、僕はGOの実装を読んで「ここ改善できそうじゃない?」と当たりをつけて試していました。</p> <p><strong>もう1つ、これやって良かったなと思うのが、実際にチームで過去問を本番形式でやる予行練習を設けたことです。</strong></p> <p>これは非常に恐縮ながら、僕の方から言い出したことでした。</p> <p>なぜなら当日に『僕が』やらかしそうで不安だったからです(笑)。</p> <p>ただ後日談ですが、先輩が「予行練習をやったおかげで、当日に余計なところでつまずかなくて良かった」と言っていて、ちょっと嬉しかったです。</p> <p>まとめると、僕がやったことは、ISUCON公式の書籍を読み込む、GO言語に触れておく、「予行練習やりましょう!」と図々しくもお願いする、の3つです。</p> <p>「もっとやれたのではないか?」と言われればどうですが、まあ最低限のことはちゃんとやれたのではないかなという感じです。</p> <h3 id="本番の結果は">本番の結果は?</h3> <p>皆さん気になるのはここかと思います。</p> <p>「色々勉強とかしたっぽいけど、それをやって実際はどうだったの?」と。</p> <p><strong>結論から言うと、661チーム中253位でした。</strong></p> <p>何とも微妙ですが、個人的には「初出場で本番の空気感も分からない中、3分の1には入り込めるんだ…」という印象でした。</p> <p>多分、予想外のトラブルが発生しなければ、180位くらいにはなっていたのではないでしょうか(数字はテキトーです)。</p> <p><strong>その予想外のトラブルというのが、DNSの名前解決でベンチマーカーが失敗しまくるというものです。</strong></p> <p>僕はあんまり分からないですが、本当に結構謎いかったらしいです。</p> <p>そのベンチマーカーエラーのせいで、僕がせっかく加えたアプリケーション側の変更も適用できませんでしたし…</p> <p>正確には、1回はデプロイが通ったのですが、シンプルにリクエストエラーが出て、僕の書き換えたコードが間違ってました(笑)。</p> <p>とはいえ、何回かトライしてベンチマーク通るものだと思うので、数回でもデプロイできなかったのは痛かったです。</p> <p><strong>ちなみに、開始初期に1回30位以内まで順位が上がったり、前半4時間はずっと上位だったりしたんですよ。</strong></p> <p>最初は凄く順調に進んでて、「これは予行練習が効いたのでは?」という感じです。</p> <p>まあ、100%先輩の手柄でしたが…(笑)。</p> <h3 id="ISUCONやってみて">ISUCONやってみて</h3> <p>何度も言いますが、インフラなんて全然分からなかった僕が、書籍を通して、準備を通して、本番を通して、知識の底上げができたことが良かったです。</p> <p>ISUCONに一緒に出場した方は、普段の業務じゃ関わらない方なので、ISUCONによってできた繋がりがあったとも言えます。</p> <p><strong>こういうエンジニアの大会というのも初めて触れましたし、総じて楽しかったなと思います。</strong></p> <p>ということで、僕の第13回ISUCONの振り返りはここまでにします。</p> <p>次回の10周年記念ブログもお楽しみに!</p> r_shigenaga 【Snowflake】SnowPro Core 合格体験記 hatenablog://entry/6801883189064679484 2023-12-10T12:00:00+09:00 2023-12-11T11:17:45+09:00 Snowflake 認定試験 Snowpro Core の合格体験記。試験内容、勉強方法、試験を受ける流れを紹介している。 <p>こんにちは。 株式会社FLINTER10周年記念 133日間ブログリレーの93日目を担当します、出口です。</p> <p>先日、Snowflakeの認定資格である「SnowPro Core」を取得しました。 <br/> ここでは、Snowpro Coreの試験内容やまた合格するまでにやったことを書いていこうと思います。</p> <h3 id="試験を受けた背景">試験を受けた背景</h3> <p>私の所属するデータチームではデータ基盤としてSnowflakeを利用しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.flinters.co.jp%2Fentry%2F2023%2F09%2F23%2F120000" title=" TreasureData -&gt; Snowflake へのデータ基盤移行のお話 - FLINTERS Engineer&#39;s 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://blog.flinters.co.jp/entry/2023/09/23/120000">blog.flinters.co.jp</a></cite></p> <p>業務でSnowflakeに触れる時間が多くなっていき、その中でもっとSnowflakeを理解したいという気持ちが高まりました。 <br/> そのため、Snowflakeの認定試験について勉強することで知識が増えるだろうと思い、受験しました。</p> <h3 id="Snowpro-Coreとは">Snowpro Coreとは?</h3> <p>Snowflakeの認定資格には「SnowPro Core」とその上級資格にあたる「SnowPro Advanced」があります。 <br/> 「SnowPro Advanced」にはその中に3種類の資格がありますが、「SnowPro Core」は1種類のみです。</p> <p>また弊社には「SnowPro Advanced: Architect」を取得している者もいるので、こちらも良ければご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.flinters.co.jp%2Fentry%2F2023%2F10%2F21%2F120000" title="Snowflakeの認定資格「SnowPro Advanced: Architect」を取得するまでにやったこと - FLINTERS Engineer&#39;s 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://blog.flinters.co.jp/entry/2023/10/21/120000">blog.flinters.co.jp</a></cite></p> <h4 id="試験形式">試験形式</h4> <blockquote><p>問題数: 100 <br/> 制限時間: 115分 <br/> 言語: 英語、日本語 <br/> 登録料: $175 <br/> 合格点: 750点(1000点満点) <br/> 受験方法: オンライン / テストセンター</p></blockquote> <p><a href="https://learn.snowflake.com/en/certifications/snowpro-core-jpn/">https://learn.snowflake.com/en/certifications/snowpro-core-jpn/</a></p> <p>問題は、4〜6択の選択肢からから正しい記述を1つ選択する、または複数選択するという方式でした。 <br/> 複数選択の場合は「2つ選択せよ」のように書かれているので特に迷うことなく回答できるようになっていました。</p> <p>制限時間は115分となっていますが、回答を見直す時間を含めても十分すぎる時間があるので、時間を気にして回答する必要はないと思います。 <br/> 私は60分くらいで回答を提出しました。</p> <p>受験する言語に関して、日本語で受験する場合でも原文の英語の文章を見ることができるので、<strong>一時テーブルと一時テーブル</strong><a href="#f-f1851451" id="fn-f1851451" name="fn-f1851451" title="temporary table and transient tableを翻訳ツールで訳した結果">*1</a>のような文章が出てきた場合でも安心です。私は日本語で受験しましたが変な日本語には遭遇しなかったので、日本語が母語の人は日本語で良いと思います。</p> <h4 id="出題分野">出題分野</h4> <table> <thead> <tr> <th> 分野 </th> <th> 予想される割合 </th> </tr> </thead> <tbody> <tr> <td> Snowflake Cloud Data Platformの機能とアーキテクチャ </td> <td> 25% </td> </tr> <tr> <td> アカウント アクセスとセキュリティ </td> <td> 20% </td> </tr> <tr> <td> パフォーマンスの概念 </td> <td> 15% </td> </tr> <tr> <td> データのロードとアンロード </td> <td> 10% </td> </tr> <tr> <td> データ変換 </td> <td> 20% </td> </tr> <tr> <td> データ保護とデータシェアリング </td> <td> 10% </td> </tr> </tbody> </table> <p><a href="https://learn.snowflake.com/en/certifications/snowpro-core-jpn/">https://learn.snowflake.com/en/certifications/snowpro-core-jpn/</a></p> <h3 id="学習方法">学習方法</h3> <h4 id="問題集を解く">問題集を解く</h4> <p>私はドキュメントを黙々と読む行為がかなり苦手なので、勉強する最初の段階から問題集を解きました。 <br/> そこで間違えたりよく分からなかったところについては公式ドキュメントを読んで理解する、という方法で勉強していきました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Fsnowflake-snowpro-core-certification-exam-practice-sets%2F" title="[COF-C02] Snowflake SnowPro Core Certification Practice Sets" 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.udemy.com/course/snowflake-snowpro-core-certification-exam-practice-sets/">www.udemy.com</a></cite></p> <p>問題集はこちらを使いました。 <br/> この問題集を2周(合計6個のテストのうち後半の3個は前半の3個の問題を分野別に分けたものなので実質的には4周)して、問題集の問題は完璧に答えられるような状態まで勉強しました。 <br/> 実際の試験の問題でも、この問題集の類似問題がよく出てきて5割は即答できました。 <br/> ただし、問題集の問題は300問なので、完全に問題集で全ての知識を網羅することはできません。そのため、問題に出てきたところだけをドキュメントで調べるだけでなく、その周辺知識も調べる必要があります。私はこの部分がややおろそかになっていたので、受験中は合格できるか内心ヒヤヒヤしていました。</p> <h3 id="試験当日">試験当日</h3> <p>試験はオンラインでも受けられますが、私はテストセンターで受験しました。 <br/> 筆記用具は特に必要ありません。メモ用のものはテストセンターで用意されてありました。防音ヘッドホンもあるので物音にとらわれず試験に集中できます。 <br/> 注意すべきは本人確認書類が2種類必要で、そのうち1つは運転免許証やマイナンバーカードなどの写真付きのものが必要です。忘れないように携帯しましょう。</p> <p>回答を提出して簡単なアンケートに答えた後、すぐ試験結果が表示されます。 <br/> 合格の文字が見えたので、叫びたい声を抑えて内心でガッツポーズしました。</p> <p>その後、受付で認定スコアレポートがもらえるのですが、あまりにも点数がギリギリだったので肝を冷やしました🥶 <br/> もう少し幅広くドキュメントを読めばよかったかなぁと思いました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/s_deguchi/20231206/20231206233047.png" width="725" height="205" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="終わりに">終わりに</h3> <p>ここまでお読みいただきありがとうございました。この記事が Snowpro Core 受験の参考になれば幸いです。</p> <div class="footnote"> <p class="footnote"><a href="#fn-f1851451" id="f-f1851451" name="f-f1851451" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">temporary table and transient tableを翻訳ツールで訳した結果</span></p> </div> s_deguchi 论我在日本找工作的心路历程以及现在的工作生活 hatenablog://entry/6801883189064255397 2023-12-09T12:00:00+09:00 2023-12-09T12:00:04+09:00 写在前面 大家好,我是在FLINTERS工作的张。 首先,在这里我想要恭喜FLINTERS成立10周年,希望FLINTERS将来事业蒸蒸日上,兴旺不断,鹏程似锦。为了庆祝这一让人开心的事情,公司決定举行博客接力。而我则非常荣幸地作为第92位发表者,发表下面的文章。这次我非常感谢公司能给我用母语写博客的机会。我用中文写这篇文章呢,一是希望通过这篇文章能有更多的国人也能对FLINTERS产生兴趣,二是希望FLINTERS在将来能够越来越朝向国际化发展。 其实关于这次文章要写什么我也思考了很久,最终我想要聊一聊硕士毕业后找工作、以及工作后我生活的变化和一些感受。 关于我为什么选择来日本 想必这个问题… <h4 id="写在前面">写在前面</h4> <p>  大家好,我是在FLINTERS工作的张。   首先,在这里我想要恭喜FLINTERS成立10周年,希望FLINTERS将来事业蒸蒸日上,兴旺不断,鹏程似锦。为了庆祝这一让人开心的事情,公司決定举行博客接力。而我则非常荣幸地作为第92位发表者,发表下面的文章。这次我非常感谢公司能给我用母语写博客的机会。我用中文写这篇文章呢,一是希望通过这篇文章能有更多的国人也能对FLINTERS产生兴趣,二是希望FLINTERS在将来能够越来越朝向国际化发展。 其实关于这次文章要写什么我也思考了很久,最终我想要聊一聊硕士毕业后找工作、以及工作后我生活的变化和一些感受。</p> <h5 id="关于我为什么选择来日本">关于我为什么选择来日本 </h5> <p>  想必这个问题对于在日本上学啊或者已经就职的大家已经是被问过很多次了吧。我来日本后,每到一个陌生的环境,接触人生中新的伙伴的时候,大家都会问到我这个问题。也许很多人也和我一样,是从动漫和日剧对日本有了接触,逐渐地就喜欢上了日本独特的文化。对那些好看的樱花,那些听起来很温柔的话语和那些看起来特别精致好吃的点心开始感兴趣。起初我觉得兴趣终归是兴趣,并没有对能够来日本学习啊生活抱有什么想法。但在我将自己想要出国学习的想法告诉父母,没想到很快便得到了同意。也许是因为了解我知道我很喜欢日本,也许也是因为日本距离中国很近,国家也很安全,所以才能放心我出国吧。总之我对父母能支持我出国表示感谢。</p> <h5 id="关于我如何来到现在的公司就职">关于我如何来到现在的公司就职</h5> <p>  只是凭着喜欢来到日本的我,一开始也是相当不安的。毕竟我自己也很清楚,很多事情只是凭着一腔热血其实很难取得成功。   首先我直面的最大问题其实是语言问题,在来日本求学之前我根本没有系统地学习过日语。凭借着多年看动漫啊日剧积攒下来的词汇量,我在听力方面一开始并没有太大问题,但在说话和写字方面面临着很大的难题。所以一開始選擇進入語言學校磨練一下自己的語言技能,也為後面的考取研究生資格做一個鋪墊。   我在国内读的是计算机专业,所以研究生也就考虑考同样方向的专业。一是考虑到专业对口,其次日本的计算机領域也有很多值得我學習的地方,还有就是将来就业也会比较容易一些。一边学习语言一边打工,一边复习专业知识的我,在来日本一年后,成功考取了国立大学的研究生。   在校学习一年后我也开始考虑找工作的事了。在我的印象里自己是在毕业前一年开始找工作的,相比起中国的大学生找工作的期间而言,日本这边仿佛更加早一些。我相信很多人应该和我一样,这时候考虑到底是回国还是在继续留在日本的问题。我的家长是把这件事的决定权完全交给我的,对于这件要决定我的将来的事,我自己也知道需要慎重考虑。就像我当时来日本深造时的想法一样,其实我个人是很想在人生里有更多不同的经历的。在二十到三十岁的阶段,比起稳定,我更想去做一些以前没有做过的事情。所以我也就本着现在日本找工作试一试的想法,再加上同一个研究室里有学姐问我有专业对口的工作要不要来试一试,我也就顺理成章来到了现在的公司应聘。   初来面试的时候其实我更在意的是我作为外国人的身份。首先当时我并没有来日本很久,再加上自己是工科生,学科本身对于日语的要求并没有那么的高,所以我当时的口语其实也很一般,很怕别人听不懂我在讲什么。但当年的新卒面试让我觉得公司不一样的地方是, 它並不是刻板的需要面試者一个一个去面试官面前回答问题,而是采取的是分组实践的方式(当然了,这里所说的只是当年我入社时候的面试方式,并不代表这之后面试也按照这个方式举行)。所谓分组实践,即是随机将应聘者分组,像在实际上班一样出勤三天,分组做出一些东西,比如网络软件。当然,具体做出什么也是需要通过组内讨论决定的。这让我觉得很很新鲜,也很有效率,这既能观察到每个人实际上班时的状态,又能够检验大家团体活动和工作的能力。现在想一想,那几日我过得很开心。不仅体验了团队协作,也认识了一些朋友。不管是我们的领队,还是向我们分享经验的前辈和为我们准备良好环境的后勤员工,我真的很感谢你们当时对我的照顾。   后来我也就通过了考核,顺利地进入了现在公司。</p> <h5 id="关于我的职场">关于我的职场</h5> <p>  在公司工作的这些年里,首先我最大的感触就是,同样作为程序员,和国内的程序员朋友相比,我的工作压力仿佛没有那么大。仔细想一想,大概是因为我们公司对于员工的工作舒适度是十分重视的缘故吧。比如,其实我们在工作中遇到什么挫折,或是因为对接客户的言行让我们有了压力的时候,在我和团队经理讲之前,他们往往会被我更早察觉到我的情绪变化,并立刻介入解决问题。这为我们能经常有愉快的工作环境打下了基础。再者,我在工作的过程中,我不仅能做自己想要做的工作,并且总能接触到新的技术,开发小组的成员也总是能在技术方面对我们进行支持,这为我们能够放心地挑战新鲜事物打下了基础。也正因为如此,我总能在工作过程中明显感觉到自己工作能力的提升。</p> <p>  2020年年初,全球爆发了新冠疫情。在大家都怕因为被疫情影响人心惶惶的时候,公司又要如何既在保障社员身体健康的前提下又能保证大家正常的出勤工作呢?公司表示,要应对这个新场景,需要开发新打法,找到社员们的痛点,开发新赛道,建立新的工作环境。在宅作业是公司决策的主要抓手,通过整合当下IT行业的资源,串联了公司员工们的意愿,建立了新的工作方式。   这个新的工作方式实现了目标对齐,吃透了在疫情环境下社员们的需求,集成拆解了原来工作环境的不足之处,形成了工作战力的稳定,布置了新的工作环境矩阵,实现了IT行业比起其他行业的优点的破圈。   通过这几年的在宅作业的感受,从一开始的稍微不适应和一些新鲜感,到现在完全适应。形成了工作到生活的生态闭环,提高了社员们的感知度。开会也不需要带着资料奔赴会议室,直接就可以建立会议房间,会议时间开始,大家只需要在各自的电脑前就位即可。保证了社员们的端到端的短平快式处理,而且这样使得公司结构组织扁平化,去中心化,实现了工作模式的简单复用和快速响应,一切行动都重视结果导向,是在疫情下社员们正常工作生活的护城河。</p> <h3 id="最后">最后</h3> <p>  时光似箭,岁月如梭,回首自己来日本的这些日子,总是幸福的日子是更多的。来日本时候虽然有很多担忧,但实际去体验过,总是能在里面找到很多的快乐。我也在各个阶段结交了很多的朋友,认识了可爱的后辈。   人们常说“活到老,学到老”,人的生活就是一个不断学习和完善的过程。在未来我们需要学习的还有很多,需要在工作中汲取更多的知识储备。同时我也希望FLINTERS在为我们提供更好的工作环境的同时,能够更加有着眼于未来的战略眼光,勇于挑战,拼搏进取,不断突破陈规;快速反应,抓住一切机遇发展自己,在未来有更多的周年庆。</p> y_zhang ObsidianとZettelkastenを使って技術メモを取る hatenablog://entry/6801883189063126388 2023-12-07T12:00:00+09:00 2023-12-07T12:00:03+09:00 ObsidianとZettelkastenを用いて、メモの取り方を見直しました。この記事では、そこで実践した方法とやってみた感想などを記載します。 <p>こんにちは。FLINTERS エンジニアの早瀬です。 この記事はFLINTERS設立10周年ブログリレーの90日目の記事になります。</p> <p>技術者の皆さん、日々の業務で技術メモは活用していますか? 今回ObsidianとZettelkastenを用いて、メモの取り方を見直しました。この記事では、そこで実践した方法とやってみた感想などを記載します。</p> <h2 id="目次">目次</h2> <ul class="table-of-contents"> <li><a href="#目次">目次</a></li> <li><a href="#現状のメモの課題">現状のメモの課題</a></li> <li><a href="#用語の説明">用語の説明</a><ul> <li><a href="#Zettelkasten">Zettelkasten</a></li> <li><a href="#Obsidian">Obsidian</a></li> </ul> </li> <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><ul> <li><a href="#メモ作成にコストが掛かる">メモ作成にコストが掛かる</a></li> </ul> </li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#参考資料">参考資料</a></li> </ul> <h2 id="現状のメモの課題">現状のメモの課題</h2> <p>日々のタスクやメモをObsidianのDaily Notesで記録しています。 この方法は物事を理解したりする上で有効ですが、次のような課題を感じています。</p> <ul> <li>メモの再利用性が低い</li> <li>後から見返す際の理解に時間がかかる</li> </ul> <h2 id="用語の説明">用語の説明</h2> <h3 id="Zettelkasten">Zettelkasten</h3> <p>Zettelkasten(ツェッテルカステン)は、ドイツの社会学者ニクラス・ルーマンが研究と論文執筆のために考案したシステムです。 このシステムでは、一つのアイデアをカードに書き、それを箱に収めることで情報を整理・管理していました。 今回は、MarkdownエディタのObsidianを利用して、このシステムを実践します。 私は書籍「TAKE NOTES!」を読んでこのシステムに興味を持ちました。より詳細な内容は、その書籍でご確認いただけます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbookplus.nikkei.com%2Fatcl%2Fcatalog%2F21%2FS00410%2F" title="TAKE NOTES!メモで、あなただけのアウトプットが自然にできるようになる" 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://bookplus.nikkei.com/atcl/catalog/21/S00410/">bookplus.nikkei.com</a></cite></p> <p>こちらでは3種類のノートを使い分けます。</p> <ol> <li><strong>走り書きメモ</strong> <ul> <li>思いついたことを書き出します。</li> <li>数日内に見直し、永久保存版メモに書き直すか、廃棄します。</li> </ul> </li> <li><strong>文献メモ(LiteratureNote)</strong> <ul> <li>本や記事を読んだ際のメモ。</li> <li>読んだ内容を自分の言葉で記述し、理解を深めるために使用します。</li> </ul> </li> <li><strong>永久保存版メモ(PermanentNote)</strong> <ul> <li>必要な情報を自己完結し、理解可能な形で記述します。</li> <li>1つのテーマに絞り、簡潔にまとめます。</li> <li>メモは、スクロールせずに一画面で見られる量を目安にしています。</li> </ul> </li> </ol> <p>ノートのライフサイクルは次のようになります。(図1)</p> <p><figure class="figure-image figure-image-fotolife" title="Zettelkastenのライフサイクル"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/s_hayase/20231201/20231201122603.png" width="1040" height="375" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>図1: Zettelkastenのライフサイクル</figcaption></figure></p> <p>個人的には</p> <ol> <li>「走り書きメモ」にざっくりとした考えを書く</li> <li>「文献メモ」を1をもとに作成する</li> <li>1,2で作成したメモをもとに「永久保存版メモ」を作る</li> <li>作成したメモは各種フォルダに保存する</li> </ol> <p>という流れでやっています。</p> <h3 id="Obsidian">Obsidian</h3> <p><a href="https://obsidian.md/">Obsidian</a>はMarkdownエディタで、オフラインでの保存が可能です。ノート間のリンクを用いて関連性を視覚化できます。</p> <p>今回は、下記の2種類のプラグインを利用します。</p> <p>プラグインの有効化は、Obsidianの「設定」メニューから「コアプラグイン」セクションにアクセスしてください。</p> <ol> <li><strong>Unique note creator</strong> <ul> <li>このプラグインは、「文献メモ」や「永久保存版メモ」を作成する際に特に便利です。それぞれのメモにユニークな識別子を付与することで、情報の整理と検索が容易になります。</li> <li>このプラグインの有効化方法とテンプレート設定に関しては、<a href="https://zubora-seikatsu.com/obsidian-zettelkasten-setteings/">こちらの記事</a>を参考にさせていただきました。</li> </ul> </li> <li><strong>Daily notes</strong> <ul> <li>私は日々のタスクリストを常に目に見える場所に置いています。そのため、Daily Noteを「走り書きメモ」としても積極的に使用しています。</li> <li>メモの取り方は個人によって異なるため、必須項目ではないです。</li> </ul> </li> </ol> <h2 id="実践例">実践例</h2> <p>過去にDockerのイメージスキャンまわりで調べた内容を例に実際のフローを紹介します。</p> <ol> <li>「走り書きメモ」にざっくりとした考えを書く <ul> <li>テーマ <ul> <li>Dockerイメージスキャンをコスパ良く行うには?</li> </ul> </li> <li>調べること <ul> <li>ローカルで利用できるツールの一覧と料金は?</li> <li>AWSで利用できるツールの一覧と料金は?</li> </ul> </li> </ul> </li> <li>「文献メモ」を1をもとに作成する <ul> <li>「調べること」から「文献メモ」を作成する</li> </ul> </li> <li>1,2で作成したメモをもとに「永久保存版メモ」を作る <ul> <li>コスパの良いイメージスキャン方法はなにか?</li> </ul> </li> <li>新たな疑問を「走り書きメモ」に追加する <ul> <li>ローカルとAWSのスキャンの内容に違いがあるか?</li> <li>自分たちは開発の中でどのくらいのイメージスキャンを行う必要があるのだろうか?</li> </ul> </li> <li>このプロセスを繰り返す。</li> </ol> <h2 id="感想">感想</h2> <h3 id="良かった点">良かった点</h3> <h4 id="まとめが容易になった">まとめが容易になった</h4> <p>テーマごとに整理されたメモが作成され、情報の取り扱いが容易になりました。特に、「永久保存版メモ」により、背景情報の記述が必須となるため、後でのまとめ作業がスムーズに進むようになりました。</p> <h4 id="使えるメモがわかりやすい">使えるメモがわかりやすい</h4> <p>以前は日々のノートにタグを付けてメモを管理していましたが、この方法を用いることで、特定の文脈における利用可能なメモが明確になり、情報の検索が迅速に行えるようになりました。</p> <h3 id="苦労した点">苦労した点</h3> <h4 id="メモ作成にコストが掛かる">メモ作成にコストが掛かる</h4> <p>背景情報から調査した内容を自己完結できるような形でまとめることは、普段のメモ以上に労力がかかります。これは慣れもあると思うので、もう少し試してみてから判断してもいいのかなと思っています。</p> <h2 id="まとめ">まとめ</h2> <p>ObsidianとZettelkastenの組み合わせは、当初の課題であった「後から見返す際の理解に時間がかかる」という部分に関してはある程度解消できました。</p> <p>「メモの再利用性が低い」という課題に関しては解決し切ることはできませんでした。こちらはZettelkastenのメモ作成とObsidianのリンク機能を組み合わせて継続的に利用することで、メモ間のつながりが強化され、情報を再利用しやすくなると考えています。</p> <p>技術メモの取り方を見直したい方の参考になれば幸いです。ありがとうございました。</p> <h2 id="参考資料">参考資料</h2> <ul> <li><a href="https://obsidian.md/">Obsidian - Sharpen your thinking</a></li> <li><a href="https://zubora-seikatsu.com/obsidian-zettelkasten-setteings">Obsidian&#x3067;Zettelkasten&#x3092;&#x884C;&#x3046;&#x521D;&#x671F;&#x8A2D;&#x5B9A; &ndash; &#x30BA;&#x30DC;&#x30E9;&#x306E;&#x751F;&#x5B58;&#x6226;&#x7565;&#x30D6;&#x30ED;&#x30B0;</a></li> <li><a href="https://www.soenkeahrens.de/en/takesmartnotes">Take Smart Notes &mdash; S&ouml;nke Ahrens</a></li> <li><a href="https://bookplus.nikkei.com/atcl/catalog/21/S00410/">TAKE NOTES!&#x30E1;&#x30E2;&#x3067;&#x3001;&#x3042;&#x306A;&#x305F;&#x3060;&#x3051;&#x306E;&#x30A2;&#x30A6;&#x30C8;&#x30D7;&#x30C3;&#x30C8;&#x304C;&#x81EA;&#x7136;&#x306B;&#x3067;&#x304D;&#x308B;&#x3088;&#x3046;&#x306B;&#x306A;&#x308B; | &#x65E5;&#x7D4C;BOOK&#x30D7;&#x30E9;&#x30B9;</a></li> </ul> s_hayase SageMaker Pipeline、ローカルでぶんぶん動かしちゃうよ〜♪クラウドだと時間かかっちゃうんですけど〜 hatenablog://entry/6801883189060644376 2023-12-06T12:00:00+09:00 2023-12-06T12:00:50+09:00 SageMaker Pipeline をローカルで実行する方法を紹介します。 <p>やっほー!✋ この記事はね、FLINTERS 10周年記念ブログリレーの89日目なんだって〜!<br/> しかもー、10周年までなんとあと44日なんだよね〜🎉 ついでに創立記念日もあと31日!感動しちゃうねー!💖</p> <p>※この記事は ChatGPT に頼んでギャル風にアレンジしてもらい、それをほとんど改変しています</p> <p>今日は AWS SageMaker Pipeline をローカルでスイスイ動かす方法をバッチリ教えちゃうよ〜💅<br/> なんかクラウドで動かすと時間が掛かっちゃってマジイライラするんですけど〜😵<br/> って思ってるそこのきみー!手元で動かしてワクワク好循環しちゃお〜テンアゲ〜〜↑↑↑</p> <h2 id="なんでローカルなのー">なんでローカルなのー?</h2> <p>マネージド環境でガンガンやるのって楽しいけど、時にはちょっと待たされることもあるよね?<br/> 動作確認の時にはサクサク動いてほしいのに、なんで待たされないといけないの?😵</p> <p>そんな時にローカルでビシバシ動いてくれればストレスフリーなんだから、それだけでも価値あるよね?<br/> イライラナシで、気軽に SageMaker Pipeline を楽しめれば最高でしょ?</p> <p>おおっ、これはヤバい、やっちゃおっかな〜?🚀✌️</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fabout-aws%2Fwhats-new%2F2022%2F08%2Famazon-sagemaker-pipelines-testing-machine-learning-workflows-local-environment%2F" title="Amazon SageMaker Pipelines が、ローカル環境での機械学習ワークフローのテストのサポートを開始 " 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/about-aws/whats-new/2022/08/amazon-sagemaker-pipelines-testing-machine-learning-workflows-local-environment/">aws.amazon.com</a></cite></p> <h2 id="SageMaker-Pipeline-をローカルでビシビシ動かしちゃう方法">SageMaker Pipeline をローカルでビシビシ動かしちゃう方法!</h2> <p>さっそく SageMaker Pipeline をローカルで動かす方法を伝授しちゃうよっ💖</p> <h3 id="ステップ1-準備しとくこと">ステップ1: 準備しとくこと!</h3> <p>みんな <code>session</code> ってこんな感じで使ってるよね?</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sagemaker.workflow.pipeline_context <span class="synPreProc">import</span> PipelineSession session = PipelineSession() </pre> <p>でもさ、今回は import 文と <code>session</code> をこんな感じに変更してみて!</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> sagemaker.workflow.pipeline_context <span class="synPreProc">import</span> LocalPipelineSession session = LocalPipelineSession() </pre> <p>これだけで準備完了だよ〜!✨</p> <p>オプションで簡単に切り替えれるようにしておいた方が後で楽だよ🌈</p> <h3 id="ステップ2-ローカルで-SageMaker-Pipelineチャチャっと実行してみようね">ステップ2: ローカルで SageMaker Pipeline、チャチャっと実行してみようね!</h3> <p>もうステップ1 の変更はバッチリでしょ?</p> <p>あとはみんながいつもやってるコマンドをガンガン叩いちゃってOK!<br/> おもいっきり Enter 叩いちゃおっ!🚀💖</p> <h2 id="まとめタイム">まとめタイム〜</h2> <p>なんだかね、超簡単だったよね✨たったこれだけでローカルで動かせるなんてびっくり!</p> <p>最後になるけど、ローカルで SageMaker Pipeline を実行するメリット、まとめてみようっかな〜?</p> <h3 id="ローカルでのメリット">ローカルでのメリット🌟</h3> <ul> <li>クラウドで実行するよりも待ち時間がすんごく少ない!🎉</li> <li>自分のペースで、気楽にモクモク作業できる!</li> <li>課金されないから、お財布もハッピーに節約できるっ💰✨ <ul> <li>S3 とかでちょっと課金されるんだけどね😜</li> </ul> </li> </ul> <h3 id="デメリットもちょっとだけ考えてみたりしてー">デメリットもちょっとだけ考えてみたりしてー</h3> <ul> <li>クラウドと比べるとスケーラビリティがちょっと限られるかも? <ul> <li>ローカルだとリソースに制約があるから、でっかいプロジェクトだと若干物足りないことがあるかも</li> <li>各ステップで使うデータの量を少なめで動作確認して、動いたら今度は全データでクラウドで動かすのがいいかもね!🚀</li> </ul> </li> <li>もちろんこれはあくまでローカルで動作確認することが目的だからね!👌 <ul> <li>でもプロジェクトによってはクラウドではなくローカルで動かしたいこともあるかも?</li> <li>その時は docker-compose の知識があるととっても助かるよ!💻</li> </ul> </li> </ul> <p>てな感じかな?雑にパパッと書いてみたっ💅✨</p> <h2 id="最後に">最後に</h2> <p>ここまで読んでくれた読者のみんな、ホントにありがとー!共有できて嬉しかったよ💖<br/> これからも新たな冒険が待ってるから、また一緒にワクワクしようね!🚀✨</p> t_hirohata 日常の仕事をするためSublime Text hatenablog://entry/6801883189062109637 2023-12-05T12:00:00+09:00 2023-12-05T12:00:31+09:00 通常の仕事をするため、よく使っているSublime Text を紹介したいと思います。 <h3 id="日常の仕事をするためSublime-Text">日常の仕事をするためSublime Text</h3> <p>こんにちは、株式会社FLINTERSでFlinters Data Hubのプロジェクトマネージャー・プロダクトオーナー(以下PM・PO)をやっているTungです。 この記事はFLINTERS設立10周年ブログリレーの88日目の記事になります。 今回は通常の仕事をするため、よく使っているSublime Text を紹介したいと思います。</p> <h2 id="はじめに">はじめに</h2> <p>私の日常業務では、主にMeta、Googleなどのメディアから取得した生データファイルと頻繁に作業することがあります。これらの生データファイルの特徴は:</p> <ul> <li>それぞれのメディアの生データファイルは、それぞれのファイル構造を持っており、同じメディア内でも複数種類のデータがある。</li> <li>1つの生データファイルには非常に多くの情報が含まれており、手作業で迅速に情報を抽出することはできない。</li> </ul> <p>以下は、ランダムに作成された多くのオブジェクトを含むJSONファイルの例</p> <pre class="code lang-json" data-lang="json" data-unlink><span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">account_id</span>&quot;: &quot;<span class="synConstant">85be60dd15b06758</span>&quot;, &quot;<span class="synStatement">account_name</span>&quot;: &quot;<span class="synConstant">Non reprehenderit est aute do eu exercitation et.</span>&quot;, &quot;<span class="synStatement">isActive</span>&quot;: <span class="synConstant">false</span>, &quot;<span class="synStatement">balance</span>&quot;: &quot;<span class="synConstant">$2,860.89</span>&quot; <span class="synSpecial">}</span>, <span class="synSpecial">{</span> &quot;<span class="synStatement">account_id</span>&quot;: &quot;<span class="synConstant">63aa1161a90a0501</span>&quot;, &quot;<span class="synStatement">account_name</span>&quot;: &quot;<span class="synConstant">Ut culpa mollit exercitation quis exercitation nostrud ut sunt quis fugiat cupidatat nisi ipsum quis.</span>&quot;, &quot;<span class="synStatement">isActive</span>&quot;: <span class="synConstant">false</span>, &quot;<span class="synStatement">balance</span>&quot;: &quot;<span class="synConstant">$3,653.88</span>&quot; <span class="synSpecial">}</span>, <span class="synSpecial">{</span> &quot;<span class="synStatement">account_id</span>&quot;: &quot;<span class="synConstant">70a94aee3f2461a2</span>&quot;, &quot;<span class="synStatement">account_name</span>&quot;: &quot;<span class="synConstant">Dolore commodo id deserunt quis ad esse ut irure ea minim.</span>&quot;, &quot;<span class="synStatement">isActive</span>&quot;: <span class="synConstant">true</span>, &quot;<span class="synStatement">balance</span>&quot;: &quot;<span class="synConstant">$2,794.52</span>&quot; <span class="synSpecial">}</span>, <span class="synSpecial">{</span> &quot;<span class="synStatement">account_id</span>&quot;: &quot;<span class="synConstant">4627a8e0e113f8c1</span>&quot;, &quot;<span class="synStatement">account_name</span>&quot;: &quot;<span class="synConstant">Ea proident nisi velit irure id adipisicing nisi ipsum sint irure ipsum.</span>&quot;, &quot;<span class="synStatement">isActive</span>&quot;: <span class="synConstant">true</span>, &quot;<span class="synStatement">balance</span>&quot;: &quot;<span class="synConstant">$2,403.56</span>&quot; <span class="synSpecial">}</span> <span class="synSpecial">]</span> </pre> <h2 id="課題">課題</h2> <p>大規模データ処理システムがあったとしても、いくつかの場合では、エンジニアでないPOが迅速に調査または集約する必要があります。例えば以降の例記述 - メディアが未処理の新しいタイプのデータを生成する場合 - 異常が発生した場合、生のファイルから一括でデータを抽出して迅速に調査する必要がある場合 - 次の処理フェーズのinput情報を生成するために、一括でデータを処理する必要がある場合</p> <p>具体的には以下のことを行なっています</p> <ul> <li>JSONサンプルファイル内のすべてのaccount_idを抽出する必要がある</li> <li>一連のaccount_idに特殊文字を追加し、その後、カンマで区切られた配列の形でアカウントIDリストを作成、次のステップでの調査API呼び出しの入力として使用する</li> </ul> <h2 id="なぜSublime-Text">なぜSublime Text</h2> <p>この問題に関して、Sublime Textは要件を満たす適切なツールです:</p> <ul> <li>Window、macOS、Ubuntuを含む複数のプラットフォームで動作するエディター。</li> <li>非常に軽量であり、大容量のJSONファイルを簡単に開くことができるため、大きなメモリを必要としません。</li> <li>非常に素晴らしいマルチ編集モードを持っている。</li> </ul> <h2 id="よく使えるパータンの例">よく使えるパータンの例</h2> <p>同じ項目の全てのデータ選択</p> <p> - account_id項目選択</p> <p> - 同じ項目の全て選択</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tung_nth/20231127/20231127143805.gif" width="1098" height="762" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>同時編集機能で操作</p> <p> - 一連のaccount_idに特殊文字を追加し</p> <p> - カンマで区切られた配列の形でアカウントIDリストを作成</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tung_nth/20231127/20231127143648.gif" width="1098" height="762" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>操作に慣れれば、おおよそ5秒でアカウントIDリストを配列の形で作成できます。</p> <h2 id="結論"> 結論</h2> <p>同時処理機能を備えたSublime Textは、上記の例のように柔軟に適用して、たくさんの問題をたった1つのシンプルなツールで解決できます。POとして、さまざまな要求を持つ生データを頻繁に調査する立場で、Sublime Textは私の日々の業務に大いに役立っています。</p> tung_nth 夫婦共働きで子供を幼稚園に通わせるチャレンジ hatenablog://entry/6801883189063146916 2023-12-02T12:00:00+09:00 2023-12-02T12:00:02+09:00 夫婦共働きで子供を幼稚園に通わせるチャレンジ <p>こんにちは。</p> <p>私はFLINTERSでプロジェクトマネジメント職を担当しております桜井と申します。</p> <p>株式会社FLINTERSは設立10周年記念として133日間ブログを書き続けるチャレンジに挑戦中です。今回は夫婦でフルタイム勤務をしながら子供を幼稚園に入園させた経緯と方法について書きたいと思います。</p> <h4 id="幼稚園入園までの経緯">幼稚園入園までの経緯</h4> <p>私は2018年にFLINTERSに入社しました。その翌年5月に娘が誕生。まだ業務で覚えることが多かったこともあり育児休暇は2週間のみの取得としました。ですがFLINTERSには1年以上取得された方もいて弊社は男性の育児休暇取得には非常に理解のある会社と思います。</p> <p>その後妻の産休が1年で終了しフルタイム勤務で復帰。そのタイミングで娘は保育園に入園し2023年3月まで在園しました。</p> <p>そして今年4歳になる娘は4月から幼稚園に転園しました。かねてから幼稚園の教育方針に興味があり家族で話し今の環境であれば可能だろうと判断し決断しました。</p> <h4 id="入園後の試行錯誤">入園後の試行錯誤</h4> <h5 id="幼稚園の課外授業を利用する">幼稚園の課外授業を利用する</h5> <p>幼稚園は通常13:30頃に降園ですが、園によってはその後の時間も希望者に別費用で特別授業を実施している場合があります。</p> <p>娘が通う園では16:00までの課外授業があるためこちらを利用することで帰宅は16:30頃にすることができました。</p> <p>授業の内容はネイティブ外国人講師の方による英語や、専門トレーナーの方が指導してくれる体操クラブ等充実しています。</p> <h5 id="会社の業務形態を利用する">会社の業務形態を利用する</h5> <p>弊社は在宅勤務が許可されているため特別な事情がない限りは出社せず通勤時間を育児に充てることが可能です。</p> <p>また弊社は裁量労働制を採用しているため、プロジェクトチーム内でのコアタイムはありますがそれ以外の時間は比較的自由に一時退席等が可能です。それを活用して子供が体調を崩したりした際の通院や早退時のお迎え等をすることができています。ちなみに子供は風邪をひきやすくうちの子の場合多い時期は2週間に1回くらいのペースで体調を崩していました。</p> <h5 id="福利厚生を利用する">福利厚生を利用する</h5> <p>弊社の福利厚生でポピンズシッターサービスを3割負担で利用することができます。さらに内閣府から発行される割引券も利用できるため、これらを組み合わせれば少ない負担でシッターサービスを利用できます。</p> <p>全額自己負担の場合シッターの平均的な価格が2,200円/時間くらいですので、例えば課外授業の無い日に幼稚園から帰宅後14:00-18:00まで利用するとなると、</p> <p>2,200×4=8,800円+交通費となりますが、会社の補助を利用すれば3割負担で2,640円。これに割引券が1枚2,200円として利用できるため差し引いて440円+交通費という低負担額で利用できます。</p> <p>とはいえ子供がシッターさんになついてくれず仕事部屋に入ってきてしまうことがあったりと苦労する場面は多々ありました。子供が気に入ってくれるかつ希望の日時に空きのあるシッターさんを根気よく探す必要はあります。</p> <p>上記のようなトライを繰り返し現在はなんとかフルタイム勤務での共働きで幼稚園に通わせることができています。</p> <p>働き方の自由度が高いFLINTERSだからこそ可能な育児と仕事を両立させるチャレンジではないかと思います。</p> <p>同じような試みを考えている方のご参考になれば幸いです。</p> ka_sakurai パスキーについて諸々 hatenablog://entry/6801883189059456295 2023-11-28T12:00:00+09:00 2023-11-28T12:00:29+09:00 最近パスワードに変わる認証手段としてパスキーという名前をよく聞くようになってきました。 私の生活範囲内だと任天堂アカウントやGoogle、GitHubなどなど、ログインする際にパスキーが選択肢できるサイトが増えてきています。 実際に使ってみたのですが、パスワードが必要なく指紋認証で済むので楽なものです。 私は新しいもの好きなのですぐに乗っかりたいのですが、同時に疑り深いので困った事態が起きないか気になります。 そのあたりについて簡単に調べてみました。 <p>こんにちは。河内です。 FLNTERS は2024年1月に10周年を迎えるにあたり全社員でブログを書くチャレンジをしています。 この記事は81日目の記事です。</p> <p>最近パスワードに変わる認証手段としてパスキーという名前をよく聞くようになってきました。 私の生活範囲内だと任天堂アカウントやGoogle、GitHubなどなど、ログインする際にパスキーが選択肢できるサイトが増えてきています。 実際に使ってみたのですが、パスワードが必要なく指紋認証で済むので楽なものです。</p> <p>私は新しいもの好きなのですぐに乗っかりたいのですが、同時に疑り深いので困った事態が起きないか気になります。 そのあたりについて簡単に調べてみました。</p> <h2 id="パスキーって">パスキーって?</h2> <p>まずパスキーって何者?という点について Wikipedia の引用です。</p> <p><a href="https://ja.wikipedia.org/wiki/%E3%83%91%E3%82%B9%E3%82%AD%E3%83%BC">https://ja.wikipedia.org/wiki/%E3%83%91%E3%82%B9%E3%82%AD%E3%83%BC</a></p> <blockquote><p>パスキー(英語: Passkeys)は、FIDO Allianceが策定したパスワードレス認証技術。パスワードに代えて生体情報を用いる。</p></blockquote> <p>典型的には指紋認証など生体情報での認証後、公開鍵暗号を用いてサーバーとやりとりします。</p> <p>記憶情報、所持情報、生体情報の複数を組み合わせて認証を強固にすることを多要素認証と言います。 スマートフォンで指紋認証する場合を考えると、スマートフォンの所持(所持情報)と指紋(生体情報)の2要素で認証していることになります。 パスワードは記憶情報のみ(1要素)なので、要素数の観点からパスキーのほうが優れているといえます。 また記憶情報を使わないということは、イコール覚えておくことが減るという意味ですので、人間に優しいと言えます。 人間が記憶できることには限りがありますからね。</p> <p>公開鍵暗闘を用いるということは、秘密鍵と公開鍵のペアを持っていることになります。 秘密鍵はデバイス上に保存されているので、デバイスを紛失した場合はログインできなくなります。これは悲劇です。 そこでパスキーでは、秘密鍵をクラウドで同期することで様々なデバイスから同じ秘密鍵でアクセスできるようになっているようです。</p> <p>GitHubでパスキーをつかってみた記事もご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.flinters.co.jp%2Fentry%2F2023%2F11%2F01%2F120000" title="GitHubから正式リリース 「Passkey」使ってみた - FLINTERS Engineer&#39;s 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://blog.flinters.co.jp/entry/2023/11/01/120000">blog.flinters.co.jp</a></cite></p> <h2 id="パスキーでロックアウトされないか">パスキーでロックアウトされないか?</h2> <p>私が利用者として一番心配なのは、何らかの事情でログインできなくなることです。 秘密鍵はクラウドで同期されるとのことですが、どの範囲で同期されるのでしょうか? いざ使いたいときに手元の端末に秘密鍵がなくてログインできない…という事態は避けたいです。</p> <p>同期の範囲は秘密鍵を保存するプラットフォームによって変わります。 例えば iCloud keychain の場合は iCloud keychain が使える範囲、1Passwordなどのパスワードマネージャーの場合はそのパスワードマネージャーが使える範囲になります。 同期の範囲は限定的であり、現状では私にとって十分に広いとは言えなさそうです。</p> <p>「えっ、じゃあAndroidスマートフォンに保存した秘密鍵(Googleで同期)は macOS の Safari では使えないの?」と思ってしまいますが、端末に秘密鍵が同期されていなくても認証を通す手段があるようです。 QR コードを用いる方法が用意されており、表示された QR コードを秘密鍵を保持する端末で読み取り、読み取った端末で認証する方法です。 (ただしカメラを起動して読み取る必要があるので少し面倒)</p> <p><figure class="figure-image figure-image-fotolife" title="認証用QRコード"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/t_kawachi/20231117/20231117102323.png" width="449" height="476" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>認証用QRコード</figcaption></figure></p> <p>QRコードによる認証を前提に考えると、カメラの付いた端末に秘密鍵を保持して持ち歩けば、秘密鍵が手元に無くて困ることを防げそうです。 現実的にはスマートフォンになるでしょうか。 (ただし仕事で使う秘密鍵の保存は規定に従ってください。)</p> <p>所持しているスマートフォンに秘密鍵を保存していた場合に、ログインできなくなることはあるでしょうか? ひとつはアカウントが ban される場合が考えられます。 スマートフォンから写真管理サービスに自動同期された写真が規約違反だと判定されてbanされたケースを見聞きしたこともあり、稀なケースだとは想いますが、可能性としてはゼロではありません。 ban に対応するには、他の認証手段(メールやSMSなど)を通じてパスキーを再設定できるようにしておく必要がありそうです。 また認証情報の定期的なエクスポート・バックアップも被害の軽減に有効だと思います。</p> <p>以上より、いつでも使いたい秘密鍵については「普段持ち歩くスマートフォンに保存し、万が一のために他の認証手段を用意しておく」というのが良さそうだと思います。</p> <h2 id="自分が作っているサービスにも実装できる">自分が作っているサービスにも実装できる?</h2> <p>プラットフォームが秘密鍵を同期してくれることがわかったので、サービスに組み込む部分は少なそうです。 ウェブサービス提供側としては WebAuthn API による認証を実装すれば良さそうです。</p> <p>WebAuthn の概要は以下のサイトがわかりやすかったです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwebauthn.guide%2F" title="Guide to Web Authentication" 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://webauthn.guide/">webauthn.guide</a></cite></p> <p>新規登録時は <code>navigator.credentials.create()</code> 、認証時は <code>navigator.credentials.get()</code> を使います。 当然ではありますが、APIからは秘密鍵にアクセスできないようになっており、悪意のある JavaScript が実行されても秘密鍵が漏れることは無いようになっています。</p> <p>サーバー側では、クライアント入力の生成と出力の検証、公開鍵の保存などを実装する必要があります。</p> <p><a href="https://webauthn.io/">WebAuthn.io</a> には WebAuthn API を利用するためのライブラリも紹介されているので、実装時には参考にすると良さそうです。</p> <h2 id="まとめ">まとめ</h2> <ul> <li>パスキーによる秘密鍵の同期範囲は限定的</li> <li>QRコードで他の端末で認証を委譲できる</li> <li>自前のサービスに対応するには WebAuthn API による認証を実装する</li> </ul> t_kawachi