Studyplus Engineering Blog スタディプラスの開発者が発信するブログ 2024-03-04T10:00:00+09:00 studyplus Hatena::Blog hatenablog://blog/17391345971634419537 Rails7.1へのアップグレードで発生した暗号化のエラーとその対応 hatenablog://entry/6801883189085696845 2024-03-04T10:00:00+09:00 2024-03-04T10:00:02+09:00 こんにちは。サーバーグループ エンジニアの山田です。 今回はRails7.0からRails7.1へのアップグレードを行なった際に、ActiveRecord Encryptionで発生したエラーとその対応について紹介します。 同様のエラーに遭遇した方の参考になれば幸いです。 ActiveRecord Encryptionによる属性の暗号化 決定論的暗号化と非決定論的暗号化 発生した事象 エラーの原因 SHA256に変わった経緯 対応方法 hash_digest_class support_sha1_for_non_deterministic_encryption 採用した方法 まとめ Activ… <p>こんにちは。サーバーグループ エンジニアの山田です。</p> <p>今回はRails7.0からRails7.1へのアップグレードを行なった際に、ActiveRecord Encryptionで発生したエラーとその対応について紹介します。</p> <p>同様のエラーに遭遇した方の参考になれば幸いです。</p> <ul class="table-of-contents"> <li><a href="#ActiveRecord-Encryptionによる属性の暗号化">ActiveRecord Encryptionによる属性の暗号化</a><ul> <li><a href="#決定論的暗号化と非決定論的暗号化">決定論的暗号化と非決定論的暗号化</a></li> </ul> </li> <li><a href="#発生した事象">発生した事象</a></li> <li><a href="#エラーの原因">エラーの原因</a><ul> <li><a href="#SHA256に変わった経緯">SHA256に変わった経緯</a></li> </ul> </li> <li><a href="#対応方法">対応方法</a><ul> <li><a href="#hash_digest_class">hash_digest_class</a></li> <li><a href="#support_sha1_for_non_deterministic_encryption">support_sha1_for_non_deterministic_encryption</a></li> <li><a href="#採用した方法">採用した方法</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h2 id="ActiveRecord-Encryptionによる属性の暗号化">ActiveRecord Encryptionによる属性の暗号化</h2> <p>本題に入る前の前提知識としてActiveRecord Encryptionについてふれます。</p> <p>ActiveRecordでは属性を暗号化して保存する仕組みが用意されています。</p> <p>例えばUserのemailを暗号化して保存しようとした場合、以下のように <code>encrypts</code> で属性を指定すること実現できます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">class</span> <span class="synType">User</span> &lt; <span class="synType">ApplicationRecord</span> encrypts <span class="synConstant">:email</span> <span class="synPreProc">end</span> </pre> <p>以下のように暗号化していない場合と同様に保存したり元の値を取得できます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">User</span>.create(<span class="synConstant">email</span>: <span class="synSpecial">&quot;</span><span class="synConstant">hoge@studyplus.jp</span><span class="synSpecial">&quot;</span>) <span class="synType">User</span>.last.email =&gt; <span class="synSpecial">&quot;</span><span class="synConstant">hoge@studyplus.jp</span><span class="synSpecial">&quot;</span> </pre> <p>準備として暗号化で使用するランダムなキーセットを設定しておく必要があります。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ bin/rails db:encryption:init Add this entry to the credentials of the target environment: active_record_encryption: primary_key: CEJ8coRHt4obyziTV0ZRBpY6CYMtdfq7 deterministic_key: Ywp91AWdNBS886dQg6SSx5EgcRtsvX6X key_derivation_salt: joWvNGHMA0dTpO91xbeIwpl4UT1jLnu6 </pre> <p>生成したキーをRails credentialsにコピーして貼り付けることで保存できます。また、環境変数などで設定も可能です。</p> <p>詳しくはRailsガイドの<a href="https://railsguides.jp/active_record_encryption.html">Active Record と暗号化</a>を参照ください。</p> <h3 id="決定論的暗号化と非決定論的暗号化">決定論的暗号化と非決定論的暗号化</h3> <p>ActiveRecord Encryptionで使用する暗号化は大きく分けて決定論的暗号化と非決定論的暗号化があります。</p> <p><a href="https://railsguides.jp/active_record_encryption.html#%E6%B1%BA%E5%AE%9A%E8%AB%96%E7%9A%84%E6%9A%97%E5%8F%B7%E5%8C%96%E3%81%A8%E9%9D%9E%E6%B1%BA%E5%AE%9A%E8%AB%96%E7%9A%84%E6%9A%97%E5%8F%B7%E5%8C%96%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">Railsガイド 2.3 決定論的暗号化と非決定論的暗号化について</a>に書かれているようにActiveRecord暗号化ではデフォルトでは非決定論的暗号化が使用されますが、決定論的暗号化を用いることもできます。</p> <blockquote><p>ActiveRecord暗号化では、デフォルトで非決定論的な(non-deterministic)暗号化を用います。ここで言う非決定論的とは、同じコンテンツを同じパスワードで暗号化しても、暗号化のたびに異なる暗号文が生成されるという意味です。非決定論的な暗号化手法によって、暗号解析の難易度を高めてデータベースへのクエリを不可能にすることで、セキュリティを向上させます。</p></blockquote> <p>今回の対応では非決定論的暗号化を使用したアプリケーションで発生した事象のため、以降は<strong>非決定論的な暗号化を前提</strong>に記載しています。</p> <h2 id="発生した事象">発生した事象</h2> <p>ここからが本題です。</p> <p>Rails7.0でActiveRecord Encryptionによる暗号化を使用していたアプリケーションをRails7.1にアップグレードしようとしました。その際に、暗号化した属性の値を参照しようとする処理で<code>ActiveRecord::Encryption::Errors::Decryption</code>が発生しました。</p> <pre class="code" data-lang="" data-unlink>/usr/local/bundle/gems/activerecord-7.1.0/lib/active_record/encryption/encryptor.rb:58:in `rescue in decrypt&#39;: ActiveRecord::Encryption::Errors::Decryption (ActiveRecord::Encryption::Errors::Decryption) /usr/local/bundle/gems/activerecord-7.1.0/lib/active_record/encryption/cipher/aes256_gcm.rb:79:in `rescue in decrypt&#39;: ActiveRecord::Encryption::Errors::Decryption (ActiveRecord::Encryption::Errors::Decryption) /usr/local/bundle/gems/activerecord-7.1.0/lib/active_record/encryption/cipher/aes256_gcm.rb:75:in `final&#39;: OpenSSL::Cipher::CipherError /usr/local/bundle/gems/activerecord-7.1.0/lib/active_record/encryption/encryptor.rb:58:in `rescue in decrypt&#39;: ActiveRecord::Encryption::Errors::Decryption (ActiveRecord::Encryption::Errors::Decryption) /usr/local/bundle/gems/activerecord-7.1.0/lib/active_record/encryption/cipher/aes256_gcm.rb:79:in `rescue in decrypt&#39;: ActiveRecord::Encryption::Errors::Decryption (ActiveRecord::Encryption::Errors::Decryption) /usr/local/bundle/gems/activerecord-7.1.0/lib/active_record/encryption/cipher/aes256_gcm.rb:75:in `final&#39;: OpenSSL::Cipher::CipherError</pre> <p>例外のクラスからわかるように暗号化した属性の復号化に失敗しています。</p> <h2 id="エラーの原因">エラーの原因</h2> <p>原因を調査したところRails7.1からActiveRecord Encryptionで使用されるHash Digest Algorithmのデフォルトが変更されたためでした。<code>OpenSSL::Digest::SHA1</code>から<code>OpenSSL::Digest::SHA256</code>へ変わったことでSHA1を使って暗号化していた属性がSHA256で複合化できなくなりました。(以降はそれぞれSHA1、SHA256のように短縮して書きます。)</p> <h3 id="SHA256に変わった経緯">SHA256に変わった経緯</h3> <p>元々Rails7.0のActiveRecord EncryptionではHash Digest AlgorithmとしてSHA256を使う想定でしたが、バグにより非決定論的暗号化の場合はデフォルトでSHA1が使われてしまっていました。</p> <p>以下のConversationに詳細が書かれています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F48530" title="Add a encryption option to support previous data encrypted non-deterministically with a SHA1 hash digest by jorgemanrubia · Pull Request #48530 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/48530">github.com</a></cite></p> <blockquote><p>There is currently a problem with Active Record encryption for users updating from 7.0 to 7.1 Before #44873, data encrypted with non-deterministic encryption was always using SHA-1. The reason is that ActiveSupport::KeyGenerator.hash_digest_class was set in an after_initialize block in the railtie config, but encryption config was running before that, so it was effectively using the previous default SHA1. That means that existing users are using SHA256 for non deterministic encryption, and SHA1 for deterministic encryption.</p></blockquote> <p>しかしこの挙動は意図したものとは異なりRails7.1では <code>config.active_record.encryption.hash_digest_class</code> が導入されデフォルトでSHA256が使われるようになりました。<br> そのためSHA256が使用される前に非決定論的暗号化を利用していた場合、アルゴリズムが異なるためRails7.1にアップデートするとエラーが発生します。</p> <h2 id="対応方法">対応方法</h2> <p> <code>config.active_record.encryption</code> に用意された以下のオプションを使うことでエラー回避が可能です。</p> <h3 id="hash_digest_class">hash_digest_class</h3> <pre class="code lang-ruby" data-lang="ruby" data-unlink>config.active_record.encryption.hash_digest_class = <span class="synType">OpenSSL</span>::<span class="synType">Digest</span>::<span class="synType">SHA1</span> </pre> <p>hash_digest_classのデフォルト値は<code>OpenSSL::Digest::SHA256</code>ですが、<code>OpenSSL::Digest::SHA1</code>を指定し今後もSHA1を使い続けることが可能です。</p> <h3 id="support_sha1_for_non_deterministic_encryption">support_sha1_for_non_deterministic_encryption</h3> <pre class="code lang-ruby" data-lang="ruby" data-unlink>config.active_record.encryption.support_sha1_for_non_deterministic_encryption = <span class="synConstant">true</span> </pre> <p>このオプションを有効化すると暗号化でSHA256を使用しつつ復号化はSHA256とSHA1で暗号化された属性の両方を複合化できる状態となります。</p> <h3 id="採用した方法">採用した方法</h3> <p>どちらの設定も暫定対応としては問題ありません。ただ今後もSHA1がサポートされ続けるとは限らないため 、以下の流れで最終的に<code>SHA256</code> のみが使われるように対応しました。</p> <ol> <li>以下の設定を入れてSHA1で生成された古い値を読み込めるようにしつつ新規で保存される場合はSHA256で暗号化した値が保存されるようにする <ul> <li><code>Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256</code></li> <li><code>support_sha1_for_non_deterministic_encryption = true</code></li> </ul> </li> <li>SHA1で暗号化された属性をSHA256で暗号化し直して保存するデータマイグレーションの実施</li> <li><code>support_sha1_for_non_deterministic_encryption = true</code>を外し、SHA256のみが使われる状態にする</li> </ol> <p>以下はデータマイグレーションの実装例になります。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># Userモデルのlast_name, first_nameに対して更新する例</span> <span class="synConstant">%i[last_name first_name]</span>.each <span class="synStatement">do</span> |attr| recrypt_attribute(attr) <span class="synStatement">end</span> <span class="synPreProc">def</span> <span class="synIdentifier">recrypt_attribute</span>(attr) encryptor = <span class="synType">ActiveRecord</span>::<span class="synType">Encryption</span>::<span class="synType">Encryptor</span>.new sha1_key_provider = <span class="synType">ActiveRecord</span>::<span class="synType">Encryption</span>::<span class="synType">DerivedSecretKeyProvider</span>.new(<span class="synType">ActiveRecord</span>::<span class="synType">Encryption</span>.config.primary_key, <span class="synConstant">key_generator</span>: <span class="synType">ActiveRecord</span>::<span class="synType">Encryption</span>::<span class="synType">KeyGenerator</span>.new(<span class="synConstant">hash_digest_class</span>: <span class="synType">OpenSSL</span>::<span class="synType">Digest</span>::<span class="synType">SHA1</span>)) sha256_key_provider = <span class="synType">ActiveRecord</span>::<span class="synType">Encryption</span>::<span class="synType">DerivedSecretKeyProvider</span>.new(<span class="synType">ActiveRecord</span>::<span class="synType">Encryption</span>.config.primary_key, <span class="synConstant">key_generator</span>: <span class="synType">ActiveRecord</span>::<span class="synType">Encryption</span>::<span class="synType">KeyGenerator</span>.new(<span class="synConstant">hash_digest_class</span>: <span class="synType">OpenSSL</span>::<span class="synType">Digest</span>::<span class="synType">SHA256</span>)) <span class="synType">User</span>.in_batches <span class="synStatement">do</span> |relation| records = [] relation.each <span class="synStatement">do</span> |user| <span class="synStatement">next</span> <span class="synStatement">if</span> user[attr].blank? || !need_recrypt?(user, attr, encryptor, sha256_key_provider) raw_value = user.read_attribute_before_type_cast(attr) record = { <span class="synConstant">id</span>: user.id } record[attr] = encryptor.decrypt(raw_value, <span class="synConstant">key_provider</span>: sha1_key_provider) records &lt;&lt; record <span class="synStatement">end</span> <span class="synType">User</span>.upsert_all records, <span class="synConstant">update_only</span>: [attr], <span class="synConstant">record_timestamps</span>: <span class="synConstant">false</span> <span class="synStatement">end</span> <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synIdentifier">need_recrypt?</span>(record, attr, encryptor, sha256_key_provider) msg = record.read_attribute_before_type_cast(attr) encryptor.decrypt(msg, <span class="synConstant">key_provider</span>: sha256_key_provider) <span class="synConstant">false</span> <span class="synPreProc">rescue</span> <span class="synType">ActiveRecord</span>::<span class="synType">Encryption</span>::<span class="synType">Errors</span>::<span class="synType">Decryption</span> <span class="synConstant">true</span> <span class="synPreProc">end</span> </pre> <p><code>need_recrypt?</code>メソッドで復号化前の値をSHA256でdecript可能か確認し、できない場合は値を書き換える対象のレコードとしています。</p> <p>今回対応したテーブルはデータ量が多くなかったため上記のようなコードをRakeタスクで実行して一気に更新しました。 <br> レコード数が多いテーブルの場合は、パフォーマンス面やDBの負荷への考慮が必要となるかもしれません。</p> <p>更新完了後に<code>support_sha1_for_non_deterministic_encryption = true</code>を外すことで、SHA1を扱う必要がなくなり、7.1のデフォルトと同様のSHA256のみで暗号化が使える状態となりました🎉</p> <h2 id="まとめ">まとめ</h2> <p>ActiveRecord EncryptionをRails7.0で使っている状態で7.1へアップグレードした際に発生するエラーの対応例を紹介しました。</p> <p>簡単に暗号化できるようになり便利な機能ですが、今回紹介したように使い始めるバージョンによっては注意が必要です。同様のエラーに遭遇した方の参考になれば幸いです。</p> yshunske 競プロ勉強会を開催しました hatenablog://entry/6801883189078945437 2024-02-05T09:00:00+09:00 2024-02-05T09:00:01+09:00 はじめに 2023年7月からWebアプリケーショングループの内定者インターンとして働いている羽鳥です。 インターンでは研修としてRuby、Ruby on Rails、RSpec、データベース設計、オブジェクト指向プログラミング、Reactなどを学んでいます。 私は趣味で競技プログラミングに取り組んでおり、社内勉強会の開催を提案していただいたので、競技プログラミングの勉強会を開催しました。 この記事ではその勉強会についてお話しします。 競技プログラミングとは 競技プログラミングはAtCoderで次のように説明されています。 決められた条件のもとで与えられた問題、課題をプログラミングを用いて解決し… <h2 id="はじめに">はじめに</h2> <p>2023年7月からWebアプリケーショングループの内定者インターンとして働いている羽鳥です。<br/> インターンでは研修としてRuby、Ruby on Rails、RSpec、データベース設計、オブジェクト指向プログラミング、Reactなどを学んでいます。</p> <p>私は趣味で競技プログラミングに取り組んでおり、社内勉強会の開催を提案していただいたので、競技プログラミングの勉強会を開催しました。<br/> この記事ではその勉強会についてお話しします。</p> <h2 id="競技プログラミングとは">競技プログラミングとは</h2> <p>競技プログラミングは<a href="https://atcoder.jp/">AtCoder</a>で次のように説明されています。</p> <blockquote><p>決められた条件のもとで与えられた問題、課題をプログラミングを用いて解決し、その過程や結果を競うものを競技プログラミングといいます。</p></blockquote> <p>引用元:<a href="https://info.atcoder.jp/overview/about/competitive">https://info.atcoder.jp/overview/about/competitive</a></p> <p>以下、競技プログラミングを競プロと表記します。</p> <h2 id="意識したこと">意識したこと</h2> <p>勉強会の開催にあたって、以下のことを意識しました。</p> <h3 id="競プロを全く知らない人でも楽しめる">競プロを全く知らない人でも楽しめる</h3> <p>競プロを知ってもらうというのがこの勉強会の目的だったので、基本的なプログラミングの文法を知っていれば解ける・解説を理解できるような問題に絞りました。<br/> また、解説も平易なものになるように心がけました。</p> <h3 id="事前準備なしで参加できる">事前準備なしで参加できる</h3> <p>参加のハードルを少しでも下げるために事前準備はなしにしました。<br/> 後述する<a href="https://atcoder.jp/">AtCoder</a>のアカウントを事前に作成してもらおうかと考えたのですが、それほど時間がかからないので勉強会の最中に登録してもらう時間を設けました。</p> <h3 id="実際にプログラムを書いてもらう">実際にプログラムを書いてもらう</h3> <p>競プロは四苦八苦しながらコードを書き、提出して正解することで達成感を得られることが醍醐味の1つだと私は考えます。なので、勉強会の中で実際にプログラムを書いて提出することを体験してもらおうと考えました。</p> <h2 id="勉強会の内容">勉強会の内容</h2> <p>勉強会では、以下のことをお話ししました。</p> <ul> <li>競プロについて</li> <li>実際に問題を解いてみよう</li> <li>勉強方法</li> </ul> <h3 id="競プロについて">競プロについて</h3> <p>競プロを何も知らない方向けに、簡単に競プロについて説明しました。<br/> また、競プロのサイトである<a href="https://atcoder.jp/">AtCoder</a> に登録してもらいました。</p> <h3 id="実際に問題を解いてみよう">実際に問題を解いてみよう</h3> <p>AtCoderの以下の2問を解いてもらいました。</p> <h4 id="Counting-Passes"><a href="https://atcoder.jp/contests/abc330/tasks/abc330_a">Counting Passes</a></h4> <ul> <li>問題文</li> </ul> <blockquote><p>N 人の人1,2,…,N がある試験を受け、人i はAi点を取りました。<br/> この試験では、L 点以上を取った人のみが合格となります。<br/> N 人のうち何人が合格したか求めてください。</p></blockquote> <ul> <li>選定理由</li> </ul> <p>基本的な入出力に慣れてもらうことと、正解したときの爽快感や達成感を味わってもらうために出題しました。</p> <ul> <li>計算量</li> </ul> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20O%28N%29" alt=" O(N)"/> </div> <ul> <li>解説</li> </ul> <p>Aの要素を1つずつ走査し、L以上なら答えをカウントするという処理を繰り返せば答えが求まります。</p> <h4 id="Otoshidama"><a href="https://atcoder.jp/contests/abc085/tasks/abc085_c">Otoshidama</a></h4> <ul> <li>問題文</li> </ul> <blockquote><p>日本でよく使われる紙幣は、10000 円札、5000 円札、1000 円札です。以下、「お札」とはこれらのみを指します。<br/> 青橋くんが言うには、彼が祖父から受け取ったお年玉袋にはお札がN 枚入っていて、合計でY 円だったそうですが、嘘かもしれません。このような状況がありうるか判定し、ありうる場合はお年玉袋の中身の候補を一つ見つけてください。なお、彼の祖父は十分裕福であり、お年玉袋は十分大きかったものとします。</p></blockquote> <ul> <li>選定理由</li> </ul> <p>計算量について知ってもらうために出題しました。<br/> 愚直に全探索する解法では正解できないので、計算量について考える必要があります。</p> <ul> <li>計算量</li> </ul> <div> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20O%28N%5E2%29" alt=" O(N^2)"/> </div> <ul> <li>解説</li> </ul> <p>10000円札、5000円札、1000円札の枚数をそれぞれi, j, kとして条件を満たす組み合わせを探索すれば答えは求まります。<br/> しかし、N &lt;= 2000なのでi, j, kを探索すると計算量が</p> <div><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20O%28N%5E3%29" alt=" O(N^3)"/></div> <p>となり、この問題の実行時間制限である2秒に間に合いません。<br/> ここで、お札の枚数が全部でN枚になることに着目すると、i, jが定まっていれば1000円札の枚数はN - (i + j)として求まり、kを探索する必要がなくなるので計算量を</p> <div><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20O%28N%5E2%29" alt=" O(N^2)"/></div> <p>に落とすことができます。</p> <h3 id="勉強方法">勉強方法</h3> <p>競プロについて興味を持って頂けた方向けに以下の勉強方法について紹介しました。</p> <ul> <li>過去問を解く</li> <li>コンテストに参加する</li> </ul> <p>また、AtCoderの過去問を解く際に便利なサービスである<a href="https://kenkoooo.com/atcoder/#/table/">AtCoder Problems</a>も紹介しました。</p> <h2 id="参加者の感想">参加者の感想</h2> <p>参加していただいた方々から、Slackで以下のような温かいお言葉を頂きました!</p> <ul> <li>競プロ勉強会、ありがとうございました!</li> <li>参加した甲斐がありました。色々と質問にも回答いただいてありがとうございます🙇</li> <li>先程はありがとうございました!またAtCoderやっていくぞという気持ちになりました💪</li> <li>ありがとうございました〜久しぶりにやって楽しかったです〜🙌 大学時のC言語の授業でこんな問題解いてたな〜と懐かしかったです!</li> </ul> <h2 id="私の感想">私の感想</h2> <p>「自分なんかが勉強会なんて......」と思ってしまうタイプなので、勉強会開催の提案を頂けて嬉しかったです。<br/> 計算量について軽く説明しましたが、改めて自分が説明すると難しかったです。<br/> 発表中もコメントをたくさんいただけたので、発表しやすかったのが印象的です。</p> <h2 id="おわりに">おわりに</h2> <p>誰でも、どんな内容の勉強会でも歓迎してもらえる雰囲気があり、気楽に楽しく開催できました。<br/> これからも競プロに限らず、様々な技術や知識について勉強会を開催したいと思いました。<br/> 開催したらまたブログも書きたいです。</p> s_hato rspec-openapiを導入した話 hatenablog://entry/6801883189068147038 2024-01-22T09:00:00+09:00 2024-01-22T11:07:09+09:00 こんにちは、サーバーサイドグループの五十嵐です。 今年の4月に弊社に転職して初めて技術ブログを書きます。 今回はAPIドキュメントの作成ツールとしてGemrspec-openapiを導入した話について書いてみます。 弊社では以前からOpenAPIを導入しドキュメント管理を行なってきました。 しかし、OpenAPIを導入する前からあったAPIに対するドキュメントは不足していたり内容が不十分なものもありました。 このようなツールは導入当初はモチベーションが高くても、年月が経つ内にモチベーションが低下したり、メンバーが変わることで存在自体なかったことになったりはよくあることだと思います。 実際にOp… <p>こんにちは、サーバーサイドグループの五十嵐です。<br/> 今年の4月に弊社に転職して初めて技術ブログを書きます。<br/> 今回はAPIドキュメントの作成ツールとしてGem<code>rspec-openapi</code>を導入した話について書いてみます。</p> <p>弊社では以前からOpenAPIを導入しドキュメント管理を行なってきました。<br/> しかし、OpenAPIを導入する前からあったAPIに対するドキュメントは不足していたり内容が不十分なものもありました。<br/> このようなツールは導入当初はモチベーションが高くても、年月が経つ内にモチベーションが低下したり、メンバーが変わることで存在自体なかったことになったりはよくあることだと思います。</p> <p>実際にOpenAPIは全て手で書こうとするとはっきり言って非常にめんどうで、そこそこ工数もかかります。<br/> API新規作成時は自分で必要な情報がわかっているので書きやすいですが、既存のAPIで新しくドキュメントを作成しようとするとかなりの負担です。</p> <p>そこで、ドキュメント作成の負担をなるべく下げるためにGem<code>rspec-openapi</code>を導入してみることになりました。</p> <h3 id="rspec-openapiの導入">rspec-openapiの導入</h3> <p>rspec-openapiはrspecのRequest SpecからOpenAPIを生成するGemで使い方も非常に簡単です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fexoego%2Frspec-openapi%23rspec-openapi---" title="GitHub - exoego/rspec-openapi: Generate OpenAPI schema from RSpec request specs" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/exoego/rspec-openapi#rspec-openapi---">github.com</a></cite></p> <h4 id="インストール">インストール</h4> <p>まずはGemをインストールします。<br/> <code>test</code>でしか使わないので<code>group :test</code>に追加しましょう。</p> <pre class="code" data-lang="" data-unlink> group :test do gem &#34;rspec-openapi&#34; end</pre> <h4 id="実行">実行</h4> <p>以下のように作成したいAPIに対応するRequest Specを指定して実行します。<br/> 普段のspec実行の際に頭に<code>OPENAPI=1</code>と入れるだけでspec実行後にOpenAPIを生成します。</p> <pre class="code" data-lang="" data-unlink>OPENAPI=1 rspec spec/requests/hoge_spec.rb</pre> <p>もちろん1つずつの実行でなくても問題なく実行できます。</p> <pre class="code" data-lang="" data-unlink>OPENAPI=1 rspec spec/requests/</pre> <h4 id="Open-APIの確認">Open APIの確認</h4> <p>実行するとリクエスト情報とレスポンス情報を元にOpenAPIを生成してくれます。<br/> デフォルトでは<code>doc/openapi.yaml</code>に作成されます。<br/> キー情報や型情報だけでなく、実際のレスポンスを元にExampleも生成してくれます。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">openapi</span><span class="synSpecial">:</span> 3.0.3 <span class="synIdentifier">info</span><span class="synSpecial">:</span> <span class="synIdentifier">title</span><span class="synSpecial">:</span> rspec-openapi <span class="synIdentifier">paths</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/tables&quot;</span><span class="synSpecial">:</span> <span class="synIdentifier">get</span><span class="synSpecial">:</span> <span class="synIdentifier">summary</span><span class="synSpecial">:</span> index <span class="synIdentifier">tags</span><span class="synSpecial">:</span> <span class="synStatement">- </span>Table <span class="synIdentifier">parameters</span><span class="synSpecial">:</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> page <span class="synIdentifier">in</span><span class="synSpecial">:</span> query <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> integer <span class="synIdentifier">example</span><span class="synSpecial">:</span> <span class="synConstant">1</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> per <span class="synIdentifier">in</span><span class="synSpecial">:</span> query <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> integer <span class="synIdentifier">example</span><span class="synSpecial">:</span> <span class="synConstant">10</span> <span class="synIdentifier">responses</span><span class="synSpecial">:</span> <span class="synConstant">'200'</span><span class="synSpecial">:</span> <span class="synIdentifier">description</span><span class="synSpecial">:</span> returns a list of tables <span class="synIdentifier">content</span><span class="synSpecial">:</span> <span class="synIdentifier">application/json</span><span class="synSpecial">:</span> <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> array <span class="synIdentifier">items</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> object <span class="synIdentifier">properties</span><span class="synSpecial">:</span> <span class="synIdentifier">id</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> integer <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string <span class="synComment"> # ...</span> </pre> <p>これはサンプルでほんの一部ですがこれを各APIそれぞれ手作業で書いていく・・・<br/> 今考えると寒気がします。</p> <h3 id="rspec-openapiを触ってみて"><code>rspec-openapi</code>を触ってみて</h3> <p>触ってみての第一印象は「何これ!?便利すぎ!!」でした。<br/> API作成時必ず書くRequest Specから作成してくれるとはなんて素敵なのでしょう!!</p> <p>今まで既存のAPIのドキュメント作成時はソースを読んでレスポンスを追い、「これとこれがこう言う形で返るよな・・・サンプル値は・・・」と手作業で書いていましたが、<code>rspec-openapi</code>はrspec実行時に頭に<code>OPENAPI=1</code>とつけるだけです。</p> <p>これに感動しない人はいるでしょうか?いや、いないはずです。</p> <p>こんなにも便利で素晴らしいGemですが、触ってみていくつか注意点もありました。</p> <h4 id="注意点">注意点</h4> <ul> <li><p><strong>specをしっかり書いていないと期待するクオリティーのOpenAPIが生成されない</strong><br/> rspec-openapiはテストケースとして書いたspecを実行しリクエストのパラメータにしたりレスポンスのサンプル値にします。<br/> よってテストをしっかり書いておかないと、必須ではないパラメータやレスポンスの記載が漏れてしまうことがあります。</p></li> <li><p><strong>必要のないrequiredが付与される場合がある</strong><br/> specの実行結果からOpenAPIが作られる都合上、どれが必須なのかは判断仕切れないと思われます。<br/> テスト時に値があるものにはrequiredがつく傾向があり、テストではparamsを設定したが実際は必須ではない場合はOpenAPI作成後に手動で削除する必要がある場合があります。</p></li> <li><p><strong>ステータスコードが重複するテストケースはどれか1つから生成される</strong><br/> レスポンスのステータスコードが重複するテストケースがある場合、このテストケースでOpenAPIを作成する!という指定はできません。<br/> 指定したい場合は以下のようにテストケースを指定してあげることで解決可能です。</p></li> </ul> <pre class="code" data-lang="" data-unlink> OPENAPI=1 rspec spec/requests/hoge_spec.rb:10</pre> <ul> <li><p><strong>不要になったエンドポイントやキーの削除、レスポンス結果の更新が行われない</strong><br/> <a href="https://github.com/exoego/rspec-openapi/blob/4d021af4beaeb19d3f840d2ae6edc31f9f466e02/lib/rspec/openapi/schema_merger.rb#L31-L56">実装上</a>、一度生成するとキーが同じなら上書きされません。<br/> よって後から手動で修正しても修正したものが残りますが、<code>doc/openapi.yaml</code>がある状態でspecを修正して再実行したとしても、不要になったエンドポイントやキーの削除、レスポンス結果の更新が行われません。<br/> 一旦既存のファイルを削除することで再作成されますが、手直しで修正した部分も消えてしまうので注意が必要です。</p></li> <li><p><strong><code>$ref</code>を使った重複schemaなどの参照は手作業で行う必要がある</strong><br/> 自動作成時はschemaが重複した場合も冗長に生成されてしまいます。<br/> しかし、手動で<code>$ref</code>を使うように修正することで再実行時には<code>components/schema</code>を使ってくれます。</p> <ul> <li>以下例</li> </ul> </li> </ul> <p>  一度OpenAPIを作成します。<br/>   以下の場合、responseのUserが重複しています。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synIdentifier">paths</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/users&quot;</span><span class="synSpecial">:</span> <span class="synIdentifier">get</span><span class="synSpecial">:</span> <span class="synIdentifier">responses</span><span class="synSpecial">:</span> <span class="synConstant">'200'</span><span class="synSpecial">:</span> <span class="synIdentifier">content</span><span class="synSpecial">:</span> <span class="synIdentifier">application/json</span><span class="synSpecial">:</span> <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> array <span class="synIdentifier">items</span><span class="synSpecial">:</span> <span class="synIdentifier">User</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> object <span class="synIdentifier">properties</span><span class="synSpecial">:</span> <span class="synIdentifier">id</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string <span class="synIdentifier">role</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> array <span class="synIdentifier">items</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string <span class="synConstant">&quot;/users/{id}&quot;</span><span class="synSpecial">:</span> <span class="synIdentifier">get</span><span class="synSpecial">:</span> <span class="synIdentifier">responses</span><span class="synSpecial">:</span> <span class="synConstant">'200'</span><span class="synSpecial">:</span> <span class="synIdentifier">content</span><span class="synSpecial">:</span> <span class="synIdentifier">application/json</span><span class="synSpecial">:</span> <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">User</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> object <span class="synIdentifier">properties</span><span class="synSpecial">:</span> <span class="synIdentifier">id</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string <span class="synIdentifier">role</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> array <span class="synIdentifier">items</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string </pre> <p>  生成されたOpenAPIを以下のように<code>$ref</code>を使うように修正します。<br/>   <strong>この時、<code>components/schema/User</code>は自分で書いておく必要はありません。</strong></p> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synIdentifier">paths</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/users&quot;</span><span class="synSpecial">:</span> <span class="synIdentifier">get</span><span class="synSpecial">:</span> <span class="synIdentifier">responses</span><span class="synSpecial">:</span> <span class="synConstant">'200'</span><span class="synSpecial">:</span> <span class="synIdentifier">content</span><span class="synSpecial">:</span> <span class="synIdentifier">application/json</span><span class="synSpecial">:</span> <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> array <span class="synIdentifier">items</span><span class="synSpecial">:</span> <span class="synIdentifier">$ref</span><span class="synSpecial">:</span> <span class="synConstant">&quot;#/components/schemas/User&quot;</span> <span class="synConstant">&quot;/users/{id}&quot;</span><span class="synSpecial">:</span> <span class="synIdentifier">get</span><span class="synSpecial">:</span> <span class="synIdentifier">responses</span><span class="synSpecial">:</span> <span class="synConstant">'200'</span><span class="synSpecial">:</span> <span class="synIdentifier">content</span><span class="synSpecial">:</span> <span class="synIdentifier">application/json</span><span class="synSpecial">:</span> <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">$ref</span><span class="synSpecial">:</span> <span class="synConstant">&quot;#/components/schemas/User&quot;</span> </pre> <p>  この状態で再実行します。<br/>   以下のように<code>components/schemas/User</code>が自動生成されます。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink> <span class="synIdentifier">paths</span><span class="synSpecial">:</span> <span class="synConstant">&quot;/users&quot;</span><span class="synSpecial">:</span> <span class="synIdentifier">get</span><span class="synSpecial">:</span> <span class="synIdentifier">responses</span><span class="synSpecial">:</span> <span class="synConstant">'200'</span><span class="synSpecial">:</span> <span class="synIdentifier">content</span><span class="synSpecial">:</span> <span class="synIdentifier">application/json</span><span class="synSpecial">:</span> <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> array <span class="synIdentifier">items</span><span class="synSpecial">:</span> <span class="synIdentifier">$ref</span><span class="synSpecial">:</span> <span class="synConstant">&quot;#/components/schemas/User&quot;</span> <span class="synConstant">&quot;/users/{id}&quot;</span><span class="synSpecial">:</span> <span class="synIdentifier">get</span><span class="synSpecial">:</span> <span class="synIdentifier">responses</span><span class="synSpecial">:</span> <span class="synConstant">'200'</span><span class="synSpecial">:</span> <span class="synIdentifier">content</span><span class="synSpecial">:</span> <span class="synIdentifier">application/json</span><span class="synSpecial">:</span> <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synIdentifier">$ref</span><span class="synSpecial">:</span> <span class="synConstant">&quot;#/components/schemas/User&quot;</span> <span class="synIdentifier">components</span><span class="synSpecial">:</span> <span class="synIdentifier">schemas</span><span class="synSpecial">:</span> <span class="synIdentifier">User</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> object <span class="synIdentifier">properties</span><span class="synSpecial">:</span> <span class="synIdentifier">id</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string <span class="synIdentifier">role</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> array <span class="synIdentifier">items</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> string </pre> <p>  このように<code>$ref</code>を使うことで重複箇所を最小化できます。</p> <h3 id="まとめ">まとめ</h3> <p>私たちのグループでは<code>rspec-openapi</code>は、OpenAPIの土台作りには非常に便利で、そこから軽く手直しをする程度で運用できると結論づけて運用を開始しました。 このおかげで現在はほぼ全てのAPIがドキュメント化され、最新の状態になってきています。 便利なGemを使わせていただき、モチベーションを高く保ち続けることで、しっかりとAPIドキュメント管理を行なっていきたいと思います。</p> haya_programming RubyによるGoogleSSO IDトークンの検証 hatenablog://entry/6801883189067003800 2024-01-09T09:00:00+09:00 2024-01-09T09:00:00+09:00 こんにちは、サーバーサイドグループエンジニアの芳賀です。 今回は、業務で関わる機会があったため、Rubyを使用してgoogleauthライブラリを介してGoogleSSO IDトークンを検証する方法について解説します。Google Sign-Inを使用すると、ウェブアプリケーションにおいてセキュアで簡単なシングルサインオン(SSO)を実現できます。 GoogleSSO IDトークンとは? GoogleSSO IDトークンは、Google Sign-Inの一環として生成されるJSON Web Token(JWT)です。このトークンには、ユーザーの認証情報と権限情報が含まれています。トークンの検証… <p>こんにちは、サーバーサイドグループエンジニアの芳賀です。</p> <p>今回は、業務で関わる機会があったため、Rubyを使用してgoogleauthライブラリを介してGoogleSSO IDトークンを検証する方法について解説します。Google Sign-Inを使用すると、ウェブアプリケーションにおいてセキュアで簡単なシングルサインオン(SSO)を実現できます。</p> <h2 id="GoogleSSO-IDトークンとは">GoogleSSO IDトークンとは?</h2> <p>GoogleSSO IDトークンは、Google Sign-Inの一環として生成されるJSON Web Token(JWT)です。このトークンには、ユーザーの認証情報と権限情報が含まれています。トークンの検証を通じて、ユーザーが正当なGoogleアカウントで認証されたことを確認できます。</p> <h2 id="RubyGemの導入">RubyGemの導入</h2> <p>IDトークンを検証するために、<a href="https://github.com/googleapis/google-auth-library-ruby">googleauth</a>ライブラリを使用します。Gemfileに以下の行を追加します。</p> <pre class="code" data-lang="" data-unlink>gem &#39;googleauth&#39;</pre> <p>そして、bundle installを実行して依存関係を解決します。</p> <h2 id="IDトークンの検証コードの実装">IDトークンの検証コードの実装</h2> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">'</span><span class="synConstant">googleauth</span><span class="synSpecial">'</span> <span class="synPreProc">def</span> <span class="synIdentifier">validate_google_id_token</span>(id_token) client_id = <span class="synSpecial">'</span><span class="synConstant">YOUR_GOOGLE_CLIENT_ID</span><span class="synSpecial">'</span> <span class="synComment"># Google Developer Consoleで取得したクライアントIDを設定</span> verifier = <span class="synType">Google</span>::<span class="synType">Auth</span>::<span class="synType">IDTokens</span>::<span class="synType">Verifier</span>.new payload = verifier.verify(id_token, <span class="synConstant">aud</span>: client_id) <span class="synComment"># 検証成功時の処理</span> puts <span class="synSpecial">&quot;</span><span class="synConstant">検証成功: </span><span class="synSpecial">#{</span>payload<span class="synSpecial">}&quot;</span> <span class="synStatement">return</span> <span class="synConstant">true</span> <span class="synPreProc">rescue</span> <span class="synType">Google</span>::<span class="synType">Auth</span>::<span class="synType">IDTokens</span>::<span class="synType">VerificationError</span> =&gt; e <span class="synComment"># 検証エラー時の処理</span> puts <span class="synSpecial">&quot;</span><span class="synConstant">検証エラー: </span><span class="synSpecial">#{</span>e.message<span class="synSpecial">}&quot;</span> <span class="synStatement">return</span> <span class="synConstant">false</span> <span class="synPreProc">end</span> </pre> <p><code>verifier.verify</code> には必要に応じて <code>azp</code> や <code>iss</code> などのクレームの検証が可能ですが、今回の例では <code>aud</code> のみ渡しています。</p> <h2 id="トークンの検証">トークンの検証</h2> <pre class="code lang-ruby" data-lang="ruby" data-unlink>id_token_to_validate = <span class="synSpecial">'</span><span class="synConstant">YOUR_ID_TOKEN_TO_VALIDATE</span><span class="synSpecial">'</span> validate_google_id_token(id_token_to_validate) </pre> <p>これで、Rubyを使用してgoogleauthライブラリを介してGoogleSSO IDトークンを検証する手順が完了しました。これを適切に組み込むことで、ウェブアプリケーションでのGoogle Sign-Inを安全かつ効果的に実装できます。</p> <h2 id="まとめ">まとめ</h2> <p>GoogleSSO IDトークンの検証は、ウェブアプリケーションのセキュリティを向上させ、ユーザー認証プロセスを堅固にします。googleauthライブラリを使用することにより、開発者は簡潔かつ効率的なコードでGoogle Sign-Inのセキュリティを確保できます。是非、参考にしてみてください。</p> func09 go_routerによる画面遷移時のパラメーターの渡し方 hatenablog://entry/820878482933558534 2023-12-18T10:00:00+09:00 2023-12-18T10:00:01+09:00 こんにちは。クライアントグループの上原です。 StudyplusのFlutterアプリでの開発には、go_routerが利用されています。 今回は、利用していて感じたどういう状況でどの方法を利用して画面遷移時にパラメーターを渡すのが良いのかを紹介します。 また、上記のやり方をgo_router_builderを利用して行った場合に、安全にデータをやり取りする方法も紹介します。 利用したFlutterのバージョンは、3.13.7です。 利用したライブラリのバージョンは、下記になります。 ライブラリ名 バージョン go_router 12.0.1 go_router_builder 2.3.4 画… <p>こんにちは。クライアントグループの上原です。 StudyplusのFlutterアプリでの開発には、go_routerが利用されています。 今回は、利用していて感じたどういう状況でどの方法を利用して画面遷移時にパラメーターを渡すのが良いのかを紹介します。 また、上記のやり方をgo_router_builderを利用して行った場合に、安全にデータをやり取りする方法も紹介します。</p> <p>利用したFlutterのバージョンは、<code>3.13.7</code>です。 利用したライブラリのバージョンは、下記になります。</p> <table> <thead> <tr> <th>ライブラリ名</th> <th>バージョン</th> </tr> </thead> <tbody> <tr> <td> go_router </td> <td> 12.0.1 </td> </tr> <tr> <td> go_router_builder </td> <td> 2.3.4 </td> </tr> </tbody> </table> <ul class="table-of-contents"> <li><a href="#画面遷移時のパラメーターの渡し方について">画面遷移時のパラメーターの渡し方について</a><ul> <li><a href="#パスパラメーター">パスパラメーター</a></li> <li><a href="#クエリパラメーター">クエリパラメーター</a></li> <li><a href="#Extraパラメーター">Extraパラメーター</a></li> </ul> </li> <li><a href="#go_router_builderを利用した型安全なパラメーターの渡し方">go_router_builderを利用した型安全なパラメーターの渡し方</a><ul> <li><a href="#事前準備">事前準備</a></li> <li><a href="#TypedGoRouteの定義">TypedGoRouteの定義</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h2 id="画面遷移時のパラメーターの渡し方について">画面遷移時のパラメーターの渡し方について</h2> <p>go_routerでは、3つのやり方でパラメーターを次の画面へ渡すことができます。</p> <h3 id="パスパラメーター">パスパラメーター</h3> <p>パスの中に<code>:パスパラメーター名</code>といった形で指定できます。 stateのpathParameters(Map&lt;String, String>)にパスパラメーター名を利用してアクセスすることでパスに渡ってきた各パラメーターを取得できます。</p> <p>下記例では、<code>/book/detail/1</code>といったパスへアクセスが来た時に<code>state.pathParameters['bookId']</code>により<code>1</code>が取得されBookScreenにbookId <code>1</code>を設定した画面が表示されるようになります。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synType">final</span> _router = <span class="synType">GoRouter</span>( routes<span class="synStatement">:</span> [ <span class="synType">GoRoute</span>( path<span class="synStatement">:</span> <span class="synConstant">'/book/detail/:bookId'</span>, builder<span class="synStatement">:</span> (<span class="synType">BuildContext</span> context, <span class="synType">GoRouterState</span> state) { <span class="synStatement">return</span> <span class="synType">BookScreen</span>( bookId<span class="synStatement">:</span> state.pathParameters[<span class="synConstant">'bookId'</span>], ); }, ), ], ); </pre> <p>この渡し方を利用する場面としては、本が複数種類存在していて<code>id</code>などで一意に定まるデータを取得する場面での利用が考えられます。</p> <h3 id="クエリパラメーター">クエリパラメーター</h3> <p>state.uri.queryParameters(Map&lt;String, String>)にクエリパラメーター名を利用してアクセスすることで渡ってきた各クエリパラメーターを取得できます。 下記例では、<code>/book/search?query=title&amp;order=desc</code>といったパスへアクセスが来た時に<code>state.uri.queryParameters['query']</code>により<code>title</code>が取得<code>state.uri.queryParameters['order']</code>により<code>desc</code>が取得されBookSearchScreenにquery:<code>title</code>, order:<code>desc</code>を設定した画面が表示されるようになります。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synType">final</span> _router = <span class="synType">GoRouter</span>( routes<span class="synStatement">:</span> [ <span class="synType">GoRoute</span>( path<span class="synStatement">:</span> <span class="synConstant">'book/search'</span>, builder<span class="synStatement">:</span> (<span class="synType">BuildContext</span> context, <span class="synType">GoRouterState</span> state) { <span class="synType">final</span> query = state.uri.queryParameters[<span class="synConstant">'query'</span>]; <span class="synType">final</span> order = state.uri.queryParameters[<span class="synConstant">'order'</span>]; <span class="synStatement">return</span> <span class="synType">BookSearchScreen</span>( query<span class="synStatement">:</span> query, order<span class="synStatement">:</span> order, ); }, ), ], ); </pre> <p>この渡し方を利用する画面としては、上記の例のように、検索のオプションのようなデータを操作したものを取得したい場合に利用します。 クエリパラメーターの場合、クエリパラメーターが渡ってくるかどうかは分からないので基本的には、どれかが欠けていても問題ないようにすることが大事です。</p> <h3 id="Extraパラメーター">Extraパラメーター</h3> <p>stateのextra(Object?)を利用しオブジェクトを渡すことでデータを取得できます。 下記例では、<code>_tap</code>メソッドを叩くとBookオブジェクトを渡して<code>/book</code>へ遷移します。 <code>/book</code>パスへアクセスが来た時に<code>state.extra</code>をBookにキャストしてBookScreenに設定しています。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synType">void</span> <span class="synIdentifier">_tap</span>() <span class="synStatement">=&gt;</span> context.<span class="synIdentifier">go</span>( <span class="synConstant">'/book'</span>, extra<span class="synStatement">:</span> <span class="synType">Book</span>( id<span class="synStatement">:</span> <span class="synConstant">1</span>, title<span class="synStatement">:</span> <span class="synConstant">'タイトル'</span>, ), ); <span class="synType">final</span> _router = <span class="synType">GoRouter</span>( routes<span class="synStatement">:</span> [ <span class="synType">GoRoute</span>( path<span class="synStatement">:</span> <span class="synConstant">'/book'</span>, builder<span class="synStatement">:</span> (<span class="synType">BuildContext</span> context, <span class="synType">GoRouterState</span> state) <span class="synStatement">=&gt;</span> <span class="synType">BookScreen</span>( book<span class="synStatement">:</span> state.extra<span class="synStatement">!</span> <span class="synStatement">as</span> <span class="synType">Book</span>, ), ), ], ); </pre> <p>Extraパラメーターを利用すると様々な状況で便利にデータを渡すことが可能になります。 例えば、本の一覧画面から本のオブジェクトを本の詳細画面へ渡すことで、パスパラメーターでは、本のidを利用して本を取得していた箇所がオブジェクトをそのまま渡す形で置き換えることも出来るようになります。 しかし、便利な反面、オブジェクトを渡すことになるのでidを渡していた時にはなかった問題もあります。 遷移元がオブジェクトをきちんと保持していることが前提なので、ディープリンクなどの直接的な画面遷移には対応できません。 またWebでの利用を考えると、戻るボタンを押して前画面に戻った時や、リロードやURLの直打ちといった事例の際にオブジェクトを利用できない問題などもあります。 Studyplusでは、今後WebでFlutterを利用予定のためExtraパラメーターを利用せず、パスパラメーター、クエリパラメーターを利用しての画面遷移を行なっています。</p> <h2 id="go_router_builderを利用した型安全なパラメーターの渡し方">go_router_builderを利用した型安全なパラメーターの渡し方</h2> <p>3つのパラメーターの渡し方を紹介しましたが、パスパラメーター・クエリパラメーターはStringでデータが渡され、ExtraパラメーターはObjectでデータが渡される形になっています。 しかし、上記の渡し方では、本来渡したいはずの型情報が抜け落ちており安全にパラメーターを渡して画面遷移を実現出来ているとは言えません。 安全にパラメーターを渡せていないとどうなるかというと下記コードを見てください。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synType">GoRoute</span>( path<span class="synStatement">:</span> <span class="synConstant">'/book/detail/:bookId'</span>, builder<span class="synStatement">:</span> (<span class="synType">BuildContext</span> context, <span class="synType">GoRouterState</span> state) { <span class="synType">final</span> bookId = <span class="synType">int</span>.<span class="synIdentifier">parse</span>(state.pathParameters[<span class="synConstant">'bookId'</span>]<span class="synStatement">!</span>); <span class="synStatement">return</span> <span class="synType">BookScreen</span>( bookId<span class="synStatement">:</span> bookId, ); }, ), </pre> <p>この定義では、<code>/book/detail/:bookId</code>の<code>:bookId</code>がintを要求している状況です。 しかし、実際には、<code>context.go('/book/detail/id1');</code>のようなコードが書けてしまいます。このコードを動かすとStringが渡りそれをintにパースしようとして失敗してしまうことになります。それを防ぐために、go_router_builderを利用します。</p> <h3 id="事前準備">事前準備</h3> <p>Routeを定義するファイルでgo_router_builderで生成されるファイルを利用するためにpartで参照します。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synPreProc">import</span> <span class="synConstant">'package:go_router/go_router.dart'</span>; <span class="synPreProc">part</span> <span class="synConstant">'this_file.g.dart'</span>; </pre> <p>GoRouterの定義部分のroutesを<code>$appRoutes</code>に変更します。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synType">final</span> <span class="synType">GoRouter</span> router = <span class="synType">GoRouter</span>( debugLogDiagnostics<span class="synStatement">:</span> <span class="synConstant">true</span>, routes<span class="synStatement">:</span> $appRoutes, ); </pre> <h3 id="TypedGoRouteの定義">TypedGoRouteの定義</h3> <p>それでは、実際にRouteの定義をしていきます。<code>/</code>配下に<code>book/detail/:bookId</code>を設定して<code>TypedGoRoute</code>に<code>BookDetailRoute</code>を設定します。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synPreProc">@TypedGoRoute</span><span class="synStatement">&lt;</span><span class="synType">HomeRoute</span><span class="synStatement">&gt;</span>( path<span class="synStatement">:</span> <span class="synConstant">'/'</span>, routes<span class="synStatement">:</span> <span class="synStatement">&lt;</span><span class="synType">TypedGoRoute</span><span class="synStatement">&lt;</span><span class="synType">GoRouteData</span><span class="synStatement">&gt;&gt;</span>[ <span class="synType">TypedGoRoute</span><span class="synStatement">&lt;</span><span class="synType">BookDetailRoute</span><span class="synStatement">&gt;</span>( path<span class="synStatement">:</span> <span class="synConstant">'book/detail/:bookId'</span>, ), ], ) <span class="synComment">// HomeRouteの定義は省いています。</span> <span class="synType">class</span> <span class="synType">BookDetailRoute</span> <span class="synType">extends</span> <span class="synType">GoRouteData</span> { <span class="synType">const</span> <span class="synType">BookDetailRoute</span>({ <span class="synStatement">required</span> <span class="synType">this</span>.bookId, }); <span class="synType">final</span> <span class="synType">int</span> bookId; <span class="synPreProc">@override</span> <span class="synType">Widget</span> <span class="synIdentifier">build</span>(<span class="synType">BuildContext</span> context, <span class="synType">GoRouterState</span> state) <span class="synStatement">=&gt;</span> <span class="synType">BookScreen</span>( bookId<span class="synStatement">:</span> bookId, ); } </pre> <p>上記処理を書いた後にbuild_runnerを走らせます。 画面遷移をしたい時は下記のように、<code>BookDetailRoute</code>を作成し<code>push</code>などの遷移をします。</p> <pre class="code lang-dart" data-lang="dart" data-unlink>onTap<span class="synStatement">:</span> () <span class="synStatement">=&gt;</span> <span class="synType">const</span> <span class="synType">BookDetailRoute</span>(bookId<span class="synStatement">:</span> <span class="synConstant">1</span>).<span class="synIdentifier">push</span>(context) </pre> <p>go_router_builderを利用することで、安全に画面遷移時にパラメータを渡すことが出来るようになりました。 また、画面遷移時のパス設定の間違えも<code>BookDetailRoute</code>を他の場所でも使うことができるおかげで少なくできます。</p> <h2 id="まとめ">まとめ</h2> <p>go_routerを利用した画面遷移時のパラメーターの渡し方について、どういう渡し方があるのか、どういう場面で利用するべきなのかを整理できました。また、go_router_builderを利用することで安全にパラメーターを渡すことができ、開発時のミスを防ぐことが出来るようになりました。</p> nappannda スタディプラスに新卒入社してからの半年間を振り返る hatenablog://entry/6801883189060206073 2023-11-27T09:00:00+09:00 2023-11-27T09:00:00+09:00 はじめに どうも、クライアントグループエンジニアの後藤です。 最近寒くなってきましたね。昔は冬が圧倒的に好きだったんですが、歳を重ねるごとに夏の方が好きになっていっている自分がいます。 そんな自分がスタディプラスに入社してからもう半年が経ちました。この記事では入社してからの半年間を振り返って、取り組んだことや感じたことをお話しします。 はじめに 内定承諾後の流れ 内定者インターン(入社前) エンジニアのマインドセット研修 アプリ開発研修 振り返り Flutter、Dartの基礎を習得できた 伝えることの難しさを感じた 新卒研修(入社後) アプリ開発研修 勉強会(抜粋) 振り返り 業務開発に入る… <h2 id="はじめに">はじめに</h2> <p>どうも、クライアントグループエンジニアの後藤です。 最近寒くなってきましたね。昔は冬が圧倒的に好きだったんですが、歳を重ねるごとに夏の方が好きになっていっている自分がいます。</p> <p>そんな自分がスタディプラスに入社してからもう半年が経ちました。この記事では入社してからの半年間を振り返って、取り組んだことや感じたことをお話しします。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#内定承諾後の流れ">内定承諾後の流れ</a></li> <li><a href="#内定者インターン入社前">内定者インターン(入社前)</a><ul> <li><a href="#エンジニアのマインドセット研修">エンジニアのマインドセット研修</a></li> <li><a href="#アプリ開発研修">アプリ開発研修</a></li> <li><a href="#振り返り">振り返り</a><ul> <li><a href="#FlutterDartの基礎を習得できた">Flutter、Dartの基礎を習得できた</a></li> <li><a href="#伝えることの難しさを感じた">伝えることの難しさを感じた</a></li> </ul> </li> </ul> </li> <li><a href="#新卒研修入社後">新卒研修(入社後)</a><ul> <li><a href="#アプリ開発研修-1">アプリ開発研修</a></li> <li><a href="#勉強会抜粋">勉強会(抜粋)</a></li> <li><a href="#振り返り-1">振り返り</a><ul> <li><a href="#業務開発に入るための準備ができた">業務開発に入るための準備ができた</a></li> </ul> </li> </ul> </li> <li><a href="#業務開発">業務開発</a><ul> <li><a href="#振り返り-2">振り返り</a><ul> <li><a href="#慣れることが大半">慣れることが大半</a></li> <li><a href="#今後の課題">今後の課題</a></li> </ul> </li> </ul> </li> <li><a href="#全体を通して">全体を通して</a><ul> <li><a href="#Flutterたのしい">Flutterたのしい</a></li> <li><a href="#スタプラでの働き方">スタプラでの働き方</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h2 id="内定承諾後の流れ">内定承諾後の流れ</h2> <pre class="code" data-lang="" data-unlink>2022年 6月~10月 内定者インターン 2023年 4月〜6月 新卒研修   6月〜     業務開発</pre> <p>以下ではそれぞれについて内容と感じたことなどを詳しくお話しします。</p> <h2 id="内定者インターン入社前">内定者インターン(入社前)</h2> <pre class="code" data-lang="" data-unlink>## インターン内容 - エンジニアのマインドセット研修(1ヶ月) - アプリ開発研修(3~4ヶ月)</pre> <h3 id="エンジニアのマインドセット研修">エンジニアのマインドセット研修</h3> <p>最初の1ヶ月間はMicrosoftのトレーニングワークをチームのメンバーと一緒に取り組みました。 取り組んだのは<a href="https://learn.microsoft.com/ja-jp/training/modules/develop-growth-mindset/">成長型マインドセット</a>や<a href="https://learn.microsoft.com/ja-jp/training/modules/enhance-self-efficacy/">自己効力感を高める</a>をはじめとした計4コースで、どれも今後定期的に見返したいと思えるような良い内容でした。</p> <h3 id="アプリ開発研修">アプリ開発研修</h3> <p>その後の3~4ヶ月間はFlutterを使ったアプリ開発を行いました。</p> <table> <thead> <tr> <th> 目的 </th> <th> 内容 </th> </tr> </thead> <tbody> <tr> <td> - Flutterの基礎習得</br> - アプリ開発を一通り経験 </td> <td> <a href="https://hansard-api.parliament.uk/swagger/ui/index">英国議会議事録API</a>を利用したFlutterアプリの開発</br>アプリの仕様、開発計画の作成から開発まで行う </td> </tr> </tbody> </table> <p>開発は画面や機能単位でGitHubのPullRequestを作って、クライアントチームのメンバーからレビューしてもらう形で進めました。また、分からないことや実装の方針で詰まった際は、Slackでいつでも質問できるような体制を作っていただいたので非常にありがたかったです。</p> <p>最後に、成果物を四半期に1回行われているエンジニアのLT大会にて発表させてもらいました。</p> <p>内定者インターンは上記で一旦区切りがついた形ですが、その後10月〜研究が忙しくなる2月くらいまでは希望次第で実務の開発タスクに挑戦させていただけるという話になり、そちらにも取り組みました。</p> <h3 id="振り返り">振り返り</h3> <h4 id="FlutterDartの基礎を習得できた">Flutter、Dartの基礎を習得できた</h4> <p>自分は元々iOSアプリ開発の経験が少しありましたが、Flutterは完全未経験でした。しかし、主要となる部分(Widgetや宣言的UIについて)をあらかじめ説明していただいたことや、公式ドキュメントが充実していたこともあって、習得はしやすかったです。 研修を通してUI、画面遷移、API通信、ページネーションなどのアプリ開発頻出の機能を一通り実装することで、<strong>宣言的UIでの書き方やDartの基礎文法</strong>といった最低限の技術を身につけることができました。</p> <h4 id="伝えることの難しさを感じた">伝えることの難しさを感じた</h4> <p>学生時代は個人開発をすることが多く、メンターのような形で開発を見てもらうことはあまりしてきませんでした。そのため、わからないことや行き詰まってしまった際に質問するタイミングや上手く現状を整理して質問することに難しさを感じました。しかし、内容をあらかじめドキュメントに整理してから質問したり、質問するタイミングを気持ち早めたりなどの試行錯誤をしながら、結果的に3~4ヶ月の間で次第に慣れていきました。</p> <h2 id="新卒研修入社後">新卒研修(入社後)</h2> <pre class="code" data-lang="" data-unlink>## 研修内容 - アプリ開発研修 - 勉強会(週2回) - メンター面談(週1回)</pre> <h3 id="アプリ開発研修-1">アプリ開発研修</h3> <ul> <li>新卒2人でインターン時に開発した研修アプリを交換</li> <li>業務で使う技術を用いてリファクタリング</li> <li>お互いのコードをレビュー</li> </ul> <p>業務のFlutter開発では状態管理に<a href="https://pub.dev/packages/riverpod">riverpod</a>と<a href="https://pub.dev/packages/flutter_hooks">flutter_hooks</a>、画面遷移に<a href="https://pub.dev/packages/go_router">go_router</a>を導入しているため、それらの基礎を習得するというのが研修の主な目的でした。</p> <h3 id="勉強会抜粋">勉強会(抜粋)</h3> <ul> <li>Flutter関連</li> <li>コードレビューについて</li> <li>良いコードとはなにか、リーダブルコード</li> <li>Material Design, Human Interface Guidelines</li> </ul> <p>週2回の勉強会では、主にFlutterの基礎的な内容と、チーム開発する上で重要になってくるコードの書き方やレビューについて勉強しました。 また、Flutterに関する勉強会では、研修で習得中のライブラリについての内容に加えて、<a href="https://dart.dev/effective-dart">Effective Dart</a>、Widgetツリー、Sliverなどについて学びました。</p> <p>参考資料: <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloudsmith.co.jp%2Fblog%2Fefficient%2F2021%2F08%2F1866630.html" title="Googleに学ぶコードレビューのポイント | エンジニア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://cloudsmith.co.jp/blog/efficient/2021/08/1866630.html">cloudsmith.co.jp</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmedium.com%2Fflutter-jp%2Fdive-into-flutter-4add38741d07" title="Flutter の Widget ツリーの裏側で起こっていること" 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://medium.com/flutter-jp/dive-into-flutter-4add38741d07">medium.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.flutter.dev%2Fui%2Flayout%2Fscrolling%2Fslivers" title="Using slivers to achieve fancy scrolling" 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.flutter.dev/ui/layout/scrolling/slivers">docs.flutter.dev</a></cite></p> <h3 id="振り返り-1">振り返り</h3> <h4 id="業務開発に入るための準備ができた">業務開発に入るための準備ができた</h4> <p>研修を通して、riverpod、flutter_hooksの2つに関しては各<code>Provider</code>や<code>use〇〇</code>をどういった場面で使い分けるのかなどが習得できました。また、go_routerに関しては<code>go</code>と<code>push</code>での遷移、パラメータ渡しなどといった基本的なことは習得できました。 正直これらパッケージを使う背景やメリットなどについてもインプットはしていましたが、当時は理解しきれていない部分も多かったなという印象です。</p> <p>また研修では、Flutterに関することだけでなく、業務での開発をする上で必要な知識を体系的にインプットできたことも大きかったです。 具体的には<strong>メンテナンス性</strong>や<strong>可読性</strong>を考えたコードの書き方や<strong>コードレビューのやり方やポイント</strong>などが挙げられます。 特にコードレビューに関しては経験がなかったため、様々な<strong>レビュー観点</strong>があることや、実際に先輩方がどのようにレビューしているかをこのタイミングでインプットできて良かったです。</p> <h2 id="業務開発">業務開発</h2> <pre class="code" data-lang="" data-unlink>## 取り組んだこと - 機能追加 - デザイン指摘項目やレイアウト修正 - コードのリファクタリング - ライブラリの導入や更新 - コードレビュー - 業務効率化</pre> <p>最初に取り組んだのは、モバイルのFlutterアプリへの機能追加でした。 内容は検索や入力フォーム、ページングリストなどの実装が必要な<strong>一機能をまるまる実装</strong>するというものです。 複数画面に渡る状態管理やダイアログ、スナックバーが絡む画面遷移などの実装の際は設計面で苦労しましたが、チームの先輩方に助けてもらいながらなんとかやり遂げました。</p> <p>その後はFlutter Webにてアプリの<strong>Navigation周り全般</strong>を任せてもらったり、レビュワーとしてコードレビューにも参加させてもらったりなど、大小さまざまなタスクに取り組ませてもらいました。</p> <p>また、上記の開発に加えて<strong>Slackワークフロー+GAS</strong>を使って日々の業務を効率化するプロジェクトにも取り組みました。 内容はワークフローのフォームの内容をスプレッドシートに自動入力するといったもので、約3ヶ月間で行いました。社内向けではありますが、ツールを<strong>設計から実装して使ってもらうまで一通り経験</strong>できて、とても勉強になりました。</p> <h3 id="振り返り-2">振り返り</h3> <h4 id="慣れることが大半">慣れることが大半</h4> <p>初めのうちは、やはりプロジェクトの大きさがこれまでと段違いだったので、全体像を把握するまでに時間がかかっていました。 しかし、機能追加やデザイン修正のタスクをいくつかこなしていくうちに慣れていき、コードリーディングのスピードも上がってきました。 また、Flutter周りの知識についても同様で、タスクをこなすたびに知らない新しい発見がありその分知識もついていきました。 特に状態管理周りを実装する際は、状態が更新・保持されなかったり、不要なrebuildが走ったり...などといった問題にぶつかり、その度に一層理解が深まっていった気がします。</p> <h4 id="今後の課題">今後の課題</h4> <p>逆に難しかった点や課題という面では、様々な<strong>エラーケースを考えながら実装</strong>することや、新機能実装の際にその後の<strong>要素追加や変更に耐えうる設計を考えて実装</strong>できるかなどが挙げられます。 タスクに取り掛かる時点で、どう実現するかばかりに集中してしまい、<strong>ユーザー体験やコードのメンテナンス性</strong>をあまり考えられていない節があるので、今後はそういった部分も考えながら実装できるようになりたいです。</p> <h2 id="全体を通して">全体を通して</h2> <h3 id="Flutterたのしい">Flutterたのしい</h3> <p>学生時代はずっとiOSで開発していて、今年から本格的にFlutterを触るようになったのですが、個人的に<strong>開発体験の良さ</strong>は気に入っています。簡単なUIの変更はホットリロードですぐに確認できるところだったり、エディターもiOSと違って選べるのが良いですね。自分はAndroidStudioしか使っていないですが笑(VSCodeでも開発してみたい) あとはFlutterや周辺のパッケージはまだまだ<strong>変化が激しい</strong>点も楽しいところですね。できないことができるようになったり、次々と更新されていく情報のキャッチアップもやりがいがあってとても楽しいです。</p> <h3 id="スタプラでの働き方">スタプラでの働き方</h3> <p>自分は元々、成長したいという意欲はありますがあまりガツガツ挑戦できるような人間ではありませんでした。 そんな自分がスタディプラスに入社してからの半年間、業務の中で色々なことに挑戦できました。 それは、やりたいと手を挙げたことをサポートしてくださるチームの先輩方を初めとした周囲の皆さんと、挑戦に対して温かいフィードバックをもらえる環境があったからです。 スタディプラスはValueの1つとして<strong>Fail Forward</strong>という言葉を掲げています。その言葉の通り、失敗を歓迎して挑戦を応援してくれる環境があると身をもって感じました。 加えて、<strong>リモート+コアタイムなしのフルフレックス制</strong>ということもあり、柔軟に働ける点もよかったです。新卒1年目から本当に恵まれた環境で働かせてもらっているなとつくづく感じています。</p> <h2 id="まとめ">まとめ</h2> <p>いかがだったでしょうか?振り返るととても充実した半年間でした。来年春には1年を迎えますが、残る半年ももっと色んなことに挑戦して成長していきたいです。 最後になりましたが、現在スタディプラスでは新卒ソフトウェアエンジニアを募集中です。この記事を読んで少しでも興味を持っていただけた方は、この記事の最後にあるリンクから気軽にカジュアル面談をお申し込みください!</p> <p>以上、後藤でした!</p> kosuke_goto 新規プロジェクトでモブ設計を実施してみた hatenablog://entry/6801883189050049372 2023-11-20T09:00:00+09:00 2023-11-20T09:00:03+09:00 こんにちは。ウェブアプリケーショングループのエンジニアの川井です。 最近、新しいプロジェクトを立ち上げる際、開発を始める前に新しい仕様や設計に関する整理やコミュニケーションを強化するため、モブ設計を導入しました。この記事では、モブ設計の概要と実施方法について紹介します。 背景 新しいプロジェクトを始める際、従来は1人のエンジニアが最初の段階の設計と仕様の整理を担当し他のメンバーにレビューしてもらってました。 このやり方では以下の問題がありました。 設計を担当するメンバーの大きな負担になっていた。 設計の検討や仕様の整理もそのメンバーに属人化していた。 設計に関してチーム内でのコミュニケーション… <p>こんにちは。ウェブアプリケーショングループのエンジニアの川井です。 最近、新しいプロジェクトを立ち上げる際、開発を始める前に新しい仕様や設計に関する整理やコミュニケーションを強化するため、モブ設計を導入しました。この記事では、モブ設計の概要と実施方法について紹介します。</p> <h1 id="背景">背景</h1> <p>新しいプロジェクトを始める際、従来は1人のエンジニアが最初の段階の設計と仕様の整理を担当し他のメンバーにレビューしてもらってました。 このやり方では以下の問題がありました。</p> <ul> <li>設計を担当するメンバーの大きな負担になっていた。</li> <li>設計の検討や仕様の整理もそのメンバーに属人化していた。</li> <li>設計に関してチーム内でのコミュニケーションが不足していた。</li> </ul> <p>この問題を解決するため、モブ設計を実施してみました。</p> <h3 id="モブ設計とは">モブ設計とは</h3> <p>チームではモブプログラミングを過去に実施したことがあり、そこではチーム全員で同期的にコミュニケーションを取りながら同じタスクの開発をやってました。設計や仕様の整理も同じようにチーム全員で同期的にコミュニケーションを取りながら進められないかと考え、モブ設計という名前をつけて実施してみました。</p> <h3 id="使用ツールの紹介">使用ツールの紹介</h3> <p>チームがモブ設計を実施するために利用しているツールは、主にFigmaとGoogle Meetです。</p> <p><strong>Figma</strong>: FigmaのFigJamを利用し設計を共有し、リアルタイムで協力できるようにしています。Figmaを使用することで、チーム全体が同じボードを見ながら、共有でき画面のデザインもFigmaで作成されているため簡単にモブ設計用のボードに写し開発向けのコメントやメモを残すことができます。</p> <p><strong>Google Meet</strong>: チームのメンバーは基本フルリモートのため、Google Meetを使用して同期的にコミュニケーションをとりながら、設計に関する議論を進めるようにしています。最近ではSlackのハドルを使うこともあります。</p> <p>これらのツールを利用して設計の議論を効果的に行い、モブ設計を実施しています。</p> <h3 id="モブ設計の進め方">モブ設計の進め方</h3> <p>モブ設計は、以下のステップに従って進行しています。画面がないAPIの実装のみの場合など、プロジェクトの内容によってステップを省略することもあります。</p> <p><strong>STEP 1. 関連エンティティの特定:</strong><br /> 最初に、新しい仕様に関連するエンティティを特定します。これには、既存のエンティティと新たに必要なエンティティを洗い出し、ER図を作成します。このステップでは、プロジェクトの新しい仕様を整理しながら必要なデータ構造を明確にします。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KevinKawai2022/20231030/20231030124858.png" width="1115" height="624" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>STEP 2. 画面とエンティティの関連付け:</strong><br /> 次に、プロジェクトの仮デザインをもとに、どの画面や画面の部分がどのエンティティを参照するかを明確にします。画面とエンティティの関連性を洗い出すことで、データの流れを整理できます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KevinKawai2022/20231030/20231030131042.png" width="1200" height="548" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>STEP 3. エンティティのエンドポイント決定:</strong><br /> 画面ごとにエンティティをグループ化し、エンティティのエンドポイントを仮で決定します。このステップでは、新しく必要なエンドポイントのURLのパラメータやエラー時の対応、他に考慮するものがありましたら検討します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KevinKawai2022/20231030/20231030131710.png" width="1116" height="570" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>STEP 4. ライフサイクルとドキュメント:</strong><br /> 次に、各エンティティについてライフサイクル図を作成し、そのエンティティがどのように作成・編集・削除されるか、どこから変更が加えられるかを洗い出します。複雑なプロセスが存在する場合、シーケンス図を作成し、プロセスの手順を明確にします。また、新たに作成されるエンティティに関するリソースドキュメントを作成し、既存エンティティに変更がある場合にはドキュメントを更新します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KevinKawai2022/20231030/20231030132454.png" width="848" height="398" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>STEP 5. 開発タスクの作成:</strong><br /> 最後に以上のステップで洗い出したER図やエンドポイントの情報を踏まえて必要なタスクを洗い出します。タスク同士の依存関係やグルーピングを行いタスク管理ツールに具体的なタスクを作成します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/K/KevinKawai2022/20231030/20231030132833.png" width="935" height="622" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="モブ設計をやってみて">モブ設計をやってみて</h3> <p>モブ設計を実施し、以下のことが改善できたと感じました。</p> <h4 id="1-新規仕様と必要な設計の整理">1. 新規仕様と必要な設計の整理</h4> <p>チームでモブ設計を導入した目的は1人の負担を減らし、チーム全員が新プロジェクトについて基本的な整理を持つことでした。以前のアプローチでは、初期の設計や仕様の整理が1人のメンバーに依存しており、他のメンバーは後から設計書を理解しなければなりませんでした。モブ設計を導入することで、プロジェクトの設計に関する議論と設計を同期的に行い、チーム全体で新プロジェクトを理解が上がったと思いました。</p> <h4 id="2-知識共有">2. 知識共有</h4> <p>モブ設計を通して、新しい仕様の疑問点や既存設計について話し合うことができました。Figmaを使用して議論した項目を記録しているため、後でそのFigmaを見てドキュメントとしてみることができました。変更がある場合、コメントも追加できました。これにより、議論の内容や決定事項を失うことなく、新規プロジェクトの開発ドキュメントとして保管できました。</p> <h4 id="3-コミュニケーション">3. コミュニケーション</h4> <p>Google Meetを使用して初期の設計を同期的に行ったため、各メンバーが持つ質問や懸念事項を議論したり、新しいメンバーが入った時の既存機能の説明も簡単にできました。非同期でコミュニケーションをする場合、整理に時間を結構かけたり、誤解することがあります。モブ設計により、チームはリアルタイムでのコミュニケーションを通じて意見を交換し、効果的なコミュニケーションを実現しました。</p> <h1 id="まとめ">まとめ</h1> <p>モブ設計は、新しいプロジェクトの設計や仕様を整理するために、チームにとってすごく効果的でした。プロジェクトの初期段階での協力とコミュニケーションが向上し、アイデアの知識共有が実現しました。モブ設計により、各メンバーがプロジェクトの方向性を把握し、プロジェクト全体の基盤を共有できるようになりました。今後も、モブ設計を活かしながら、プロジェクトを進めていきます。</p> KevinKawai2022 スタディプラスはJSConf JP 2023に協賛します hatenablog://entry/6801883189059253454 2023-11-17T13:17:18+09:00 2023-11-17T13:17:18+09:00 こんにちは。 スタディプラスのカンファレンス/OSSサポートチーム、後藤です。 スタディプラスは、2023年11月19日に九段坂上KSビルにて開催されるJSConf JP 2023に協賛します。 jsconf.jp JSConf JP 2023とは? JSConf JPはJapan Node.js Associationが主催するテックカンファレンスです。 日本での開催は4度目になりますが国内だけでなく海外で働く開発者のウェブ開発に関わるセッションが数多く企画されています。 【概要】 主催 :Japan Node.js Association 開催日:2023年11月19日(日) 会場 :九段… <p>こんにちは。 スタディプラスのカンファレンス/OSSサポートチーム、後藤です。</p> <p>スタディプラスは、2023年11月19日に九段坂上KSビルにて開催されるJSConf JP 2023に協賛します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjsconf.jp%2F2023%2F" title="JSConf JP" 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://jsconf.jp/2023/">jsconf.jp</a></cite></p> <h3 id="JSConf-JP-2023とは">JSConf JP 2023とは?</h3> <p>JSConf JPはJapan Node.js Associationが主催するテックカンファレンスです。<br/> 日本での開催は4度目になりますが国内だけでなく海外で働く開発者のウェブ開発に関わるセッションが数多く企画されています。</p> <p>【概要】<br/>  主催 :Japan Node.js Association<br/>  開催日:2023年11月19日(日)<br/>  会場 :九段坂上KSビル<br/>  公式HP:<a href="https://jsconf.jp/2023/">https://jsconf.jp/2023/</a></p> <h3 id="今年の協賛について">今年の協賛について</h3> <p>今年もJSConf JPに協賛させていただくことになりました。昨年に引き続き2回目の協賛となります。<br/> スタディプラスは今後もJavaScriptのコミュニティへ貢献していきます。</p> kosuke_goto FlutterKaigi 2023にスポンサー&参加しました hatenablog://entry/6801883189058946950 2023-11-16T13:09:26+09:00 2023-11-16T13:09:26+09:00 こんにちは!クライアントグループの後藤です。 11月10(金)に株式会社ナビタイムジャパンにて開催されたFlutterKaigi 2023に参加してきたので、その感想などをまとめます。 はじめに 今回、弊社スタディプラスはFlutterKaigi 2023にシルバースポンサーとして協賛させていただきました。 昨年に続き2回目の協賛で、今年もFlutterKaigiを一緒に盛り上げていけることを大変嬉しく思っております。 今年は会場のノベルティコーナーにスタディプラス付箋セットを提供させていただきました。 受け取った方はぜひご活用いただけると幸いです。 flutterkaigi.jp 感想 今年… <p>こんにちは!クライアントグループの後藤です。 11月10(金)に株式会社ナビタイムジャパンにて開催されたFlutterKaigi 2023に参加してきたので、その感想などをまとめます。</p> <h1 id="はじめに">はじめに</h1> <p>今回、弊社スタディプラスはFlutterKaigi 2023にシルバースポンサーとして協賛させていただきました。 昨年に続き2回目の協賛で、今年もFlutterKaigiを一緒に盛り上げていけることを大変嬉しく思っております。 今年は会場のノベルティコーナーにスタディプラス付箋セットを提供させていただきました。 受け取った方はぜひご活用いただけると幸いです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fflutterkaigi.jp%2F2023" title="FlutterKaigi 2023" 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://flutterkaigi.jp/2023">flutterkaigi.jp</a></cite></p> <h1 id="感想">感想</h1> <p>今年初めてのオフライン開催となったFlutterKaigiですが、弊社クライアントグループからは計5名が参加しました。 うち3名がオフラインで参加し、残る2名はYoutubeLive視聴という形でオンライン参加しています。 以下では参加したメンバーの気になったセッションや感想などを紹介します。</p> <h2 id="後藤">後藤</h2> <p>今年新卒でスタディプラスに入社した後藤です。Flutter歴としては内定を頂いてからのインターンと入社してからの期間を合わせて1年ほどになります。 FlutterKaigiはYoutubeのアーカイブ視聴などはしていましたが参加自体は初で、今回はオフラインで参加しました。</p> <p><img width="500" alt="IMG_0548.jpg (1.6 MB)" src="https://s3-ap-northeast-1.amazonaws.com/esa.s3.prod.lithium.studylog.jp/uploads/production/attachments/8682/2023/11/13/127892/fe0469a3-6a7e-47f5-b5b2-49b0b56f2d04.jpg"></p> <p>ノベルティコーナーから拝借。dashくんのステッカーとM3株式会社さんの完全に理解したステッカーが個人的に良かったです。</p> <p>セッションはYoutubeのアーカイブで後日視聴可能なので、会場ではその時の興味の赴くままに見ていました。<br/> 個人的に印象に残ったセッションは<strong>Dartのコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法について</strong>でした。 自動生成系のパッケージを構成するための主要な登場人物とその役割を把握できたので、簡単なパッケージなら作れそうという気にさせてくれました。また、普段使っているfreezedなどのパッケージの中身を探索してみるのも面白いなと思いました。</p> <p><a href="https://flutterkaigi.jp/2023/sessions/d9cc75af-a3a2-4d0e-af6c-f12aa143ba4c">https://flutterkaigi.jp/2023/sessions/d9cc75af-a3a2-4d0e-af6c-f12aa143ba4c</a><cite class="hatena-citation"><a href="https://flutterkaigi.jp/2023/sessions/d9cc75af-a3a2-4d0e-af6c-f12aa143ba4c">flutterkaigi.jp</a></cite></p> <p>オフラインでは最後に懇親会もあり、そちらにも参加しました。色々なバックグラウンドを持った方々と当日視聴したセッションについての話や普段のFlutter開発についてお話しさせていただき、いい刺激をもらうことができました。</p> <p>初参加でしたが、とても楽しめました。来年もぜひ参加したいです!</p> <h2 id="上原">上原</h2> <p>Flutterエンジニアの上原です。 今回のFlutterKaigiはYoutubeのライブ配信で参加をしました。</p> <p>セッションは、<strong>Flutterアプリのセキュリティ対策を考えてみる</strong>が印象に残りました。 自分の開発歴はAndroid(Java)→iOS(Swift)→Flutter(dart)のような形で変わっており、Androidの時には難読化させるなど少しだけセキュリティに触れるようなこともありました。 しかし、モバイルアプリで積極的にセキュリティを考えることがなかったため、知る機会が出来よかったです。また、OWASP MASVSなどセキュリティ周りの知らない知識を知ることが出来たり、実際の攻撃のやり方やそれの対策方法などを知れて有意義なセッションでした。</p> <h2 id="隅山">隅山</h2> <p>AndroidとFlutter開発をやっている隅山です。 今はがっつりFlutter開発を行なっているため、勉強のためFlutterKaigiにオフライン参加しました。 自分の前職がナビタイムだったため、懐かしい場所と知り合いに出会うことができました。</p> <p>印象に残ったセッションは、<strong>Master of Flutter lifecycle</strong>でした。</p> <p><a href="https://flutterkaigi.jp/2023/sessions/f76c37b8-172d-4072-ad4a-bd870bc15728/">https://flutterkaigi.jp/2023/sessions/f76c37b8-172d-4072-ad4a-bd870bc15728/</a><cite class="hatena-citation"><a href="https://flutterkaigi.jp/2023/sessions/f76c37b8-172d-4072-ad4a-bd870bc15728/">flutterkaigi.jp</a></cite></p> <p>Android開発ではライフサイクルへの理解が非常に大事でしたが、Flutter開発ではライフサイクルをあまり気にせず実装できたので、ライフサイクルについて勉強するいい機会となりました。 発表スライドもMaterialDesign3のレイアウトが採用されていたのも面白かったです。 発表ではAndroid/iOSでの操作パターンと発火イベントの差異がそれぞれ細かく図でまとめられており、OSの違いとFlutter3.13.0前後でライフサイクルイベントがこんなにも変わっているのかと驚きました。 一番はWebのフルスクリーン化の際にinactiveとresumedが何度も繰り返されるのが言われれば確かにとなりますが、気をつけないといけないなと思わされました。</p> <h2 id="樋口">樋口</h2> <p>今年新卒で入社して、Flutter開発を行なっている樋口です。</p> <p>初めて参加するカンファレンスだったので楽しみにしていたのですが、期待通りにすごく楽しいカンファレンスでした。 印象に残ったセッションは 「我々にはなぜRiverpodが必要なのか-InheritedWidgetから始まる app state 管理手法の課題」 です。</p> <p><a href="https://flutterkaigi.jp/2023/sessions/0b32515e-2c80-45f4-8ea2-c6c269d2609f/">https://flutterkaigi.jp/2023/sessions/0b32515e-2c80-45f4-8ea2-c6c269d2609f/</a><cite class="hatena-citation"><a href="https://flutterkaigi.jp/2023/sessions/0b32515e-2c80-45f4-8ea2-c6c269d2609f/">flutterkaigi.jp</a></cite></p> <p>RiverpodはFlutterを使って開発をしている人の多くが利用している状態管理のライブラリです。発表では、「なぜ状態管理に多くの人がRiverpodを採用しているのか」という疑問の提示から始まり、最後まで興味が惹かれる発表内容でした。自分自身「なぜ他のライブラリではなく、Riverpodを使っているのか」ということを考えたことがなかったので、それを考えるいい機会になりました。 具体的なRiverpodの説明も、「状態管理する上での課題」と「Riverpodはどのようにしてその課題を解決しているのか」という構成でした。そのため、内容が頭に入ってきやすくて今後の開発に活かしやすい内容でした。</p> <h1 id="おわりに">おわりに</h1> <p>以上FlutterKaigi 2023の参加レポートでした。 最後になりますが、FlutterKaigi2023実行委員会の皆さんをはじめ、登壇された皆さん、ありがとうございました!参加された皆さんもお疲れ様でした!</p> kosuke_goto FlutterでDriftを使ったデータ保存でのつまづきと対策 hatenablog://entry/6801883189055225522 2023-11-13T09:00:00+09:00 2023-11-13T13:08:59+09:00 こんにちは、クライアントグループの樋口です。 今回は、弊社のアプリにてFlutterで使えるローカルDBパッケージの「Drift」を用いた開発した際に生じた、データ保存でのつまづきとその対処法を紹介します。さらに、Driftの簡単な使い方と使用例も併せて紹介します。 環境 Driftについて Driftの簡単な使い方 テーブルクラス作成 レコードクラスの定義 データの追加・取得・削除 Driftを使ってつまづいたポイント Listが保存できない List<String>型のデータの保存・復元 保存 復元 端末の画像データを保存・復元 保存 復元 まとめ 環境 Flutter 3.13.8 dr… <p>こんにちは、クライアントグループの樋口です。</p> <p>今回は、弊社のアプリにてFlutterで使えるローカルDBパッケージの「Drift」を用いた開発した際に生じた、データ保存でのつまづきとその対処法を紹介します。さらに、Driftの簡単な使い方と使用例も併せて紹介します。</p> <ul class="table-of-contents"> <li><a href="#環境">環境</a></li> <li><a href="#Driftについて">Driftについて</a></li> <li><a href="#Driftの簡単な使い方">Driftの簡単な使い方</a><ul> <li><a href="#テーブルクラス作成">テーブルクラス作成</a></li> <li><a href="#レコードクラスの定義">レコードクラスの定義</a></li> <li><a href="#データの追加取得削除">データの追加・取得・削除</a></li> </ul> </li> <li><a href="#Driftを使ってつまづいたポイント">Driftを使ってつまづいたポイント</a><ul> <li><a href="#Listが保存できない">Listが保存できない</a></li> <li><a href="#ListString型のデータの保存復元">List&lt;String&gt;型のデータの保存・復元</a><ul> <li><a href="#保存">保存</a></li> <li><a href="#復元">復元</a></li> </ul> </li> <li><a href="#端末の画像データを保存復元">端末の画像データを保存・復元</a><ul> <li><a href="#保存-1">保存</a></li> <li><a href="#復元-1">復元</a></li> </ul> </li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h1 id="環境">環境</h1> <ul> <li>Flutter 3.13.8</li> <li>drift 2.13.0</li> <li>freezed 2.4.5</li> </ul> <h1 id="Driftについて">Driftについて</h1> <p>DriftとはDartを利用してデータを永続化できるパッケージです。端末内部にデータを永続化し、DartAPIまたはSQLによってデータの操作を行えます。 サーバーを経由しないため、オフラインでも保存し操作できるというのが特徴です。弊社でも、オフラインでデータを保存する際にDriftを用いています。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fpub.dev%2Fpackages%2Fdrift" title="drift | Dart Package" 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://pub.dev/packages/drift">pub.dev</a></cite></p> <h1 id="Driftの簡単な使い方">Driftの簡単な使い方</h1> <p>Driftの導入やインストールに関しては、今回は割愛させていただきます。必要な方は<a href="https://drift.simonbinder.eu/docs/getting-started/">公式ドキュメント</a> を参考にしてみていただけると幸いです。このパートでは、公式ドキュメントを参考にしながら簡単なデータ処理の方法のみご紹介します。不要な方は、このパートは読み飛ばしていただいても大丈夫です。</p> <h2 id="テーブルクラス作成">テーブルクラス作成</h2> <p>まずはデータベースにアクセスするためのテーブルクラスを作成します。下記のようにTableクラスを継承したTodoItemsを作成しました。int型は<code>integer()</code>、String型は<code>text()</code>のように指定します。<code>nullable()()</code>をつけることによってオプショナルにできます。TodoItemsを作ったら、@DriftDatabaseを作成しbuild_runnerを実行し、ファイルを自動生成します。</p> <pre class="code Dart" data-lang="Dart" data-unlink>import &#39;package:drift/drift.dart&#39;; part &#39;database.g.dart&#39;; class TodoItems extends Table { IntColumn get id =&gt; integer().autoIncrement()(); TextColumn get title =&gt; text().withLength(min: 6, max: 32)(); TextColumn get content =&gt; text().named(&#39;body&#39;)(); IntColumn get category =&gt; integer().nullable()(); } class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override int get schemaVersion =&gt; 1; } LazyDatabase _openConnection() { return LazyDatabase(() async { final dbFolder = await getApplicationDocumentsDirectory(); final file = File(join(dbFolder.path, &#39;app_db.sqlite&#39;)); return NativeDatabase.createInBackground(file); }); }</pre> <h2 id="レコードクラスの定義">レコードクラスの定義</h2> <p>次に、 データベースとのデータのやり取りに使用する、レコードクラスであるTodoItemsModelを定義しておきます。</p> <pre class="code Dart" data-lang="Dart" data-unlink>@freezed class TodoItemsModel with _$TodoItemsModel { const factory TodoItemsModel({ required int id, required String title, String? content, String? category, }) = _TodoItemsModel; factory TodoItemsModel.fromJson(Map&lt;String, Object?&gt; json) =&gt; _$TodoItemsModelFromJson(json); }</pre> <h2 id="データの追加取得削除">データの追加・取得・削除</h2> <p>次に簡単なデータの挿入・取得・削除の方法の実装例を下記に示します。 それぞれの処理の関数は、データの追加を<code>add()</code>、全てのデータ取得を<code>getAll()</code>、全てのデータ削除を<code>deleteAll()</code>としています。</p> <pre class="code Dart" data-lang="Dart" data-unlink>class TodoItemsRepository { TodoItemsRepository({ required this.database, }); final AppDatabase database; Future&lt;void&gt; add(TodoItemsModel todoItemsModel) async { await database.into(database.todoItems).insert(todoItemsModel); } Future&lt;List&lt;TodoItemsModel&gt;&gt; getAll() async { final todoItemsList = await database.select(database.todoItems).get(); return todoItemsList.map( (todoItems) { return TodoItemsModel( id: todoItems.id, title: todoItems.title, content: todoItems.content, category: todoItems.category, ); }, ).toList(); } Future&lt;void&gt; deleteAll() async { await database.delete(database.todoItems).go(); } } </pre> <h1 id="Driftを使ってつまづいたポイント">Driftを使ってつまづいたポイント</h1> <h2 id="Listが保存できない">Listが保存できない</h2> <p>Driftはデータを保存する際に、int、Stringのような基本的な型しか保存できない問題があり、Listなどはそのまま保存できません。(<a href="https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/">ドキュメント</a>参照)</p> <h2 id="ListString型のデータの保存復元">List&lt;String&gt;型のデータの保存・復元</h2> <p>List&lt;String&gt;型のデータに関しても、直接テーブルクラスで定義できません。そのためList&lt;String&gt;→Stringという形で変換します。</p> <h3 id="保存">保存</h3> <p>listDataのようなList&lt;String&gt;のリストがあったとします。</p> <pre class="code Dart" data-lang="Dart" data-unlink> final listData = [ &#39;Data1&#39;, &#39;Data2&#39;, &#39;Data3&#39;, &#39;Data4&#39;, &#39;Data5&#39;, ];</pre> <p>これを以下のように、カンマ区切りでString型に変換します。</p> <pre class="code Dart" data-lang="Dart" data-unlink> final stringData = listData.split(&#39;,&#39;);</pre> <p>listDataを含むデータクラスをextensionで事前にList&lt;String&gt;からString型に変換しておくとDBへの保存がスムーズになります。SampleDataからNewSampleDataへ変換する実装例はこちらです。</p> <pre class="code Dart" data-lang="Dart" data-unlink>@freezed class SampleData with _$SampleData { const factory TodoItemsModel({ List&lt;String&gt;? listData, int? intData, }) = _SampleData; factory SampleData.fromJson(Map&lt;String, Object?&gt; json) =&gt; _$SampleDataFromJson(json); } @freezed class NewSampleData with _$NewSampleData { const factory NewSampleData({ String stringData, int? intData, }) = _NewSampleData; } extension SampleDataExtensions on SampleData { NewSampleData convertToListData() { final newListData = listData?.split(&#39;,&#39;) ?? &#39;&#39;; return NewSampleData(stringData: newListData, intData: intData); } } </pre> <p>これでString型としてローカルDBへ保存できます。</p> <h3 id="復元">復元</h3> <p>先ほどのカンマ区切りのstringDataの値は以下のようになっています。</p> <pre class="code Dart" data-lang="Dart" data-unlink>print(stringData); //&#39;Data1,Data2,Data3,Data4,Data5&#39;</pre> <p>stringDataをList&lt;String&gt;に復元する変換が下記のコードになります。</p> <pre class="code Dart" data-lang="Dart" data-unlink> final newStringData = stringData?.join(&#39;,&#39;) ?? &#34;&#34;; </pre> <p>extensionでSampleDataにコンバートした形が下記のコードです。</p> <pre class="code Dart" data-lang="Dart" data-unlink>extension NewSampleDataExtensions on NewSampleData { NewSampleData convertToStringData() { final newStringData = stringData?.join(&#39;,&#39;) ?? &#34;&#34;; return SampleData(listData: newStringData, intData: intData); } } </pre> <p>このようにすると、問題なくDriftに保存前のデータを同じ形で取得できます!</p> <h2 id="端末の画像データを保存復元">端末の画像データを保存・復元</h2> <p>弊社ではオフラインで保存するデータの一部に画像データが含まれているため、その際にDriftを用いています。画像データをString型へ変換する方法が2通りあり、どちらを採用するか迷ったので判断基準と実装例を合わせてご紹介します!</p> <h3 id="保存-1">保存</h3> <p>画像をDriftでローカルDBへ保存するにはString型にしないといけません。 画像データをStringで保存して復元する方法は次の2つです。</p> <ul> <li>Uint8Listを保存し、<a href="https://api.flutter.dev/flutter/widgets/Image/Image.memory.html">Image.memory</a>で復元</li> <li>pathを保存し、<a href="https://api.flutter.dev/flutter/widgets/Image/Image.file.html">Image.file</a>で復元</li> </ul> <p>Uint8Listへ変換する方法は、画像データがデータベースに直接保存されるため容量が大きくなってしまいます。 pathから取得する場合デバイスのストレージを活用しているため、データベースのサイズを節約できます。 しかし、pathはデバイスのストレージから削除された場合、画像データが取得できないというデメリットがあります。 弊社の場合、画像データが相対的に必要な要素ではないため、pathを保存する方法が適していると判断し、そちらを採用しました。</p> <p>画像のデータに関しては<a href="https://pub.dev/packages/image_picker">ImagePicker()</a>を使えば、端末のアルバムの情報を取ってこれます。その画像データをpathのStringへ変換してからデータベースに追加するという手順です。</p> <pre class="code Dart" data-lang="Dart" data-unlink>Future getImageFile() async { final picker = ImagePicker(); final pickedImage = await picker.pickImage( source: ImageSource.gallery, ); if (pickedImage == null) { return null; } final imagePath = pickedImage.path; return imagePath; }</pre> <p>このようにすると、String型としてそのまま保存できます。</p> <p>imagePathの中身は</p> <pre class="code" data-lang="" data-unlink>/data/user/.../example.jpg</pre> <p>のようになっています!</p> <h3 id="復元-1">復元</h3> <p>ローカルDBから画像データのパスを取得後の復元のコードは下記になります。</p> <pre class="code Dart" data-lang="Dart" data-unlink> final imageFile = File(imagePath); final imageData = Image.file(imageFile);</pre> <p><a href="https://api.flutter.dev/flutter/dart-io/File-class.html">File()</a>で復元可能で、 復元後は<a href="https://api.flutter.dev/flutter/widgets/Image-class.html">Image.file()</a>で画像を表示できます。</p> <h1 id="まとめ">まとめ</h1> <p>今回はローカルDBに画像データとList&lt;String&gt;を保存する方法をご紹介しました。 どちらも保存の仕方ではなく、保存前のデータ加工が重要でした。</p> <p>Driftで保存するためには「どのようなデータに変換したら復元しやすいか」という観点で開発していただけると幸いです。</p> xtomoya1021 スタディプラスはFlutterKaigi 2023にシルバースポンサーとして協賛します hatenablog://entry/6801883189056484387 2023-11-08T11:00:00+09:00 2023-11-08T11:00:02+09:00 こんにちは。 スタディプラスのカンファレンス/OSSサポートチーム、後藤です。 スタディプラスは、2023年11月10日に開催されるFlutterKaigi 2023に、シルバースポンサーとして協賛します。 flutterkaigi.jp FlutterKaigi 2023とは? FlutterKaigiは、国内でも最大規模を誇る、Flutterを中心に据えたカンファレンスです。 FlutterやDartの深い知見を持つ開発者によるセッションが多数企画されています。 【概要】 主催 :FlutterKaigi実行委員会 開催日:2023年11月10日(金) 会場 :株式会社ナビタイムジャパン … <p>こんにちは。<br/> スタディプラスのカンファレンス/OSSサポートチーム、後藤です。</p> <p>スタディプラスは、2023年11月10日に開催されるFlutterKaigi 2023に、シルバースポンサーとして協賛します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fflutterkaigi.jp%2F2023%2F" title="FlutterKaigi 2023" 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://flutterkaigi.jp/2023/">flutterkaigi.jp</a></cite></p> <h3 id="FlutterKaigi-2023とは">FlutterKaigi 2023とは?</h3> <p>FlutterKaigiは、国内でも最大規模を誇る、Flutterを中心に据えたカンファレンスです。<br/> FlutterやDartの深い知見を持つ開発者によるセッションが多数企画されています。</p> <p>【概要】<br/>  主催 :FlutterKaigi実行委員会<br/>  開催日:2023年11月10日(金)<br/>  会場 :株式会社ナビタイムジャパン<br/>  公式HP:<a href="https://flutterkaigi.jp/2023/">https://flutterkaigi.jp/2023/</a></p> <h3 id="協賛">協賛</h3> <p>スタディプラスはFlutterKaigi 2023にシルバースポンサーとして協賛させていただくことになりました。<br/> FlutterKaigiは、2021年から新たに始まったカンファレンスで、弊社としては昨年に引き続き3回目の協賛となります。 今年は会場にて配布されるノベルティとして付箋をご用意させていただきましたので、そちらもぜひご活用ください。</p> <p>現在弊社では、Flutterを採用したアプリケーションの開発を行っていることもあり、非常に関心の高いカンファレンスになります。<br/> そんなFlutterKaigiを一緒に盛り上げていけることを大変嬉しく思っており、今後の発展にも貢献していきたいと考えております。</p> <h3 id="スタディプラスで一緒にFlutterアプリの開発をしませんか">スタディプラスで一緒にFlutterアプリの開発をしませんか?</h3> <p>現在、スタディプラスではFlutter開発を行うモバイルアプリエンジニアを募集しております。<br/> 詳細は以下の採用情報のモバイルアプリエンジニアをご覧ください。<br/> カジュアル面談も随時受け付けていますので、お気軽にお申し込みください。</p> kosuke_goto Amazon Aurora MySQLのRollbackSegmentHistoryListLength hatenablog://entry/6801883189053072783 2023-11-06T10:00:00+09:00 2023-11-06T10:00:01+09:00 こんにちは。サーバーサイドエンジニアの青山です。 先日、弊社のAmazon Aurora MySQLのCPU負荷が急に上昇してDBが再起動するという事象が発生しました。その原因を調査した時の話をRollbackSegmentHistoryListLengthを中心に紹介します。 当時の状況 レプリケーション遅延による再起動 RollbackSegmentHistoryListLengthが増加する原因 非同期処理による書き込み おわりに 当時の状況 Amazon RDSのコンソールやDatadogのメトリクスから確認できたCPU負荷が上昇した時の状況は以下の通りです。 ライターインスタンスのR… <p>こんにちは。サーバーサイドエンジニアの青山です。</p> <p>先日、弊社のAmazon Aurora MySQLのCPU負荷が急に上昇してDBが再起動するという事象が発生しました。その原因を調査した時の話をRollbackSegmentHistoryListLengthを中心に紹介します。</p> <ul class="table-of-contents"> <li><a href="#当時の状況">当時の状況</a></li> <li><a href="#レプリケーション遅延による再起動">レプリケーション遅延による再起動</a></li> <li><a href="#RollbackSegmentHistoryListLengthが増加する原因">RollbackSegmentHistoryListLengthが増加する原因</a></li> <li><a href="#非同期処理による書き込み">非同期処理による書き込み</a></li> <li><a href="#おわりに">おわりに</a></li> </ul> <h2 id="当時の状況">当時の状況</h2> <p>Amazon RDSのコンソールやDatadogのメトリクスから確認できたCPU負荷が上昇した時の状況は以下の通りです。</p> <ul> <li>ライターインスタンスのRollbackSegmentHistoryListLengthが増加</li> <li>ライターインスタンスのDMLThroughputが増加</li> <li>リーダーインスタンスのCPUが上昇</li> <li>AuroraReplicaLagが増加</li> <li>大幅なレプリケーション遅延のためリーダーインスタンスが再起動</li> </ul> <p>この時点で、RollbackSegmentHistoryListLengthの増加がトリガーになってレプリケーション遅延を引き起こし、インスタンスが再起動したと仮説を立てて調査を開始しました。</p> <h2 id="レプリケーション遅延による再起動">レプリケーション遅延による再起動</h2> <p>似たような事例はないか探していると「<a href="https://repost.aws/ja/knowledge-center/aurora-read-replica-restart">Amazon Auroraリードレプリカが遅れ、再起動されたのはなぜですか。</a>」という記事を見つけました。</p> <p>RollbackSegmentHistoryListLength(HLL)が増加している状況と照らし合わせると、以下が可能性としてありそうです。</p> <blockquote><p>増大化が進む History List Length (HLL) を調べる (Aurora MySQL - 互換)</p> <p>MySQL InnoDB エンジンには、デフォルトで MVCC (multi-version concurrency control) が組み込まれています。これは、トランザクションの全体を通じて、影響を受けるすべての行で発生したすべての変更を追跡する必要があることを意味します。長時間実行されるトランザクションが完了すると、パージスレッドアクティビティの急増が始まります。長時間実行されるトランザクションによって作成されるバックログの量が原因で、この突然のパージが Aurora レプリカの遅延を引き起こす場合があります。</p></blockquote> <p>しかし、長時間実行されるトランザクションがないか調べたところ、そのような痕跡を見つけることはできませんでした。</p> <h2 id="RollbackSegmentHistoryListLengthが増加する原因">RollbackSegmentHistoryListLengthが増加する原因</h2> <p>さらに公式ドキュメントを見ていると「<a href="https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/proactive-insights.history-list.html">InnoDB履歴リストの長さが大幅に増加しました</a>」を見つけました。</p> <p>こちらには長時間実行されるトランザクション以外にも書き込み負荷が高いことも原因の1つとして挙げられています。</p> <blockquote><p>履歴リストが長くなる一般的な原因には次のものがあります。</p> <p>・実行時間の長いトランザクション (読み取りまたは書き込み)<br/> ・書き込み負荷が高い</p></blockquote> <p>確かに、DMLThroughputが増加していたので書き込み負荷が高かったことも可能性としてはありそうです。</p> <p>また、「<a href="https://repost.aws/ja/knowledge-center/aurora-mysql-slow-select-query">Amazon Aurora MySQL DB クラスターで SELECT クエリの実行が遅いのはなぜですか?</a>」には以下のような記載もあります。</p> <blockquote><p>注: HLL の急増の原因は、長時間実行されるトランザクションだけではありません。消去スレッドが DB の変更に追い付かない場合でも、HLL は高いままになることがあります。</p></blockquote> <h2 id="非同期処理による書き込み">非同期処理による書き込み</h2> <p>DMLThroughputについて調べていると該当の時間帯に非同期処理のジョブが大量に実行され、同時に大量の<code>INSERT</code>が発行されていることが分かりました。</p> <p>弊社では非同期処理のジョブが実行された時にその履歴を残すため、RDBにジョブの情報を書き込むような仕組みが用意されています。そのため、非同期処理のジョブ1件につき<code>INSERT</code>が追加で実行されることになります。</p> <p>ただし、頻度の高いジョブについてはRDBへの負荷対策のため履歴を残さないようにしているのですが、今回のジョブはその対象になっていませんでした。</p> <p>そこで、このジョブの履歴を残さないような修正を入れることで様子を見ることにしました。</p> <h2 id="おわりに">おわりに</h2> <p>RollbackSegmentHistoryListLengthについては馴染みのない指標だったため、今回の件で発生しうる原因について知ることができて良かったです。</p> <p>今のところ同じ事象は再発していませんが、Datadogのアラートを設定してRollbackSegmentHistoryListLengthが増加したら気付けるようにしたので、今後も注視していきます。</p> aocha51 Kaigi on Rails 2023 参加レポート hatenablog://entry/6801883189054602718 2023-11-02T16:00:00+09:00 2023-11-02T16:21:13+09:00 こんにちは、サーバーグループの山田です。10/27(金)、28(土)開催のKaigi on Rails 2023に参加しました。その感想などをレポートします。 Kaigi on Railsとは? Kaigi on Railsのコアコンセプトは 「初学者から上級者までが楽しめるWeb系の技術カンファレンス」 です。Kaigi on Railsは技術カンファレンスへの参加の敷居を下げることを意図して企画されています。また、名前の通りRailsを話題の中心に据えるカンファレンスではありますが、広くWebに関すること全般(例えばフロントエンドやプロトコルなど)についてもカバーすることで参加者の知見を深… <p>こんにちは、サーバーグループの山田です。10/27(金)、28(土)開催のKaigi on Rails 2023に参加しました。その感想などをレポートします。</p> <h2 id="Kaigi-on-Railsとは">Kaigi on Railsとは?</h2> <blockquote><p>Kaigi on Railsのコアコンセプトは 「初学者から上級者までが楽しめるWeb系の技術カンファレンス」 です。Kaigi on Railsは技術カンファレンスへの参加の敷居を下げることを意図して企画されています。また、名前の通りRailsを話題の中心に据えるカンファレンスではありますが、広くWebに関すること全般(例えばフロントエンドやプロトコルなど)についてもカバーすることで参加者の知見を深め、また明日からの仕事に役立てていただければと考えています。 (<a href="https://kaigionrails.org/2023/about/">公式Webサイト</a>より引用)</p></blockquote> <h2 id="感想">感想</h2> <p>Kaigi on Rails初のオンライン、オフラインのハイブリッド開催でした。わたしはオフラインで参加しましたが、活気を感じられてよかったです。 多くのセッションがありましたが、特に印象に残っているセッションについて感想を書いていきます。</p> <h3 id="TracePointを活用してモデル名変更の負債解消をした話">TracePointを活用してモデル名変更の負債解消をした話</h3> <p><iframe id="talk_frame_1097523" class="speakerdeck-iframe" src="//speakerdeck.com/player/f6db6f70c7a642558d5e74a67fc3a28a" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/alpacatc/tracepointwohuo-yong-sitemoderuming-bian-geng-nofu-zhai-jie-xiao-wositahua">speakerdeck.com</a></cite></p> <p>TracePointの機能を使って、モデル名変更に伴う大量の修正を可能な限り自動化して対応したという内容の話でした。</p> <p>技術的負債を解消する際に、修正量が多すぎると工数やバグのリスクと天秤にかけて見送ってしまうこともあります。それを技術力で何とかするというのはエンジニアリングの腕の見せ所でもあり素晴らしいなと思いました。</p> <p>また、発表の中でTracepointのTipsをいくつか紹介されていました。私自身Tracepointを活用したことがなかったため、すごくためになる内容でした。会議後に早速手元で試してみています。</p> <h3 id="やさしいActiveRecordのDB接続のしくみ">やさしいActiveRecordのDB接続のしくみ</h3> <p><iframe id="talk_frame_1097682" class="speakerdeck-iframe" src="//speakerdeck.com/player/4b455a54b9eb4d6bb6259bb788913cd1" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/boro1234/yasasiiactiverecordnodbjie-sok-nosikumi">speakerdeck.com</a></cite></p> <p>ActiveRecordのDB接続について、どのクラスで何をしているかを順を追って説明する発表でした。</p> <p>図を使った説明を交えながらbacktraceやコードを追って丁寧に説明されており、非常に理解しやすかったです。ActiveRecordなどの複雑なコードでも丁寧に追っていくことで理解を深められることがわかる発表でした。</p> <p>今回は接続部分についての説明でしたが、調査や整理の仕方は他のコードでも応用できるなと思いました。</p> <h3 id="32個のPRでリリースした依存度の高いコアなモデルの安全な弄り方">32個のPRでリリースした依存度の高いコアなモデルの安全な弄り方</h3> <p><iframe id="talk_frame_1097963" class="speakerdeck-iframe" src="//speakerdeck.com/player/92847206c4c64bacbd98bb5b56c7c06e" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/shoheimitani/32ge-noprderirisusitayi-cun-du-nogao-ikoanamoderunoan-quan-nanong-rifang">speakerdeck.com</a></cite></p> <p>サービスのコアとなるモデルの大きな仕様変更に伴い、テーブルの定義変更やモデルの機能拡張をどのようにサービス無停止で進めたかについて紹介されていました。 Studyplusでも昨年サービスの中心となる巨大なテーブルのカラム追加で苦労したことがあったため、気になるセッションでした。</p> <p>前半のオンラインDDLについての説明では、オンラインDDLになる条件や注意すべきポイントを紹介されており、有益な情報でした。 実際の進め方では8つの手順に分けて進められていましたが、コアドメインの変更を安全に行うことは大変な作業で近道はないなと思いました。</p> <p>「安全なリリース方法を知り、決断を先延ばしできるための技術を身につけておくことが大事」という話はとても共感できる内容でした。今後の事業やサービスの成長を予測してあらかじめ実装することは難しいです。一方で必要となった場合に対応できる技術を身につけておくことでリスクを減らして開発を進めることができると思いました。</p> <h3 id="A-Decade-of-Rails-Bug-Fixes">A Decade of Rails Bug Fixes</h3> <p><a href="https://kaigionrails.org/2023/talks/byroot/">A Decade of Rails Bug Fixes by byroot - Kaigi on Rails 2023</a></p> <p>カンファレンス最終セッションの基調講演でした。Railsのバグについて、デバッグ方法やその対応の中で学んだことなどについての話でした。</p> <p>ActiveRecordのカウンターキャッシュに関するバグの対応では、再現が難しいrace conditionのバグを根気強くデバッグして再現方法を調査されていました。簡単な再現コードを作れたところから、解決に向けて一気に進んでいた印象でした。バグの解消において再現できることの重要性をあらためて認識しました。</p> <p>実際の修正においては過去のIssueやPull Requetのやりとりを見てパフォーマンス面が原因でバグ修正が取り込まれなかったことを踏まえて修正をされていました。また、メンテナーにレビューをしてもらう際のコミュニケーションの失敗などの話もされていました。</p> <p>修正が取り込まれるまでの一連の話を聞いて、Railsのような巨大なOSSでうまく取り込んでもらうための動きをイメージできたと同時にその大変さも知ることができました。</p> <h2 id="まとめ">まとめ</h2> <p>どの発表も素晴らしい内容でとても勉強になりました。会場の雰囲気も良くこのような素晴らしいカンファレンスを開いていただいた運営の皆様に感謝です。来年開催される際は、登壇できるように日々の業務を頑張っていきたいと思いました!</p> yshunske FlutterのAdd-to-appを導入しているiOSアプリ保守で起きたライブラリ競合と対処法 hatenablog://entry/820878482972487726 2023-10-30T09:00:00+09:00 2023-10-30T09:00:01+09:00 プロダクト部クライアントグループの明渡です。 昨年9月から今年4月半ばまで産育休をとり、約1年ぶりの当番ブログです。 産んだ子どもは1歳になりました。食欲魔人という文言がしっくりくる食べっぷりで、成長曲線の上辺すれすれで推移しております。 今回は、同チームに所属している隅山がFlutterKaigiのプロポーザルへ提出したものの、残念ながら不採択だった内容を一部供養します。 fortee.jp 内容としては、Add-to-appを導入済みのStudyplus iOSアプリにてFlutterのバージョンを3.7系から3.10系へアップデート作業中に直面した事象と対処方法です。 なお、Flutte… <p>プロダクト部クライアントグループの明渡です。 昨年9月から今年4月半ばまで産育休をとり、約1年ぶりの当番ブログです。</p> <p>産んだ子どもは1歳になりました。食欲魔人という文言がしっくりくる食べっぷりで、成長曲線の上辺すれすれで推移しております。</p> <p>今回は、同チームに所属している隅山がFlutterKaigiのプロポーザルへ提出したものの、残念ながら不採択だった内容を一部供養します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fflutterkaigi-2023%2Fproposal%2F7170a8ad-7222-4455-ad68-1e8fae2783ea" title="Add-to-appを2年運用して起きたこと" 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://fortee.jp/flutterkaigi-2023/proposal/7170a8ad-7222-4455-ad68-1e8fae2783ea">fortee.jp</a></cite></p> <p>内容としては、Add-to-appを導入済みのStudyplus iOSアプリにてFlutterのバージョンを3.7系から3.10系へアップデート作業中に直面した事象と対処方法です。 なお、Flutterのバージョンアップデート作業自体は滞りなく完了し、発生した事象はAdd-to-app内の参照先モジュールにおける機能追加もまとめて取り込んだことが起因でした。</p> <ul class="table-of-contents"> <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="#ネイティブ側CropViewControllerとAdd-to-appモジュール側image_cropperで競合してビルドエラー">ネイティブ側CropViewControllerとAdd-to-appモジュール側image_cropperで競合してビルドエラー</a><ul> <li><a href="#対処方法">対処方法</a></li> </ul> </li> <li><a href="#SwiftのCropViewControllerからTOCropViewController移行により発生したアプリ起動後実行時エラー">SwiftのCropViewControllerからTOCropViewController移行により発生したアプリ起動後実行時エラー</a><ul> <li><a href="#対処方法-1">対処方法</a></li> </ul> </li> </ul> </li> <li><a href="#さいごに">さいごに</a></li> <li><a href="#おまけ-Flutterバージョン3107の参照方法">おまけ: Flutterバージョン3.10.7の参照方法</a><ul> <li><a href="#バージョン3107とは">バージョン3.10.7とは</a></li> <li><a href="#ローカル環境">ローカル環境</a></li> <li><a href="#GitHub-Actions">GitHub Actions</a></li> </ul> </li> </ul> <h2 id="環境">環境</h2> <h3 id="動作環境">動作環境</h3> <ul> <li>Xcode 14.3.1 (Swift 5.8.1)</li> <li>Flutter 3.10.<strong>7</strong> <ul> <li>archiveバージョンに含まれないもので、参照方法は<a href='#omake'>おまけとして後述</a></li> </ul> </li> </ul> <h3 id="ライブラリの管理方法">ライブラリの管理方法</h3> <ul> <li>ネイティブ側 <ul> <li>Swift Package Manager</li> </ul> </li> <li>Add-to-appモジュール側 <ul> <li>Swift Package Manager <ul> <li><a href="https://docs.flutter.dev/add-to-app/ios/project-setup#option-b---embed-frameworks-in-xcode">Option B</a>を採用しており、ネイティブ側と中身が同じライブラリのバージョン指定や除外をAdd-to-appモジュール配下のPackage.swiftで対応</li> </ul> </li> <li>Cocoa Pods <ul> <li>pubspec.yamlに記載の自動インポート分</li> </ul> </li> </ul> </li> </ul> <h2 id="発生した事象">発生した事象</h2> <h3 id="ネイティブ側CropViewControllerとAdd-to-appモジュール側image_cropperで競合してビルドエラー">ネイティブ側CropViewControllerとAdd-to-appモジュール側image_cropperで競合してビルドエラー</h3> <p>ネイティブ側で画像のトリミング機能をサポートするため、CropViewControllerを導入済みでした。</p> <p><a href="https://github.com/TimOliver/TOCropViewController">GitHub - TimOliver/TOCropViewController: A view controller for iOS that allows users to crop portions of UIImage objects</a></p> <p>そして、Flutterで開発した機能内にて新たに同じ機能をサポートするため、image_cropperを導入しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fpub.dev%2Fpackages%2Fimage_cropper" title="image_cropper | Flutter Package" 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://pub.dev/packages/image_cropper">pub.dev</a></cite></p> <p>このimage_cropperですが、各プラットフォーム毎に選定されたライブラリをラッピングして呼び分ける形式のライブラリです。 iOSでは、弊社アプリケーションのネイティブ側で導入済みであるCropViewControllerのObjective-C版、TOCropViewControllerが採用されています。</p> <p>そして、ネイティブ側で導入済みのCropViewControllerは依存先としてTOCropViewControllerが指定されています。</p> <p><a href="https://github.com/TimOliver/TOCropViewController/blob/main/Package.swift#L39C52-L39C52">TOCropViewController/Package.swift at main &middot; TimOliver/TOCropViewController &middot; GitHub</a></p> <p>ネイティブ側とAdd-to-appモジュール側でTOCropViewControllerを二重に参照してしまう形となり、CropViewControllerの利用箇所でビルドエラーが発生しました。</p> <h4 id="対処方法">対処方法</h4> <p>結論としては、Swift対応版であるCropViewControllerの利用をやめてObjective-C版のTOCropViewControllerへ統一しました。 ネイティブ側とAdd-to-appモジュール側で同じライブラリを利用すれば、競合してエラーが発生することはなくなります。</p> <p>まず、ネイティブ側のSPMからCropViewControllerを削除します。 そして、Add-to-appモジュール側のSPMへ親に当たるimage_cropperだけでなくTOCropViewControllerも併せて明記します。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">let</span> <span class="synIdentifier">items</span> <span class="synIdentifier">=</span> [ <span class="synComment">// ... 省略 ...</span> <span class="synConstant">&quot;image_cropper&quot;</span>, <span class="synConstant">&quot;TOCropViewController&quot;</span> ] <span class="synPreProc">let</span> <span class="synIdentifier">package</span> <span class="synIdentifier">=</span> Package( name<span class="synSpecial">:</span> <span class="synConstant">&quot;module_add-to-app&quot;</span>, <span class="synComment">// ... 省略 ...</span> products<span class="synSpecial">:</span> <span class="synSpecial">[</span> <span class="synType"> .library</span><span class="synSpecial">(</span> <span class="synType"> name: &quot;module_add-to-app&quot;</span>, <span class="synType"> targets: items</span> <span class="synType"> </span><span class="synSpecial">)</span><span class="synType">,</span> <span class="synType"> </span><span class="synSpecial">]</span>, dependencies<span class="synSpecial">:</span> <span class="synSpecial">[</span> <span class="synType"> // ... 省略 ...</span> <span class="synType"> </span><span class="synSpecial">]</span>, targets<span class="synSpecial">:</span> <span class="synType">items.map</span>({ name <span class="synStatement">in</span> Target.binaryTarget( name<span class="synSpecial">:</span> <span class="synType">name</span>, path<span class="synSpecial">:</span> <span class="synConstant">&quot;build/Release/</span><span class="synSpecial">\(</span>name<span class="synSpecial">)</span><span class="synConstant">.xcframework&quot;</span> ) }) ) </pre> <p>こうすることで、ネイティブ側からもTOCropViewControllerを参照できるようになりました。</p> <p>あとはネイティブ側でもともとCropViewControllerを利用している箇所で、クラスおよび同じ役割のプロパティやメソッドへ差し替えれば完了です。</p> <h3 id="SwiftのCropViewControllerからTOCropViewController移行により発生したアプリ起動後実行時エラー">SwiftのCropViewControllerからTOCropViewController移行により発生したアプリ起動後実行時エラー</h3> <p>上記のビルドエラーを解消し終えてアプリを起動してみたところ、起動直後にクラッシュが発生してしまいました。</p> <p>実行時エラーはObjective-Cのコードを触っていた頃はわりとあるあるだったことを今回の記事を書き始めて思い出しましたが、遭遇するのが久々で当時対応に悩みました。</p> <p>原因としてはAdd-to-app要素は特に関係なく、ネイティブ側で完結する要素でした。 利用しているライブラリを今回のような諸事情でSwift対応版からObjective-C版に移行すると発生するケースがありそうです。</p> <p>Studyplus iOSアプリでは<a href="https://tech.studyplus.co.jp/entry/2021/07/05/100000">AppDelegateからSceneDelegateへの移行が完了しています</a>。 移行タイミングでAppDelegateにてwindowプロパティの保持をやめており、Objective-C版ライブラリから存在しないwindowプロパティを参照しようとして発生したエラーでした。</p> <h4 id="対処方法-1">対処方法</h4> <p>AppDelegateでのwindowプロパティの保持を復帰せざるおえませんでした。 SceneDelegateにて、アプリ起動時の処理タイミングでAppDelegateのwindowプロパティへも併せてインスタンスを保持するようにすることで解決しました。</p> <pre class="code lang-swift" data-lang="swift" data-unlink><span class="synPreProc">func</span> <span class="synIdentifier">scene</span>(_ scene<span class="synSpecial">:</span> <span class="synType">UIScene</span>, willConnectTo session<span class="synSpecial">:</span> <span class="synType">UISceneSession</span>, options connectionOptions<span class="synSpecial">:</span> <span class="synType">UIScene.ConnectionOptions</span>) { <span class="synStatement">guard</span> <span class="synPreProc">let</span> <span class="synIdentifier">scene</span> <span class="synIdentifier">=</span> (scene <span class="synStatement">as?</span> <span class="synType">UIWindowScene</span>) <span class="synStatement">else</span> { <span class="synStatement">return</span> } <span class="synPreProc">let</span> <span class="synIdentifier">window</span> <span class="synIdentifier">=</span> UIWindow(windowScene<span class="synSpecial">:</span> <span class="synType">scene</span>) <span class="synIdentifier">self</span>.window <span class="synIdentifier">=</span> window <span class="synComment">// Flutter Add-to-app都合でObjective-C版を参照しているTOCropViewControllerにて、</span> <span class="synComment">// SceneDelegateのwindow未対応のためAppDelegateでやむなくwindowを保持</span> <span class="synPreProc">let</span> <span class="synIdentifier">appDelegate</span> <span class="synIdentifier">=</span> UIApplication.shared.delegate <span class="synStatement">as?</span> <span class="synType">AppDelegate</span> appDelegate?.window <span class="synIdentifier">=</span> window <span class="synComment">// ... 省略 ...</span> } </pre> <h2 id="さいごに">さいごに</h2> <p>Add-to-appを利用しているアプリを継続的に保守していないとなかなか遭遇しないであろう事象と、実際に行なった対処方法の書き起こしでした。</p> <p>FlutterのAdd-to-appを利用すると、複数プラットフォームにわたり共通のコードで同じ機能を提供できてとても便利です。 しかしながら、事例が少なめでトラブルに遭遇すると都度解決に手間取りがちなので、同じような問題に直面した方の一助になれば幸いです。</p> <p>本編は以上です。</p> <p>おまけとして、バージョンの切られ方が特殊なため、特にCI上での指定に少し手間取ったFlutterバージョン3.10.7の参照方法をしたためておきます。</p> <h2 id="おまけ-Flutterバージョン3107の参照方法"><a id="omake">おまけ: Flutterバージョン3.10.7の参照方法</a></h2> <h3 id="バージョン3107とは">バージョン3.10.7とは</h3> <p><a href="https://docs.flutter.dev/release/archive?tab=macos">Flutter SDK Archive</a>に存在しない、hotfixバージョンです。</p> <p>Flutterバージョン3.10.6以下で構築されたアプリケーションをiOS 16.6以上の環境で動作させた際に、深刻なパフォーマンス低下が発生する問題に対応されています。</p> <p>上記への対応はバージョン3.13以上にも含まれるため、事情がなければバージョン3.13以上に上げてしまうのが最もおすすめです。</p> <h3 id="ローカル環境">ローカル環境</h3> <p>flutterリポジトリのWikiに記載通りのコマンドで参照できます。</p> <p><a href="https://github.com/flutter/flutter/wiki/Upgrading-from-3.10.6-to-3.10.7">Upgrading from 3.10.6 to 3.10.7 &middot; flutter/flutter Wiki &middot; GitHub</a></p> <p>正式バージョンとしてアーカイブはしないけどtagは切ったので、どうしても利用したい場合はそちらを参照してね、という方針らしいです。</p> <h3 id="GitHub-Actions">GitHub Actions</h3> <p>弊社ではGitHub ActionsにおけるCI環境でFlutterを利用するため、<a href="https://github.com/subosito/flutter-action">flutter-action</a>を導入しています。</p> <p>こちらのアクションで利用を想定しているFlutterバージョンは、あくまで正式バージョンとしてアーカイブされたものです。 この想定は当たり前で、バージョン3.10.7の扱いが例外的だったため、flutter-actionのREADME.mdへ記載済みの指定方法では参照できませんでした。</p> <p>flutter-actionリポジトリのIssueで同じことに悩んでる方が質問しており、回答を参考にして無事参照できました。</p> <p><a href="https://github.com/subosito/flutter-action/issues/242#issuecomment-1699782139">How do I pull 3.10.7? &middot; Issue #242 &middot; subosito/flutter-action &middot; GitHub</a></p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synStatement">- </span><span class="synIdentifier">uses</span><span class="synSpecial">:</span> subosito/flutter-action@v2 <span class="synIdentifier">with</span><span class="synSpecial">:</span> <span class="synIdentifier">channel</span><span class="synSpecial">:</span> <span class="synConstant">'stable'</span> <span class="synIdentifier">flutter-version</span><span class="synSpecial">:</span> <span class="synConstant">'3.10.x'</span> <span class="synIdentifier">architecture</span><span class="synSpecial">:</span> <span class="synConstant">'x64'</span> <span class="synStatement">- </span><span class="synIdentifier">name</span><span class="synSpecial">:</span> Checkout Flutter 3.10.7 <span class="synIdentifier">shell</span><span class="synSpecial">:</span> bash <span class="synIdentifier">run</span><span class="synSpecial">:</span> | cd $FLUTTER_ROOT; git fetch --tags; git checkout 3.10.7; flutter --version; </pre> <p>ローカル環境で該当のtagを指定する手順をそのままCI環境で実行する、という内容です。</p> <p>注意点として、指定したバージョンから半ば無理矢理想定外のバージョンを参照し直すこの方法は、<code>cache</code>オプションを有効にしてもバージョン3.10.7を参照し続ける保証がありません。</p> <p><code>cache</code>オプションが無効な場合はCIを走らせる都度Flutterを丸ごとインストールすることになってしまい、CIの実行回数×インストール時間の分だけ発生する料金がかさみます。</p> <p>こういった事情も鑑みて、<code>cache</code>オプションも問題なく効かせられるバージョン3.13以上へ可能な限り早めに対応する方が良いです。</p> m_yamada1992 スタディプラスはKaigi on Rails 2023にシルバースポンサーとして協賛します hatenablog://entry/6801883189053273402 2023-10-26T10:00:00+09:00 2023-10-26T10:00:01+09:00 こんにちは。 スタディプラスのカンファレンス/OSSサポートチーム、菅原です。 スタディプラスは、2023年10月27日・28日にハイブリッド(オンライン・オフライン)開催されるKaigi on Rails 2023にシルバースポンサーとして協賛します。 kaigionrails.org Kaigi on Rails 2023とは? Kaigi on Railsのコアコンセプトは「初学者から上級者までが楽しめるWeb系の技術カンファレンス」です。Kaigi on Railsは技術カンファレンスへの参加の敷居を下げることを意図して企画されています。また、名前の通りRailsを話題の中心に据えるカ… <p>こんにちは。 スタディプラスのカンファレンス/OSSサポートチーム、菅原です。</p> <p>スタディプラスは、2023年10月27日・28日にハイブリッド(オンライン・オフライン)開催されるKaigi on Rails 2023にシルバースポンサーとして協賛します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkaigionrails.org%2F2023%2Fsponsors%2F" title="Sponsors - Kaigi on Rails 2023" 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://kaigionrails.org/2023/sponsors/">kaigionrails.org</a></cite></p> <h3 id="Kaigi-on-Rails-2023とは">Kaigi on Rails 2023とは?</h3> <blockquote><p>Kaigi on Railsのコアコンセプトは「初学者から上級者までが楽しめるWeb系の技術カンファレンス」です。Kaigi on Railsは技術カンファレンスへの参加の敷居を下げることを意図して企画されています。また、名前の通りRailsを話題の中心に据えるカンファレンスではありますが、広くWebに関すること全般(例えばフロントエンドやプロトコルなど)についてもカバーすることで参加者の知見を深め、また明日からの仕事に役立てていただければと考えています。</p></blockquote> <p>【概要】<br/>  主催 :Kaigi on Rails Team<br/>  開催日:2023年10月27日(金)・28日(土)<br/>  会場 :浅草橋ヒューリックホール&amp;カンファレンス or オンライン<br/>  公式HP:<a href="https://kaigionrails.org/2023/">https://kaigionrails.org/2023/</a></p> <h3 id="協賛">協賛</h3> <p>昨年に引き続き、シルバースポンサーとして協賛させていただくこととなりました。今回が3回目の協賛となります。<br/> 今年はオンラインとオフラインのハイブリッド開催ということで会場の盛り上がりも気になりますね。<br/> 非常に楽しみなセッションやイベントが盛り沢山なので、ぜひ参加してみて下さい。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkaigionrails.org%2F2023%2Fschedule%2F%23day1" title="Schedule - Kaigi on Rails 2023" 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://kaigionrails.org/2023/schedule/#day1">kaigionrails.org</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkaigionrails.org%2F2023%2Fevents%2F" title="Events - Kaigi on Rails 2023" 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://kaigionrails.org/2023/events/">kaigionrails.org</a></cite></p> ksugahara08 SRE NEXT 2023 参加レポート hatenablog://entry/6801883189052029088 2023-10-24T11:00:00+09:00 2023-10-24T11:07:41+09:00 SREグループの菅原です。 9/29(金)に九段会館テラスにて開催されたSRE NEXT 2023に参加しました。 ちょっと遅くなってしまいましたが、カンファレンスやセッションの感想についてレポートします。 SRE NEXT 2023とは 信頼性に関するプラクティスに深い関心を持つエンジニアのためのカンファレンスです。 同じくコミュニティベースのSRE勉強会である「SRE Lounge」のメンバーが中心となり運営・開催されます。 (公式Webサイトより引用) 感想 菅原 今年は九段下会館テラスということでレトロモダンに統一され、とても素敵な会場でした。 会場で視聴したのですが、登壇者の紹介やセ… <p>SREグループの菅原です。 9/29(金)に九段会館テラスにて開催されたSRE NEXT 2023に参加しました。 ちょっと遅くなってしまいましたが、カンファレンスやセッションの感想についてレポートします。</p> <h1 id="SRE-NEXT-2023とは">SRE NEXT 2023とは</h1> <blockquote><p>信頼性に関するプラクティスに深い関心を持つエンジニアのためのカンファレンスです。 同じくコミュニティベースのSRE勉強会である「SRE Lounge」のメンバーが中心となり運営・開催されます。 (<a href="https://sre-next.dev/2023/">公式Webサイト</a>より引用)</p></blockquote> <h1 id="感想">感想</h1> <h2 id="菅原">菅原</h2> <p>今年は九段下会館テラスということでレトロモダンに統一され、とても素敵な会場でした。 会場で視聴したのですが、登壇者の紹介やセッション前のカウントダウン映像などスタイリッシュで過去1番カッコいいカンファレンスでした。</p> <h3 id="プロダクトオーナーの視座から見た信頼性とオブザーバビリティ">プロダクトオーナーの視座から見た信頼性とオブザーバビリティ</h3> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/C3vmwZwThu4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="【SRE NEXT 2023】プロダクトオーナーの視座から見た信頼性とオブザーバビリティ / FUJII Yoshitaka"></iframe><cite class="hatena-citation"><a href="https://youtu.be/C3vmwZwThu4">youtu.be</a></cite></p> <p><iframe id="talk_frame_1084194" class="speakerdeck-iframe" src="//speakerdeck.com/player/b5db0dc067e6490f882eca31078508d3" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/yoshiyoshifujii/reliability-and-observability-from-the-perspective-of-a-product-owner">speakerdeck.com</a></cite></p> <p>プロダクトオーナーの視点から信頼性やオブザーバビリティがどのように映っているのか興味があり視聴しました。期待していた通り、ユーザー体験を損なわないようにどうするか?という話からオブザーバビリティに話を繋げており、Chatworkさんでどのように取り組んだのかという話が聞けました。</p> <p>「ユーザー満足度は誰の関心なのか?と考えた時プロダクトオーナーこそが最もSLOに関心を持つべき」という言葉にすごく共感しました。</p> <p>弊社でもオブザーバビリティ・エンジニアリングの読書会やHoneycombの勉強会をプロダクトオーナーを巻き込みながら開きたいと思いました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.oreilly.co.jp%2Fbooks%2F9784814400126%2F" title="オブザーバビリティ・エンジニアリング" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.oreilly.co.jp/books/9784814400126/">www.oreilly.co.jp</a></cite></p> <h3 id="SREの組織類型に応じたリーダーシップの考察">SREの組織類型に応じたリーダーシップの考察</h3> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/q2IBPIY9pb4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="【SRE NEXT 2023】SREの組織類型に応じたリーダーシップの考察 / 菱田 健太"></iframe><cite class="hatena-citation"><a href="https://youtu.be/q2IBPIY9pb4">youtu.be</a></cite></p> <p><iframe id="talk_frame_1084377" class="speakerdeck-iframe" src="//speakerdeck.com/player/aedbb2348ec84a63836cb7b76bcf4879" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/kenta_hi/srenozu-zhi-lei-xing-niokeruritasituhunokao-cha">speakerdeck.com</a></cite></p> <p>このセッションは目から鱗でした!視聴してない方でSREと組織のあり方やリーダーシップ理論などに興味のある方はこんな見方もあるのかとなるのでオススメです。</p> <p>菱田氏はSL理論や組織の信頼性のマインドセットを説明した上で、SL理論の部下の発達度を組織の信頼性マインドセットのフェーズに当てはめることができるという話をしていました。</p> <p>これによりSRE組織のリーダーシップスタイルを決めることができるのでは?という話は聞いていて興味深かったです。 SRE組織に対して技術支援をしているTopotalさんならではの話だったのではないでしょうか?</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fblog%2Fja%2Fproducts%2Fdevops-sre%2Fthe-five-phases-of-organizational-reliability" title="組織の信頼性のマインドセット:Google SRE の知見 | Google Cloud 公式ブログ" 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://cloud.google.com/blog/ja/products/devops-sre/the-five-phases-of-organizational-reliability">cloud.google.com</a></cite></p> <h2 id="水口">水口</h2> <p>2022年5月からスタディプラスのSREグループで働き始めた水口です。SRE NEXTの存在は以前から知っていましたが、SREのプラクティスを実践する役割になってからの参加は初めてです。</p> <p>九段会館に来るのは10年以上前の「向井秀徳アコースティック&エレクトリック」のライブに行って以来であったり、そもそも久しぶりのカンファレンス現地参加ということでとても楽しみにしておりました。会場ではワイワイと技術の話をしていたり、元同僚との久しぶりの再会を喜んでいる人などがたくさんおり、「SREに関心を持っているソフトウェアエンジニアってこんなにたくさんいたんだ・・・?」と私は衝撃を受けておりました。</p> <p>個別のセッションについての感想は書ききれないので簡単に1つだけ書きます。各セッションに共通していたこととして「信頼性を語る上で最も大事なのはユーザーである」ことが語られており、改めて重要なことだと認識しました。</p> <p>また、懇親会にも参加しました。私は知り合いもほぼいない中1人参加だった上、あまり懇親会が得意な方ではなく戦々恐々としておりましたが、同じ話題を共有する方々ばかりというのもあって色々な方とお話しできました。SRE NEXTの良かった点ですね!</p> <p>『みてね』のセッションなどでもEKSのバージョンアップなどの運用が大変という話がありましたが、プロダクトをEKSで動かしている会社の方々とお話させてもらって、色々な苦労話を聞けました。「ああ、泥臭く大変な運用を地道に1つずつやっているのは我々だけではないんだ。外から見てキラキラして見える会社の方々も地道な苦労の積み重ねのもとでエンジニアリングしているんだ・・・!」と分かって勇気づけられました。 </p> <p>また、『Kubernetes完全ガイド』の著者で有名な青山真也さんとお話できたのは個人的に大きなトピックです。「勉強し始めの頃に<a href="https://developers.cyberagent.co.jp/blog/archives/27443/">おうちKubernetes</a>を作って楽しくKubernetesに入門できました」など色々お伝えできてよかったです。青山さんには「k8sの業務経験も1年経つなら、そろそろプロポーザルを出せる頃ですね?」という <del>圧</del> 激励をいただいたので、今後はアウトプットにも力を入れていきたいと思っています。</p> <p>お話させてもらった方一人一人について名前を出してお礼をしたいぐらい楽しかったのですが、長くなってしまうので私の感想は以上にします。来年はSRE NEXTにプロポーザルを出したいですね!</p> <h2 id="蜂須賀">蜂須賀</h2> <p>2022年10月からスタディプラスのSREグループで働き始めた蜂須賀です。 今回初めてSRE NEXTに参加しました。開会から閉会まで各社の取り組みを知ることができ、とても勉強になりました。</p> <p>特に印象に残ったセッションを2つ紹介します。</p> <h3 id="Warningアラートを放置しないアラート駆動でログやメトリックを自動収集する仕組みによる恩恵">Warningアラートを放置しない!アラート駆動でログやメトリックを自動収集する仕組みによる恩恵</h3> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/NulfYftU2Yw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="【SRE NEXT 2023】Warningアラートを放置しない!アラート駆動でログやメトリックを自動収集する仕組みによる恩恵 / 池田 将士"></iframe><cite class="hatena-citation"><a href="https://youtu.be/NulfYftU2Yw">youtu.be</a></cite></p> <p><iframe id="talk_frame_1084251" class="speakerdeck-iframe" src="//speakerdeck.com/player/7ae64f9683114c19be660fb8fc7496f8" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/mashiike/warningaratowofang-zhi-sinai-aratoqu-dong-deroguyametoritukuwozi-dong-shou-ji-surushi-zu-miniyoruen-hui">speakerdeck.com</a></cite></p> <p>アラート駆動でログやメトリクスを自動収集する<a href="https://github.com/mashiike/prepalert">prepalert</a>というOSSを使用した事例について紹介していました。 このセッションの中で私が大切だと感じたのは、優先度が低いが定期的に実施したほうが良い調査の効率化です。</p> <p>Warningアラートの調査は問題として顕在化する段階まで達していることが少なく、優先度が低くながりがちです。 この調査を効率化することで工数自体を削減できますが、工数を短くすることで定期的に調査する習慣が作りやすくなったり、週次MTG等で調査分析する時間を取ることができます。</p> <p>Warningアラートだけでなく、優先度が低いが定期的に調査したほうが良いものは効率化するメリットがあると感じました。</p> <h3 id="SREを以てセキュリティエンジニアリングを制す--class-DevSecOpsの実装に向けて">SREを以てセキュリティエンジニアリングを制す ― class Dev&quot;Sec&quot;Opsの実装に向けて</h3> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/qCD03riMLK8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="【SRE NEXT 2023】SREを以てセキュリティエンジニアリングを制す ― class Dev&quot;Sec&quot;Opsの実装に向けて / 米内 貴志"></iframe><cite class="hatena-citation"><a href="https://youtu.be/qCD03riMLK8?feature=shared">youtu.be</a></cite></p> <p><iframe id="talk_frame_1085217" class="speakerdeck-iframe" src="//speakerdeck.com/player/8a936d7313394e6cbd5e1418b7f4bdbe" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/lmt_swallow/sre-security-engineering-and-you">speakerdeck.com</a></cite></p> <p>セキュリティスキャナのスコアをSLO/SLI的な観点で運用したい場合の問題点について紹介されていました。</p> <p>このセッションでは、SLO/SLIに関して再考する機会を得ることができました。 セキュリティスキャナのスコアをそのままSLO/SLIで導入してしまうと、そのSLOの数値が何を表しているのかわからなくなってしまいます。それだけでなくまとめて数値化することで各問題の重大さがわからなくってしまいます。</p> <p>SLO/SLI的な観点で運用するなら、メトリクスの特性やそのメトリクスが悪化した場合のリスクの特性を吟味して導入する必要があると感じました。</p> <h1 id="まとめ">まとめ</h1> <p>今回紹介できなかったセッションの中にもとても素晴らしいものがたくさんありました。 毎回SRE NEXTではSREという枠組みで色々な事例が聞けて、参加した後に「仕事頑張るぞ!」という気持ちにさせてくれます。来年以降のSRE NEXTも楽しみですし、弊社SREグループももっと発信していかないとなと思わされました。</p> ksugahara08 Amazon CloudWatchの費用を半額削減した話 hatenablog://entry/820878482971296274 2023-10-16T10:00:00+09:00 2023-10-16T10:00:03+09:00 Amazon CloudWatchの費用を半額削減した話 こんにちは。スタディプラスのSREグループの蜂須賀です。 今回はAmazon CloudWatchの費用を半額削減した話を紹介します。 Amazon CloudWatchの費用を半額削減した話 経緯 CloudWatch Metric Streamsの試験導入 背景 試験導入の結果 実施内容 不必要なリージョンを除外する 不必要なサービスを除外する 不必要なリソースを除外する まとめ 経緯 スタディプラスでは主にAWSでシステムを構築しており、そのモニタニングツールとしてはDatadogを使用しています。 2023年初頭にCloudWa… <h1 id="Amazon-CloudWatchの費用を半額削減した話">Amazon CloudWatchの費用を半額削減した話</h1> <p>こんにちは。スタディプラスのSREグループの蜂須賀です。<br/> 今回はAmazon CloudWatchの費用を半額削減した話を紹介します。</p> <ul class="table-of-contents"> <li><a href="#Amazon-CloudWatchの費用を半額削減した話">Amazon CloudWatchの費用を半額削減した話</a><ul> <li><a href="#経緯">経緯</a></li> <li><a href="#CloudWatch-Metric-Streamsの試験導入">CloudWatch Metric Streamsの試験導入</a><ul> <li><a href="#背景">背景</a></li> <li><a href="#試験導入の結果">試験導入の結果</a></li> </ul> </li> <li><a href="#実施内容">実施内容</a><ul> <li><a href="#不必要なリージョンを除外する">不必要なリージョンを除外する</a></li> <li><a href="#不必要なサービスを除外する">不必要なサービスを除外する</a></li> <li><a href="#不必要なリソースを除外する">不必要なリソースを除外する</a></li> </ul> </li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h2 id="経緯">経緯</h2> <p>スタディプラスでは主にAWSでシステムを構築しており、そのモニタニングツールとしてはDatadogを使用しています。 <br/> 2023年初頭にCloudWatchの費用が昨年比で2倍に増加していたことをグループ内で問題視していました。<br/> 調査したところ、DatadogのAWSインテグレーションによるメトリクスの収集で発生する費用が、CloudWatchの費用の大半を占めいていることがわかったので改善することにしました。</p> <h2 id="CloudWatch-Metric-Streamsの試験導入">CloudWatch Metric Streamsの試験導入</h2> <h3 id="背景">背景</h3> <p>CloudWatchのメトリクスをDatadogに収集するパターンとしては2種類あります。</p> <p>1つ目はDatadogがCloudWatchのGetMetricDataというAPIを利用してメトリクスを収集するパターンです。GetMetricDataは1000件のメトリクスごとに0.01ドルの費用が発生し、収集するまでに10分程度の遅延が発生します。</p> <p>2つ目はCloudWatch Metric StreamsからDatadogにメトリクスを送信するパターンです。1000件のメトリクスごとに0.003ドルの費用が発生し、遅延に関しては2-3分程度に抑えられます。</p> <p>費用が10分の3となり、遅延も短くなりそうだったため2つ目のCloudWatch Metric Streamsを試験導入をすることにしました。</p> <h3 id="試験導入の結果">試験導入の結果</h3> <p>CloudWatch Metric Streamsを試験導入してみましたが、以前の費用よりも1.5倍ほど高くなるという結果になりました。</p> <p>Datadogから収集するパターンではメトリクスは平均値のみを収集しますが、CloudWatch Metric Streamsでは最小、最大、サンプル数、合計も収集されます。リクエスト料金は10分の3になりますが、おそらくそれ以上に収集するメトリクス量が増えてしまったために、費用が上がってしまったと推測されます。</p> <p>特定のリソースに紐づくメトリクスのみ収集するといった設定もできなかったので、今回は見送ることにしました。</p> <p><figure class="figure-image figure-image-fotolife" title="CloudWatchMetricStream試験導入後の料金"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/shatisuka/20231003/20231003114343.png" width="473" height="386" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CloudWatchMetricStream試験導入後の料金</figcaption></figure></p> <h2 id="実施内容">実施内容</h2> <h3 id="不必要なリージョンを除外する">不必要なリージョンを除外する</h3> <p>不必要なリージョンでCloudWatchサービスのGMD-MetricsやRequestsの費用が月100ドル程度発生していました。Datadogでメトリクスを収集していることが原因だったので、不必要なリージョンを除外することにしました。</p> <p>DatadogのAmazon Web ServicesのIntegrationsのGeneralで各アカウントごとにリージョン設定ができます。</p> <p>デフォルトでは、全てのリージョンが有効になっているので、使用しているリージョンのみ有効にしました。なお、Route53や請求関連のメトリクスはus-east-1リージョンで取得しているため、us-east-1も有効にしています。</p> <p>一般提供の開始したリージョンがあると、自動的に有効になるため定期的に設定の確認をしています。</p> <p><figure class="figure-image figure-image-fotolife" title="Datadogでメトリクスを収集するAWSのリージョンの指定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/shatisuka/20230928/20230928134419.png" width="1200" height="552" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Datadogでメトリクスを収集するAWSのリージョンの指定</figcaption></figure></p> <h3 id="不必要なサービスを除外する">不必要なサービスを除外する</h3> <p>不必要なメトリクスを収集していると費用が膨らんでしまうので、不必要なサービスのメトリクスの収集を除外することにしました。</p> <p>DatadogのAmazon Web ServicesのIntegrationsのMetric Collectionで各アカウントごとに収集するサービスを設定できます。</p> <p>CloudWatchの「すべてのメトリクス」からメトリクスを収集しているサービスとメトリクス自体を参照できるので、これらの情報を元に精査しました。</p> <p>リージョンと同様に、一般提供の開始したサービスがあると自動的にメトリクス収集が有効になるため、定期的に設定の確認をしています。</p> <p><figure class="figure-image figure-image-fotolife" title="Datadogでメトリクスを収集するAWSのサービスの指定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/shatisuka/20230928/20230928134509.png" width="1200" height="513" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Datadogでメトリクスを収集するAWSのサービスの指定</figcaption></figure></p> <h3 id="不必要なリソースを除外する">不必要なリソースを除外する</h3> <p>収集することを決めたサービスの中には、収集の不必要なリソースも含まれていたので精査することにしました。</p> <p>DatadogのAmazon Web ServicesのIntegrationsのMetric Collectionで各アカウントごとに必要なリソースのみメトリクス収集する設定ができます。具体的には、Limit Metric Collection to Specific Resourcesの項目になります。</p> <p>設定できるサービスは少ないですが、不必要なリソースを除外することで費用を削減できました。</p> <p><figure class="figure-image figure-image-fotolife" title="Datadogでメトリクスを収集するリソースの制限設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/shatisuka/20230928/20230928135207.png" width="1200" height="411" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Datadogでメトリクスを収集するリソースの制限設定</figcaption></figure></p> <h1 id="まとめ">まとめ</h1> <p>今回の取り組みによって、CloudWatchの費用を半額ほど削減できました。<br/> 必要なリソースのみに絞ってメトリクスを収集するように設定しただけですが、長年放置されていた箇所に気づけてよかったです。<br/> また、それほど工数をかけずにかなりの費用を削減できたのでとても満足です。</p> <p><figure class="figure-image figure-image-fotolife" title="CloudWatchのコスト推移"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/studyplus/20230721/20230721192847.png" width="1110" height="578" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CloudWatchのコスト推移</figcaption></figure></p> shatisuka 「アジャイル開発におけるQAエンジニア」 hatenablog://entry/820878482963275716 2023-10-04T10:00:00+09:00 2023-10-04T10:00:00+09:00 こんにちは。QAエンジニアの和田谷です。 残暑続く中ですが相も変わらずサウナは最高です。 今回は「アジャイル開発におけるQAエンジニア」について私がQAとして行ってきた活動をお話しできればと思います。*1 そもそもアジャイル開発とは? アジャイル(Agile)とは、直訳すると「素早い」「機敏な」「頭の回転が速い」という意味です。 システムやソフトウェア開発におけるプロジェクト開発手法の1つで、小単位で実装とテストを繰り返して開発を進めていきます。 アジャイル開発に対してQAとして最初に感じたこと 初めてアジャイル開発という言葉を聞いたのは今から7年ほど前で、当時はそもそも「アジャイル」って何?… <p>こんにちは。QAエンジニアの和田谷です。 残暑続く中ですが相も変わらずサウナは最高です。</p> <p>今回は「アジャイル開発におけるQAエンジニア」について私がQAとして行ってきた活動をお話しできればと思います。<a href="#f-6e53c338" name="fn-6e53c338" title="前提としてアジャイル開発「スクラム」に対してのQA対応のお話となります。">*1</a></p> <h3 id="そもそもアジャイル開発とは">そもそもアジャイル開発とは?</h3> <p>アジャイル(Agile)とは、直訳すると「素早い」「機敏な」「頭の回転が速い」という意味です。 システムやソフトウェア開発におけるプロジェクト開発手法の1つで、小単位で実装とテストを繰り返して開発を進めていきます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuto_wadatani/20230831/20230831110225.png" width="1200" height="555" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="アジャイル開発に対してQAとして最初に感じたこと">アジャイル開発に対してQAとして最初に感じたこと</h3> <p>初めてアジャイル開発という言葉を聞いたのは今から7年ほど前で、当時はそもそも「アジャイル」って何?という状態でした。</p> <p>そしてアジャイル開発が小単位で短い期間での対応の為、必然的にQA期間も短くなり、品質保証を行いながら進めることが困難なのではないかという不安しかありませんでした。</p> <p>しかしQAであるのであれば品質保証を行いながら進めることをしなければいけません。</p> <h3 id="アジャイル開発スクラムに対して行ってきたQA対応方法">アジャイル開発「スクラム」に対して行ってきたQA対応方法</h3> <h4 id="1スプリント内で開発とQAは同時に行わない">1スプリント内で開発とQAは同時に行わない</h4> <p>1スプリント内でQAも行うとなると開発が遅れた際にはTest対象を削る、最悪だとリリース日をずらすなどということが起きる為、開発の次スプリントでQAを行うようにします。</p> <p><figure class="figure-image figure-image-fotolife" title="QA導入"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuto_wadatani/20230831/20230831105436.png" width="1200" height="661" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>QA導入</figcaption></figure> 1スプリント内でQAも行いたいのであれば、「1スプリント=2週間開発+2週間QA対応」の計4週間にするようにステークホルダーと定めておくと良いと思います。</p> <h4 id="QA見積もりを行いどのくらいの期間でQAを完了することができるか把握し共有">QA見積もりを行いどのくらいの期間でQAを完了することができるか把握し共有</h4> <p>開発完了後にQAを開始したとしても、リリース日に間に合わないと意味がないです。また、次スプリントにまでQA対応がはみ出してしまうと、その後の案件対応の時間が短くなり、結果その後の対応が遅れていってしまいます。</p> <p>その為、QA見積りを行い必要期間をステークホルダーと決め進めることが必要です。私の経験した理想は1スプリント(2週間)で開発、開発完了後の2週間でQA対応(リリース含む)というサイクルで行うと1か月で2度のリリースが実現でき、ユーザに新しいものを素早く提供でき良いと思います。</p> <h4 id="Test対象を早く決める">Test対象を早く決める</h4> <p>Test対象が多ければ多いほどTest工数は増えていき、決められた期間での準備、Test実行時間が減っていきます。その為、上流工程でTestを行うものを素早く決めておくことが大切です。</p> <p>過去に以下のような活動をした際は、プロジェクト全体が効率よく動けていたと思います。</p> <ul> <li>案件仕様書の作成段階からQAとして入り、目的にあったものをどう作っていくかなどの議論を行い、その際にTest対象を絞り込む</li> <li>規模が大きな機能に関しては、Phaseを分ける提案をする</li> </ul> <h4 id="Test準備を早く行う">Test準備を早く行う</h4> <p>仕様書review、TestCase作成などは早く正確に行わなければなりません。早さを取りすぎていたため、TestCaseに漏れがでていまい結果障害発生というQAさんは何人も見てきました。(私もその一人です。)</p> <p>では早く正確に準備を行うためにはどうしたらよいのかですが、仕様をしっかり理解できていることが前提条件の一つかと私は思っております。</p> <p>仕様を理解というのは単純に仕様書に書いてあることを把握するだけではなく、仕様には記載されていないもの(他機能への影響など)も含めて把握するということです。把握していればいるほど、必要なTestがわかる為、何よりも大事なことだと思います。</p> <p>余談:この辺を書いてる時に私をQAエンジニアにしてくれたQAエンジニアの方が「QAエンジニアで一番大切なのは仕様理解」と言ってくださったのを思い出した。</p> <h4 id="次スプリントを把握">次スプリントを把握</h4> <p>今スプリントのことだけを考えていると次スプリントでのQA対応が後手に回ってしまいます。</p> <p>その為、次のスプリントを把握して、並行して準備を進めることが必要になってきます。当たり前のように少人数で対応しているQATeamだと工数が足りなくなります。</p> <p>私は案件リリースScheduleをステークホルダーと握ることで、1スプリントでのタスクをコントロールして凌いでました。</p> <h3 id="課題">課題</h3> <h4 id="Refactoring時の動作確認に回すQA工数がない">Refactoring時の動作確認に回すQA工数がない</h4> <p>スプリント内のQA準備、QA実行でRefactoringTestに回すコストが無くなります。早期段階でTest自動化を入れるなどをしないと残業が増えたり、回らなくなります。</p> <h4 id="QAだけでなく開発デザインにも潤沢な工数が必要">QAだけでなく開発、デザインにも潤沢な工数が必要</h4> <p>初回の1スプリントの開発、デザイン対応ができたとしても、2スプリント目での開発、デザイン対応中にはQA実施による不具合の修正、改善案の対応などが発生するので、2スプリント目の開発、デザイン対応に集中できなくなり、結果対応が遅れ、Scheduleが破綻してしまいます。</p> <p>ローテーションを組めるくらいの潤沢な工数を用意するか、1スプリントでの開発内容をよりコンパクトにするなどの提案が必要になります。</p> <h3 id="今後">今後</h3> <p>上述した改善を進めることが今後の取り組みになっていくのですが、 人員を増やすなどは会社の予算との相談にもなっていきますので、 当面は一回のリリースで行う案件規模をより小規模(必要機能は絶対に入れる)にするような提案などを進めていきます。 Test自動化に関してはMagicPodでの導入を考えている最中で、どうにか進めていきたいと考えています。</p> <h3 id="最後に">最後に</h3> <p>アジャイル開発におけるQAの課題はQAエンジニア単体で解決できるものばかりではなく、他職種を含めたチームメンバーの協力が不可欠です。 そのためQAについてメンバーの理解を獲得するための啓蒙活動から始めるのが良いと思います。</p> <div class="footnote"> <p class="footnote"><a href="#fn-6e53c338" name="f-6e53c338" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">前提としてアジャイル開発「スクラム」に対してのQA対応のお話となります。</span></p> </div> yuto_wadatani DroidKaigi 2023に参加しました hatenablog://entry/820878482967912080 2023-09-25T10:01:14+09:00 2023-09-25T10:01:14+09:00 こんにちは!クライアントグループの後藤です。 この度、9/14(木)~16(土)にベルサール渋谷にて開催されたDroidKaigi 2023に参加しましたので、その様子をお伝えします。 はじめに 弊社スタディプラス株式会社は、サポータースポンサーとしてDroidKaigi 2023に協賛させていただきました。今年は弊社から2名がスポンサーチケットを利用して参加しています。 また、今年の6月まで弊社にモバイルアプリエンジニアとして在籍していた中島さんが、Flutterのアプリ内課金実装についてのセッションで登壇しました。視聴していただいた皆さん、ありがとうございました! Flutterにおけるア… <p>こんにちは!クライアントグループの後藤です。 この度、9/14(木)~16(土)にベルサール渋谷にて開催されたDroidKaigi 2023に参加しましたので、その様子をお伝えします。</p> <h1 id="はじめに">はじめに</h1> <p>弊社スタディプラス株式会社は、サポータースポンサーとしてDroidKaigi 2023に協賛させていただきました。今年は弊社から2名がスポンサーチケットを利用して参加しています。</p> <p>また、今年の6月まで弊社にモバイルアプリエンジニアとして在籍していた中島さんが、Flutterのアプリ内課金実装についてのセッションで登壇しました。視聴していただいた皆さん、ありがとうございました!<br/> <a href="https://2023.droidkaigi.jp/timetable/495123/">Flutter&#x306B;&#x304A;&#x3051;&#x308B;&#x30A2;&#x30D7;&#x30EA;&#x5185;&#x8AB2;&#x91D1;&#x5B9F;&#x88C5; -Android/iOS &#x5B8C;&#x5168;&#x306A;&#x308B;&#x7D71;&#x4E00;- | DroidKaigi 2023</a></p> <h1 id="感想">感想</h1> <p>今回参加した2名は、今年の4月に入社したばかりの新人でAndroidアプリ開発の経験がほとんどない後藤と、現在StudyplusのAndroidアプリの開発を担当している隅山です。 以下では、それぞれの視点から感想を述べていきます。</p> <h2 id="後藤">後藤</h2> <p>Day1~2の日程で参加しました! 参加前はAndroidアプリ開発についての知識がほとんどない状態だったため、楽しめるかどうか不安ではありました。 しかしいざ乗り込んでみると、セッションによってはあまり知識の必要ないものや、他のOS開発に通じる部分も見られ、意外にも楽しめました。 また、スポンサーブースでは各社の開発体制やマルチプラットフォーム対応を中心に話を聞き、サービスの規模やフェーズによって取り組みや技術選定も様々でとても興味深かったです。</p> <p>今回初めて参加したDroidKaigiですが、いい刺激をもらうことができました。来年はもっと知識をつけた上で参加して、もっと深くまで楽しみたいと思いました!</p> <p>以下では個人的に印象に残ったセッションを紹介します。</p> <h3 id="よく見るあのUIをJetpack-Composeで実装する方法〇選">よく見るあのUIをJetpack Composeで実装する方法〇選</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2023.droidkaigi.jp%2Ftimetable%2F495090%2F" title="よく見るあのUIをJetpack Composeで実装する方法〇選 | DroidKaigi 2023" 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://2023.droidkaigi.jp/timetable/495090/">2023.droidkaigi.jp</a></cite></p> <p>アプリでよく使われているUIコンポーネントをJetpack Composeではどう実装するかを紹介したセッションでした。 Flutter開発を行っていることもあって、同じ宣言的UIであるJetpack Composeに興味があったため視聴しました。 一般的なアプリを作るのに十分な内容が網羅されており、Jetpack Composeを使ってUIを作る時に辞書的な形でぜひ参照したいと思いました。 また、クレジットカード番号の入力欄といった特殊なTextFieldの実装方法も紹介されており、勉強になりました。</p> <h3 id="突撃隣のコードレビュー">突撃!隣のコードレビュー</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2023.droidkaigi.jp%2Ftimetable%2F494546%2F" title="突撃!隣のコードレビュー | DroidKaigi 2023" 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://2023.droidkaigi.jp/timetable/494546/">2023.droidkaigi.jp</a></cite></p> <p>DMMさんの3つのアプリチームのレビュー体制と工夫点、レビューの際に個人的に意識している点などをまとめたセッションでした。 自身はコードレビューの経験が浅いので、経験年数のあるエンジニアの方々がレビュー時に意識していることなどはとても勉強になりました。 また、30分のレビュー時間を1日に2回設けたり、レビューのリマインドをしたりなど、チームで行っている工夫や取り組みも興味深かったです。 ぜひともチームに共有したいと思えるような充実した内容でした!</p> <h3 id="できるアクセシビリティ向上">できる!アクセシビリティ向上</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2023.droidkaigi.jp%2Ftimetable%2F495062%2F" title="できる!アクセシビリティ向上 | DroidKaigi 2023" 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://2023.droidkaigi.jp/timetable/495062/">2023.droidkaigi.jp</a></cite></p> <p>アクセシビリティの概要と、日経さんの日経電子版アプリでアクセシビリティ対応したときの話をまとめたセッションでした。 概要パートでは、身体の状況だけでなく周囲の環境や言語など様々な状況においてアクセシビリティの必要性があることを知ることができました。 また、日経電子版アプリでの取り組みでは、施策立案から優先順位決めをして、開発、リリースするまでの流れが紹介されていました。 実装の前段階で、他の開発タスクと並行して作業可能なものかどうかや他の操作で代替できないかなどの観点から優先順位を決めをしていたところなど、アクセシビリティ対応の実例として学ぶところが多かったです。</p> <h2 id="隅山">隅山</h2> <p>DroidKaigi 2020から積極的に参加していましたが、今回が初めてのオフライン参加となりました。 今までのオンライン参加とは違い、知り合いエンジニアと直接話が出来て非常に勉強になりました。 余談ですが会場近くのスターバックス リザーブ ロースタリーに行って飲んだコーヒーが美味しかったです。</p> <p>最近Flutterしか触ってないので、Flutter開発にも活かせそうなセッションに参加するようにしました。 以下に印象に残ったセッションを紹介します。</p> <h3 id="Unleashing-the-Power-of-Android-Studio">Unleashing the Power of Android Studio</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2023.droidkaigi.jp%2Ftimetable%2F495251%2F" title="Unleashing the Power of Android Studio | DroidKaigi 2023" 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://2023.droidkaigi.jp/timetable/495251/">2023.droidkaigi.jp</a></cite></p> <p>生産性を向上させるAndroid Studioの最新機能を紹介するセッションでした。 Flutterのホットリロードのような機能であるLive Edit、ビルド時間が計測できるBuildAnalyzer、build.gradleがKotlinで書ける機能などが紹介されていました。 その中でも特に驚いたのがAndroid Studio上で実機をミラーリングできる機能とFirebase Crashlyticsの問題分析できるApp Quality Insightsでした。</p> <p>実機のミラーリングではスクショや動画も撮ることができるし、Android Studioからデバイスを操作することもできるので他の方への共有などが非常に便利になるなと感じました。 App Quality InsightsはIguanaからですが、クラッシュが起きていた当時のコードを確認することができるので現在修正されているかどうかも確認することができそうでした。</p> <h3 id="Unofficial-Guide-to-App-Architecture-Guide-Vol-2">(Unofficial) Guide to App Architecture Guide Vol. 2</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2023.droidkaigi.jp%2Ftimetable%2F494987%2F" title="(Unofficial) Guide to App Architecture Guide Vol. 2 | DroidKaigi 2023" 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://2023.droidkaigi.jp/timetable/494987/">2023.droidkaigi.jp</a></cite></p> <p>このセッションではアプリアーキテクチャの観点から依存性注入とマルチモジュールの話をしていました。 依存性注入は様々なライブラリ紹介もしながら、有向非巡回グラフ(Directed Acyclic Graphs: DAG)を考慮することが重要と説明していました。 また、マルチモジュールではカプセル化ができるかがいかに大事で、そのメリットやアンチパターンの紹介もされていました。</p> <p>弊社のアプリはマルチモジュールを対応しているので、循環参照を回避することやInterfaceの利用場面などマルチモジュールの方針が言語化されたセッションで参考になりました。 英語のスライドでの発表だったため、改めてセッションを見返してより深く理解すべき内容だなと思いました。</p> <h1 id="おわりに">おわりに</h1> <p>DroidKaigi 2023はとても充実した内容で、とても勉強になりました!来年もぜひ参加したいと思います! 運営の皆さんをはじめ、登壇された皆さんや参加された皆さん、ありがとうございました!</p> kosuke_goto Android 13への対応の注意事項 hatenablog://entry/820878482965178027 2023-09-19T10:00:00+09:00 2023-09-19T10:00:00+09:00 こんにちは、クライアントグループの隅山です。 しばらくFlutter開発していましたが、Android 13への対応が必要になったため直近はAndroidの更新作業を行なっていました。 今回はAndroid 13への対応項目と対応時に注意すべき点について紹介します。 Android 13への対応項目 通知に関する実行時の権限 広告IDに必要な権限 注意事項 Android 13での変更箇所 クラッシュの原因 対応内容 まとめ Android 13への対応項目 まずは、Android 13への対応項目として下記の公式ドキュメントを参考にしました。 developer.android.com de… <p>こんにちは、クライアントグループの隅山です。 しばらくFlutter開発していましたが、Android 13への対応が必要になったため直近はAndroidの更新作業を行なっていました。 今回はAndroid 13への対応項目と対応時に注意すべき点について紹介します。</p> <ul class="table-of-contents"> <li><a href="#Android-13への対応項目">Android 13への対応項目</a><ul> <li><a href="#通知に関する実行時の権限">通知に関する実行時の権限</a></li> <li><a href="#広告IDに必要な権限">広告IDに必要な権限</a></li> </ul> </li> <li><a href="#注意事項">注意事項</a><ul> <li><a href="#Android-13での変更箇所">Android 13での変更箇所</a></li> <li><a href="#クラッシュの原因">クラッシュの原因</a></li> <li><a href="#対応内容">対応内容</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h3 id="Android-13への対応項目">Android 13への対応項目</h3> <p>まずは、Android 13への対応項目として下記の公式ドキュメントを参考にしました。</p> <p><cite class="hatena-citation"><a href="https://developer.android.com/about/versions/13/behavior-changes-13">developer.android.com</a></cite><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.android.com%2Fabout%2Fversions%2F13%2Fbehavior-changes-13" title="Behavior changes: Apps targeting Android 13 or higher  |  Android Developers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p><cite class="hatena-citation"><a href="https://developer.android.com/about/versions/13/summary">developer.android.com</a></cite><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.android.com%2Fabout%2Fversions%2F13%2Fsummary" title="Android 13 features and changes list  |  Android Developers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p>弊社で対応した項目をピックアップしていきます。</p> <h4 id="通知に関する実行時の権限">通知に関する実行時の権限</h4> <p>Android 13で通知に関する権限が追加されました。 Android 13を搭載した端末でAndroid 13未満をターゲットにしているアプリを起動した場合、アプリ起動時に通知の権限ダイアログが表示されるようになります。 しかし、Android 13以上をターゲットにしているアプリの場合は権限ダイアログの表示を実装する必要があります。</p> <p>対応方法は下記を参考にしました。</p> <p><cite class="hatena-citation"><a href="https://developer.android.com/about/versions/13/changes/notification-permission">developer.android.com</a></cite><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.android.com%2Fabout%2Fversions%2F13%2Fchanges%2Fnotification-permission" title="Notification runtime permission  |  Android Developers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h4 id="広告IDに必要な権限">広告IDに必要な権限</h4> <p>Google Play開発サービスの広告IDを使用し、Android 13以上をターゲットにしているアプリでは、マニフェストに広告IDの必要な権限を宣言する必要があります。 ただし、利用ライブラリのマニフェストで広告IDに必要な権限が宣言されている場合は統合されるため、改めて権限をマニフェストに追加する必要はありません。</p> <p>マニフェストの統合についてや統合されたマニフェストの確認方法は下記にありますのでご参考にしてください。 弊社では統合されたマニフェストに宣言されていたため、広告IDについては特に対応しませんでした。</p> <p><cite class="hatena-citation"><a href="https://developer.android.com/build/manage-manifests#merge-manifests">developer.android.com</a></cite><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdeveloper.android.com%2Fbuild%2Fmanage-manifests%23merge-manifests" title="Manage manifest files  |  Android Studio  |  Android Developers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <h3 id="注意事項">注意事項</h3> <p>上記の公式ドキュメントに沿って対応した後に動作確認したら、ある1画面でクラッシュしていました。 デバッグしてみると<code>lateinit property binding has not been initialized</code>が発生していました。 エラー内容としては<code>lateinit</code>変数が初期化される前に参照されているというものですが、公式ドキュメントにも該当しそうな内容の記載がなかったため調査してみました。</p> <h4 id="Android-13での変更箇所">Android 13での変更箇所</h4> <p>調査してみると画面生成時に呼ばれるメソッドの順番が変わっていることがわかりました。 Android 13をターゲットにする以前はActivity生成時に<code>onCreate</code>が呼ばれ、その後にメニューを再描画させる<code>invalidateOptionsMenu</code>が呼ばれていました。 しかし、Android 13をターゲットにすると呼ばれる順番が変わり、Activity生成時に<code>onCreate</code>が呼ばれる前に<code>invalidateOptionsMenu</code>が呼ばれるようになっていました。</p> <p>更に深く調査するとComponentActivityに変更が入っていることがわかりました。 下記が該当箇所の変更です。</p> <p><cite class="hatena-citation"><a href="https://android.googlesource.com/platform/frameworks/support/+/28424b74edde20f61f98ab313bbf71dfa7458c37%5E%21">android.googlesource.com</a></cite><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fframeworks%2Fsupport%2F%2B%2F28424b74edde20f61f98ab313bbf71dfa7458c37%255E%2521" title="Diff - 28424b74edde20f61f98ab313bbf71dfa7458c37^! - platform/frameworks/support - Git at Google" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p> <p>ComponentActivityのPrivateフィールドにMenuHostHelperが追加され、そのインスタンス生成時に<code>invalidateMenu</code>が呼ばれるように変更されていました。 この変更により弊社のアプリのある1画面でクラッシュが起きるようになっていました。</p> <h4 id="クラッシュの原因">クラッシュの原因</h4> <p>上記の変更がクラッシュを引き起こす致命的な修正かといえばそうではなく、弊社の実装に問題がありました。</p> <p>弊社ではDataBindingを利用しており<code>onCreate</code>でBindingオブジェクトを作成していました。 そして、クラッシュが起きていた画面では<code>invalidateOptionsMenu</code>でBindingオブジェクトのViewの表示非表示を制御するコードが書かれていました。</p> <pre class="code lang-kotlin" data-lang="kotlin" data-unlink><span class="synType">private</span> <span class="synType">lateinit</span> <span class="synType">var</span> binding: MainBinding <span class="synType">override</span> <span class="synType">fun</span> onCreate(savedInstanceState: Bundle?) { <span class="synStatement">super</span>.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(<span class="synStatement">this</span>, R.layout.activity_main) } <span class="synType">override</span> <span class="synType">fun</span> invalidateOptionsMenu() { <span class="synStatement">super</span>.invalidateOptionsMenu() <span class="synComment">// 参照データによって画面のボタンの表示非表示を制御</span> binding.button.isVisible = <span class="synConstant">true</span> } </pre> <p>何故<code>invalidateOptionsMenu</code>でBindingオブジェクトを呼び出しているのかと調査しているときに思ったが、上記の箇所がAndroid 13対応時にクラッシュしていました。</p> <h4 id="対応内容">対応内容</h4> <p>そもそも<code>invalidateOptionsMenu</code>でBindingオブジェクトを呼び出しているのが問題なのでViewModel経由でViewの表示非表示を制御する形に対応しました。 本来は<code>invalidateOptionsMenu</code>でメニュー以外のViewの制御することがあまりよくないのですが、暫定対応として下記のような形で対応しました。</p> <pre class="code lang-kotlin" data-lang="kotlin" data-unlink><span class="synType">private</span> <span class="synType">lateinit</span> <span class="synType">var</span> binding: MainBinding <span class="synType">private</span> <span class="synType">val</span> viewModel <span class="synStatement">by</span> viewModels() <span class="synType">override</span> <span class="synType">fun</span> onCreate(savedInstanceState: Bundle?) { <span class="synStatement">super</span>.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(<span class="synStatement">this</span>, R.layout.activity_main) viewModel.isVisibleButton.observe(<span class="synStatement">this</span>) { binding.button.isVisible = it } } <span class="synType">override</span> <span class="synType">fun</span> invalidateOptionsMenu() { <span class="synStatement">super</span>.invalidateOptionsMenu() <span class="synComment">// 参照データによって画面のボタンの表示非表示を制御</span> viewModel.isVisibleButton.value = <span class="synConstant">true</span> } </pre> <h3 id="まとめ">まとめ</h3> <p>今回はAndroid 13への対応で行ったことを紹介しました。 注意事項は実装に問題があるときに起きることですが、大きいアプリでリファクタリングが間に合っていない場合は起こり得る事象なのかなと思います。 今回の教訓としては画面制御は適切な箇所で行い、できていない場合はリファクタリングを早めに行うべきだと学びました。 今後Android 13への対応が必須になりますが、ドキュメントに書かれている以外にも注意すべきことがあり、この記事が役に立てばいいなと思います。</p> JASON13 iOSDC Japan 2023に参加しました hatenablog://entry/820878482966551116 2023-09-15T10:00:00+09:00 2023-09-15T10:00:02+09:00 こんにちは、クライアントグループの樋口です。 8/31~9/2に開催されたiOSDC Japan 2023へ参加した件をブログにします。 iosdc.jp はじめに 弊社スタディプラス株式会社はシルバースポンサーとして、ノベルティボックスにスタディプラス付箋セットを提供させていただきました。もし、機会がありましたら活用してもらえればと思います。(切実) tech.studyplus.co.jp 感想 今回は弊社クライアントグループのエンジニアが勉強会・カンファレンス参加補助で業務参加しました。 クライアントグループではiOSとAndroidのお互いの開発をできるようにするという取り組んでおり、… <hr /> <p>こんにちは、クライアントグループの樋口です。</p> <p>8/31~9/2に開催されたiOSDC Japan 2023へ参加した件をブログにします。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fiosdc.jp%2F2023%2F" title="iOSDC Japan 2023" 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://iosdc.jp/2023/">iosdc.jp</a></cite></p> <h1 id="はじめに">はじめに</h1> <p>弊社スタディプラス株式会社はシルバースポンサーとして、ノベルティボックスにスタディプラス付箋セットを提供させていただきました。もし、機会がありましたら活用してもらえればと思います。(切実)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.studyplus.co.jp%2Fentry%2F2023%2F08%2F25%2F090000" title="スタディプラスはiOSDC Japan 2023にシルバースポンサーとして協賛します - Studyplus Engineering Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.studyplus.co.jp/entry/2023/08/25/090000">tech.studyplus.co.jp</a></cite></p> <h1 id="感想">感想</h1> <p>今回は弊社クライアントグループのエンジニアが勉強会・カンファレンス参加補助で業務参加しました。 クライアントグループではiOSとAndroidのお互いの開発をできるようにするという取り組んでおり、Androidエンジニアでも興味があればiOSDC Japanへ自由に参加しています。</p> <p>現在はFlutterを使った新機能の開発を行なっております。全エンジニアがFlutterの開発も行えるような体制を目指して、各自の新しい開発スキルの習得、組織としては将来的にはエンジニアリソースの効率化に取り組んでいます。 私たちのグループに興味がありましたら以下をお読みいただければ幸いです。</p> <p>それでは各メンバーの感想です。</p> <h2 id="樋口">樋口</h2> <p>自分は、初めて技術カンファレンスに参加いたしました!day0はオンラインで参加し、day1とday2は現地にてオフライン参加いたしました!初めての参加ということもあり、緊張もしていましたが、とても楽しめました!</p> <p>技術系のセッションについては、難しめの技術でもスピーカーの方々がわかりやすく説明してくれたため、置いてけぼりになることもほぼなく、楽しみながら聴くことができました。オフライン参加の場合は、現地での盛り上がりもあったりなどであっという楽しく間に終わってしまったなという感想です。</p> <p>懇親会もまだ働いてない方や、大手で勤務されている方、Flutterを触っている方などいろんな方とお話しする機会がありました。技術系のお話やシンプルな雑談などで交友関係も広げることができてとても楽しかったです。</p> <p>以下は特に印象に残ったセッションです。</p> <h3 id="iOSは自動作曲の夢を見るかオンデバイス音楽生成の可能性">iOSは自動作曲の夢を見るか?~オンデバイス音楽生成の可能性</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2023%2Fproposal%2F358b5cc8-406f-4f67-a2ab-c9257b37a262" title="iOSは自動作曲の夢を見るか? 〜オンデバイス音楽生成の可能性" 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://fortee.jp/iosdc-japan-2023/proposal/358b5cc8-406f-4f67-a2ab-c9257b37a262">fortee.jp</a></cite></p> <p>こちらは、iOSで深層学習を用いて音楽の自動生成を試みるというものです。結論としては、クラシックなどの音楽は人間の手直しがあればある程度形にはなるが、J-POPのようは大衆に受けるような音楽の生成は難しいというものでした。</p> <p>スピーカーさんが音楽理論やAIの様な専門的な話をわかりやすく説明してくれたため、事前知識があまりない状態でも楽しんでセッションを聞くことができました!</p> <h3 id="スタートアップ企業のフェーズ転換期を乗り越えるためのリアーキテクト戦略--個からチームへ--by-horitamon">スタートアップ企業のフェーズ転換期を乗り越えるためのリアーキテクト戦略 -個からチームへ- by horitamon</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2023%2Fproposal%2F0dc5f8ba-ea28-4391-8d07-eceecd13d024" title="スタートアップ企業のフェーズ転換期を乗り越えるためのリアーキテクト戦略 -個からチームへ-" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://fortee.jp/iosdc-japan-2023/proposal/0dc5f8ba-ea28-4391-8d07-eceecd13d024">fortee.jp</a></cite></p> <p>こちらは、Voicyさんのセッションでスタートアップ企業が事業成長のフェーズ変化で直面する課題と、それ乗り越えるための戦略をお話ししてくださいました。 機能開発をしながら、小さな不具合やコードの品質を担保することは難しいみたいでした。 そこで品質向上改善チームという専門のチームを立ち上げたというのが特に印象に残りました! 会社の規模を拡大していく上でぶつかる壁のような話は自分が経験したことないため、とても興味深く印象に残ったセッションでした!</p> <h2 id="後藤">後藤</h2> <p>今回初めてday0 ~ day2までオフラインで参加してみましたが、とても楽しく勉強になった3日間でした! オフライン参加はセッションだけではなく各スポンサー企業のブースを回って取り組みを聞いたり、企画に参加するのがお祭りみたいで、そこが個人的に楽しかったです。</p> <p>また、自身は入社してFlutterを触り始めてからiOS開発に触れることがめっきり減ってしまっていたのでフレームワークの進化に対する驚きや発見がたくさんあったこともよかったです。</p> <p>最終日の懇親会では、個人開発でリリースしているアプリを見せてもらってそれについて話したり、会社でどういった働き方をしているかなども話せてとてもいい刺激になりました。</p> <p>以下特に印象に残ったセッションです。</p> <h3 id="SwiftUIの進化についていくためにやったこと">SwiftUIの進化についていくためにやったこと</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2023%2Fproposal%2F23127d28-a62d-4042-bce6-e349a189ed99" title="SwiftUIの進化についていくためにやったこと" 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://fortee.jp/iosdc-japan-2023/proposal/23127d28-a62d-4042-bce6-e349a189ed99">fortee.jp</a></cite></p> <p>SwiftUIが登場してから早い段階で開発に取り入れており、フレームワークの変化にどのように追従していくのかなどが語られていました。 現在弊社でも、まだそんなに歴史の古くないFlutterを採用しているということもあり、割と似たような境遇になるのかなと思いながら聞いていました。 バージョンアップ周りはissueを立てて対応したり、品質改善のための時間をメインタスクとは別で管理するといった取り組みが勉強になりました。</p> <h3 id="複雑さに立ち向かうためのコードリーディング入門">複雑さに立ち向かうためのコードリーディング入門</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2023%2Fproposal%2F6229ba41-0675-4f62-839e-6cde07311585" title="複雑さに立ち向かうためのコードリーディング入門" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://fortee.jp/iosdc-japan-2023/proposal/6229ba41-0675-4f62-839e-6cde07311585">fortee.jp</a></cite></p> <p>今年から社会人になり、そこそこ規模の大きいアプリ開発に関わるようになって半年が経ちました。ちょうどコードリーディングの難しさに悩まされていたので、なぜ複雑に感じるのかを認知プロセスの観点から解明していくのはとても新鮮で勉強になりました。 とくにワーキングメモリに関しては、振り返ると結構余計な認知負荷がかかっていたなと思い、これから意識して対策していきたいところです。</p> <h2 id="大石">大石</h2> <p>オフラインで1日目と3日目のみ参加しました。いくつかセッションにも参加したのですが、今回はスポンサーブースを回ったりブースや懇親会で他の参加者と話したりすることが多かったです。 スポンサーブースでは各社、様々な催し物を用意されており楽しむことができ、スポンサーの各社の準備がとてもすごいと思いました。 また、ブースにいるエンジニアの方と使用している技術や現在の開発組織の体制や課題など深いお話ができたので、とても有意義な時間でした。 懇親会でもこれまで話したことがなかった方ともモバイルアプリの話題で楽しい会話ができました。お会いできた方々には感謝いたします。 久しぶりにオフラインのイベントに参加しましたが、数年振りに会った人とも多く話せましたし、新しい出会いもありました。とても楽しいイベントでしたので、次回も参加したいと思います。</p> <p>今回、オフラインで参加して印象に残ったことを紹介します。</p> <h3 id="ネームプレート">ネームプレート</h3> <p>直前でチケットを購入したため、ネームプレートの台紙にマジックで名前などを書く必要がありました。一定期間に購入するとSNSのアイコンや名前が事前に印刷されたものを受け取ることができます。参加者同士のコミュニケーションで名前は知らないけどアイコンは知っているという光景を眺めながら、自分も印刷されていたほうが良かったなと思いました。直前の購入だと写真のようになりますので、オフラインで参加するなら早めにチケットを購入することをお勧めします。</p> <h3 id="印象に残った神ノベルティ">印象に残った神ノベルティ</h3> <p>pixiv(ピクシブ)さんのブースでもらった首からかけるドリンクホルダーが会場でとても役に立つノベルティで良かったです。どのようなものかは写真をご覧の通りなのですが、懇親会で食べ物を取る際や、食べる際など両手を使う場面で持っていたドリンクを入れることができました。会場でもらってそのまま役に立つまさに神ノベルティだと思いました。pixivさんありがとうございます🙏</p> <p><figure class="figure-image figure-image-fotolife" title="ネームプレートとドリンクホルダーの写真"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/k_oishi/20230914/20230914092112.jpg" width="900" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ネームプレートとドリンクホルダーの写真</figcaption></figure></p> <h2 id="明渡">明渡</h2> <p>オンラインで、2年ぶりに参加させていただきました。 昨年は出産予定間近だった都合で、スケジュールが8月から9月へ変更になった辺りでやむなく参加を諦めました。</p> <p>今年もオフラインでの参加は厳しかったので、オフライン会場にも力を入れつつオンライン参加という手段を残していただけたことに感謝しかありません!</p> <p>最近メインの業務でFlutterを書いていることが多く、ネイティブのiOSについて追いきれていない実感があり、この機会を存分に活用させていただきました。</p> <p>貴重な時間を割いて準備をしてくださった運営や発表者の皆様、本当にありがとうございました!</p> <p>以降、自分の印象に残ったセッションについて記載します。</p> <h3 id="StoreKit2を使った課金システムのフルリニューアル">StoreKit2を使った課金システムのフルリニューアル</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2023%2Fproposal%2F38b684f3-86a6-43be-8d27-48d9710626bf" title="StoreKit2を使った課金システムのフルリニューアル" 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://fortee.jp/iosdc-japan-2023/proposal/38b684f3-86a6-43be-8d27-48d9710626bf">fortee.jp</a></cite></p> <p>弊社では移行予定が今のところ未定で、気になっていたものの追いきれていなかったStoreKit2の知見がギュッと詰まったありがたいセッションでした。</p> <p>移行する際にはこちらのセッションで話された内容をもとに、サーバー間との連携周りも含めて発生しうるトラブルへうまく対処しつつ進められたらいいなと思いました。</p> <p>特に、Firebase RemoteConfigによる制御でStoreKit1とStoreKit2の切り替えができるようにしておいたという話はとても参考になりました。</p> <p>弊社でもRemoteConfigは利用しているものの、実装を丸ごと切り替えるのには利用したことがなかったので、今後リスクのある実装に取りうる手段として頭に入れておきます。</p> <h3 id="CoreHaptics入門">CoreHaptics入門</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2023%2Fproposal%2Fb630fa79-2c28-41ec-a6b2-6954b62f1858" title="CoreHaptics入門" 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://fortee.jp/iosdc-japan-2023/proposal/b630fa79-2c28-41ec-a6b2-6954b62f1858">fortee.jp</a></cite></p> <p>「端末をアプリ側で振動させよう!」と思い至ったことがなかったので、とても新鮮な内容でした。</p> <p>HIGに記載されているHapticsの基礎や具体的なカスタマイズの方法、社内でデザイナーさんに検討してもらうために専用のアプリを配信した話などが印象に残っています。 実践に即した内容で取り入れた際のイメージが沸く素晴らしいセッションでした。</p> <p>弊社のサービスでも取り入れるとユーザー体験の向上が見込めるかもしれない要素に心当たりが浮かんで、さっそく社内の企画担当に相談しておきました。 せっかくなので実際に取り入れることになれば嬉しいなと考えています。</p> <h2 id="隅山">隅山</h2> <p>day0とday1をオンライン参加しました。day2は私用のため不参加。 今年はオフライン開催に力を入れている感じがあったのでオフライン参加しようかギリギリまで悩みましたが、day2が参加できないためオンライン参加にしました。</p> <p>元々AndroidエンジニアでiOSのことはそこまで勉強してこなかったのですが、最近はFlutter開発をやっておりiOSの知識も必要となってきたので、例年以上に情報のキャッチアップに力を入れました。 iOSのモーダルの話やプライバシーの話などiOS特有の情報が非常に詳しく説明されていて非常に勉強になりました。</p> <p>Android兼Flutterエンジニアの身としてはiOS特有というよりアプリ開発全般の話の方が印象に残ったので、下記に自分が印象に残ったセッションを記載します。</p> <h3 id="6年間運用したiOSアプリのリアーキテクトについて具体的に解説">6年間運用したiOSアプリのリアーキテクトについて具体的に解説</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2023%2Fproposal%2Fbf58e5d0-a9c5-4ebb-beed-1a6c6c92de8f" title="6年間運用したiOSアプリのリアーキテクトについて具体的に解説" 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://fortee.jp/iosdc-japan-2023/proposal/bf58e5d0-a9c5-4ebb-beed-1a6c6c92de8f">fortee.jp</a></cite></p> <p>日々の開発でコードの複雑性が増した際、どのようにリアーキテクトを行ったかという内容でした。</p> <p>会社のアプリだと複数人の開発者がいて、更に開発者も交代したりなど、開発を進めるうちにコードが複雑になることは多々あるので定期的にリアーキテクトが必要となります。 しかし、現実的には新機能開発もしなければいけないので、他の会社ではリアーキテクトをどのように行っているか気になっていました。</p> <p>このセッションでは分割リアーキテクトといってモジュール分割し、モジュール単位での改善で作業スコープを小さくしていました。 大きな構成のリアーキテクトは時間がかかるが、小さな構成のリアーキテクトで時間をあまりかけず新機能開発の間に作業できるような工夫が説明されていました。 弊社でもモジュール分割しており他の作業とのコンフリクトが起きにくいなどメリットを多く感じられているので確かに大事と再確認できました。</p> <h3 id="なぜ弊社はFlutterを捨ててネイティブ化Swift--Kotlinにコミットしたのか">なぜ、弊社はFlutterを捨ててネイティブ化(Swift / Kotlin)にコミットしたのか</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ffortee.jp%2Fiosdc-japan-2023%2Fproposal%2F4a8ef72b-9c0c-40ef-8e8f-084fd0199c49" title="なぜ、弊社はFlutterを捨ててネイティブ化(Swift / Kotlin)にコミットしたのか" 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://fortee.jp/iosdc-japan-2023/proposal/4a8ef72b-9c0c-40ef-8e8f-084fd0199c49">fortee.jp</a></cite></p> <p>最初はFlutterを採用していたが後にFlutterからネイティブに切り替えた理由についてなので非常に興味深い内容でした。</p> <p>弊社ではFlutter開発が進んでおりメリットを大きく感じることができているが、Flutter開発のデメリットについては考えたことがないためこのセッションを聴講しました。 セッションの始めの方はFlutterのメリットも説明されていて、否定的な内容ではないのでとりあえず安心したのを覚えています。</p> <p>このセッションで説明されていたFlutter開発のデメリットとして1年前当時はFlutterエンジニアが全然いなかったこと、Bluetoothを利用した開発はネイティブ実装が必要なことが発表されていました。 確かに弊社でも今現在Flutterエンジニアが不足しているので、1年前は採用が非常に大変だったんだろうなと思いました。 Bluetoothなどネイティブ実装が必須な機能の実装経験はなかったのでつまづかなかったのですが、アプリの種類によってはFlutterではなくネイティブ開発にした方がよさそうでした。</p> <h2 id="iOSDCチャレンジについて">iOSDCチャレンジについて</h2> <p>今年のiOSDCチャレンジは、チャレンジトークンを入力するとポイントを獲得できます。その獲得したポイントの総額に応じて景品をかけた抽選ができるというものでした。</p> <p>弊社もシルバースポンサーとして、トークンを2つ出させていただきました!</p> <ul> <li>#学ぶ喜びを全てのひとへ(パンフレットに記載)</li> <li>#口コミで累計800万人ユーザー(tech-blogに記載)</li> </ul> <p>ご入力いただいた方々ありがとうございます!</p> <p>個人的にも参加したのですが、Tシャツとお菓子をもらいました!体感的に当たりの出る確率も高く、セッションの合間で楽しみながら参加できるイベントでした!とても楽しませてもらいました!</p> <h1 id="いかがでしたか">いかがでしたか?</h1> <p>iOSDC Japan 2023の感想のまとめでした。 運営の皆さん、登壇された皆さん、参加された皆さん、お疲れ様でした!</p> xtomoya1021 スタディプラスはDroidKaigi 2023にサポータースポンサーとして協賛します hatenablog://entry/820878482965228757 2023-09-11T09:00:00+09:00 2023-09-11T09:00:02+09:00 本文 こんにちは。 スタディプラスのカンファレンス/OSSサポートチーム、後藤です。 スタディプラスは、2023年9月14日から16日にかけてベルサール渋谷ガーデンおよびオンラインで開催される、DroidKaigi 2023に協賛します。 2023.droidkaigi.jp DroidKaigi 2023とは? DroidKaigiは、エンジニアが主役のAndroidカンファレンスです。 Android開発者有志によるDroidKaigi実行委員会が主催し、Android技術情報の共有とコミュニケーションを目的に開催されています。 【概要】 主催 :DroidKaigi実行委員会 対象 :A… <h2 id="本文">本文</h2> <p>こんにちは。<br/> スタディプラスのカンファレンス/OSSサポートチーム、後藤です。</p> <p>スタディプラスは、2023年9月14日から16日にかけてベルサール渋谷ガーデンおよびオンラインで開催される、DroidKaigi 2023に協賛します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2023.droidkaigi.jp%2F" title="DroidKaigi 2023" 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://2023.droidkaigi.jp/">2023.droidkaigi.jp</a></cite></p> <h3 id="DroidKaigi-2023とは">DroidKaigi 2023とは?</h3> <p>DroidKaigiは、エンジニアが主役のAndroidカンファレンスです。<br/> Android開発者有志によるDroidKaigi実行委員会が主催し、Android技術情報の共有とコミュニケーションを目的に開催されています。</p> <p>【概要】<br/>  主催 :DroidKaigi実行委員会<br/>  対象 :Android及び関連技術のエンジニア<br/>  開催日:2023年9月14日(木)〜16日(土)<br/>  会場 :ベルサール渋谷ガーデンおよびオンライン<br/>  公式HP:<a href="https://2023.droidkaigi.jp/">https://2023.droidkaigi.jp/</a></p> <h3 id="協賛">協賛</h3> <p>昨年に引き続き、DroidKaigiに協賛させていただくこととなりました。<br/> サポータースポンサーでの協賛となります。</p> <h3 id="登壇">登壇</h3> <p>今年は、6月まで弊社で勤めていた中島が「Flutterにおけるアプリ内課金実装 -Android/iOS 完全なる統一-」と題して登壇いたします。<br/> 現在,スタディプラスではFlutterを用いたクロスプラットフォーム開発が進行中です。<br/> 発表では、Androidで実装し公開されているアプリ内課金機能をFlutter移植した際のつまづきや意識した点について紹介しますので、ぜひご覧ください。</p> <p>登壇日時:2023年9月15日(Day 2) 12:00~12:40</p> <p><a href="https://2023.droidkaigi.jp/timetable/495123/">Flutter&#x306B;&#x304A;&#x3051;&#x308B;&#x30A2;&#x30D7;&#x30EA;&#x5185;&#x8AB2;&#x91D1;&#x5B9F;&#x88C5; -Android/iOS &#x5B8C;&#x5168;&#x306A;&#x308B;&#x7D71;&#x4E00;- | DroidKaigi 2023</a></p> kosuke_goto スタディプラスはiOSDC Japan 2023にシルバースポンサーとして協賛します hatenablog://entry/820878482960689010 2023-08-25T09:00:00+09:00 2023-08-25T09:00:01+09:00 こんにちは、スタディプラスのカンファレンス/OSSサポートチーム、菅原です。 スタディプラスは、2023年9月1日から3日にかけて早稲田大学理工学部西早稲田キャンパスおよびオンライン(ニコニコ生放送)で開催される、iOSDC Japan 2023にシルバースポンサーとして協賛します。 2018年から始まり、今回で6回目になりました。 iosdc.jp iOSDC Japan 2023とは? iOSDC Japan 2023はiOS関連技術をコアのテーマとしたソフトウェア技術者のためのカンファレンスです。今年もリアル会場とオンライン配信のハイブリッド開催です。 日本中、世界中から公募した知的好奇… <p>こんにちは、スタディプラスのカンファレンス/OSSサポートチーム、菅原です。</p> <p>スタディプラスは、2023年9月1日から3日にかけて早稲田大学理工学部西早稲田キャンパスおよびオンライン(ニコニコ生放送)で開催される、iOSDC Japan 2023にシルバースポンサーとして協賛します。 2018年から始まり、今回で6回目になりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fiosdc.jp%2F2023%2F" title="iOSDC Japan 2023" 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://iosdc.jp/2023/">iosdc.jp</a></cite></p> <h3 id="iOSDC-Japan-2023とは">iOSDC Japan 2023とは?</h3> <p>iOSDC Japan 2023はiOS関連技術をコアのテーマとしたソフトウェア技術者のためのカンファレンスです。今年もリアル会場とオンライン配信のハイブリッド開催です。</p> <p>日本中、世界中から公募した知的好奇心を刺激するトークの他にも、パンフレットに掲載された技術記事、参加者であれば誰でも作れる即興のトーク・アンカンファレンスなど、初心者から上級者まで楽しめるコンテンツが用意されています。</p> <p>【概要】<br/>  開催 :2023年9月1日(金)〜3日(日)<br/>  場所 :早稲田大学理工学部 西早稲田キャンパス + ニコニコ生放送<br/>  対象 :iOS関連技術およびすべてのソフトウェア技術者<br/>  主催 :iOSDC Japan 2023 実行委員会 (実行委員長 長谷川智希)<br/>  共催 :早稲田大学 理工学術院, 早稲田大学グローバル科学知融合研究所<br/>  協力 :WASEDA-EDGE人材育成プログラム, Beyond 2020 NEXT PROJECT, NEW<br/>  公式HP:<a href="https://iosdc.jp/2023/">https://iosdc.jp/2023/</a></p> <h3 id="今年の協賛について">今年の協賛について</h3> <p>シルバースポンサーで協賛させていただいております。<br/> ノベルティーには付箋を用意しました。ぜひご利用ください。<br/> また、会場の「ご自由にお持ちくださいコーナー」にノベルティのノートを用意します。数は少ないですが手に取っていただけると幸いです。</p> <p>今年はポスターセッションなど、会場でコミュニケーションを楽しめる企画があります。オフラインでは知ることができない情報や熱量を得る機会がたくさんありますので、ぜひ会場に行って色々な方と話してみるのはいかかでしょうか?</p> <p><code>#口コミで累計800万ユーザー</code></p> ksugahara08 スタディプラスのAWSコストを約3割削減できた話 hatenablog://entry/820878482951139481 2023-08-07T08:00:00+09:00 2024-03-26T16:09:40+09:00 この記事ではスタディプラスが2023年上半期にAWSコストを削減した取り組みを紹介しています。 <p>こんにちは。スタディプラス SREグループの<a href="https://twitter.com/capytan_el34">水口</a>です。</p> <p>この記事では当社が2023年上半期にAWSコストを削減した取り組みを紹介します。</p> <p>2022年から円安が続いており、当社のAWSコストにも大きな影響を与えています。この課題は優先順位の高いものとして認識されていました。断続的な改善を続けており、特に2023年3月から6月にかけて、より重点的に取り組みました。その結果、前年度と同様の利用状況の月と比較してAWSコストを約3割削減できました。</p> <p>また、為替レートの差を考慮しなくても大幅なAWSコストの削減に成功しています。2022年1月には米ドル対円相場が110円台であったことを思い出すと、当時の為替レートは夢のようなものだったと感じますね。</p> <p>この記事では2023年上半期のコスト最適化策に焦点を絞って紹介します。</p> <ul class="table-of-contents"> <li><a href="#基本方針">基本方針</a><ul> <li><a href="#費用対効果の高いリソースに優先的に手を入れる">費用対効果の高いリソースに優先的に手を入れる</a></li> <li><a href="#サービスの信頼性を損なわない">サービスの信頼性を損なわない</a></li> </ul> </li> <li><a href="#Aurora-IO-Optimized">Aurora I/O-Optimized</a></li> <li><a href="#マシンリソースの効率的な利用">マシンリソースの効率的な利用</a><ul> <li><a href="#EC2コスト削減の成果">EC2コスト削減の成果</a></li> <li><a href="#EKS-Nodeにスポットインスタンスを利用する">EKS Nodeにスポットインスタンスを利用する</a></li> <li><a href="#EKS-Nodeのリソースの効率化">EKS Nodeのリソースの効率化</a><ul> <li><a href="#EKSで利用しているログルーターの変更">EKSで利用しているログルーターの変更</a></li> <li><a href="#Pod配置戦略とNodeのEC2インスタンスファミリーの最適化">Pod配置戦略とNodeのEC2インスタンスファミリーの最適化</a></li> </ul> </li> <li><a href="#バッチ処理基盤をEC2からEKSに載せ替える">バッチ処理基盤をEC2からEKSに載せ替える</a></li> </ul> </li> <li><a href="#CloudFront-Security-Saving-Bundleの導入">CloudFront Security Saving Bundleの導入</a></li> <li><a href="#S3バケットオブジェクトの整理">S3バケット・オブジェクトの整理</a></li> <li><a href="#EKSで利用しているApplication-Load-Balancerの集約">EKSで利用しているApplication Load Balancerの集約</a></li> <li><a href="#Amazon-CloudWatch--Datadog">Amazon CloudWatch + Datadog</a></li> <li><a href="#パフォーマンスチューニングや不要な機能の削除">パフォーマンスチューニングや不要な機能の削除</a></li> <li><a href="#AWSコスト削減施策を行ってきた感想と今後について">AWSコスト削減施策を行ってきた感想と今後について</a><ul> <li><a href="#宣伝-Podcastのご紹介">【宣伝】 Podcastのご紹介</a></li> </ul> </li> </ul> <h2 id="基本方針">基本方針</h2> <h3 id="費用対効果の高いリソースに優先的に手を入れる">費用対効果の高いリソースに優先的に手を入れる</h3> <p>AWSコストを効果的に改善するために、費用対効果の高い領域に重点的に取り組むことを意識しました。具体的には、<a href="https://aws.amazon.com/jp/aws-cost-management/aws-cost-explorer/">AWS Cost Explorer</a>を活用して月次のコストを振り返り、支配的な要因を把握するよう心がけています。変化がある場合には原因を特定して対策を講じます。</p> <h3 id="サービスの信頼性を損なわない">サービスの信頼性を損なわない</h3> <p>コスト削減は重要ですが、サービスの信頼性を損なわないような対策を重視しています。例えば、EC2やRDSのインスタンスサイズを極端に削減するなど、サービスの信頼性を脅かすような変更は避けています。</p> <h2 id="Aurora-IO-Optimized">Aurora I/O-Optimized</h2> <p>2023年5月にAurora I/O-Optimizedという新しいストレージ構成がリリースされました。この新構成ではインスタンス料金とストレージ料金は増加しますが、一方でストレージのI/Oに対する費用が発生しません。<a href="#f-ee587831" id="fn-ee587831" name="fn-ee587831" title="以前のAurora Standardでは、I/Oに対して0.24USD/100万リクエストの費用がかかりました。詳細はこちらを参照してください https://aws.amazon.com/jp/rds/aurora/pricing/ ">*1</a></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fblogs%2Fnews%2Fnew-amazon-aurora-i-o-optimized-cluster-configuration-with-up-to-40-cost-savings-for-i-o-intensive-applications%2F" title="新規 – I/O 集約型のアプリケーションで最大 40% のコスト削減を実現する Amazon Aurora I/O-Optimized クラスター設定 | Amazon Web Services" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/blogs/news/new-amazon-aurora-i-o-optimized-cluster-configuration-with-up-to-40-cost-savings-for-i-o-intensive-applications/">aws.amazon.com</a></cite></p> <p>I/Oに対する課金が懸念されていた当社にとって、この新機能は望んでいたものであり、大幅なコスト削減が期待できると判断し、すぐに検証を行い設定を導入しました。</p> <p>スタディプラスは通常、4月から8月にかけてリクエスト数が増加する傾向にあります。6月上旬に設定を有効化した結果、RDS料金は5月よりも低くなりました。例年通り6月は5月よりも多くのリクエストが発生していたこともあり、コスト削減の効果が得られたと判断しています。</p> <p><figure class="figure-image figure-image-fotolife" title="2023年6月にAurora I/O-Optimizedを導入した"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/studyplus/20230721/20230721184545.png" width="1200" height="603" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>2023年6月にAurora I/O-Optimizedを導入した</figcaption></figure></p> <h2 id="マシンリソースの効率的な利用">マシンリソースの効率的な利用</h2> <h3 id="EC2コスト削減の成果">EC2コスト削減の成果</h3> <p>EC2コスト削減においても大きな成果を上げることができました。後述しますが、スポットインスタンス導入の結果、Saving Plansが断続的に余るタイミングが出てきたので、今年のSaving Plans更新時に再計算をしてこの対応は区切りを迎えそうです。</p> <p><figure class="figure-image figure-image-fotolife" title="EC2コスト削減の推移"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/studyplus/20230721/20230721190926.png" width="1120" height="550" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>EC2コスト削減の推移</figcaption></figure></p> <h3 id="EKS-Nodeにスポットインスタンスを利用する">EKS Nodeにスポットインスタンスを利用する</h3> <p>当社では古くからあるサービスをEC2からEKSに移行し、新しいサービスも既存のEKSクラスターにデプロイしています。</p> <p>中断耐性がない処理を行うコンテナには使えませんが、中断を許容できるPodについては配置先Nodeをスポットインスタンスにすることで大幅なコスト削減が実現できました。</p> <p><a href="https://aws.amazon.com/jp/ec2/spot/">EC2スポットインスタンス</a>は公式サイトによると <code>オンデマンドインスタンスに比べ最大90%低い価格で購入</code> できるとされており、今回のEC2コスト削減において重要な要素となっています。スポットリクエストのページから確認したところ、当社の場合は少なくとも65%以上のコストを削減できていました。</p> <p>スポットインスタンスの利用に適しているサービスの性質については、AWS re:Postにも詳細が記載されています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frepost.aws%2Fja%2Fknowledge-center%2Feks-spot-instance-best-practices" title="Amazon EKS で EC2 スポットインスタンスを使用するためのベストプラクティス" 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://repost.aws/ja/knowledge-center/eks-spot-instance-best-practices">repost.aws</a></cite></p> <h3 id="EKS-Nodeのリソースの効率化">EKS Nodeのリソースの効率化</h3> <p>EKS Nodeのリソースを効率的に利用してNode数(= EC2インスタンス数)を減らすため、以下のような取り組みを行いました。</p> <h4 id="EKSで利用しているログルーターの変更">EKSで利用しているログルーターの変更</h4> <p>本番環境ではEC2を利用していた時代からログルーティングにはFluentdを使用していましたが、EKSへの移行後はリソース効率の低さに悩まされていました。そこでリソース効率を向上させるため、より軽量で効率の良い<a href="https://fluentbit.io/">Fluent Bit</a>へ移行しました。</p> <p>移行の結果は期待通りであり、EKSの本番環境においてNode数を減らすことに成功しました。</p> <h4 id="Pod配置戦略とNodeのEC2インスタンスファミリーの最適化">Pod配置戦略とNodeのEC2インスタンスファミリーの最適化</h4> <p>EKSクラスタのNode数を削減するための取り組みとして、PodのリソースとNodeのインスタンスサイズの最適化を行いました。さらにNodeのEC2インスタンスファミリーをより最新のものに変更することで、マシンリソースの効率化しました。</p> <h3 id="バッチ処理基盤をEC2からEKSに載せ替える">バッチ処理基盤をEC2からEKSに載せ替える</h3> <p>バックグラウンドで実行するバッチ処理基盤用(Sidekiq)において、エンキューが最も多い時間帯を基準にした台数のEC2が稼働していました。このため、利用者が少ない時間帯にはマシンリソースが大量に余ってしまうという課題が生じていました。</p> <p>そこで、SidekiqをEKSに載せ替え、<a href="https://keda.sh/">KEDA</a>を利用してSidekiqのキューの状況を参照してSidekiqのPod数をオートスケーリングできるように改善しました。</p> <p>この変更により、エンキューが少ない時間帯には過剰なPodが配備されなくなり、起動しているEC2の数を減らすことに成功しました。</p> <p>また、SidekiqのEKS移行に関しては別にブログ記事も書いておりますので、よろしければご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.studyplus.co.jp%2Fentry%2F2023%2F01%2F23%2F170000" title="Amazon EKSクラスタ上で動作するSidekiqをGraceful Shutdownさせる - Studyplus Engineering Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.studyplus.co.jp/entry/2023/01/23/170000">tech.studyplus.co.jp</a></cite></p> <h2 id="CloudFront-Security-Saving-Bundleの導入">CloudFront Security Saving Bundleの導入</h2> <p><a href="https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/savings-bundle.html">CloudFront Security Savings Bundle</a>は、向こう1年間の月の最低使用量を確約することで一定の割引が得られるサービスです。WAFにも割引が適用される点も魅力的です。</p> <p>2021年にリリースされ、さまざまな企業のテックブログにもその効果が書かれているサービスですが、当社では適用が漏れておりました。サービスが成長してくるとCloudFront料金は馬鹿にならないため、利用しておくべきサービスでした。</p> <p>導入に際して、過去のCloudFrontの使用状況を元にコミットすべき最低使用量がサジェストされます。結果に違和感がなかったためサジェスト通りの使用量をコミットしました。大学入試シーズンに適用したため直接の比較はできませんが、Cost Explorerのデータから大幅なコスト削減が実現できていることを確認できました。</p> <p><figure class="figure-image figure-image-fotolife" title="CloudFront Security Saving Bundle導入前後"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/studyplus/20230719/20230719200113.png" width="1200" height="745" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CloudFront Security Saving Bundle導入前後</figcaption></figure></p> <h2 id="S3バケットオブジェクトの整理">S3バケット・オブジェクトの整理</h2> <p>会社の10年以上の歴史の中で、使われないにもかかわらず整理されずに残っていたS3バケット・オブジェクトが存在していました。これらのオブジェクトが社内ポリシー上も保存の必要がないことを調査し、削除作業を進めました。また、調査の過程でライフサイクルルールが設定されていないS3バケットも一部見つかりましたので、ライフサイクルルールの整備も行っています。既存のルールについても見直しを予定しています。</p> <p>調査にあたって、以下2つの機能が有用でしたのでご紹介します。</p> <ul> <li><p>S3 Storage Lens<br/> オブジェクトストレージの使用状況やアクティビティの傾向をOrganization全体で可視化できる機能です。すべてのS3バケットの使用状況を確認するダッシュボードを作成し、そこから整理すべきS3バケットを特定しました。</p></li> <li><p>ストレージクラス分析<br/> S3バケットごとに設定するもので、ストレージへのアクセスパターンを分析し、オブジェクトを適切なストレージクラスに移行するタイミングをサジェストしてくれる機能です。今後もライフサイクルルールの見直しに活用していく予定です。</p></li> </ul> <p>不要なファイルが蓄積されていたため、これらの施策により想定以上にコストを削減できました。</p> <p><figure class="figure-image figure-image-fotolife" title="S3のコスト推移"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/studyplus/20230721/20230721195438.png" width="1110" height="570" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>S3のコスト推移</figcaption></figure></p> <h2 id="EKSで利用しているApplication-Load-Balancerの集約">EKSで利用しているApplication Load Balancerの集約</h2> <p>改善前のEKSクラスタでは1つのIngressに対して1つのApplication Load Balancer(ALB)が作成される状態で、分ける意味がなくてもALBが別に作成され乱立する状況でした。</p> <p>ALBは起動するだけでも料金が発生してしまいます。そのため、AWS Load Balancer ControllerのIngressGroup機能を利用して、集約できる範囲でALBをまとめるアプローチを取りました。この施策によりコストの削減ができました。</p> <p><figure class="figure-image figure-image-fotolife" title="AWS ELBのコスト推移"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/studyplus/20230721/20230721192525.png" width="1106" height="704" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>AWS ELBのコスト推移</figcaption></figure></p> <h2 id="Amazon-CloudWatch--Datadog">Amazon CloudWatch + Datadog</h2> <p>DatadogではAmazon Web Services Integrationsを用いておりましたが、例えば利用していないリージョンのメトリクスを取得しているなど整備されていない箇所がありました。そこで、明らかに使っていないリージョンやサービスのメトリクスを取得しないようにしました。</p> <p><figure class="figure-image figure-image-fotolife" title="Datadog管理画面の例"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/studyplus/20230719/20230719194505.png" width="1200" height="828" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Datadog - Amazon Web Services Integration</figcaption></figure></p> <p>Datadogを使用してきた中で手が回りきっていなかった部分でしたが、DatadogのCloudWatchからのメトリクス取得範囲を減らすことで、CloudWatchの料金を50%削減できました。</p> <p><figure class="figure-image figure-image-fotolife" title="CloudWatchのコスト推移"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/studyplus/20230721/20230721192847.png" width="1110" height="578" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>CloudWatchのコスト推移</figcaption></figure></p> <h2 id="パフォーマンスチューニングや不要な機能の削除">パフォーマンスチューニングや不要な機能の削除</h2> <p>サーバーグループのメンバーと協力して、DBのパフォーマンス改善に取り組みました。特にリクエスト数が多くなる受験シーズンの直前にパフォーマンスを向上させることができたため、今年の費用削減に期待しています。</p> <p>改善内容は大まかに分類すると以下の通りです。</p> <ul> <li>DBパフォーマンス改善 (N+1改善、キャッシュ活用)</li> <li>利用していない不要な機能の削除</li> <li>設計見直し</li> </ul> <p>特にDBパフォーマンス改善に関しては、サーバーグループの青山さんがブログ記事を書いているので、詳細を知りたい方はぜひご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.studyplus.co.jp%2Fentry%2F2023%2F02%2F24%2F100000" title="Amazon RDSのPerformance Insightsを利用してアプリケーション側からDBの負荷を改善した話 - Studyplus Engineering Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.studyplus.co.jp/entry/2023/02/24/100000">tech.studyplus.co.jp</a></cite></p> <h2 id="AWSコスト削減施策を行ってきた感想と今後について">AWSコスト削減施策を行ってきた感想と今後について</h2> <p>スタディプラスのAWSコスト削減施策を振り返ると、以下のようなパターンがありました。</p> <ol> <li>利用するだけでお得になる機能や料金体系の活用</li> <li>当社で本来不要だったサービスや機能の無効化</li> <li>アプリケーション(主にDB周り)のパフォーマンス改善</li> <li>インフラの設計変更</li> </ol> <p>基本方針で述べたとおり、AWSコスト削減については、費用対効果が高い施策(1.)から優先的に実施するのが良いと感じます。</p> <p>普段からやっていてよかったこととしては、月例でAWSコストを振り返る時間を設けていたことです。AuroraのI/O課金に課題があることを認識できていたため、2023年5月にリリースされた「Aurora I/O-Optimized」は渡りに船とばかりに検証して、すぐに導入できました。Cost Explorerを用いたAWSコストの振り返り・分析は今後も続けていきたいです。</p> <p>また、DBバージョンなどのバージョンアップを普段から行えていたことで新機能を試すことができたため、改善のフットワークの軽さに繋がりました。</p> <p>当社のインフラは改善の余地が多く残されているため、引き続き改善に取り組んでいく予定です。コスト削減と同時に、パフォーマンス向上や効率的なリソース利用にも注力して、より良いインフラ環境を実現していきます。</p> <h3 id="宣伝-Podcastのご紹介">【宣伝】 Podcastのご紹介</h3> <p>スタディプラスでは所属エンジニアが出演する<a href="https://anchor.fm/studyplus-engineers">Studyplus Engineering Podcast</a>を配信しており、私も定期的にホストを務めております。第18回ではVPoE大石をゲストに当社の採用活動に関するお話をしました。是非聞いてみてください。</p> <p><iframe src="https://anchor.fm/studyplus-engineers/embed/episodes/18-a-VPoE-e25pscl" height="102px" width="400px" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://podcasters.spotify.com/pod/show/studyplus-engineers/episodes/18-a-VPoE-e25pscl/a-aa0onbi">podcasters.spotify.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-ee587831" id="f-ee587831" name="f-ee587831" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">以前のAurora Standardでは、I/Oに対して0.24USD/100万リクエストの費用がかかりました。詳細はこちらを参照してください <a href="https://aws.amazon.com/jp/rds/aurora/pricing/">https://aws.amazon.com/jp/rds/aurora/pricing/</a> </span></p> </div> studyplus Rails製APIとReact製画面の並行開発をする際の進め方 hatenablog://entry/820878482944659069 2023-07-28T08:00:00+09:00 2023-07-28T08:00:16+09:00 こんにちは。開発部エンジニアの石上です。会社のブログ記事を書くのが久しぶりすぎて、ここにいつも書いていたちょっとした日常エピソードも何を書いたらいいのかわからなくなっています。ここに悩んでも仕方ないので、本題に入ります! 今回は、 Studyplus for Schoolの新機能開発において、Rails製のバックエンドとReact製のフロントエンドをどう並行して開発しているかを紹介します。 APIと画面を並行して開発したい Studyplus for Schoolのシステムには、主にRails製のバックエンドとReact製のフロントエンドがあります。基本的に画面はAPIを叩いてデータを取ってき… <p>こんにちは。開発部エンジニアの石上です。会社のブログ記事を書くのが久しぶりすぎて、ここにいつも書いていたちょっとした日常エピソードも何を書いたらいいのかわからなくなっています。ここに悩んでも仕方ないので、本題に入ります!</p> <p>今回は、 Studyplus for Schoolの新機能開発において、Rails製のバックエンドとReact製のフロントエンドをどう並行して開発しているかを紹介します。</p> <h2 id="APIと画面を並行して開発したい">APIと画面を並行して開発したい</h2> <p>Studyplus for Schoolのシステムには、主にRails製のバックエンドとReact製のフロントエンドがあります。基本的に画面はAPIを叩いてデータを取ってきます。代わりの仕組みを用意せずにやると、APIができるまで、データがあるときの表示ができません。この制限は、開発を遅くしてしまいます。</p> <p>そこで、なるべくこういった待ちを発生させずに機能実装を進められるように、いくつかの工夫をしています。この記事ではその工夫について紹介していきます。</p> <p>今回APIと画面の並行開発の進め方として紹介するのは、以下の4ステップです。</p> <ul> <li>モブ設計で開発者同士の認識合わせ</li> <li>API仕様とSwagger UIでの表示</li> <li>committeeでAPI仕様と実装が合っているかの確認</li> <li>MSWを使ってAPIレスポンスをモック</li> </ul> <h2 id="モブ設計で開発者同士の認識合わせ">モブ設計で開発者同士の認識合わせ</h2> <p>まずはバックエンドとフロントエンドで、どんなデータのやり取りになるかを知っておく必要があります。Studyplus for Schoolを開発するチームで最近始まった取り組みとして、モブ設計があります。</p> <p>開発者が、オンラインで書き込みができるボードに集まって、必要なエンティティや関連から始まり、最後には必要になりそうなAPIのパスや外部とのやり取りまで話し合います。この詳細は別記事に譲るとして、並行開発のための工夫としては、ここでやり取りするデータやパスの認識がざっくり合うので、それ以降のレビューコストが下がります。</p> <h2 id="API-仕様と-Swagger-UI-での表示">API 仕様と Swagger UI での表示</h2> <p>認識合わせができたら、今度はOpenAPIのフォーマットでAPI仕様を記述します。記述したら、関係する開発者にコードレビューの依頼を飛ばします。細かいパラメータやプロパティの仕様を、バックエンド・フロントエンド両方の視点から問題ないか確認します。</p> <p>メインのブランチへ取り込まれてデプロイされると、社内向けの画面でAPIの仕様が確認できるようになります。Studyplus for SchoolではSwagger UIを使用して、アプリケーションが使うAPIの仕様を表示しています。</p> <p><figure class="figure-image figure-image-fotolife" title="Swagger UIでAPIの仕様を表示。ここでパスやリクエスト・レスポンスの仕様が確認できます"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/shgam/20230717/20230717133439.png" width="1200" height="543" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Swagger UIでAPIの仕様を表示。ここでパスやリクエスト・レスポンスの仕様が確認できます</figcaption></figure></p> <h2 id="committee-で-API-仕様と実装が合っているかの確認">committee で API 仕様と実装が合っているかの確認</h2> <p>API仕様の記述とともに、APIから仮のデータを返してテストコードも書いておく場合があります。本実装をする前に仮のデータを返すことで、フロント側の実装をしたときにつないで確認できるようになりますし、先にテストを記述し、そのテストが通る状態にできます。この辺は過去に、<a href="http://blog.hatena.ne.jp/atomiyama/">id:atomiyama</a>さんの記事に詳しく書かれています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.studyplus.co.jp%2Fentry%2F2019%2F06%2F17%2F094925" title="スキーマ駆動開発のススメ - Studyplus Engineering Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.studyplus.co.jp/entry/2019/06/17/094925">tech.studyplus.co.jp</a></cite></p> <p>他のプロジェクトではAPI仕様は書いているが実装と合っていないというのも見ることがあります。4年も前からずっと続けてきていることなので恩恵を忘れていましたが、committeeを使ったテストコードでそこに差分が出ないようにしているのは改めて考えると大事なことだと感じます。</p> <h2 id="MSW-を使って-API-レスポンスをモック">MSW を使って API レスポンスをモック</h2> <p>OpenAPIの仕様まで決まったところで、バックエンドとフロントエンドは並行して開発を進めます。先述の<a href="http://blog.hatena.ne.jp/atomiyama/">id:atomiyama</a>さんの記事に書かれているように、これまでは、開発中のエンドポイントからは先にダミーデータを返すようにして、フロントではそれを使って実装を進めるということをしていました。</p> <p>しかし、画面の確認という点では固定のダミーデータでは物足りない場面があります。たとえば、文字数がとても長い場合にレイアウトが崩れないか?とか、プロパティの値によって細かく表示が変わる場合、すべてのパターンが確認できるか?など。データのバリエーションごとの見た目を確認したいようなときです。1人で確認する分には手元で書き換えてみても良いのですが、気になるパターンを予めモックして、デザイナーさんにレビューを依頼したい場面もあります。</p> <p>そこで、最近ではフロントエンド側でAPIレスポンスを差し替えて、ほしいパターンの画面確認ができるようにしようと、<a href="https://mswjs.io/">MSW</a>を使っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmswjs.io%2F" title="Mock Service Worker" 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://mswjs.io/">mswjs.io</a></cite></p> <p>開発環境でこれを有効にして、開発中の機能のAPIのみ、実際のバックエンドからのレスポンスではなく、フロントエンドの実装者が定義したダミーデータを返すようにします。デザイナーさんに確認してもらいたいパターンを用意しておき(あるいはデザイナーさんから、このパターンで見てみたいなどあれば追加して)、確認用の環境にデプロイして見た目の確認をしてもらうことが可能です。</p> <p>(余談ですが、実はMSW自体は以前からテストコードで利用しています。テストコードでの利用方法について知りたい方は以下の記事をご覧ください。)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.studyplus.co.jp%2Fentry%2F2020%2F10%2F05%2F090000" title="ReactのSPAでUIへのテストを真面目に取り組んでいく話 - Studyplus Engineering Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.studyplus.co.jp/entry/2020/10/05/090000">tech.studyplus.co.jp</a></cite></p> <p>バックエンドの実装ができたら、MSWからダミーデータを取るのをやめ、実際のAPIにつなぐようにすれば、新機能の出来上がりです。バックエンドとフロントエンドがつながった状態で確認ができるようになるので、ここからQAにテストの実施を依頼します。</p> <h2 id="課題">課題</h2> <p>工夫をしてきてはいるものの、まだ課題もあります。</p> <p>MSWによるモックは見た目の確認には便利ですが、それを外すのが遅くなると、APIと結合した確認をするタイミングが遅れ、結果として結合レベルでの不具合に気づくのが遅くなります。画面側でのモックはAPI側が未実装の内だけにとどめて、実際のAPIと繋げられるようになったらすぐにモックを外すようにしたほうが良さそうです。</p> <p>また、MSWから配信するデータの定義がやや煩雑になっています。API側のテストコードで使っているFactoryBotのようなものを画面側でも用意して、様々な値のデータをさっと作れる環境を用意できると、テスト用のデータを用意する手間が減りそうです。</p> <p>その他、OpenAPIの仕様から画面側で使うTypeScriptの型定義を生成したいという話は以前から何回か出ています(これは最近ようやく着手できたところです)。</p> <p>こういった課題も、都度解決していけたらと考えています。</p> <h2 id="まとめ">まとめ</h2> <p>現在Studyplus for Schoolの開発チームで行っている、APIと画面の並行開発のプロセスを紹介しました。</p> <p>おそらく、この辺の進め方はチームやプロダクトによって微妙に違うところでしょう。この記事を読んだ方で、もっと効率の良いやり方があるよ!という方がいましたら教えていただけると嬉しいです。</p> shgam 開発エンジニアからQAエンジニアになって3ヶ月やってみた感想 hatenablog://entry/4207112889982057153 2023-04-24T11:00:00+09:00 2023-10-03T14:12:39+09:00 こんにちは。QAエンジニアの森長です。 QAエンジニアにジョブチェンして3ヶ月が経ちました。今回は3ヶ月でやってきたことを振り返りつつ、弊社QAの様子をご紹介できればと思います。 なぜQAエンジニアになったのか 弊社は10年以上続くサービスですが仕様に関するドキュメントなどがあまり残っておらず、開発する際には既存仕様の調査に多くの時間がかかっておりスムーズに開発できないことがありました。 また、開発完了後のテストもQAがいなかったためプロジェクトメンバーで集まり動作確認する会をリリース前に行っており、私自身「テストは十分だっただろうか」とモヤモヤした気持ちを抱えていました。 そんな中、知人の開… <p>こんにちは。QAエンジニアの森長です。</p> <p>QAエンジニアにジョブチェンして3ヶ月が経ちました。今回は3ヶ月でやってきたことを振り返りつつ、弊社QAの様子をご紹介できればと思います。</p> <h1 id="なぜQAエンジニアになったのか">なぜQAエンジニアになったのか</h1> <p>弊社は10年以上続くサービスですが仕様に関するドキュメントなどがあまり残っておらず、開発する際には既存仕様の調査に多くの時間がかかっておりスムーズに開発できないことがありました。</p> <p>また、開発完了後のテストもQAがいなかったためプロジェクトメンバーで集まり動作確認する会をリリース前に行っており、私自身「テストは十分だっただろうか」とモヤモヤした気持ちを抱えていました。</p> <p>そんな中、知人の開発エンジニアが「QAエンジニアがチームにいた時は開発がしやすかった」という話をしてくれて、チームに貢献できるQAエンジニアという仕事を認識しました。また、参加した社外勉強会にたまたま登壇していたQAエンジニアの話を聞き「QAの活動はユーザーに対して品質を保証するだけでなく、開発・企画が製品を作りやすくなることにも繋がるかも」と感じたのがきっかけでした。</p> <p>こういった出来事もあり「開発に関わるメンバーが安心して素早くユーザーに価値ある製品を届けるサポートがしたい」と思いQAエンジニアになりました。</p> <h1 id="QAエンジニアになって3ヶ月で取り組んだこと">QAエンジニアになって3ヶ月で取り組んだこと</h1> <p>私がQAエンジニアにジョブチェンした頃には、弊社に1人目のQAエンジニアがジョインしていたのでメンターとしてついてもらいました。</p> <h2 id="既存のテストケース実行">既存のテストケース実行</h2> <p>一番最初に、既存システムの仕様把握のためにテストケースの実行から始めました。</p> <p>テストケースの実行から始めた理由としては、以下です</p> <ul> <li>今後テストするシステムの仕様を把握してないと何もできないため</li> <li>システム全体の仕様書がなかったので既存のテストケースが仕様書がわりだったため</li> </ul> <p>正直最初は「書かれている通りに操作するだけ」と思っていましたが、実際にはやっていく中で考えるべきことはたくさんありました。</p> <p>まず使っているテスト管理ツールに慣れる必要があります。</p> <p>スプレッドシートを使っている会社も多いそうですが、弊社では<a href="https://qase.io/">Qase</a>というツールを使っています。シンプルで直感的に使えるので、慣れるのにそこまで時間を要さなかったのは良かった点です。</p> <p>また、そのテストケースで機能の確認が網羅できてるのか?どういう順番でケース実行すれば効率が良いのか?どのデバイスまで確認するか?なども考慮が必要です。</p> <p>バグが出た際にはただ報告をするのではなく、開発者が迅速に対応できるよう以下のことを行う必要があります。</p> <ul> <li>誰もが再現できる再現手順を報告内容に入れる</li> <li>原因を追求し報告内容に添える</li> <li>対応の優先順位づけを行う</li> </ul> <p>このあたりは実際にテストケース実行をしていく中で、先輩QAがマンツーマンで指導していただき非常に助かりました。</p> <p>テストケース実行を通して、書かれているテストケースの実行だけではなくて、思ったよりもずっと意識することが多いなという感想を持ちました。</p> <h2 id="開発案件を通して一通りのテストプロセスを経験">開発案件を通して一通りのテストプロセスを経験</h2> <p>弊社のテストプロセスは以前、こちらの記事で紹介しております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.studyplus.co.jp%2Fentry%2F2023%2F02%2F27%2F100000" title="初めてのQAエンジニア導入 - Studyplus Engineering Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.studyplus.co.jp/entry/2023/02/27/100000">tech.studyplus.co.jp</a></cite></p> <p>プロセスの中で特に苦労した(している)点は、テスト分析、テスト設計の過程で行う仕様書レビューとテストケース作成です。</p> <p>仕様書レビューとは、企画者が書いた仕様書をレビューすることで、以下が目的です。</p> <ul> <li>不具合の作り込みを防ぐ</li> <li>ユーザーがより使いやすくなるための提案をする</li> <li>QAエンジニアの仕様理解を深める</li> </ul> <p>最初はレビュー観点が足りず、何をレビューすべきか悩むことが多かったです。特にもともと開発エンジニアだったこともあり「どうすればこの仕様を実現できるか?」に考えが偏り、「そもそもこの仕様が本当にユーザーの課題を解決できるか?」という前提が抜けてしまうことが多かったです。</p> <p>観点が足りない問題に関しては、仕様書レビューを先輩QAとモブ作業で行い、どのようにその観点に至ったのか?など深掘りしていく中で、仕様書レビューの考え方が少しずつ身についてきました。</p> <p>テストケースの作成に関しては、誰が行っても同じ結果になるように作ることがなかなか難しいなと日々思っています。 開発エンジニアなら数ヶ月後に自分の書いたコードを見返すとよくわからないという経験が誰しもあると思うのですが、自然言語で書いているテストケースでも同じことが起きるんだなとやってみて実感しました。</p> <p>読み返してわからなくなってしまう原因としては、暗黙知が存在しているからだと思うので、QAチームで互いにテストケースをレビューすることで防ぐようにしています。 互いにレビューすることでテストケースの完成度が上がるだけでなく、自分が対応していない開発案件も仕様理解ができる、テスト実行をスムーズに行える、などのメリットもあるなと感じています。</p> <h1 id="3ヶ月やってみて">3ヶ月やってみて</h1> <p>この3ヶ月で、仕様書レビューで不具合の作り込みを防いだり、本番リリース前に不具合を見つけられたのは貢献できている点かなと思っております。</p> <p>また、QAチームに人が増えたことで、ライブラリアップデート・リファクタ時などにリグレッションテストを実施できるようになってきたのは良かったです。</p> <p>QAになることを決心した際に持っていた「開発に関わるメンバーが安心して、素早く、ユーザーに価値ある製品を届けるサポートがしたい」という気持ちは実際にやってみて今でも変わっていません。</p> <p>少し変わったところがあるとすれば、開発に関わるメンバーだけでなく、自分と同じQAチームのメンバーもサポートしていきたいと思うようになったところです。</p> <p>現状、弊社ではリグレッションテストは全て手動で行っており、リグレッションテストの必要なタイミングが重なるとQAの人手不足になることがあります。 今後は、こういった状況を改善したい+開発エンジニアだったバックグラウンドを活かすべく、SET(テスト自動化をメインとしたQAエンジニア)を目指していきたいです。</p> nagamina2 カジュアル面談でよく聞かれる質問とその回答を紹介します hatenablog://entry/4207112889978435389 2023-04-12T10:00:00+09:00 2023-12-18T16:49:41+09:00 はじめに こんにちは。開発部の大石です。2019年5月にスタディプラスへ入社してから、もうすぐ丸4年になります。入社した時に高校生だった長男が就職し、中学生だった次男は大学生になりました。 そんな私ですが、昨年4月からはソフトウェア事業本部 開発部の部長/VPoEというポジションを担当しており、一昨年からソフトウェアエンジニア(以下、エンジニアと略します)の採用責任者を兼任しております。 今回はこれまでカジュアル面談させていただいた候補者の方からよく聞かれる質問をピックアップしまして、普段私がどのように答えているか紹介します。 なお、候補者の方からの質問は採用ピッチに書かれている内容に関する質… <h1 id="はじめに">はじめに</h1> <p>こんにちは。開発部の大石です。2019年5月にスタディプラスへ入社してから、もうすぐ丸4年になります。入社した時に高校生だった長男が就職し、中学生だった次男は大学生になりました。</p> <p>そんな私ですが、昨年4月からはソフトウェア事業本部 開発部の部長/VPoEというポジションを担当しており、一昨年からソフトウェアエンジニア(以下、エンジニアと略します)の採用責任者を兼任しております。 今回はこれまでカジュアル面談させていただいた候補者の方からよく聞かれる質問をピックアップしまして、普段私がどのように答えているか紹介します。</p> <p>なお、候補者の方からの質問は採用ピッチに書かれている内容に関する質問もありましたので、参考までにリンクしておきます。当社のエンジニア職にご興味がありましたらぜひこちらもお読みください。</p> <iframe src="https://docs.google.com/presentation/d/e/2PACX-1vT5K0YNO-YOiXTwUgNxB27lN7uuOsgrHfm8_qKHUGYCk8fa6IZeYVQLqWUgLcl9rGFkeqqZLnwdELpc/embed?start=false&loop=false&delayms=3000" frameborder="0" width="960" height="569" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> <p>今回紹介する質問はいくつかのカテゴリに分けています。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#カジュアル面談でよく聞かれる質問とその回答">カジュアル面談でよく聞かれる質問とその回答</a><ul> <li><a href="#事業について">事業について</a></li> <li><a href="#プロダクトについて">プロダクトについて</a></li> <li><a href="#組織について">組織について</a></li> <li><a href="#技術について">技術について</a></li> <li><a href="#働き方について">働き方について</a></li> <li><a href="#制度について">制度について</a></li> <li><a href="#選考について">選考について</a></li> </ul> </li> </ul> <h1 id="カジュアル面談でよく聞かれる質問とその回答">カジュアル面談でよく聞かれる質問とその回答</h1> <p>それではカジュアル面談でよく聞かれる質問とその回答を紹介します。</p> <h3 id="事業について">事業について</h3> <ul> <li>事業の柱はどこですか? <ul> <li>大きくは2つあります。BtoC向けに提供しているStudyplusの広告事業、BtoB向けに提供しているStudyplus for SchoolのSaaS事業の2つです。</li> </ul> </li> <li>事業のターゲットは高校生ですか? <ul> <li>現在のメインターゲットは高校生のユーザーです。事業としても高校生や学生向けのビジネスモデルとしての側面が強いです。</li> <li>しかし、Studyplusのユーザー全体の2割ほどは社会人のユーザーに使っていただいており、学生以外のユーザー層にも使っていただいています。</li> <li>社会人の方は英会話や資格の取得、読書などにお使いいただいている方が多いです。</li> </ul> </li> <li>Studyplusはどのようにユーザーを獲得していますか? <ul> <li>Studyplusはこれまで広告やプロモーションなどを使用したことがなく、ユーザーの口コミで多くの方に使っていただいています。</li> </ul> </li> </ul> <h3 id="プロダクトについて">プロダクトについて</h3> <ul> <li>プロダクトの開発方針は誰が決めて、どのように開発していますか? <ul> <li>プロダクトの開発方針は、企画部のプロダクトオーナーが事業の戦略案件として決めたプロジェクトを、四半期ごとのロードマップに落とし込んでいます。</li> <li>各プロジェクトの仕様については物にもよりますが、企画段階からエンジニアも一緒に入って検討することが多いです。</li> <li>プロダクトの機能開発以外にも、エンジニア視点での機能の改修や実行環境のアップグレードなどもロードマップに盛り込んでいます。</li> </ul> </li> <li>ユーザーの声や要望はどのように見えるようになっていますか?どのようなフローで改善タスクとして対応していますか? <ul> <li>ユーザーからの質問や要望、レビューなどが個人情報を除かれた状態でSlackに流れて来ます。エンジニアもユーザーからの反応などを目にする機会があります。</li> <li>ユーザーからの要望は各プロダクトオーナーがプロダクト毎に集計しており、費用対効果や優先度を考慮して何をどのように実現するか検討しています。</li> </ul> </li> <li>プロダクトの開発については、Studyplus Engineering Podcastの第17回でStudyplus for SchoolのPOを担当している花田さんが詳しく話していますのでぜひ聞いてみてください!</li> </ul> <iframe src="https://podcasters.spotify.com/pod/show/studyplus-engineers/embed/episodes/17-a-Studyplus-for-School-e21egsj" height="102px" width="400px" frameborder="0" scrolling="no"></iframe> <h3 id="組織について">組織について</h3> <ul> <li>サービスの規模からするとエンジニアの人数が少なく、少数精鋭のように感じます <ul> <li>エンジニアの入れ替わりもあり現在はやや少なく見えるというのもありますが、現在は比較的少人数でできることをやっているのは間違いありません。現在も一部のポジションでエンジニアを募集していますので年に数人ずつは開発組織の規模を大きくしていく予定です。</li> <li>2023年から新卒エンジニア2名も入社しており、これからも若い世代のエンジニアも増やしていく予定です。</li> </ul> </li> <li>休日、深夜などの障害対応はどのような体制を組んでいますか? <ul> <li>当番制などは決まっておらず、Slackのアラートなどで気がついたメンバーが対応するベストエフォートで対応しています。休日や深夜の障害対応は年に数回程度です。</li> </ul> </li> <li>社員のエンゲージメントについてどのような運用をされていますか? <ul> <li>wevoxというサービスを使って毎月チームや組織の健康状態をチェックしており、結果を基にチーム単位でメンバーと話して会社や組織に対する不満や疑問の解消をしています。</li> </ul> </li> <li>どのような社内勉強会を行なっていますか? <ul> <li>有志による勉強会を開催しており、勉強会の開催テーマは様々ですが、以下のような勉強会を定期開催していました。</li> <li>「アジャイルメトリクス」の輪講会</li> <li>「ソフトウェアアーキテクチャの基礎」の輪講会</li> <li>「Kubernetes完全ガイド」の輪講会</li> <li>Ruby 3.0の勉強会</li> <li>Datadogの共有会</li> </ul> </li> </ul> <h3 id="技術について">技術について</h3> <ul> <li>開発に使用しているRuby, Ruby on Rails, Reactのバージョンはいくつですか? <ul> <li>当社のシステムは複数のマイクロサービスで構成されており、2023年12月現在では9割のリポジトリが以下のバージョンです。これらは継続的にアップデートするために開発のロードマップへ組み込んでいます。 <ul> <li>Ruby 3.2.2</li> <li>Ruby on Rails 7.1.2</li> <li>React 18.2.0</li> </ul> </li> </ul> </li> <li>技術面での課題は何ですか? <ul> <li>ユーザー数増加にともなうパフォーマンス面の課題 <ul> <li>ユーザーが年々増加したり、ユーザーのデータが増加していますので、大量レコードを持ったデータベースの最適化やチューニング、アーキテクチャの見直しなどを行っています。</li> </ul> </li> <li>開発効率の向上 <ul> <li>既にサービス開始当初のコードはほぼ残っていませんが、10年ほど歴史のあるサービスのため、既存コードのメンテナビリティの向上に日々取り組んでいます。</li> <li>モバイルアプリと一部のWebではクロスプラットフォームのFlutterへの移行を推進しています。</li> </ul> </li> </ul> </li> <li>技術選定はどのように行っていますか? <ul> <li>機能開発や課題解決の実現に使用する技術はチームで相談して決定しています。チーム間の連携はリーダーの定例ミーティングで情報交換して開発部全体の技術連携をしています。</li> </ul> </li> <li>技術的負債の改善やリファクタリングはどのように行っていますか? <ul> <li>チームによって具体的な取り組み方は異なりますが、基本的にはサービスの開発と並行して取り組めるような開発体制としています。</li> <li>チームによっては改善デーをある曜日に設定してその日は改善タスクのみを実施する、スクラムのバックログのタスクが終わって手が空いたら着手するなどしています。</li> </ul> </li> <li>GitHub Copilotを業務で使用していますか? <ul> <li>使用しています。GitHub Copilotについては開発効率向上に寄与することを確認し、2023年3月より開発を行う全エンジニアにGitHub Copilot for Businessを付与しています。</li> </ul> </li> <li>技術スタックのCIにCircleCIがありますが、Jenkinsもあるのはなぜですか? <ul> <li>マイクロサービスでリポジトリが別れており、一部のサービスのデプロイのみJenkinsが残っているためです。それ以外はCircleCIを使用しています。</li> </ul> </li> </ul> <h3 id="働き方について">働き方について</h3> <ul> <li>業務中のコミュニケーションはどのように行っていますか? <ul> <li>Slackによる非同期のコミュニケーションがメインですが、必要に応じてSlackのハドルやGoogle Meetでのビデオ通話や画面共有でコミュニケーションしながら開発しています。</li> <li>モブプロ、ペアプロなどは必要があればその都度開催されており、自分から他のチームメンバーに声をかければ気軽にできる雰囲気があるチームです。</li> </ul> </li> <li>エンジニアはどれくらいリモートワークしていますか? <ul> <li>ほぼ100%フルリモートで働いており、オフィスにPCや開発端末の受け取りや返却など何か用事があれば出社するくらいです。 <ul> <li>なお、2023年4月から新卒のエンジニアが入社したチームのみ週1日を出社日としています。</li> </ul> </li> <li>2019年以降は通勤圏外に住んでいるエンジニアを採用しており、岩手・静岡・福岡に住んでいるメンバーがいます。</li> <li>都心から岩手・群馬などへ引っ越して働いているメンバーもいます。</li> </ul> </li> <li>エンジニアはどれくらい残業していますか? <ul> <li>一般のエンジニアの一ヶ月あたりの平均残業時間は10時間未満です。ほぼ残業せずに働いているメンバーもいます。</li> </ul> </li> <li>フルフレックスで具体的にどのような働き方ができますか? <ul> <li>例えば、朝早めの時間から始業して夕方に終わる人、お子さんの保育園の送り迎えの予定に合わせてその時間だけ離席して働く人などがいます。</li> <li>必ず1日8時間を働く必要はなく、月全体で規定の労働時間を満たしていればOKです。</li> </ul> </li> <li>副業する場合の条件はありますか? <ul> <li>業務への支障がないことを前提に競合他社以外の副業はOKです。</li> <li>申請フローはなく、上長に口頭で伝えるのみで良いというルールになっています。</li> </ul> </li> </ul> <h3 id="制度について">制度について</h3> <ul> <li>技術書の購入補助は電子書籍でも購入可能ですか? <ul> <li>可能です。一人当たり年6万円の予算を取っており、予算内であれば事前の申請なしで物理の本または電子書籍を購入できます。電子書籍の購入は個人のアカウントで購入して問題ありません。</li> </ul> </li> </ul> <h3 id="選考について">選考について</h3> <ul> <li>選考はどれくらいの期間がかかりますか? <ul> <li>大体1ヶ月程度になりますが、候補者の方のご都合にもなるべく合わせる形で進めています。</li> </ul> </li> <li>コーディングテストはどのような内容ですか? <ul> <li>コーディングテストはサーバーサイドもしくはフロントエンドのポジションの候補者に実施します。</li> <li>募集ポジションに合わせてアルゴリズム・Rails・Reactの問題を宿題形式で1週間の期間で解いてもらい、解答を共有していただきます。 <ul> <li>早い方だと数時間〜半日くらいで解答いただいているので、それほどボリュームのある問題ではありません。</li> </ul> </li> <li>アルゴリズムはいわゆる競技プログラミングの問題のような、標準入力から引数を受け取って特定のアルゴリズムを実装、結果を標準出力します。</li> <li>Railsはすでにある程度書かれているコードに対して特定の課題を解決するように改修してもらう問題です。</li> <li>Reactは用意された比較的シンプルな仕様の画面を実装してもらう問題です。</li> </ul> </li> <li>モバイルアプリのポジションの選考にコーディングテストはありますか? <ul> <li>モバイルアプリのポジションはコーディングテストはありません。1次面接にてご自身がこれまで開発されたアプリのプレゼンテーションや技術的な質問をさせていただきます。</li> </ul> </li> </ul> <p>今回は以上となります。</p> <p>今回のブログで当社にご興味を持たれたり、以前から興味をお持ちだった方により興味を持っていただけたら嬉しいです。 カジュアル面談は随時募集していますので、今回紹介した質問の深掘りや、紹介されていなかった質問もお待ちしています。 当社にご興味を持っていただいた方とお会いできることを楽しみにしています。</p> k_oishi スタディプラスはRubyKaigi 2023にゴールドスポンサーとして協賛します hatenablog://entry/4207112889976209949 2023-04-04T10:00:00+09:00 2023-04-04T10:00:00+09:00 こんにちは、スタディプラスのカンファレンス/OSSサポートチーム、菅原です。 スタディプラスは、2023年5月11日から13日にかけて長野県まつもと市民芸術館およびオンラインで開催されるRubyKaigi 2023にゴールドスポンサーとして参加します。 rubykaigi.org 今年は長野県松本市で開催ということで、現地に行かれる方は松本城や浅間温泉も堪能できそうですね。 RubyKaigi 2023とは? RubyKaigiは、プログラミング言語Rubyに関する世界最大級の国際カンファレンスです。 Ruby on Railsの普及により世界中に利用者がいることから、近年は世界各国から参加者… <p>こんにちは、スタディプラスのカンファレンス/OSSサポートチーム、菅原です。</p> <p>スタディプラスは、2023年5月11日から13日にかけて長野県まつもと市民芸術館およびオンラインで開催されるRubyKaigi 2023にゴールドスポンサーとして参加します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Frubykaigi.org%2F2023%2F" title="RubyKaigi 2023" 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://rubykaigi.org/2023/">rubykaigi.org</a></cite></p> <p>今年は長野県松本市で開催ということで、現地に行かれる方は松本城や浅間温泉も堪能できそうですね。</p> <h3 id="RubyKaigi-2023とは">RubyKaigi 2023とは?</h3> <p>RubyKaigiは、プログラミング言語Rubyに関する世界最大級の国際カンファレンスです。 Ruby on Railsの普及により世界中に利用者がいることから、近年は世界各国から参加者を集めています。 Rubyに関する最新情報やノウハウが展開され、エンジニア同士の交流を図れる場ともなっています。</p> <p>【概要】<br/>  主催 :RubyKaigi 2023チーム ・ 一般社団法人日本Rubyの会<br/>  開催日:2023年5月11日(木)〜13日(土)<br/>  会場 :長野県まつもと市民芸術館およびオンライン<br/>  公式HP:<a href="https://rubykaigi.org/2023/">https://rubykaigi.org/2023/</a></p> <h3 id="今年の協賛について">今年の協賛について</h3> <p>RubyKaigiにスポンサーさせていただくのは、今年で6回目となりました。 <a href="#f-4020cb48" name="fn-4020cb48" title="2020年度の中止時を含みます">*1</a> 今回も、ゴールドスポンサーでの協賛です。</p> <p>今年もRubyKaigiのスポンサーとして関われることをとても嬉しく思っています。 スポンサーとしてRubyコミュニティへの貢献を行い、盛り上げていきたいと思います。</p> <div class="footnote"> <p class="footnote"><a href="#fn-4020cb48" name="f-4020cb48" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">2020年度の中止時を含みます</span></p> </div> ksugahara08 JaSST’23 Tokyoに参加しました hatenablog://entry/4207112889972293849 2023-03-27T10:00:00+09:00 2023-03-27T10:00:00+09:00 こんにちは。QAエンジニアの森長です。 前回のブログ執筆時点ではサーバーサイドエンジニアでしたが、最近QAエンジニアにジョブチェンジしました。 3月9日(木)~10日(金)で開催されたJaSST’23 Tokyoに視聴者として参加しました。 弊社QAチームは立ち上がったばかりなこと、私自身QAエンジニアになったばかりなことから、他社での取り組みを知りたく視聴しました。 JaSST’23 Tokyoとは? 感想 Chaos Engineering to Continuous Verification 「テストプロセス、プロダクト品質評価のための実用的なODC分析とCATによる管理方法」 「ローカ… <p>こんにちは。QAエンジニアの森長です。</p> <p>前回のブログ執筆時点ではサーバーサイドエンジニアでしたが、最近QAエンジニアにジョブチェンジしました。</p> <p>3月9日(木)~10日(金)で開催されたJaSST’23 Tokyoに視聴者として参加しました。</p> <p>弊社QAチームは立ち上がったばかりなこと、私自身QAエンジニアになったばかりなことから、他社での取り組みを知りたく視聴しました。</p> <ul class="table-of-contents"> <li><a href="#JaSST23-Tokyoとは">JaSST’23 Tokyoとは?</a></li> <li><a href="#感想">感想</a><ul> <li><a href="#Chaos-Engineering-to-Continuous-Verification">Chaos Engineering to Continuous Verification</a></li> <li><a href="#テストプロセスプロダクト品質評価のための実用的なODC分析とCATによる管理方法">「テストプロセス、プロダクト品質評価のための実用的なODC分析とCATによる管理方法」</a></li> <li><a href="#ローカル環境を用いたアジャイルテスティングの実践事例より高速なフィードバックを目指して">「ローカル環境を用いたアジャイルテスティングの実践事例~より高速なフィードバックを目指して~」</a></li> <li><a href="#テストの設計意図を届けよう2023テストしたいことをよりスマートに伝えるための第一歩テスト設計コンテストU-30セッション--">「テストの設計意図を届けよう2023~テストしたいことを、よりスマートに伝えるための第一歩テスト設計コンテストU-30セッション -」</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h1 id="JaSST23-Tokyoとは">JaSST’23 Tokyoとは?</h1> <p>JaSST Tokyoは、ソフトウェアテスト分野のシンポジウムです。ソフトウェアテストやソフトウェアの品質に関心を持つ人々が情報を発信したり学びを得たり、参加者同士で交流ができる場となっています。</p> <p>今年はオンラインで、2023年3月9日(木)~10日(金)の2日間、開催されました。</p> <p>公式HP:<a href="https://jasst.jp/symposium/jasst23tokyo.html">https://jasst.jp/symposium/jasst23tokyo.html</a></p> <h1 id="感想">感想</h1> <p>2日間で9セッション視聴した中で特に印象に残ったセッションを4つご紹介します。</p> <h2 id="Chaos-Engineering-to-Continuous-Verification">Chaos Engineering to Continuous Verification</h2> <p>Netflixのカオスエンジニアリングチームのエンジニアリングマネージャーを務めていたCasey Rosenthal氏による基調講演でした。複雑なシステムに対してどのように安全性を担保していくべきかといった内容が話されました。</p> <p>特に講演の最後に話された”Don’t fight complexity. Navigate it.”という言葉が非常に印象に残っています。</p> <p>直訳すると「複雑性と闘うな、複雑性を操縦しろ」ですが、複雑なシステムを無理に簡素化したりシステムの全てを把握したりするのは不可能なので、抗うのではなくうまく対応しろということかなと解釈しました。</p> <p>想定できないことに気づくために、Chaos Engineeringのような手段で意図的に障害を発生させる検証を継続的に行うことが重要なのだと改めて感じました。</p> <p>弊社もいくつかのマイクロサービスで構成されており、ある箇所で障害が起きた際の影響を全ては予測できていないので、実験的に障害を起こすことで新しい気づきを得られるようにしていきたいなと思いました。</p> <h2 id="テストプロセスプロダクト品質評価のための実用的なODC分析とCATによる管理方法">「テストプロセス、プロダクト品質評価のための実用的なODC分析とCATによる管理方法」</h2> <p>本セッションは品質評価に関するセッションで、ODC分析という欠陥分析手法の説明と実際に現場で活用していく際に使えるツールの説明が行われました。テストプロセスやプロダクト品質の分析をしていきたいという話を弊社QAチーム内でしていたので参考になる部分があるかと思い参加しました。</p> <p>ODC分析は、RCA分析と統計分析のいいとこ取りをしたもので、障害を属性ごとに分類することで効率よく原因や傾向を分析できる手法です。属性は、例えば、発生トリガー(=どんなテストで発生したのか)や障害タイプ(どのようにバグが実装されたのか)などがあります。</p> <p>弊社では障害を品質特性で分類し分析していこうとしており、ラベリングして分析する点ではODC分析と似ている部分があります。 様々な分析手法がある中で分類による分析のメリットを知れ、自分達の取り組みの方向性に自信が持てたのでよかったです。</p> <p>弊社での分析作業の流れは、Qaseというツールでテスト実行結果を記録→障害があればmondayというプロジェクト管理ツールでチケットを切って報告→スプレッドシートに障害を一覧化し分類・分析、という流れで行ってます。現状3つのツールを使っているので、今後管理方法をシンプルにしていきたいなと本講演を聞いて思いました。</p> <h2 id="ローカル環境を用いたアジャイルテスティングの実践事例より高速なフィードバックを目指して">「ローカル環境を用いたアジャイルテスティングの実践事例~より高速なフィードバックを目指して~」</h2> <p>アジャイル開発におけるテストに関するセッションでした。弊社はアジャイル開発をおこなっておりその中でQAのプロセスをどう取り入れるか課題に感じていたので参加しました。</p> <p>講演を行ったfreeeさんでは、開発がキリがいいところまで実装できたらQAがローカル環境に自ら反映して、検証環境での通しテストの前にこまめにテストをおこなっているそうです。 これによって、通しテストの前に全体の50%のバグが見つけられたそうです。</p> <p>弊社では全ての開発が完了した後にまとめてテストをしています。freeeさんのような取り組みができれば開発してから日が経つ前にバグ報告可能になり、開発者の記憶が新しいうちに修正ができ良さそうです。</p> <p>一方で、開発エンジニア的に都合がいい開発順序とQAエンジニア的に都合がいい開発順序が異なるのでは?と思うところもあります。 ローカル環境でのテストを本格的に導入したいとなった場合、この辺りの認識を開発エンジニアとすり合わせていくことが重要だなと感じました。</p> <h2 id="テストの設計意図を届けよう2023テストしたいことをよりスマートに伝えるための第一歩テスト設計コンテストU-30セッション--">「テストの設計意図を届けよう2023~テストしたいことを、よりスマートに伝えるための第一歩テスト設計コンテストU-30セッション -」</h2> <p>テスト設計に関するセッションでした。日々テスト設計レビューで大量の指摘をいただいてる状態なので、少しでも良いテスト設計をしたいと思い参加しました。</p> <p>テスト観点の広げ方として、テスト対象の構図を見せる(=特定の着眼点に基づいて、状態遷移図、画面遷移図、IPOなどで図として表す)などすぐにでも使える様々なテクニックが紹介されました。仕様書をなぞっただけのテスト設計をしがちなので、こういった小さなテクニックから始めて観点を広げていきたいと思いました。</p> <h1 id="まとめ">まとめ</h1> <p>初めての参加でしたが非常に濃い時間を過ごせました。</p> <p>どのセッションでも登壇者が一方的に話すのではなく、視聴者がリアルタイムでDiscordに感想や疑問を書いていく形式だったので、理解が深まったりお祭り的な雰囲気で楽しかったです。</p> <p>テスト観点の広げ方や分析の手法など、今日から使えるテクニックを学べたので早速日々の業務で活用していきたいと思いました。</p> <p>一方で手法を学んだり観点を広げるだけでなく、互いの違いを理解し周囲(開発者やPOなど)をうまく巻き込んでいくことが品質向上のために重要であることを、全セッションを通して改めて感じました。</p> <p>今後は社内でのQA啓蒙活動にも力を入れ、チームのプロセス改善に全員が前向きに参加できる環境を作ってきたいです。改善をおこなっていく中で、最終的に皆の仕事が少しでも楽になる手助けをしていきたいと思いました。</p> <p>以上、JaSST’23 Tokyoの感想でした。</p> nagamina2 Kubernetesの既存DeploymentにHPAを導入する際の注意点 hatenablog://entry/4207112889969010749 2023-03-13T10:00:00+09:00 2023-03-13T10:00:01+09:00 こんにちは。2022年10月に入社したスタディプラスのSREグループの蜂須賀です。 今回はKubernetesの既存DeploymentにHPA(Horizontal Pod Autoscaler)を追加した際の話を紹介します。 経緯 スタディプラスではKubernetesでシステムを構築しておりますが、 一部のDeploymentはreplicasの設定によってレプリカ数を複数台固定で起動するように設定していました。 レプリカ数はHPAによって負荷に応じて調整した方がシステムの安定性向上につながるので導入することを決めました。 新規Deploymentと違い、既存DeploymentにHPA… <p>こんにちは。2022年10月に入社したスタディプラスのSREグループの蜂須賀です。<br/> 今回はKubernetesの既存DeploymentにHPA(Horizontal Pod Autoscaler)を追加した際の話を紹介します。</p> <h2 id="経緯">経緯</h2> <p>スタディプラスではKubernetesでシステムを構築しておりますが、 一部のDeploymentはreplicasの設定によってレプリカ数を複数台固定で起動するように設定していました。 レプリカ数はHPAによって負荷に応じて調整した方がシステムの安定性向上につながるので導入することを決めました。<br/> 新規Deploymentと違い、既存DeploymentにHPAを導入する際にはreplicasの設定を削除する工程が必要になります。<br/> 今回はHPAに必要なメトリクスサーバやHPAの設定については割愛し、replicasの設定の削除を中心に話していきます。</p> <h2 id="Deploymentからreplicasの設定を削除する理由">Deploymentからreplicasの設定を削除する理由</h2> <p>そもそもなぜreplicasの設定を削除する必要があるかと言いますと、HPAによってレプリカ数を調整しているにもかかわらず、再デプロイした際にマニフェストのレプリカ数に戻されてしまうからです。<br/> デプロイ時に使用されるkubectl applyコマンドは「適用するマニフェスト」と「実際のリソース」と比較して、設定値に差分があれば「適用するマニフェスト」の値を正として反映させます。<br/> HPAはDeploymentのreplicasの値を変えることでレプリカ数を指定しているので、マニフェストからreplicasの設定を削除する必要があります。</p> <h3 id="Deploymentのreplicasの設定を残した場合のレプリカ数の遷移">Deploymentのreplicasの設定を残した場合のレプリカ数の遷移</h3> <ul> <li>適用したマニフェスト(HPA側で最小レプリカ数は5に設定されている想定)</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">3</span> </pre> <ul> <li>実際のリソース</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">5</span> <span class="synComment"> # HPAによってレプリカ数が5に変更された。</span> </pre> <ul> <li>もう一度マニフェストを適用した時の実際のリソース</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">3</span> <span class="synComment"> # 「実際のリソース」と「適用するマニフェスト」を比較して、差分があることを検知したため「適用するマニフェスト」の「3」に変更された。</span> </pre> <h3 id="Deploymentのreplicasの設定を削除した場合のレプリカ数の遷移">Deploymentのreplicasの設定を削除した場合のレプリカ数の遷移</h3> <ul> <li>適用したマニフェスト(HPA側で最小レプリカ数は5に設定されている想定)</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synComment"># replicas: 3 # replicasの設定を削除。</span> </pre> <ul> <li>実際のリソース</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">5</span> <span class="synComment"> # HPAによって、レプリカ数が5に設定された。</span> </pre> <ul> <li>もう一度マニフェストを適用した時の実際のリソース</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">5</span> <span class="synComment"> # 「マニフェスト」にreplicasの設定がないので変更されない。</span> </pre> <h2 id="前回適用したDeploymentのマニフェストからreplicasの設定を削除">前回適用したDeploymentのマニフェストからreplicasの設定を削除</h2> <p>Deploymentからreplicasの設定を削除したマニフェストをそのままデプロイすると初回デプロイではレプリカ数が1になってしまいます。<br/> kubectl applyコマンドは「適用するマニフェスト」と「前回適用したマニフェスト」を比較して、「適用するマニフェスト」から削除されたものを検知し、「実際のリソース」から設定を削除します。<br/> 「実際のリソース」からreplicasの設定が削除されるとレプリカ数はデフォルト値の1になります。 これを防ぐために「前回適用したマニフェスト」からもreplicasの設定を削除することで、削除の検知をさせなくする必要があります。</p> <h3 id="削除方法">削除方法</h3> <p>「前回適用したマニフェスト」はDeploymentのlast-applied-configurationに保存されているので、直接編集してreplicasの設定を削除します。</p> <p>編集エディターを起動させます。(Deploymentがnginxの想定)</p> <pre class="code bash" data-lang="bash" data-unlink>kubectl edit deployment nginx</pre> <p>以下のような表示が出るので、<code>kubectl.kubernetes.io/last-applied-configuration</code>の<code>"replicas":3,</code>の部分を削除します。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">annotations</span><span class="synSpecial">:</span> <span class="synIdentifier">kubectl.kubernetes.io/last-applied-configuration</span><span class="synSpecial">:</span> | <span class="synSpecial">{</span><span class="synConstant">&quot;apiVersion&quot;</span><span class="synSpecial">:</span><span class="synConstant">&quot;apps/v1&quot;</span>,<span class="synConstant">&quot;kind&quot;</span><span class="synSpecial">:</span><span class="synConstant">&quot;Deployment&quot;</span>,<span class="synConstant">&quot;metadata&quot;</span><span class="synSpecial">:{</span><span class="synConstant">&quot;annotations&quot;</span><span class="synSpecial">:{}</span>,<span class="synConstant">&quot;name&quot;</span><span class="synSpecial">:</span><span class="synConstant">&quot;nginx&quot;</span>,<span class="synConstant">&quot;namespace&quot;</span><span class="synSpecial">:</span><span class="synConstant">&quot;default&quot;</span><span class="synSpecial">}</span>,<span class="synConstant">&quot;spec&quot;</span><span class="synSpecial">:{</span><span class="synConstant">&quot;replicas&quot;</span><span class="synSpecial">:</span>3,<span class="synConstant">&quot;selector&quot;</span><span class="synSpecial">:{</span><span class="synConstant">&quot;matchLabels&quot;</span><span class="synSpecial">:{</span><span class="synConstant">&quot;name&quot;</span><span class="synSpecial">:</span><span class="synConstant">&quot;nginx&quot;</span><span class="synSpecial">}}</span>,<span class="synConstant">&quot;template&quot;</span><span class="synSpecial">:</span> <span class="synSpecial">{</span><span class="synConstant">&quot;metadata&quot;</span><span class="synSpecial">:{</span><span class="synConstant">&quot;labels&quot;</span><span class="synSpecial">:{</span><span class="synConstant">&quot;name&quot;</span><span class="synSpecial">:</span><span class="synConstant">&quot;nginx&quot;</span><span class="synSpecial">}}</span>,<span class="synConstant">&quot;spec&quot;</span><span class="synSpecial">:{</span><span class="synConstant">&quot;containers&quot;</span><span class="synSpecial">:[{</span><span class="synConstant">&quot;image&quot;</span><span class="synSpecial">:</span><span class="synConstant">&quot;nginx:latest&quot;</span>,<span class="synConstant">&quot;name&quot;</span><span class="synSpecial">:</span><span class="synConstant">&quot;nginx&quot;</span>,<span class="synConstant">&quot;ports&quot;</span><span class="synSpecial">:[{</span><span class="synConstant">&quot;containerPort&quot;</span><span class="synSpecial">:</span>80<span class="synSpecial">}]}]}}}}</span> </pre> <h3 id="last-applied-configurationを編集しない場合のレプリカ数の遷移">last-applied-configurationを編集しない場合のレプリカ数の遷移</h3> <ul> <li>前回適用したマニフェスト/適用前の実際のリソース</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">3</span> <span class="synComment"> # HPAまだ入れていない想定なのでマニフェスト適用時と実際のリソースは変わらず。</span> </pre> <ul> <li>適用するマニフェスト</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx </pre> <ul> <li>適用後の実際のリソース</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synComment"># replicas: 3 # 「前回適用したマニフェスト」からreplicasの設定がなくなったことを検知して削除される。レプリカ数は3→1(デフォルト)に変更される、</span> </pre> <h3 id="last-applied-configurationを編集した場合のレプリカ数の遷移">last-applied-configurationを編集した場合のレプリカ数の遷移</h3> <ul> <li>実際に前回適用したマニフェスト</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">3</span> </pre> <ul> <li>Kubernetesに保存されている前回適用したマニフェスト</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synComment"># replicas: 3</span> </pre> <ul> <li>適用前の実際のリソース</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">3</span> </pre> <ul> <li>適用するマニフェスト</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx </pre> <ul> <li>適用後の実際のリソース</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">apiVersion</span><span class="synSpecial">:</span> apps/v1 <span class="synIdentifier">kind</span><span class="synSpecial">:</span> Deployment <span class="synIdentifier">metadata</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> nginx <span class="synIdentifier">spec</span><span class="synSpecial">:</span> <span class="synIdentifier">replicas</span><span class="synSpecial">:</span> <span class="synConstant">3</span> <span class="synComment"> # 「適用するマニフェスト」と「前回適用したマニフェスト」にreplicasの設定がないので、削除されない。</span> </pre> <h2 id="まとめ">まとめ</h2> <p>上記の方法でreplicasの設定を削除してからデプロイすることで、レプリカ数を下げずにHPAの導入ができました。<br/> HPA以外にも、Deploymentの設定値をKubernetes側で変更する仕組みを導入する際には同じような手順が必要になるのでご参考にしてください。</p> shatisuka