TECH PLAY

株式会社ラクス

株式会社ラクス の技術ブログ

927

はじめに メールディーラー開発課のyamamuuuです。 2024/03/7(木) ~ 03/9(土)の3日間に渡ってPHPerKaigi 2024が開催されました。 今回もオンライン・オフライン両方のハイブリッド開催でした。 phperkaigi.jp ラク スはシルバースポンサーとして協賛し、3名が登壇した他、数名のメンバーが参加しました。 今回は ラク スからの登壇者本人と参加者によるレポートを紹介させていただきます。 はじめに 参加レポート php-src debug マニュアル 10年モノのレガシーPHPアプリケーションを移植しきるまでの泥臭くも長い軌跡 ウキウキ手作りミニマリストPHP Laravel OpenAPIによる "辛くない" スキーマ駆動開発 こんな静的解析導入は負けフラグ 帰ってきた「完成度低いの歓迎LT大会」(PHPerKaigi出張版) WebAssembly を理解する 〜VM の作成を通して〜 初PHPでサーバーモデルについて考えた話 PHP8の機能を使って堅牢にコードを書く 「わたしたちのコード」を安定させるためにフレームワークとの距離を保つ PHP 8.3で追加されたjson_validate()を徹底的に深掘りしてみよう B+木入門:PHPで理解するデータベースインデックスの仕組み PHP 本体のバグを見つけたら適切に報告しよう Webアプリケーションの効率を再定義するBEAR.Sundayの分散キャッシングフレームワーク キャッシュと向き合う、キャッシュと共に生きる 保守開発メインでやってきたエンジニアが『リーダブルコード』を機能削除の観点から語る 超巨大!超重要!な処理のリファクタリングにどのように向き合っているか privateメソッドのテストって書かない方がいいんだっけ? Composerを便利に使うために私がやっていること by きんじょうひでき ラクスからの登壇セッションのご紹介 レガシーシステムへのPHPStan導入から半年での課題と効果 PHP8.2にバージョンアップしたら文字化けが発生して道頓堀に飛び込みたくなった話 PHPでOfficeファイルを取り扱う! ~PHP Officeライブラリをプロダクトに組み込んだ話~ まとめ PHPerのためのコミュニティ PHPTechCafe 参加レポート php -src debug マニュアル report by id:dd_fortran おのぽんさん( @onopon_engineer )による発表です speakerdeck.com PHP 本体の ソースコード である php -srcの デバッグ 方法について話されていました。 PHP の デバッグ 環境の構築は少しハードルがありますが丁寧に解説されていたので、試したことがない人でも参考になると思います。 デバッグ した内容が echo だったこともあり、Opcodeの話をされており少し初学者には難しい内容だったかと思いました。 デバッグ したことがある人がさらに1歩理解を進めようと思っている方にはおすすめです! もし、初めて デバッグ してみようと考えている方は var_dump などの標準関数で試してみるのがよいと思います。 10年モノのレガシー PHP アプリケーションを移植しきるまでの泥臭くも長い軌跡 report by : id:rakuinoue toshimaruさん( @toshimaru_e )による発表です speakerdeck.com レガシーな PHP アプリケーションを Rails アプリへ移植を進めていく中での課題や解決方法、実際に行われた事などをお話されていました。 レガシーなアプリケーションの技術的負債をどのように解消するかの方法はいくつかありますが、 それぞれの手法について課題やメリットなどを整理し、フィットする手法を見定め実践されたことが説明されていたので、非常に参考になりました。 各チームともいろいろな調整や決定事項をさだめて移植を完了させていくステップはリーダー的ポジションの人などには有益なお話だったと思います。 ウキウキ手作り ミニマリスト PHP report by : id:hirobex uzullaさん( @uzulla )による発表です speakerdeck.com PHP を自分でビルドしたことがある人は少ないのではないでしょうか? Dockerやapt-get、 brew でインストールする人が多いと思います。 そんな人こそ、この トーク を見て自前でビルドした PHP と、自動でインストールされる PHP に、どのように違いがあるのか知ってみましょう! Laravel OpenAPIによる "辛くない" スキーマ 駆動開発 report by id:takaram 武田 憲太郎さん ( @KentarouTakeda )による発表です。 speakerdeck.com WebアプリのフロントエンドをReactなどで作ることが一般的になり、バックエンドで API を作るケースは増えていると思います。この場合、フロントエンドとバックエンドのエンジニア間で API の仕様を定めて開発を行う必要がありますが、 Excel で作ったような従来の仕様書を使う開発では様々な課題があります。 そこで、 API の仕様を記述するフォーマットであるOpenAPI、およびOpenAPIの周辺ツールを上手く利用することで、スピードと品質を両立した開発を行うことができる、ということを紹介してくださいました。 ちょうど個人的にOpenAPIが気になっていたので、ぜひ活用してみたいと思えるセッションでした! 余談ですが、「"仕様"という抽象に実装を依存させる」ことを依存関係逆転の原則になぞらえて説明されていたのが分かりやすく 目から鱗 でした。 こんな静的解析導入は 負けフラグ report by : id:hirobex うさみけんた さん( @tadsan )による発表です fortee.jp PHPStanを導入するにあたり、どのような バッドノウハウ があるかを教えていただける トーク です。 実際にPHPStanを導入している人や、これからPHPStanを導入しようと考えている人にオススメです! 帰ってきた「完成度低いの歓迎LT大会」(PHPerKaigi出張版) report by id:dd_fortran すぎやまさん ( @oogFranz ) による発表です note.com サイボウズ のGaroon開発チームでは「完成度低いの歓迎LT大会」を開催しているといった内容で、実際に3人の方がLTで登壇されていました。 登壇のハードルを下げるために実施しており、登壇経験が少ないメンバーでも参加しやすい取り組みいいなと思いました。 弊社でもビアバッシュが定期的に開催されていますが、発表のクオリティが高くて発表しづらいといった声もあるので導入してみたいと思いました! WebAssembly を理解する 〜 VM の作成を通して〜 report by id:dd_fortran nsfisisさん( @nsfisis )による発表です blog.nsfisis.dev WebAssembly(Wasm)を実行する VM を PHP で作成され、その作成方法の概略についての内容でした。 Wasmに興味がある方(特にPHPerでもある方)にはとても興味深い内容でした。 どのように処理を作っていけばいいかやWasmの仕様書の読み進め方などについて話されており、とても参考になりました。 私も時間を作ってWasmを触ってみようと思いました! 初 PHP でサーバーモデルについて考えた話 report by : id:hirobex sadnessOjisanさん( @sadnessOjisan )による発表です speakerdeck.com 当初とは若干 トーク タイトルが変わっており「サーバーとは何かを理解して、コンテナ1つで実行しよう」というタイトルになっています。 皆さんが本番環境で PHP を”動かす"ときって、どうしていますか? 基本的には、 Apache やNginx経由で PHP ファイルを呼び出していると思います。 ビルトインサーバーという手もありますが、本番環境では推奨されていません。 最近ではDockerを利用することも多いと思いますが、 PHP と Apache が入ったDockerコンテナは、1プロセス1コンテナの原則を破っています。 なぜ PHP がこのような動かし方になっているのか、どうすれば理想のDockerを作れるのか。 その答えが、この トーク にあります。 PHP8の機能を使って堅牢にコードを書く report by id:dd_fortran Endo Futoshi さん( @Fendo181 )による発表です speakerdeck.com PHP8以降の新機能を使って、堅牢なコードを書くためのテクニックを話されていました。 読み取り専用クラス(Readonly Classes)や 列挙型( enum )を使って、コードを堅牢にする方法を紹介していました。 PHP8以降の新機能を利用したことがない方でも理解しやすい内容だったので初学者にもおすすめです! 「わたしたちのコード」を安定させるために フレームワーク との距離を保つ report by id:takaram 大橋 佑太さん ( @blue_goheimochi ) による発表です。 speakerdeck.com Laravelなどの フレームワーク には、多くの便利な機能が用意されていて、それらを使いこなすことで迅速な実装が可能になります。 しかし、依存しすぎてしまうと フレームワーク 側の仕様変更の影響をもろに受けてしまい、対応が辛くなるという諸刃の剣でもあります。 そのため、自分の実装する ビジネスロジック と フレームワーク を利用するロジックとを分離し、「 フレームワーク と適切な距離を保つ」ことが大事であると説明されていました。 処理を分けていたことで実際にメリットがあったという実例も紹介されており、今後のクラス設計などで意識したいと感じることができました。 PHP 8.3で追加された json _validate()を徹底的に深掘りしてみよう report by id:dd_fortran 柚口 ましろ うさん( @yu_mashirou )による発表です speakerdeck.com PHP8.3で追加された json_validate() について、その使い方や実装の仕組みについて解説されていました。 json_decode() があるから不要なのでは?と思っている方はぜひ見てみてください。 B+木入門: PHP で理解するデータベースインデックスの仕組み report by : Y-Kanoh 富所亮 さん( hanhan1978 )による発表です speakerdeck.com 普段は自分の業務周辺知識に関する発表をを中心に聴いて回るのですが、今回はまったく知らない領域の発表も聴いてみようと思い参加しました。 名前ぐらいしか聞いたことがないものですが、そもそも"データ構造"であることから、どのようにデータが追加され追われていくのかまで優しく説明されていました。 すごくわかりやすい解説で、知識の世界を広げることができました。 PHP 本体のバグを見つけたら適切に報告しよう report by id:dd_fortran 工藤 剛さん( @zeriyoshi )による発表です speakerdeck.com PHP でバグを見つけたときに詳細なログや スタックトレース を出力する方法を紹介していました。 PHP 本体の詳細なログを出力する方法は知らなかったのでとても参考になりました。 バグを見つけた際には詳細なログを出せるように試しておこうと思いました。 Webアプリケーションの効率を再定義するBEAR.Sundayの分散キャッシング フレームワーク report by : id:hirobex 郡山 昭仁さん( @koriym )による発表です speakerdeck.com そもそもキャッシュとは何なのか。 なぜキャッシュを利用するのか。 キャッシュの難しさとは何なのか。 理想のキャッシュとは? を語った上で、BEAR.Sundayなら達成できます。 という、キャッシュについての広い勉強ができる トーク でした。 この トーク 中に話されていた「不易流行」 「流行」だけを積み上げても、いつか時代とともに使えなくなる日が来る。 そのときにまた「流行」で作り直してもタイマーが0になるだけ。 「不易」つまり、変わらないものを積み上げることで、時代を超えて変わらない価値を届けられる。 という言葉に胸を打たれました。 キャッシュと向き合う、キャッシュと共に生きる report by id:takaram 曽根 壮大さん ( @soudai1025 )による発表です speakerdeck.com Web開発でキャッシュの利用を検討する上での注意点に関するお話でした。 「キャッシュは麻薬(=劇的な効果があるが一度使うと止めづらく、運用も難しくなる)」であるため、使う前に本当に必要か注意深く検討する必要がある、ということが繰り返し説明されています。 検討の上で必要と判断したら、キャッシュの更新・生存期間の アルゴリズム を決める必要がありますが、それらの各種 アルゴリズム についても説明がありました。 キャッシュの アルゴリズム にどんなものがあるのか、私は全然認識できていなかったので、今後のキャッシュ検討のときにこのスライドがとても役に立ちそうです! 「利用用途、 アルゴリズム を決めてから保存先のデータストアを選ぶこと」というのも重要だと感じました。ついつい「今回はRedisを使ってキャッシュをして…」のように、データストアから決めてしまいがちな気がするので気をつけたいところです。 保守開発メインでやってきたエンジニアが『リーダブルコード』を機能削除の観点から語る report by : id:ymyhero7 ririhoshiさん( @_riri_hoshi )による発表です fortee.jp 実際に機能削除をされた際に直面した問題と、そこから得た教訓をリーダブルコードの観点で3点紹介してくださいました。 命名 時に解釈の余白を持たせない、表記揺れさせない コメントをアップデートし続ける 「後で使うかもしれないから」と使わない共通関数を生み出さない どれも歴史のあるサービスを開発していると非常に共感できる内容でした。 発表の最後に、「既存仕様・実装のしがらみの中でシンプルで美しいコードを追求し続けることの積み重ねがサービスの未来をつくる」とおっしゃっていたことが心に刺さりました。 私もその言葉を胸に刻んで開発していきます! 超巨大!超重要!な処理の リファクタリング にどのように向き合っているか report by : id:hirobex 今濱 眞さん( @seainthepast )による発表です fortee.jp タイトル通り、超巨大!超重要!な処理を リファクタリング するために行っている計画と、 計画中に取り組んで良かったこと等をお話していただきました。 同じように、超巨大!超重要!な処理を リファクタリング を考えている人は一見の価値があると思いました。 privateメソッドのテストって書かない方がいいんだっけ? report by : id:hirobex asumikamさん https://twitter.com/asumikam による発表です speakerdeck.com shoulditestprivatemethods.com なぜprivateメソッドのテストを安易に作ってはいけないのか、という トーク です。 最近はprivateメソッドのテストは書かないということが広まってきているとは思いますが、 なぜ書いてはいけないのか、逆に、どういう時なら使ってもいいのか、というのを説明できる人は少ないと思います。 この トーク を聞くことで、その理由が理解できると思います。 Composerを便利に使うために私がやっていること by きんじょうひでき report by id:dd_fortran きんじょうひできさん( @o0h_ )による発表です speakerdeck.com composerの便利なコマンドや設定を紹介していました。 知らなかったコマンドもあり、とても参考になりました。 ラク スからの登壇セッションのご紹介 レガシーシステム へのPHPStan導入から半年での課題と効果 report by id:dd_fortran speakerdeck.com PHPカンファレンス 関西2024に続き、同様の内容で登壇してきました。 同じ内容での2回目の登壇となっていたのですが、 (疲れていたこともあり) 練習不足なこともあり登壇の話し方がうまくいかなかったので反省です...。 内容としては レガシーシステム にPHPStanを導入した事例について話させていただきました。 登壇後には他社のエンジニアの方から声をかけていただき、参考になったという声や同様の課題を抱えているといった話をしました。 他社のエンジニアと交流する機会は多くないため、とてもいい経験となりました!! PHP8.2にバージョンアップしたら文字化けが発生して道頓堀に飛び込みたくなった話 report by : id:neroblubros speakerdeck.com 私も PHPカンファレンス 関西2024で登壇した内容とほぼ同様の内容でお話しました。 リリース後に文字化けが発生したことを切り口に PHP のバージョンアップや運用とお客様対応の話をしました。 刺さるかな?と気になっていましたが、2件の鋭い質問をされてとてもいい経験になりました。 PHPerKaigiの規模の大きさにただただ驚きましたが、貴重な体験ができたし、 自分が世間で通じること、足りないことが整理できて登壇して良かったと思っています。 PHP でOfficeファイルを取り扱う! ~ PHP Officeライブラリをプロダクトに組み込んだ話~ report by : id:hirobex speakerdeck.com 登壇しました。 ちゃんとウケを狙ったところで笑えてもらえたので良かったです。 PHP でOfficeファイルを取り扱う時は、メモリ枯渇に気をつけてください。 LTなのにAsk the speakerで質問しに来てくださった方がいて嬉しかったです。 まとめ 今回もたくさんの参加レポートを執筆しました。 内容も レガシーシステム からの脱却や静的解析、Composerの便利コマンドと多岐に渡りなかなか読み応えのあるものになったのではないでしょうか。 PHPerのためのコミュニティ PHPTechCafe ラク スでは PHP に特化したイベントを毎月開催しております。 その名も「PHPTechCafe」!! 次回は 2024/03/26(火)に『PHPerのための「PHPTechCafeの5年間を振り返る」 PHP TechCafe』 をテーマに開催します! まだまだ参加者を募集していますので、ぜひお気軽にご参加ください。 👉 PHPerのための「PHPerのための「PHPTechCafeの5年間を振り返る」PHP TechCafe 最後までお読みいただきありがとうございました!
アバター
はじめに こんにちはこんばんは! 昨今、セキュリティへの関心が非常に高まっています。 二段階認証を取り入れる企業が多くなってきました。 最近の例で言うと、 Github が2023年3月ごろに二段階認証を義務化したのは記憶に新しいと思います。 そこで、今回は認証の基礎知識をおさらいした上でTOTPを使った二段階認証の仕組みと導入時の注意点について解説します! ※本記事の内容は、ビアバッシュ(社内の技術共有会)にて登壇発表した内容です。 ビアバッシュの取り組みについては以下の記事を読んでみてください! tech-blog.rakus.co.jp はじめに 基礎知識 二要素認証とは? 二段階認証とは? 二要素認証と二段階認証の違い ワンタイムパスワードとは? HOTPとTOTPについて HOTPとは? TOTPとは? TOTPの時刻ズレ対策 導入編 TOTPの時刻ズレ対策の実装 TOTPの注意点 まとめ 基礎知識 認証と聞くと、二段階認証と二要素認証が思い浮かびますね! この2つはどう区別されるのでしょうか? 二要素認証とは? 以下の要素を2つ以上組み合わせて認証することを二要素認証と言います。 記憶情報 ID、パスワード、出身地の情報など本人が記憶している情報 生体情報 虹彩 、指紋など本人の生体情報 所持情報 パソコン、 スマホ 、カード、USB機器など本人が所持している情報 二要素認証 たとえば、ATMでお金を引き出すケースを想定しましょう。 まずATMに本人が持っているカードを差し込む必要があります。このカードが所持情報となります。 次に4桁のパスワードを入力します。このパスワードは記憶情報となります。 ですので、所持情報と記憶情報の二要素認証をクリアして初めてATMでお金を引き出すことができます。 このケースでは二要素ですが、二要素以上ある場合は多要素認証(MFA)とも呼ばれます! 二段階認証とは? ある画面で認証を行い、さらに別の画面でも認証を行うことを二段階認証といいます。 二段階認証 たとえばログイン画面でパスワードを入力したあと、次の画面で秘密の質問に答えるようなケースです。 このケースでは二段階ですが、3つ以上の段階を踏む場合は多段階認証とも呼ばれます! 二要素認証と二段階認証の違い 二要素認証は認証の要 素数 について着目し、二段階認証は認証のステップ数について着目しています! たとえば、ログイン画面でIDとパスワードを入力後、次の画面でメールに届いた ワンタイムパスワード を入力するケースは二要素認証/二段階認証どちらに当てはまるでしょうか? 正解は「 どちらとも言える 」です。 「記憶情報であるパスワード」と、「(メールが届く端末を所持しているという意味で)所持情報であるメールに届いた ワンタイムパスワード 」の二要素の組み合わせです。 しかも、「IDとパスワード入力画面」と「 ワンタイムパスワード の入力画面」の二段階のログインです。 ですので、 二要素認証でもあり二段階認証でもある と言えます! ワンタイムパスワード とは? 一度しか使えない期間限定のパスワードのことを ワンタイムパスワード と言います。 OTP(One-Time-Password)と略されます。本記事も以降はOTPとします。 ワンタイムパスワード OTPの代表的な生成方法はHOTPとTOTPがあります。 HOTPもしくはTOTPを使って二段階認証を実現するのが基本セオリーです! HOTPとTOTPについて いずれも RFC に規定されており、秘密キーを利用してOTPを生成します。 また、OTP生成時はクライアントとサーバで同じ秘密キーを利用します。 秘密キーは認証するクライアントとサーバしか知り得ない情報です。 Github で二段階認証をする場合、 QRコード をGoogleAuthenticatorなどの認証アプリで読み取ると、6桁のOTPが表示されます。 このとき、秘密キーは QRコード 化されており、認証アプリがその秘密キーを使って6桁のOTPをHOTP/TOTP方式で生成しています。 Github の例 Github では秘密キーを QRコード で表示しているページのsetup_keyをクリックすると、秘密キーが平文で表示されます。 平文の秘密キー HOTPとは? HMACベースの ワンタイムパスワード アルゴリズム です。 RFC 4226 - HOTP: An HMAC-Based One-Time Password Algorithm 日本語訳 秘密キーと通算の認証回数からOTPを生成する方法です。 クライアント側は、クライアント側に保持した秘密キーと通算の認証回数を基にOTPを生成してサーバに送信します。 サーバ側は、サーバ側に保持したクライアントと同じ秘密キーと通算の認証回数を基にOTPを生成します。 両者とも、秘密キー・通算の認証回数・生成 アルゴリズム が同じ → 同じOTPが生成されるため、認証OKとなります! HOTP成功例 秘密キーが同じでも通算の認証回数がクライアントとサーバで異なると、生成されるOTPも異なるため認証に失敗します。 HOTP失敗例 認証が途中で中断されるなどでクライアントとサーバで認証回数のずれが発生することがあります。 また、クライアント側でHOTPを作成するときに認証回数も更新する実装かつ何度もHOTPを再生成する仕様にしてしまうと、認証回数のずれが発生します。 対応策として、認証回数ズレの補正や認証回数リセットの要件検討/実装が必要です。 TOTPとは? 秘密キーと認証時の現在時刻からOTPを生成する方式です。 RFC 6238 - TOTP: Time-Based One-Time Password Algorithm 日本語訳 クライアント側は、クライアント側に保持した秘密キーと現在時刻を基にOTPを生成してサーバに送信します。 サーバ側は、サーバ側に保持したクライアントと同じ秘密キーと現在時刻を基にOTPを生成します。 なお、現在時刻はサーバ側の方が必ず後になってしまうので、OTPには発行されてから30秒間を有効とする猶予期間があります。 両者とも、秘密キー・現在時刻・生成 アルゴリズム が同じ → 同じOTPが生成されるため、認証OKとなります! TOTP成功例 サーバ側は、30秒毎に有効なOTPを更新します。 ですので、発行されてから認証までに30秒を越えたOTPを送信すると、有効ではないOTPとして判断され認証に失敗します。 TOTP失敗例 例えば、クライアントの端末の時間とサーバ側の時間にズレがある場合は認証に失敗します。 TOTPの時刻ズレ対策 TOTPで生成されたOTPは 30秒単位で有効期限を伸ばす ことができ、時刻ズレを吸収することができます! TOTPの有効期限延長 HOTPと比較して認証のズレ対策に柔軟性があり実装コストも低いです。 導入編 では、TOTPを使って二段階認証を導入するために考慮が必要なポイントを抑えていきましょう! TOTPの時刻ズレ対策の実装 TOTPの生成と認証がセットになった リポジトリ はいくつもあります。 今回は、以下の リポジトリ のプログラムを参考にしてみます。 github.com PHPGangsta/GoogleAuthenticator.php の verifyCode 関数を見てみましょう。 PHPDocにもある通り引数 $discrepancy の値を増やすことで認証期限を延長できます。 /** * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now. * * @param string $secret * @param string $code * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) * @param int|null $currentTimeSlice time slice if we want use other that time() * * @return bool */ public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) { if ($currentTimeSlice === null) { $currentTimeSlice = floor(time() / 30); } if (strlen($code) != 6) { return false; } for ($i = -$discrepancy; $i <= $discrepancy; ++$i) { $calculatedCode = $this- > getCode($secret, $currentTimeSlice + $i); if ($this- > timingSafeEquals($calculatedCode, $code)) { return true; } } return false; } TOTPの注意点 OTPは30秒に1つ生成されます。 たとえば、OTP"555555"が11:59:30、OTP"666666"が12:00:00に生成されたとします。 OTP"555555"の有効期限は11:59:30〜11:59:59の間です。 OTP"666666"の有効期限は12:00:00〜12:00:29の間です。 このとき、12:00:00〜12:00:29の間で有効なOTPは "666666"のみです。 そのため、12:00:00〜12:00:29の間でOTP"555555"を使って認証した場合は 失敗 します。 ここで、OTPの有効期限を1分に延ばしたとき、OPT"555555"の有効期限は11:59:30〜12:00:29になります。 すると、12:00:00〜12:00:29の間で有効なOTPは "555555"と"666666"の2つになります。 そのため、12:00:00〜12:00:29の間でOTP"555555"を使って認証した場合は 成功 します。 つまり、OTPの有効期限を延ばすほど有効なOTPが増えます! 有効なOTPが増える その結果、セキュリティレベルを下げてしまいますので以下のような対策を検討する必要があります。 総当たり攻撃( ブルートフォース攻撃 )対策 ログイン回数に制限を設ける 3回ログインに失敗したらログインできない、などの アカウントロック機能 を併用する 失敗が続いたときはアクセス元の IPアドレス からのログインを不可 とする 不正アクセス 対策 ログイン時にメール通知 する 不正アクセス を判別するためのログを記録する OTP不正利用対策 認証に利用したOTPを利用不可にする 特に、総当たり攻撃対策は必須と言っても過言ではないでしょう! まとめ 二段階認証を組み込むことで、セキュリティレベルを向上させることができます。 しかし、なんとなく導入してしまうと思わぬ落とし穴に気づかない可能性があります。 システムに要求される利便性とセキュリティレベルをうまく調整しましょう!
アバター
はじめに こんにちは。フロントエンド開発課に所属している新卒1年目のm_you_sanと申します。 最近話題のRemixを使って、シンプルなTodoアプリを作成する方法をご紹介します。 Todoアプリの作成を通じて、簡単な フルスタ ック開発を体験していただければと思います。 はじめに プロジェクトの作成 モデルの定義 Root Routeについて ルーティングについて 一覧画面の作成 新規追加画面の作成 編集画面の作成 削除機能の追加 まとめ プロジェクトの作成 はじめに以下のコマンドを実行して、プロジェクトを作成します。 ※Node.js v18以上、npm v7以上がインストールされていることが前提です。 npx create-remix @latest -- template remix-run/indie-stack 今回はindie-stackというテンプレートを使用しています。 このテンプレートを使用することで、プロジェクトに SQLite 、 Prisma 、tailwind css などが予めインストールされた状態になります。 ※今回はデザインはこだわらないので、tailwind css の設定は削除しています。 さらに詳しくテンプレート機能について知りたい方は、 公式のドキュメント を読んでみてください。 npm run dev を実行し、以下のような画面が表示されれば、プロジェクトの作成は完了です。 モデルの定義 次に prisma にモデルの定義をしていきます。 schema.prisma を開いて、最終行に以下のコードを追加してください。 model Todo { id String @id @default(cuid()) title String deadline String isDone Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } 保存した後、以下のコマンドで マイグレーション を実行します。 npx prisma migrate dev --name "create todo model" 次に/modelsの直下に todo.server.ts を作成します。 todo.server.ts では、バックエンドのデータを操作するための関数をまとめています。 // todo.server.ts export const getTodos = () => { return prisma.todo.findMany (); } ; export const getTodo = ( id: string ) => { return prisma.todo.findUnique ( { where: { id } } ); } ; export const createTodo = ( todo: Pick < Todo , "title" | "deadline" >) => { return prisma.todo.create ( { data: todo } ); } ; export const updateTodo = ( id: string , todo: Pick < Todo , "title" | "deadline" | "isDone" >, ) => { return prisma.todo.update ( { where: { id } , data: todo , } ); } ; export const deleteTodo = ( id: string ) => { return prisma.todo. delete( { where: { id } } ); } ; Root Routeについて 画面を作成する前に、Root Routeについて簡単に解説したいと思います。 Root Routeは、/appにある root.tsx のことを指しています。 root.tsx は、アプリケーション全体の共通設定やヘッダー、フッターといった共通のレイアウトを定義することができます。 また、 root.tsx はアプリケーションのトッ プレベ ルで定義され、他のページの親 コンポーネント となっています。 root.tsx の子 コンポーネント は <Outlet /> 内で レンダリング されます。 // root.tsx export default function App () { return ( < html lang = "en" > < head > < meta charSet = "utf-8" / > < meta name = "viewport" content = "width=device-width,initial-scale=1" / > < Meta / > < Links / > < /head > < body > { /* ここで子がレンダリングされます */ } < Outlet / > < ScrollRestoration / > < Scripts / > < LiveReload / > < /body > < /html > ); } ルーティングについて Remixでは、 ファイルシステム ベースのルーティングになっており、app/routes配下に置いたファイルがそのままアプリケーションのURLとなります。 また、動的セグメントを利用したい場合は、 $ をファイル名に付けます。 例 ファイル名 URL _index. tsx / todos._index. tsx /todos todos.create. tsx /todos/create todos.$todoId.edit. tsx /todos/{todoId}/edit 一覧画面の作成 Todo一覧画面を作成していきます。 まず、トップページの方を修正していきます。 // _index.tsx export default function Index () { return ( < main > < h2 > トップページ < /h2 > < Link to = "todos" > Todo一覧画面へ < /Link > < /main > ); } 次に/routes直下に todos._index.tsx を作成します。 // todos._index.tsx export const loader = async () => { return json ( { todos: await getTodos () } ); } ; const Todos: FC = () => { const { todos } = useLoaderData <typeof loader >(); return ( <> < h2 > Todo一覧画面 < /h2 > < ul > { todos.map (( todo ) => ( < li key = { todo.id } > < p > タイトル: { todo.title } < /p > < p > 期限: { todo.deadline } < /p > < p > 進捗: { todo.isDone ? "完了" : "未着手" } < /p > < /li > )) } < /ul > < / > ); } ; export default Todos ; loader 関数は、データ取得する際に使用するバックエンド API の役割をしています。 この関数はサーバサイドで呼ばれ、先程 todo.server.ts で定義した getTodos で取得したデータを返却します。 コンポーネント 内で定義されている useLoaderData は、 loader 関数で返却されるデータを取得するためのhooksです。 今回は useLoaderData でtodoを全件取得し、map関数で展開して、一覧表示させています。 この時点では、todoが登録されていないため、何も表示されません。 新規追加画面の作成 /routes直下に todos.create.tsx を作成します。 // todos.create.tsx export const action = async ( { request } : ActionFunctionArgs ) => { const formData = await request.formData (); const title = formData. get( "title" ); const deadline = formData. get( "deadline" ); const errors = { title: title ? null : "タイトルは必須です" , deadline: deadline ? null : "期限は必須です" , } ; const hasErrors = Object .values ( errors ) .some (( errorMessage ) => errorMessage ); if ( hasErrors ) return json ( errors ); invariant (typeof title === "string" ); invariant (typeof deadline === "string" ); await createTodo ( { title , deadline } ); return redirect ( "/todos" ); } ; const CreateTodo: FC = () => { const errors = useActionData <typeof action >(); return ( <> < h2 > 新規追加画面 < /h2 > < Form method = "post" > < div > < label > タイトル: { errors?.title ? ( < p style = {{ color: "red" }} > { errors.title } < /p > ) : null } < input type= "text" name = "title" / > < /label > < /div > < div > < label > 期限: { errors?.deadline ? ( < p style = {{ color: "red" }} > { errors.deadline } < /p > ) : null } < input type= "date" name = "deadline" / > < /label > < /div > < div > < button type= "submit" > 登録 < /button > < /div > < /Form > < / > ); } ; export default CreateTodo ; 一覧画面と異なり loader 関数ではなく、 action 関数を使用しています。 loader 関数がデータの取得に対して、 action 関数は、データの更新やサーバーへのリク エス トなどを実行するバックエンドの API の役割をしています。 loader 関数と同じくサーバサイドで呼ばれます。 action 関数内では、まず formData.get でフォームから送られてきた title と deadline を取得しています。 ※ action 関数内で使用している formData や request は Web標準 の API です。詳細は MDN を参照してみてください。 title と deadline が未入力だった場合は、エラーメッセージを返却します。 invariant は引数の内の条件式がfalseだった場合、例外を投げて処理を中断します。今回は、 title と deadline の型情報を絞り込むために使用しています。 登録完了後は、一覧画面にリダイレクトするようにしています。 コンポーネント 側では、 useActionData で action 関数から返却されるデータを受け取っています。エラーがあった場合は、エラーメッセージをそれぞれ、ラベルの下に表示するようにしています。 次に一覧画面から新規追加画面に遷移できるようにします。 Remixの Link コンポーネント は、React Routerの Link コンポーネント をラップしたものなので、 相対パス を適用できます。そのため、 to="/todos/create" ではなく、 to="create" と記載するだけで新規追加画面へ遷移することが可能です。 // todos._index.tsx ・・・中略・・・ < h2 > Todo一覧画面 < /h2 > < Link to = "create" > 新規追加画面へ < /Link > ・・・中略・・・ 最後に動作を確認してみます。 登録完了後、一覧画面に遷移されて登録したtodoが表示されていると思います。 新規追加画面 登録完了後の一覧画面 編集画面の作成 /routes直下に todos.$todoId.edit.tsx を作成します。 // todos.$todoId.edit.tsx export const loader = async ( { params } : LoaderFunctionArgs ) => { invariant ( params.todoId ); const todo = await getTodo ( params.todoId ); invariant ( todo ); return json ( { todo } ); } ; export const action = async ( { params , request } : ActionFunctionArgs ) => { const formData = await request.formData (); const title = formData. get( "title" ); const deadline = formData. get( "deadline" ); const isDone = formData. get( "isDone" ) === "done" ; const errors = { title: title ? null : "タイトルは必須です" , deadline: deadline ? null : "期限は必須です" , } ; const hasErrors = Object .values ( errors ) .some (( errorMessage ) => errorMessage ); if ( hasErrors ) return json ( errors ); invariant ( params.todoId ); invariant (typeof title === "string" ); invariant (typeof deadline === "string" ); await updateTodo ( params.todoId , { title , deadline , isDone } ); return redirect ( "/todos" ); } ; const EditTodo: FC = () => { const { todo } = useLoaderData <typeof loader >(); const errors = useActionData <typeof action >(); return ( <> < h2 > 編集画面 < /h2 > < Form method = "POST" > < div > < label > タイトル: { errors?.title ? ( < p style = {{ color: "red" }} > { errors.title } < /p > ) : null } < input type= "text" name = "title" defaultValue = { todo.title } / > < /label > < /div > < div > < label > 期限: { errors?.deadline ? ( < p style = {{ color: "red" }} > { errors.deadline } < /p > ) : null } < input type= "date" name = "deadline" defaultValue = { todo.deadline } / > < /label > < /div > < div > < input id = "done" type= "radio" name = "isDone" value = "done" defaultChecked = { todo.isDone } / > < label htmlFor = "done" > 完了 < /label > < input id = "notDone" type= "radio" name = "isDone" value = "notDone" defaultChecked = { ! todo.isDone } / > < label htmlFor = "notDone" > 未着手 < /label > < /div > < div > < button type= "submit" > 更新 < /button > < /div > < /Form > < / > ); } ; export default EditTodo ; 編集画面では、 loader 関数を使って動的セグメントで指定した todoId を取得して、それに対応するtodoを取得しています。 更新リク エス トにも、 todoId が必要なので、 action 関数の方でも todoId を取得しています。その他の処理は、新規追加画面の action 関数とほぼ同じです。 useLoaderData でデータを取得する流れは一覧画面と同様で、フォームのデフォルト値に取得したデータを設定しています。 編集画面は完成したので、一覧画面から遷移できるようにします。 // todos._index.tsx ・・・中略・・・ < li key = { todo.id } > { /* タイトルから編集画面に遷移できるようにする */ } < Link to = { ` ${ todo.id } /edit` } > タイトル: { todo.title } < /Link > ・・・中略・・・ 最後に動作を確認してみます。 一覧画面(todo更新前) 編集画面 一覧画面(todo更新後) 削除機能の追加 最後に削除機能を追加します。 /routes直下に todos.$todoId.delete.ts を作成します。 // todos.$todoId.delete.ts export const action = async ( { params } : ActionFunctionArgs ) => { invariant ( params.todoId ); await deleteTodo ( params.todoId ); return redirect ( "/todos" ); } ; action 関数内では、動的セグメントで指定した todoId を取得して、それに対応するtodoを削除しています。削除完了後は、一覧画面にリダイレクトします。 一覧画面からtodoを削除できるようにしたいので、一覧画面に削除ボタンを追加して、押下時に /todos/{todoId}/delete にリク エス トを送るようにします。 // todos._index.tsx ・・・中略・・・ < p > 進捗: { todo.isDone ? "完了" : "未着手" } < /p > < Form action = { ` ${ todo.id } /delete` } method = "post" > < button type= "submit" > 削除 < /button > < /Form > ・・・中略・・・ 削除ボタンを Form コンポーネント で囲って、 action にパスを指定しています。これにより、ボタン押下時に todos.$todoId.delete.ts の action 関数が呼び出されます。 なお、 Form コンポーネント の action を省略した場合は、同ファイル内の action 関数が呼ばれます。 動作を確認して、削除ボタン押下時に一覧画面からtodoが削除されていれば、実装完了です。 まとめ 今回はRemixで簡単なTodoアプリを作成する方法について、紹介させていただきました。 Todoアプリの作成を通じて、フロントエンドとサーバーサイドのコードを同時に扱う体験をしていただけたと思います。 また、この記事がRemixをこれから学ぼうとする方々の理解を深める助けになれば幸いです。 私自身もまだRemix初学者なので、引き続き知識を深めていきたいと考えています!
アバター
弊社で毎月開催し、 PHP エンジニアの間で好評いただいている PHP TechCafe。 2023年5月のイベントでは「型定義」について語り合いました。 弊社のメンバーが事前にまとめてきた情報にしたがって、他の参加者に意見を頂いて語り合いながら学びました。 今回はその内容についてレポートします。 rakus.connpass.com PHPと型 静的型付け言語 動的型付け言語 一般的な誤解 PHPの型 単一の式が持つ型 型システムで扱える型 never型について void型について self,parent,static型について resource型について evalでresource型を宣言すると リテラル型について ユーザー定義型について 複合型について 型のエイリアス mixed iterable PHPで取り入れられた型表現 型宣言のメリット PHPの歴史を振り返る PHPのドキュメントを見ると、Nullの表現が3種類ある PHPの歴史的な理由 implod()の歴史的な理由 PHPマニュアル 日本語版で「歴史的」を検索したらたくさん出てくる PHPDocの役割 メリット デメリット 言語仕様の型表現とPHPDocのどちらを使うべき? 事前にいただいた質問コーナー never型(noreturn型)のうまい使い所、(一部過激派による)ジェネリクス待望論などあれば聞きたいです テストコードは静的解析の対象に入れるべきか否か 型運用のための設計についてお聞きしたいです。(DTOクラスはPHPでも使うものなのかなど) まとめ PHP と型 静的型付け言語 変数や関数の引数、戻り値などに対してあらかじめ指定された型以外を使用できなくする言語です。例としては Java があります。 入力や出力を縛る言語です。 型推論 という機能で、明示的に型を指定しなくても文脈などから コンパイラ が予測してくれるというパターンもあります。 動的型付け言語 変数や関数の引数、戻り値の型を実行時の値によって決定することが出来る言語です。 PHP はこちらに分類されます。 ただ PHP のように型を指定できる動的型付け言語もあります。 一般的な誤解 「 コンパイラ 型は静的型付け言語である」や「 インタープリタ ー型は動的型付け言語である」というのは間違いです。 静的型付け言語は コンパイル 時に変数や関数の型が確定されます。一方、動的型付け言語では実行時に型の決定を行うことができます。 「 PHP は型宣言できるので定義上は静的型付け言語だと言えてしまう。値の持ち方は動的型付け言語そのものなんですが」というコメントもありました。 PHP の型 単一の式が持つ型 null bool int float string array object callable resource 型システムで扱える型 組み込みの型 ヌル(null) スカラー 型: 論理値(bool) 整数(int) 浮動小数点数 (float) 文字列(string) 配列(array) オブジェクト(object) リソース(resource) never void クラス内での関係を示す相対型: self, parent, static リテラル 型 false true ユーザー定義型(一般的に、クラス型とも呼びます) インターフェース クラス 列挙型( Enum ) callable never型について neverは8.1以降で使えるようになった、関数が戻ってこないことを示す戻り値の型です。 exitがコールされるか、例外がスローされるか、無限ループに入るかのいずれかであることを意味します。 使い方としてはneverや@return neverで書いた関数の直後のコードが絶対実行されないとPHPStanが警告してくれるのでどこかでexitしてしまうようなコードに「exitするから気をつけろよ」ということを示すことができます。 クラスにしたときに「この関数を呼んだら終わります」と明記できるという意見がありました。 void型について 関数が値を返さないことを示す型です。 使いどころとして以下のような意見、コメントがありました。 「voidは重要、副作用があることを暗示してくれる」 「事故防止のため、コードに密結合したところに付箋を貼れる意味がある」 「voidが呼ばれていると中でなにかやっているぞ、なにか更新しているぞってなりますよね」 「純粋なコードとそうじゃないものを色分けできる」 self,parent,static型について self,parent,staticも型にあたります。返り値の型に宣言することができます。 「よく自分自身を返すときには使います」という意見がありました。 resource型について resource型は型宣言には記述できません。 evalでresource型を宣言すると 関数のパラメータにresource型を宣言するとバージョン8.0以降では「サポートされている組み込み型ではないため、クラス名として解釈されます。」というWarningが出ます。 「PG系の関数やファイル系の拡張関数がresourceではなく新しいクラスを返すようになる変更があり、元々resource型だったため型宣言が上手く出来ないからという経緯があるのでは」という意見がありました。 またバージョンによってエラー内容は異なります。 リテラル 型について false型とtrue型です。falseが返ることを宣言できます。 8.0.0でfalse型、8.2.0でtrue型がサポートされました。 8.2.0以前では、false型はunion型の一部としてのみ使える型でした。 以下の意見がありました。 「false型が出てきた背景は、 PHP の標準関数のなかにfalseを返すものが多かったため」 「trueは一応成功してくれればそのまま、失敗はエラー処理にできる」 「例外を使えという話ではあるが、レガシー向け対応」 ユーザー定義型について 一般的にクラス型と呼ばれます。 Enum もここに含まれます。 複合型について 交差型とUnion型。 交差型を構成する個別の型は & でつなぎます。Unionは | でつなぎます。 以下の意見がありました。 「Union型は引数や戻り値で指定する時によく使うかなと思います。」 「交差型を使う例として、インターフェースでイベントを受け取るんですが、実装しているどのイベントなのかっていうのをメソッドの中でチェックしなければいけない……という時に、&で繋いでいけばメソッドの中でチェックする処理を書かずに済むというやり方もあります。」 「型を指定しておけば、与えられた引数のチェックをしなくてよくなるっていうのがいいですね」 &で繋ぐ書き方の例としてはこちらの記事が参考になります。 doganoo.medium.com 型の エイリアス mixed mixedはobject | resource | array | string | float | int | bool | nullの エイリアス です。 五年前くらいに会社のチャットで「mixedって意味あんのかよ、レガシーをそのまま放置しているだけじゃないか」という意見があり、 「いやいやラベル付けのためにはいるんじゃない?」や「どうしてもここはmixedにするしかなかったって意思表示ができる」 という話をしていました。 以下の意見・コメントがありました。 「ここにresource入るんですね。 エイリアス と言いつつ型宣言出来ないじゃん!」 「ここは分からないからmixedにする、頑張ったけどって意味づけも出来るかなと」 「型がなかったプロジェクトにあとから型をつける場合に、mixedに逃げる形になっています」 「どうしようもなかったってラベリングには使えそうですね」 「is_mixed()それはTrueしか返らない」 iterable Traversable | arrayの エイリアス です。 foreachで回せます。 PHP で取り入れられた型表現 そもそもですが PHP は元々型表現があってないような動的なものでしたが、徐々に指定できるように取り入れられました。 例えば戻り値に指定しますよとか、引数に宣言できるようになりましたよというところ。 7.1.0まで遡ると、nullableという型が追加、voidが追加と徐々に増えています。 8.0.0でUnionとmixedが出来るようになりました。PHPerKaigiでmixedステッカーがありましたね。 8.1.0で戻り値にのみ指定できる型として、never型が追加されました。そして戻り値をvoidとした関数からリファレンスを返すことが非推奨になりました。交差型が追加されました。 8.2.0でnullとfalse型が独立して使えるようになり、trueが追加されました。DNF型も追加されました。DNF型とはANDとORの組み合わせを使えるものです。 以下の意見がありました。 「unionがなかったのでmixedで書かれているものがあった」 「unionのおかげでmixedってどうしても書かなきゃいけないシーンは減ったかもしれません」 型宣言のメリット 作った関数が誤った意図で使われることを防げる。 受け取った値のチェックを型宣言に任せられる。 毎回is_arrayでチェックしなくてよい。 PHP の歴史を振り返る PHP のドキュメントを見ると、Nullの表現が3種類ある 「null」と「Null」と「NULL」とあって、文脈上なんかあるのかなと思ったんですが、英語の文法上、先頭にいると大文字になってそれがそのまま翻訳されていると考えられます。 歴史的経緯と翻訳の都合。 PHP の歴史的な理由 以下のスライドで紹介されています。 お前は PHP の歴史的な理由の数を覚えているのか from Kousuke Ebihara www.slideshare.net implode() urlencode()/rawurlencode() double型/float型とgettypeの返り値 Phar アーカイブ の マニフェスト 情報 Zend EngineのHashTable implod()の歴史的な理由 inplodeは歴史的な理由により引数をどちらの順番でも受け付けることができます。 PHP2から3の間に生まれ、PHP3.0a3にて、第一引数と第二引数を交換したので、splitと同じように動作するようになりました。 わずか3ヶ月の歴史。 PHP マニュアル 日本語版で「歴史的」を検索したらたくさん出てくる Code search results · GitHub 検索でヒットしたのは12箇所。 以下の記述がありました。 「歴史的な理由によりfloatの場合は"float"ではなく、"double"が返されます」 「gettypeは歴史的な理由で決まった型の名前を返します。この関数はそれよりも実際に使われている型により近い名前を返すという点が異なります」 皆さん歴史的なものがほしいときはgettype()を、そうでないときはget_debug_type()を使いましょう。 PHPDocの役割 型宣言と切っても切り離せないのがPHPDocです。 PHP の ソースコード に記載するコメントです。 実装者がコードの内容を確認できるだけでなく、ルールに従って記載することで IDE や静的解析などのツールで活用することができます。 下記を参考にしていました。 PHPDoc リファレンス — phpDocumentor メリット 静的解析に使えること、コメントなので実際の挙動に影響を与えない。 なのでバグにならないことが保証されている。 リファクタリング でとっつきやすい。 以下資料を参考 speakerdeck.com デメリット 所詮はコメントなので、内容が間違っていても動く。 片方が書かれていない、書いてあるけど型の指定が無い、実際はstring型なのにコメントはint型になっているなど。 以下の意見がありました。 「書かれている内容が間違っている可能性があるのは問題ですね」 「 PHP を長年愛用されているレガシーの皆様にとっては、それでも IDE で縛れるのがいいのかなと思います」 「サボらずにDocもメンテしましょう」 言語仕様の型表現とPHPDocのどちらを使うべき? 以下のような意見・コメントがありました。 「新規プロダクトと既存プロダクトで変わってくる」 「新規プロダクトでは型宣言は必須。既存ならPHPDoc」 「ビジネス上でスピードを求められるならとりあえず無しで作る戦略はアリなのかな。ところが後々困るのは自分だよな......」 「既存もテストを書いて型宣言しよう」 「手戻りのバグ調査の 工数 って見えにくいですからね。既存は入出力が空のところが多いからリスクが大きすぎる」 「昔から型は口ほどに物を言うと言いますからね」 「スピードを求めるからこそ型宣言」 「既存のプロダクトに型をつけるのは危険過ぎる。テストがしっかりと出来ているなら問題ないんですが...」 「私は実際プロダクトで型をつけてすっごく変なルートで入ってきて落ちるというのを何度も見ましたし」 「複雑なデータは型を検討していると先が見えてくるのでそういう意味で重要ですね」 この方法ならレガシーにもテストを書けますという記事を共有いただきました。 qiita.com スクレイピング によるテストです。この方法なら スクレイピング してこれがあるかどうかというものなら簡単にかけます。 「既存のプロダクトは頑張って先程の方法か、無難に PHPUnit に書くのか、頑張ってテストに型をガシガシ書くのか、PHPDocから攻めるのか」 「テストを書く時間がないときはスピードを求めるからこそ型宣言に立ち返りましょう」 事前にいただいた質問コーナー never型(noreturn型)のうまい使い所、(一部過激派による) ジェネリクス 待望論などあれば聞きたいです リダイレクトしてしまう場合。レガシーコードのときに付けるなど。 ジェネリクス 待望論についてはarray_shapeを使いましょうという結論です。 PhpStormを利用していればPHPDocの中にAttributesで中のコードを書くことで静的解析のように利用できます。 また、言語組み込みのジェンネリクスは実行速度に影響があるため不要であり、PHPDocで書けば十分ということでした。 エディタが警告を出してもらうことで問題はないのでarray_shapeのような静的解析でよいのではないでしょうか。 テストコードは静的解析の対象に入れるべきか否か 静的解析の対象です。入れてもデメリットが無いと思います。 ただし、静的解析ツールからの警告やエラーメッセージに対してイライラしてしまうことがあるかも知れません。 また、データプロバイダ(テストデータを提供するメソッドや機能のこと)には多くの場合、複数のパラメータが必要となり、これらのパラメータに適切な型を指定するためにPHPDocのコメントを書くことが推奨されています。 しかし、これにより PHPUnit のコメントが長大になってしまい逆に読みにくくなるという点はデメリットと言えるかも知れません。 以下のようなコメントがありました。 「たくさんのパラメータが必要なデータプロバイダこそ複雑なことをしている自覚を持ってちゃんとした型を書いてほしい」 型運用のための設計についてお聞きしたいです。( DTO クラスは PHP でも使うものなのかなど) スピードと質を求めるなら型はしっかり設計しておくべきです。 以下のコメントと意見がありました。 「最近はCopilotである程度いい感じに補完してくれるので DTO を書きやすくなっている」 「生存期間が短いものは配列でいい、旅をするものは DTO にしてほしい」 「Readonly出来るようになったから DTO しやすくなった」 「製品コードやテストコードで実行する解析やルールを変えることがあるので、個人的には製品コードとテストコードで同等の静的解析をするかどうかが問題」 まとめ 今回は PHP の型について参加者の方々にそれぞれの型の特徴や使い所、歴史的背景などについて語り合っていただきました。 PHP は型指定ができる動的型付け言語という独特な言語であり、長い歴史からくる仕様などの事情が語られていてとても勉強になりました。 PHP の型について理解をより一層深めることができたのではないでしょうか。 「 PHP TechCafe」では今後も PHP に関する様々なテーマのイベントを企画していきます。 皆さまのご参加をお待ちしております。
アバター
はじめまして、rks_rtnkです。 ラク スでは毎年、 「Rakus Tech Lab」という チャット アプリ開発 体験を行うエンジニア インターン を開催しています。 2023年も4回開催しまして、非常に多くの学生の皆さんに参加いただきました。 今年、運営に携わった私から、2023年の インターン を振り返りつつ、紹介させていただきます。 もくじ 紹介 タイムスケジュール 開発の流れ 成果発表・懇親会 参加者の声 まとめ・所感 終わりに 紹介 まずはこの インターン 「Rakus Tech Lab」について、紹介させてください。 Rakus Tech Labは、2014年から開催しており、2023年にちょうど10年目を迎えました。 2023年の参加者を含めると、これまでに 600人以上 の学生に参加いただいており、 東京大阪あわせて20回以上開催している インターン になります。 当社に入社した新卒社員のうち、 6割以上 がRakus Tech Labの参加者となっており、 ラク スとしても採用のために、力をいれている施策の一つでもあります。 ただ、この インターン を開催するに至った経緯(目的)は、当時も今も変わらず、 ・ リアルな仕事への理解 ・ ラク スの風土を理解 ・ お互いの理解 ・ 体験を通じた「お互い」の成長 以上の4点です。 縁あって皆さんに ラク スに入社したいと思っていただけることが、何よりの喜びではありますが、 ぜひ参加される皆さんには、 ・ 実際の開発業務の雰囲気、実際のオフィスで働くイメージを感じる ・ ラク スの社員にどんな人がいるのか知る ・ 就活サイトなどでは聞けない生の社員の声を聞く といった、参加者だからこそ得られる機会を大切にしていただきたいと思っています。 ラク スとしても、学生の皆さんにそうした価値をたくさん提供できるように努めております。 「 ラク スのことそんなに知らない…」という学生の方も、この インターン 中に様々なことをお伝えする機会を設けていますので、 ぜひ、本記事と こちら から詳細を確認いただき、ご参加いただければと思います…! タイムスケジュール (前置きが長くなってしまいましたが・・・)2023年度のRakus Tech Labは、以下の内容で実施しました。 ※内容やスケジュールは毎年変更されますのでご了承ください ・ 東京3回、大阪1回 各回5日間×3時間 ・ 4名前後で1チームとなり、グループでオリジナルのチャットアプリを作成 ・ チームごとに ラク スの若手エンジニア1名ずつが付き添い、講師としてサポート 5日間の流れとしては、まずは初日に チュートリアル で開発環境に慣れていただいて、2日目から本格的な開発を行いました。 最終日には、完成したチャットアプリのお披露目として、成果発表を行っていただきました。 開発体験以外にも、1~3日目の冒頭には、開発統括部長、製品開発課長、若手エンジニア(新卒1~3年目)が それぞれの立場から、業務や製品、 ラク スについて紹介する時間を設けました。 ラク スのことを知っていただきたいというだけではなく、 インターン 中のチーム開発に役立つ話をしたり、IT・ SaaS 業界について理解を深めてもらえれば、という思いで実施しました。 開発の流れ チュートリアル  ⇒ 基本要件 ⇒ テーマ決め ⇒ 追加要件 の流れで進行しました。 学生の皆さんがスムーズに開発に入れるように、開発環境、原形となる ソースコード はこちらで用意したものを使用しました。 AWS 上に開発環境を構築して、バックエンドは Node.js 、フロントエンドは Vue.js で構成しました。 各チームごとに分かれ、 チュートリアル を通して基本的な進め方を学んでいき、 その後、こちらで定めた共通の基本要件を満たすように、チャット アプリ開発 を行ってもらいました。 <基本要件の例>  ・ログイン時、ユーザ名未入力の場合は、エラーダイアログを表示する。  ・投稿したら、自分以外のクライアントにも投稿文を表示させる。  ・新しい投稿は画面上部に表示されるようにして、古い投稿は下部に流れるようにする。 基本要件の開発が終わったら、追加機能の実装に進みます。 追加機能についても、こちらで用意した要件から選ぶこともできますが、 基本的には、各チームでチャットアプリのテーマを決めてもらい、それに合う独自機能を実装してもらいました。 チャットアプリというベースはあるものの、1からテーマを考えて、機能を考えていくことに、 皆さん初めは戸惑っている様子でしたが、議論が進み始めると、いくつも候補が出てきてしまい、絞り込むのに苦労している様子でした。 講師担当のエンジニアも、チームごとに1名ずつサポートとして付き、 何かトラブルや困りごとがあればすぐに相談できる体制を整えていたので、行き詰まることもなく、 どのチームも活発な議論を交わしながら、楽しみつつ、取り組んでいただけたのではないかと思います。 成果発表・懇親会 最終日には、各チームが作ったチャットアプリのお披露目として、成果発表を行いました。 ビジネスシーンを想定したテーマをもとに作成した、投稿に「報告」「連絡」「相談」の種別を持たせるビジネス向けチャットアプリ すでに製品化されているような機能性を短期間で実現した、ChatGPTを利用して入力支援を行ってくれるチャットアプリ など、学生の皆さんがユーザ目線になってどのように工夫を凝らしたのか、どのようなターゲットに向けて開発をしたのか、色々な思いを聞かせていただきつつ、完成したチャットアプリを見ることができて、私自身とても参考になりました。 また、講師を務めたエンジニアからも、「自分が学生だった頃はこんなに出来なかった。レベルが高い」と驚きの声が挙がっていました。 ※成果発表の様子※ 成果発表の後は、講師以外の社員も招いて懇親会を行いました。 コロナによる制限もなくなり、数年ぶりに飲食しながら学生の皆さんと社員で交流を行うことができました。 業務に関する質問やプライベートの過ごし方など、様々な話で盛り上がりを見せ、1時間の懇親会があっという間に終わってしまいました。 ※懇親会の様子※ 参加者の声 参加いただいた学生の皆さんにアンケートを実施しました。 Rakus Tech Labの満足度としては、全4回の平均で、 100点満点中 92.5点 と、我々としても非常に満足な結果(点数)でした。 良い意見だけでなく、自身の取り組みが不足していた…と悔しさを滲ませる感想もありましたが、 皆さん、RakusTechLabの目的でもある 仕事への理解 ラク スの風土を理解 お互いの理解 体験を通じた「お互い」の成長 をしっかりと達成することができたようで、満足度の高得点以上にその点が嬉しかったです。 そのアンケートの回答を一部抜粋して、紹介させていただきます。 チーム開発の雰囲気や ラク スの社風や社員の雰囲気などを掴むことができました。また企業説明や若手社員の新人時代の経験など他では聞けないことが多く、大変勉強になりました。自分が ラク スで働く社員となったときのキャリアビジョンやイメージを掴むことができました。 他社の インターン と異なり、 インターン 中に説明会のようなものや懇親会が開催があり、会社への理解度がとても高まった。他の インターン では得られないやりがいや知識や達成感を得ることができた。 オフラインでの実施だったので会社の雰囲気がよくわかった。特に作業スペースの横では実際にミーティングをしていたり、1人の作業場で集中しているところも見れて、かなり会社についてわかった。 RAKUS Tech Labを通じて単純に技術だけででなく、メンターの方やメンバーとのコミュニケーションについても学びが多くありました(むしろ後者の方が多かったかもしれないです) 以上のように、開発体験だけでなく、多くのことを感じて、学びになったとの声を頂けて、非常に嬉しい限りです。 まとめ・所感 参加学生の中で、個人開発の経験はあるという方は多くいますが、チーム開発経験があるという学生の方はそれほど多くないので、 開発中は、チーム開発特有の問題であるGitの使い方、コンフリクト、開発体制、分担で苦労している様子でした。 講師としてサポートしている若手エンジニアも、あまり干渉しすぎないようにしていますが、 学生の皆さん同士で対処できない場合には、指針を示したり、いくつか選択肢を与える等して、 「導く」ことに重きを置いてサポートしていたのがとても印象的でした。 学生の皆さんもそうした課題やトラブルに向き合うことを苦にせず、乗り越える楽しみを感じながら、取り組んでいる様子でした。 そうしたチーム開発ならではの楽しさや難しさを経験していただく、という部分が本 インターン の醍醐味ではあるのですが、今年は他の部分においても、何か学生の皆さんにとってプラスになる取り組みができないかと考え、「学生×講師の1on1」を実験的に取り入れました。 ラク スでも日常的に行っている1on1を体験いただくために、講師と学生が1対1で会話する機会を設けました。 実施前はあまり盛り上がらずに終わってしまったらどうしようか…と心配していましたが、 いざ始まると、終了時間になっても話が止まらず、時間オーバーしてしまう組が多数出る状況でした。 1対1になることで、他の人がいる場だと聞きづらい質問や相談が活発に出ていたそうで、学生の皆さんからも以下のようなお声を頂きました。 「普段の働き方やエンジニアのキャリアについてなど、現場のエンジニアの方に質問できて非常に参考になった」 「就職活動の悩みに対して、現場のエンジニアとして働かれている率直な意見を聞くことができた」 「会社の雰囲気や評価制度などかなり踏み込んだ話ができてとても参考になった」 チーム開発に取り組んでいると、開発作業優先になってしまうという部分もあるかと思うのですが、 開発体験から少し離れて、1on1という時間を設けることで、学生の皆さんが自身の考えを整理したり、 就活の不安や疑問、 ラク スの実情を聞くことができる良い機会になったようで、発案者として安堵しました。 (運営として、時間管理をもっとしなければ・・・という点は今後の反省です。。。) 終わりに 2023年度、Rakus Tech Labへご参加いただき、本当にありがとうございました。 今回のチーム開発を経て、何か少しでも得るものがあったとしたら幸いです。 私としても、学生の皆さんが意欲的に自ら学び、吸収して、アウトプットしていく様子を目の当たりにして、大きな刺激を受ける良い機会になりました。 改めまして、ありがとうございました。ご参加いただいた皆さんのさらなる活躍を期待しております! ラク スではエンジニア インターン 「Rakus Tech Lab」を東京および大阪で、毎年実施しています。 毎年春(3月~4月)頃から、エントリーを受け付けしておりますので、ご興味ある学生の方は、 ぜひ下記サイトより、エントリーいただければと思います。 ※2024年度 インターン (26卒対象)は 2024/3/1 にエントリー開始いたしました! fresh-recruit.rakus.co.jp
アバター
こんにちは!新卒1年目のos188です。 私が担当する商材は、リリースから10年以上が経過し、膨大な量の ソースコード が存在します。 大部分は オブジェクト指向 プログラミングで書かれていますが、 コードを読んで勉強しているとき、古い部分で手続き型プログラミングによって書かれているところを見つけました。 新しい部分と比較すると「読みづらいな、処理を追いかけにくいな」と感じることが多く、 大規模な ソースコード だとこんなにも差が出るのかと感心しました。 今回は、手続き型プログラミングを大きなプロジェクトや複雑な処理に適用した際のやりづらさと、 オブジェクト指向 プログラミングによる解決策について説明します。 手続き型のやりづらさ 1. データの変更が処理に影響を与えやすい カプセル化する 2. コードの重複が発生しやすい 継承とポリモーフィズムを用いる 3. データと処理が分離される クラスで管理する まとめ 手続き型のやりづらさ データの変更が処理に影響を与えやすい コードの重複が発生しやすい データと処理が分離される サンプルコードを用いてそれぞれ具体的に説明していきます。 1. データの変更が処理に影響を与えやすい 手続き型プログラミングでは、データの変更が処理に影響を与える可能性が高いです。 関数によるデータ参照がバグを生みやすく、特に大規模なプログラムでは変数名の衝突や意図しない変更が起こりやすいです。 以下の例では、 グローバル変数 が別の関数によって予期せず書き換えられています。 <?php $ tax_rate = 0.10 ; // 税率がグローバル変数 function calculate_total_price ( $ price ) { global $ tax_rate ; // 税率を取得する return $ price + ( $ price * $ tax_rate ) ; // 税込み価格を返す } function calculate_reduced_tax_price ( $ price ) { global $ tax_rate ; // 税率を取得する $ tax_rate = 0.08 ; // 税率が上書きされた…! return $ price + ( $ price * $ tax_rate ) ; // 税込み価格を返す } echo calculate_total_price ( 100 ) ; // "110" echo calculate_reduced_tax_price ( 100 ) ; // "108" echo calculate_total_price ( 100 ) ; // "108" (税率10%のつもりが…) カプセル化 する オブジェクト指向 では、 カプセル化 によってこの問題を解決できます。 クラス内のデータが隠蔽されており外部からアクセスできないため、書き換えられることがありません。 <?php private $ taxRate ; // 税率が外部からアクセスできない! public function __construct ( $ taxRate ) { $ this -> taxRate = $ taxRate ; } public function calculateTotalPrice ( $ price ) { return $ price + ( $ price * $ this -> taxRate ) ; // 税込み価格を返す } } $ taxCalculator = new TaxCalculator ( 0.10 ) ; // 税率10%のインスタンスを作成 echo $ taxCalculator -> calculateTotalPrice ( 100 ) ; // "110" $ reducedTaxCalculator = new TaxCalculator ( 0.08 ) ; // 軽減税率用のインスタンスを作成 echo $ reducedTaxCalculator -> calculateTotalPrice ( 100 ) ; // "108" echo $ taxCalculator -> calculateTotalPrice ( 100 ) ; // "110" (税率10%のまま!) 2. コードの重複が発生しやすい 手続き型プログラミングでは、コードをモジュール化して再利用することが難しいため、コードの重複が発生しやすくなります。 以下の例では、ほぼ同じ処理が書かれていたり、同じ関数を何度も呼び出しています。 これは非常に簡単な例ですが、複雑な処理が多くなるとどうしても重複が発生します。 <?php function bark_dog () { echo "犬がワンと鳴いた \n " ; } // 似たような処理… function meow_cat () { echo "猫がニャーと鳴いた \n " ; } // 何度も同じ関数を呼んでいる bark_dog () ; // "犬がワンと鳴いた" meow_cat () ; // "猫がニャーと鳴いた" bark_dog () ; // "犬がワンと鳴いた" bark_dog () ; // "犬がワンと鳴いた" meow_cat () ; // "猫がニャーと鳴いた" 継承と ポリモーフィズム を用いる 一方、 オブジェクト指向 なら継承することで同じ処理を再利用することができます。 また、 ポリモーフィズム によってコードの重複を防ぐことができます。 <?php class Animal { protected $ animal ; protected $ cry ; public function __construct ( $ animal , $ cry ) { $ this -> animal = $ animal ; $ this -> cry = $ cry ; } public function cry () { echo $ this -> animal . "が" . $ this -> cry . "と鳴いた \n " ; } } class Dog extends Animal { public function __construct () { parent ::__construct ( "犬" , "ワン" ) ; } // Animalクラスを継承しているので、cry()関数を使える! } class Cat extends Animal { public function __construct () { parent ::__construct ( "猫" , "ニャー" ) ; } // Animalクラスを継承しているので、cry()関数を使える! } // ポリモーフィズムでまとめてオブジェクトを扱える! $ animals = [ new Dog () , new Cat () , new Dog () , new Dog () , new Cat ()] ; foreach ( $ animals as $ animal ) { $ animal -> cry () ; // 出力省略 (犬、猫、犬、犬、猫の順番で鳴く) } 3. データと処理が分離される 手続き型プログラミングでは、データとそれに関連する処理が分離されがちです。これにより、意図しない副作用を招く可能性があります。 以下の例では、いろいろな値を グローバル変数 で管理しており、コードの理解や変更が困難になっています。 <?php function getAdultAge ( $ date ) { if ( $ date < strtotime ( "2022-04-01" )) { return 20 ; } else { return 18 ; } } function reachBirthday ( $ name , $ age ) { // 外部の状態を変更するような関数 $ age ++ ; echo " $ name は誕生日を迎え、 $ age 歳になりました \n " ; return $ age ; } function isAdult ( $ name , $ age , $ adult_age ) { // 外部の状態に依存した関数 if ( $ age >= $ adult_age ) { echo " $ name は成人です \n " ; } else { echo " $ name は未成年です \n " ; } } $ person_name = "John" ; $ person_age = 17 ; // 成人年齢をグローバル変数で管理する必要がある $ adult_age = getAdultAge ( strtotime ( "2024-01-01" )) ; isAdult ( $ person_name , $ person_age , $ adult_age ) ; // "Johnは未成年です" // Johnの年齢もグローバル変数で管理する必要がある $ person_age = reachBirthday ( $ person_name , $ person_age ) ; // "John は誕生日を迎え、18 歳になりました" isAdult ( $ person_name , $ person_age , $ adult_age ) ; // "Johnは成人です" クラスで管理する オブジェクト指向 プログラミングでは、クラスによってデータと処理がセットで管理されます。 それぞれのデータをクラス内で管理できるので、クラスの外ではデータの状態を気にしなくて大丈夫です。 <?php class Person { private $ name ; private $ age ; // 名前と年齢はこのクラス内で管理する public function __construct ( $ name , $ age ) { $ this -> name = $ name ; $ this -> age = $ age ; } public function reachBirthday () { $ this -> age ++ ; echo " $ this -> name は誕生日を迎え、 $ this -> age 歳になりました \n " ; } public function getName () { return $ this -> name ; } public function getAge () { return $ this -> age; } } class JudgeAdult { private $ person ; private $ adult_age ; // 成人年齢の処理はこのクラス内で完結する // つまり、成人年齢の仕様に変更があってもこのクラスを見るだけで良い! public function __construct ( $ person , $ date ) { $ this -> person = $ person ; if ( $ date < strtotime ( "2022-04-01" )) { $ this -> adult_age = 20 ; } else { $ this -> adult_age = 18 ; } } public function isAdult () { if ( $ this -> person -> getAge () >= $ this -> adult_age ) { echo $ this -> person -> getName () . "は成人です \n " ; } else { echo $ this -> person -> getName () . "は未成年です \n " ; } } } $ john = new Person ( "John" , 17 ) ; $ john_judge = new JudgeAdult ( $ john , strtotime ( "2024-01-01" )) ; // 成人年齢は JudgeAdultクラスで管理するので、気にしなくて良い! $ john_judge -> isAdult () ; // "Johnは未成年です" // Johnの年齢はPersonクラスで管理するので、気にしなくて良い! $ john -> reachBirthday () ; // "John は誕生日を迎え、18 歳になりました" $ john_judge -> isAdult () ; // "Johnは成人です" まとめ 今回は、手続き型プログラミングのやりづらさと、 オブジェクト指向 プログラミングによる解決策について取り上げました。 データの変更が処理に影響を与えやすい→ カプセル化 する コードの重複が発生しやすい→ 継承と ポリモーフィズム を用いる データと処理が分離される→ クラスで管理する 全体を通して、コードの再利用性、保守性が向上したことを感じていただけたでしょうか? 大規模な開発になればなるほど、これらの影響がどんどん大きくなります。私は毎日 オブジェクト指向 プログラミングの恩恵を享受しています! 最後まで読んでいただきありがとうございました。
アバター
こんにちは、モバイル開発チームのhyoshです。 弊社では各分野の特定のテーマに沿ってエンジニアが議論する「TechCafe」というイベントを定期開催しています。 PHPTechCafe フロントエンドTechCafe そして先日私を含めた弊社モバイル開発チームが初となる「モバイルTechCafe」を開催しました! rakus.connpass.com 本ブログでは開催までの準備過程や当日の内容についてレポーティングさせていただきます。 TechCafeについて 準備編 テーマ選定 参加者選定 打ち合わせ 当日編 紹介したイベント 複雑さに立ち向かうためのコードリーディング入門 認証体験向上のためにpasskeys(パスキー)に対応する 〜 メリット・対応方法について 集まれKotlin好き!Kotlin愛好会 WebViewと向き合う Compose で Android/iOS アプリを作る Androidエンジニアが1人という不安と向き合う Japan IT Week やってみての感想 おわりに TechCafeについて 弊社TechCafeはエンジニア同士の交流の機会を提供する、エンジニアと技術が交差する憩いの場(カフェ)になれるようなイベントを目指しています。 TechCafe というイベントそのものが学びの場となり、運営メンバーも含め、参加者全員がエンジニアとしてレベルアップしていけるように支援することを目的として開催しております。 今回最初に企画が持ち上がった際には、個人でのLT等の活動経験はあるとはいえ組織的な社外向けイベントというのは経験がなく、上手くやれるのかという不安はありました。 ただ上述の目的にもあるように例え上手くできなくても開催すること自体が参加者にとって少しでも学びのきっかけとなり、チームにとっても新たな視点をもたらす糧になるのではと思い「まずはやってみよう」の精神で引き受けました。 準備編 テーマ選定 イベントの肝となるテーマを決めるにあたってはまずはチーム内でブレストを行いました。 折角なので全員で協力して成功を目指すイベントにすべく参加者に限定せず全員で行ったのですが、いざ始めると想像よりも闊達に意見が飛び交い初回は案出しだけで終わることになりました。 ブレストの結果 そして二度目の打ち合わせで候補の絞り込みを行いましたが、選定にあたっては次の観点を重視しました。 レベルを問わずより多くの参加者に新しい気付きをもたらすことができること 時間をかけすぎずに今持っている情報を使って議論ができること 何より自分たちが「楽しんで」話せる内容であること 結果として記念すべき初回テーマは 「モバイルエンジニアにおススメの技術イベントを語る」 に決めました。 参加者選定 参加者については当初は希望者のみで考えていましたが、テーマ的に特定の技術スタックが関係ある訳でもなく誰でも参加可能なのとその方がより当チームの雰囲気が伝わるのではと考え5人全員での参加としました。 他TechCafeでは2~3人程度が基本だったので多いかなという懸念もありましたが、終わってみると初回で不慣れな中、あまり各々が多い量を話せないという状況では丁度よかったと思います。 また役割については ファシリテーター (私)、コメント確認などの分担を決めました。 打ち合わせ 初回という事もあったので当日までの打ち合わせは数回に分けて念入りに行いました。 まずは参加者別にインタビューを行いテーマに対してどのようなネタを持っているかを自身が壁打ち役となり膨らませていきました。 そして全員分出揃ったら全体の繋がり含めて本番タイムラインをイメージして当日shownoteを検討していきました。 最終的に出来上がったshownoteが以下となります。 hackmd.io 最後に全員での本番を想定したリハーサルを行い当日に臨むことになりました。 当日編 紹介したイベント ここでは簡単に当日どのようなイベントを取り上げたかを紹介させていただきます。 複雑さに立ち向かうためのコードリーディング入門 fortee.jp 長期記憶や短期記憶といった 脳科学 側面からコード読解術を解説していく一風変わっていますが納得感のあるセッションです。 モバイルに特化した話ではないですが「確かに思い返すとあの場面では長期記憶が使えていたのかも」といった体験談で当日は盛り上がりました。 認証体験向上のためにpasskeys(パスキー)に対応する 〜 メリット・対応方法について fortee.jp 最近大手企業でも導入事例が増えているpasskeysについて改めて概要から iOS アプリにおける導入方法までが解説されたセッションです。 導入によるメリット・デメリット、特にユーザー目線だとどのような価値があるのかなどの議論で盛り上がりました。 集まれKotlin好き!Kotlin愛好会 love-kotlin.connpass.com Kotlinに関する事ならジャンル問わず何でもOKの定期開催イベントです。 LT中心ですが公式リファレンスを読んで雑談したりなどフリーな雰囲気で、紹介メンバーは何かしら新しい気づきを得ることができているそうです。 ちなみにSwift版も開催されているので iOS エンジニアも安心です。 WebViewと向き合う speakerdeck.com あまり取り上げられる機会の少ないWebViewについて改めて留意すべき点など解説されており、求めている人には非常に有益なセッションです。 ネガティブな方向で語られる事もあるWebViewですが当チームでも改めてその意義や活用できるシーンなどを議論するきっかけにできました。 Compose で Android / iOS アプリを作る speakerdeck.com Compose for iOS と KMP を用いてKotlin100%で iOS アプリを作るまでを解説したセッションです。 当日は クロスプラットフォーム でどこまでネイティブに近づけられるのかといった議論で盛り上がりました。 Android エンジニアが1人という不安と向き合う speakerdeck.com エンジニアが自身1人だけで正解が分からない中でどのように正しい情報や技術を取得していったかという学習方法やキャリアへの考え方が述べられたセッションです。 自分と境遇が似ており参考になったということでメンバーから紹介されましたが、アウトプット(自分で手を動かして物を作ること)の重要さに全員共感しました。 Japan IT Week www.japan-it.jp 特にモバイル特化という訳ではないですが由緒ある大規模ITカンファレンスです。 過去に組み込み開発経験もあり参加したこともあるメンバーからの紹介でしたが、実際に体験できるオンラインイベントならではの面白さといった観点等で語り合いました。 上記元々用意していたイベント紹介以外にも「登壇時の心構え」を語り合ったりもしていると、気づけばあっという間に予定の1時間半を経過し初めてのTechCafeは無事にクロージングを迎えました。 やってみての感想 初めは緊張もあったのですが徐々に慣れていつものチームの空気が出てきてカフェに見合った自然な空気をお届けできたのではと思います。 5人という人数も終わってみれば誰かに偏ることもなくわいがやな雰囲気を作り出すことができたので正解でした。 準備については入念に行ったことで当日つつがなく進行することができましたが、次回もし機会があるならよりライブ感を楽しむためにあえて軽めにというのでも良さそうだなと思いました。 とまぁ色々気づきはあったのですが 何よりも予想以上に楽しかったです! 自分達が楽しめるかというのはテーマにも掲げていましたが、終わってみれば全員が楽しめて業務とはまた違う共同作業を通しての達成感を味わうことができたのが一番の収穫でした。 初めは不安もありましたが得られることも多いので、また機会があれば是非開催できればと思っています。 おわりに 今回は初めてモバイルTechCafeを開催してみた体験をレポートにしてみましたがいかがだったでしょうか? 改めての学習のきっかけや結束力強化にも繋がりますので、このようなイベントを検討されている方の参考になりましたら幸いです。 再掲になりますが弊社では今回のモバイルTechCafe以外にも「 PHP TechCafe」や「フロントエンド TechCafe」といったイベントを定期開催しています。 どのような方にも楽しめて学べる場を目指し活動しておりますのでぜひご参加ください!
アバター
こんにちは。大阪楽楽開発課のdaina_rksです。 Laravelの マイグレーション を活用して、テーブル定義を更新しているサービスは多いと思います。 しかしサービスが継続するにつれ、気づけば大量の マイグレーション ファイルが存在している、、、なんて経験はありませんか? 私が携わっていたプロジェクトでも同じ悩みに直面していました。 この悩みに対して、私は マイグレーション ファイルを全て削除する ということを行いました。 今回はそのときの経験について、なぜ マイグレーション ファイルを削除するに至ったのか、削除するにあたって行なったこと、削除した結果どんな効果があったのかをご紹介します! マイグレーションファイルを全て削除するに至った理由 問題 マイグレーションファイルを全て実行するのに時間がかかる マイグレーションファイルのメンテナンスコストがかかる アイデア アクション ダンプ&リストアの仕組み構築 ダンプ リストア マイグレーションファイルの削除 環境変数の更新 ディレクトリ操作 不具合対応 結果 まとめ 参考情報 マイグレーション ファイルを全て削除するに至った理由 問題 私のプロジェクトでは、 マイグレーション ファイルが大量に存在することが原因で以下のような問題がありました。 マイグレーション ファイルを全て実行するのに時間がかかる 実行するファイル数が多いと必然的に完了までの時間が長くなっていました。また、テーブル定義更新以外の処理も行なっている マイグレーション ファイルもあり、その実行にさらに時間がかかっていました。 結果として全て完了するまでにおよそ13分もかかるため、新規DBの構築や自動テスト用のDB構築に時間がかかっていました。 マイグレーション ファイルのメンテナンスコストがかかる 実行済みの マイグレーション ファイルに対しても、 PHP もしくはLaravelのEOL対応が必要であるため、メンテナンスコストがかかっていました。(当時で150個の マイグレーション ファイルが存在していたため、1~2人日ほどコストがかかっていました。) ア イデア この問題の解決策として、 DB再構築時はダンプファイルからリストアする というア イデア を思いつきました。 そうすれば マイグレーション ファイルを実行する必要がなくなるため、DB構築の速度改善が見込めます。 ※実際にア イデア 段階で簡易的に試したところ大幅な速度改善がありました。 またダンプファイルから再構築できるようになるため、1度実行した マイグレーション ファイルは不要となります。このことから既に実行済みの過去の マイグレーション ファイルを削除しても問題がないため、メンテナンスコストの削減も見込めます。 ア イデア アクション 上記のア イデア を実現するためにやるべきことは以下の2つです。 DBのダンプ&リストアの仕組み構築 過去の マイグレーション ファイルの全削除 ダンプ&リストアの仕組み構築 1つ目のアクションとして、 DBのダンプファイルを生成する仕組み と リストアする仕組み が必要となります。 ダンプ ダンプファイルは マイグレーション ファイルが実行された直後のプレーンな状態のDBから、定期的に生成される必要があります。 そこでDBのダンプ処理はJenkinsのジョブで行うこととしました。 私のプロジェクトでは、Jenkinsサーバーでアプリケーションは動いていなかったため、常にプレーンな状態のDBが存在していました。またジョブを定義すれば定期的にダンプ処理を実行することができます。(Jenkinsサーバーで1バージョンの開発が完了するたびに ソースコード のバックアップを取るジョブが実行されていたため、そのジョブの中でダンプ処理を実行するようにしました。) ダンプファイルを生成したタイミングで、実行済みの マイグレーション ファイルを削除する処理をジョブの中に加えると、今後追加される マイグレーション ファイルに関しても、実行後に削除されるという運用に乗せることができました。 ダンプの仕組み リストア 環境構築手順や自動テストの仕組みをなるべく変更したくないため、リストアの処理は LaravelのArtisanコマンド で実行できるように作成しました。( php artisan migrate を実行していた部分を php artisan restoreDb に変更するだけで済むようにしました。) リストア後に確認しなければならないことは、ダンプ元となったDBと差分がないことです。 具体的には スキーマ とテーブルの所有者や権限が同じこと、テーブル定義およびレコードに差分がないことを確認する必要があります。 所有者と権限に関しては目視で確認、テーブルに関しては平文でダンプファイルを取得しdiff コマンドで差分がないことを確認しました。 リストア時に確認すべきこと マイグレーション ファイルの削除 2つ目のアクションは不要となった実行済みの マイグレーション ファイルを削除することです。 本来ならば マイグレーション ファイルはテーブル定義の更新のみを行なっているはずなので、ダンプファイルが正しく生成されていれば削除しても問題はありません。 しかし冒頭の問題で挙げた通り、テーブル定義の更新以外の処理を行なっている マイグレーション ファイルがいくつか存在しているため、何も対応せず削除すると不具合になり得るものがありました。 テーブル定義の更新処理以外の処理を行なっている マイグレーション に関しては、処理の内容に応じて以下の対策を行いました。 環境変数 の更新 私のプロジェクトでは、 環境変数 に変更がある場合は 環境変数 のテンプレートファイル .env.example を修正する運用でした。 しかし開発初期ではこの運用が定められていなかったため、 マイグレーション ファイル内で sed コマンドを実行し、 環境変数 を更新する運用を行なっていたようです。(かなり危ない運用) 環境変数 の更新を行なっている マイグレーション ファイルに関しては更新内容を確認し、最新バージョンの .env.example に存在しない値があれば、追記してから対象の マイグレーション ファイルを削除しました。 ディレクト リ操作 マイグレーション ファイル内でmkdirコマンドやchmodコマンドを実行し、 ディレクト リ操作や権限変更を行なっていました。 おそらく、あるバージョンでファイルアップロード機能が実装された際、 マイグレーション を活用してアップロードファイルの一時置き用 ディレクト リを作成していたようです。 ディレクト リ操作の処理に関しては環境構築用の Ansible のPlaybookに、必要な ディレクト リの作成処理および権限変更処理を記述し、 マイグレーション ファイルを削除しました。 不具合対応 画像のリサイズやファイルの移動など不具合対応用の処理が、 マイグレーション ファイル内で行われていました。 このような処理は今後必要になる可能性がある(実際に複数の マイグレーション ファイルで同様の処理が行われていました)ため、 LaravelのArtisanコマンド で実行できるように処理を切り出し、対象の マイグレーション ファイルを削除しました。 結果 上記のアクションを行い、 DB構築時はダンプファイルからリストアする というア イデア を実現できました。 肝心の結果ですが、DBの構築時間は13分から1.8秒(!!)まで短縮することができました。 これにより新規環境構築時間は10分以上短縮され、DB接続を伴う自動テストの実行は数時間から約10分まで短縮されました。 (DB接続を伴う自動テストに関してはテストメソッドごとに全テーブルを DROP →全 マイグレーション を実行という仕組みだったので、CI完了時間が数時間かかっていました。 そもそも自動テストの仕組みを見直せていなかったことが要因ですが、そのことについては今回は目を瞑リました。 ) メンテナンスコストに関しても、EOL対応ごとに1~2人日分短縮されることになったため、問題を解決することができました。 まとめ 今回は過去の マイグレーション ファイルの削除についてご紹介させていただきました。 マイグレーション ファイルを全て削除した結果、新規環境構築時間や自動テストに係る時間が大幅に短縮でき、メンテナンスコストの削減効果もありました。 冒頭に挙げた問題と似た問題を抱えているエンジニアの方や、今後 マイグレーション ファイルの運用を見直したいエンジニアの方のご参考になれば幸いです。 参考情報 今回のアクションで実施した ダンプ&リストアの仕組み構築 ですが、実はLaravelの Squashing Migrations という機能ですでに実現されています。 ア イデア 段階でこの機能を利用することを検討したのですが、この機能でダンプされる対象が 1 スキーマ のテーブル定義のみ でした。 私のプロジェクトの場合、ダンプ対象は 2つの スキーマ のテーブル定義と初期データ用のレコード であり、Laravelの機能では実現できなかったため利用することを見送りました。 もし マイグレーション ファイルの削除を行う場合、LaravelのSquashing Migrationsを利用できれば運用変更に係るコストを抑えることができるため、ぜひ選択肢に入れていただけたらと思います。 laravel.com
アバター
はじめに 配配メール開発課moryosukeです。 2024/02/11(日)に PHPカンファレンス 関西 2024が開催されました。 ラク スはブロンズスポンサーとして協賛させていただいています。 2024.kphpug.jp ラク スからは5人が登壇した他、多くのメンバーが参加しました。 そこで今回は参加者によるレポート、そして ラク スからの登壇者本人によるレポートを紹介させていただきます。 はじめに 参加レポート はじめてのOSSコントリビュート Laravelでミニマム開発からスタートして個人サービスを利益化するまでの経験談! RDBアンチパターンと戦う - 削除フラグ 完全攻略ガイド 令和最新版 PHP メモリ管理術 「"品質"が高いコード」って何? CodeRevieweeが求められること アプリケーションエンジニアこそ「監視」だよね!と私が考える訳 コードを自在に操るためのPHP文法入門 20年の歴史を持つプロダクトの開発チームの変革:技術広報の本質と効果 なんで、ファイル名とクラス名を揃えるの? 知っておきたいautoloadのはなし その条件分岐って本当に必要? 実践、Interface Mutation Testingとはなにか? 〜Laravel(Pest)でInfectionを利用したライブデモ〜 モデルとは何か PHPで学ぶ、セッションの基本と応用 ほげ言語にあってPHPにない機能 擬人化で完全に理解するクリーンアーキテクチャ アンカンファレンス ラクスからの登壇セッションのご紹介 レガシーシステムへのPHPStan導入から半年での課題と効果 レガシーとモダンなシステムが混在する開発環境を改善しよう PHP8.2にバージョンアップしたら文字化けが発生して道頓堀に飛び込みたくなった話 レガシーコードに潜む奇妙なコメント ~信じるか信じないかはあなた次第~ PHP8.1で、リソースがオブジェクトに!? ~マイナーリリースの変更がレガシープロダクトに与えた影響~ まとめ PHPerのためのコミュニティ PHPTechCafe 参加レポート はじめての OSS コントリビュート report by id:takaram PHP のコントリビューターでもある、てきめん ( @youkidearitai ) さんによる発表です。 speakerdeck.com PHP を含む OSS ( オープンソース ソフトウェア)へのコントリビュートというと、ハードルが高いと感じる人も少なくないと思いますが、実際は思ったほど難しくない、だから積極的にコントリビュートしよう!という内容です。 参加者の方の感想にもありましたが、簡単なドキュメント修正や誤字修正でもありがたい、と聞くと意外と気軽にできそうな気がしてきますね! typo の修正、ドキュメントの修正、「コア開発者がコアの開発に集中できるから嬉しい」というのがコントリビューターの口から聞けるの嬉しいな...だいぶ自分の中でハードル下がった #phpkansai #a pic.twitter.com/C60CIrhXcJ — あすみ (@asumikam) 2024年2月11日 Laravelでミニマム開発からスタートして個人サービスを利益化するまでの 経験談 ! report by id:hirobex mukae さんによる発表です fortee.jp レンタルサーバ でも対応していることが多いという PHP の特性を活かして、 うまくコストカットをして運用したア イデア に感心しました。 広報活動についても紹介してくださり、 御堂筋線 に利用した広告効果の落ちが面白かったです。 RDB アンチパターン と戦う - 削除フラグ 完全攻略ガイド report by id:shinzeeyhtb 株式会社リンケージCTOの、曽根 壮大 さんによる発表です speakerdeck.com アンチパターン である削除フラグについて、その具体的なデメリットと、目の前にある削除フラグに対する向き合い方を解説していただきました。 削除フラグを削除するための具体的な移行方法は、膨大な情報を1レコードに持つテーブルを分割する際にも活用できるものであり、明日からデータベースの リファクタリング もやってみたくなる内容でした。 (そして会社にも本があるから読みたくなりました!) 令和最新版 PHP メモリ管理術 report by id:hirobex めもり〜☆ さんによる発表です speakerdeck.com PHP のメモリ管理の仕組みから始まり、メモリ消費が多くなりやすいパターンについて解説していただきました。 日頃の開発で、Zend Memory Managerの仕組みを意識して開発したことがなかったので、非常に参考になりました! 「"品質"が高いコード」って何? report by id:os188 若葉 章 さんによる発表です speakerdeck.com 品質とは何か?というところから、品質が高いコードの達成方法についてお話いただきました。 "品質が高い"とは、"要求を満たしている"状態 実装やテストをしている時、「バグがなければOK」「コーディング規約を守っていればOK」のような考え方に陥りがちでした。 この機能の要求ってなんだっけ?と都度立ち返ることを意識していきたいです。 CodeRevieweeが求められること report by id:ymyhero7 おのぽん さんによる発表です speakerdeck.com CodeReviewerが求めていることを実際のインタビュー調査から分析し、CodeRevieweeが実践するべきことを分かりやすくお話していただきました。 どのようにコードレビューを出すのが正解なのか分からず悩んでいたので大変勉強になりました。 質問されそうな部分は先回りしてコメントをしておくこと、事前に認識のすり合わせをしておくことなど、これから実践していきたいと思います。 何より、同じチームで働く者としてコードレビューを出す際にも思いやりを持つことを忘れないようにしたいと思いました。 アプリケーションエンジニアこそ「監視」だよね!と私が考える訳 report by id:hirobex きんじょうひでき さんによる発表です speakerdeck.com まさにタイトル通り、なぜアプリケーションエンジニアが「監視」に興味を持つべきかということをお話していただきました。 私は、あまり現在のプロダクトの監視には携わっていないのですが、この登壇を聞いて監視も面白そうだなぁと思いました。 たしかに、監視したときに、「なぜそのようなメトリクスになっているのか」のストーリーを組み立てやすいのは インフラエンジニアではなく開発エンジニアだよなぁと頷きながら聞いていました。 コードを自在に操るための PHP 文法入門 report by id:takaram うさみけんた ( @tadsan ) さんによる発表です fortee.jp PHPStanやPsalmといった静的解析ツールは PHP 系のカンファレンスでよく紹介されますが、今回はそれらが利用している 構文解析 ライブラリ PHP -Parserについての トーク でした。 普段コードを書いているだけではあまり意識しない「 構文木 」や「文と式の違い」、また リファクタリング ツールRectorでの 構文木 を使ったコードの書き換え方を知ることができ面白い発表でした! 20年の歴史を持つプロダクトの開発チームの変革:技術広報の本質と効果 report by id:hirobex Yasuharu Sakai さんによる発表です www.docswell.com 20年もののプロダクトが陥っていた暗黒自体から、どのような改革を行い、 チームがどう良くなっていったかが、非常にわかりやすく解説されていました! 弊社もレガシープロダクトが多く、私もレガシープロダクト担当なので、 非常に共感できる箇所が多く、またその改革方法についても興味深くお話を聞かせていただきました。 なんで、ファイル名とクラス名を揃えるの? 知っておきたいautoloadのはなし report by id:dd_fotran 赤塚啓紀 ( @aki_artisan ) さんによる発表です。 speakerdeck.com クラスのオートロードについての話です。普段気にせず使っているのでクラスのオートロードの仕組みを知らない方も多いのではないでしょうか? PSR-4 のautoloadのどのような仕組みでクラスがロードされるのかを、丁寧に説明されているので、初学者の方にもオススメです。 その条件分岐って本当に必要? report by id:hirobex 伊神 誠人 さんによる発表です speakerdeck.com 条件分岐禁止のリストバンドとともに、いかにしてif文をわかりやすくするかというお話をしていただきました。 初心者の方にぜひ聞いていただきたい登壇内容でした。 実践、Interface report by id:yamamuuu おぎ さんによる発表です speakerdeck.com Interfaceを使うと何ができるのか、何がうれしいのか。またどのような場面では使うべきで、どのような場面では使わないべきかがわかりやすく説明されていました。 「Interfaceってイマイチ理解できてない」や「どういうときに使ったらいいか分からない」という方にぜひ聞いていただきたい内容でした。 Mutation Testingとはなにか? 〜Laravel(Pest)でInfectionを利用したライブデモ〜 report by id:hirobex Kanon さんによる発表です speakerdeck.com カバレッジ メトリクスの欠点を上げ、「正しいテストケース」であるかどうか調べるための手法である 「Mutation Test」についての話をしていただきました。 私の開発しているプロダクトにも、一度やってみたいなと思える手法でした。 モデルとは何か report by id:rakusMorita 菱田裕美 さんによる発表です speakerdeck.com 「モデルって何ですか?」と聞かれたときに、わかりやすく伝えられますか? この発表ではプログラミングにおける「モデル」の役割と重要性を、 現実世界の地図やプラモデルを例に、明快にお話されていました。 特に印象的だったのは、「モデルが現実世界の複雑さを抽象化し、重要な部分のみ残して低コストで試行錯誤ができるようにする」ということです。 データベースも、現実世界の記録をモデルにしたもので、菱田さんが出会った最初のデータベースは図書室の貸し出し表だったようです。 発表を通して、複雑な概念であると思い込んでいた「モデル」がイメージしやすい身近なものに変わりました。 PHP で学ぶ、セッションの基本と応用 report by id:hirobex 富所 亮 さんによる発表です speakerdeck.com なぜHTTP(ブラウザ)が Cookie やセッションを持つようになったかという歴史的な経緯から、 どのようにして Cookie やセッションを扱うべきかというお話をしていただきました。 最近は Cookie やセッション管理を フレームワーク に任せることが多いからこそ、 なにをしているのかを理解するべきだよなぁと感じました。 ほげ言語にあって PHP にない機能 report by id:hirobex 田中ひさてる さんによる発表です speakerdeck.com 『ほげ言語の パラドックス 』の紹介から始まり、ほげ言語にあって PHP にない機能を紹介していただきました。 PHP に”ない”機能の話なので当然なのですが、なぜ PHP に無いんだ……!と心のなかで叫びながら聞いていました。 また、肩システム( 原文ママ )の落ちも非常に面白かったです! めちゃめちゃ個人的な話なのですが、年末年始休暇に勉強して挫折したNimが紹介されており、妙に納得感がありました。 擬人化で完全に理解するクリーン アーキテクチャ report by id:uemura_rks しまぶ さんによる発表です speakerdeck.com 書籍「Clean Architecture 達人に学ぶソフトウェアの構造と設計」の内容をかみ砕いて、クリーン アーキテクチャ とは何かを説明されていました。 名前は聞いたことあるけどよく知らない、という私にとっては良いとっかかりとなりました。 「ビジネスルール(方針)に向かって依存の向きを整える。」これは意識していきたいです。 おぎ さんが発表されていた「実践、Interface」も合わせて聞くことで、依存の整理についてより理解を進めることができました。 アンカンファレンス report by id:takaram PHPカンファレンス 関西2024ではアンカンファレンスも開催されました! note.com 事前にプロポーザルを募集して選ばれた通常の トーク と違い、アンカンファレンスは当日の会場で喋りたい人が自由に トーク を行うものです。 ↓ホワイトボードに各々が話したい内容を書いて枠を取っていきます #phpkansai アンカンファレンスも盛り上がってるよ! pic.twitter.com/CoSCS2Vxjy — きしもと (@getKishimoto) 2024年2月11日 Webアクセシビリティ に関する真面目な話もあれば、各々の趣味を紹介する自己紹介LT、さらにはクイズ大会(?)まで、多種多様で自由な内容の トーク が繰り広げられていました! 個人的には、 symfony/console の紹介を symfony /console 自体をスライド代わりにして話す トーク が印象的でした。 コンソールで発表いいなwww #phpkansai pic.twitter.com/dHMwpR3Z2q — やまと | ☕️ | 🐈 (@yamato_sorariku) 2024年2月11日 ラク スからの登壇セッションのご紹介 レガシーシステム へのPHPStan導入から半年での課題と効果 report by id:dd_fortran speakerdeck.com 過去にPHPerKaigiなどのイベントで登壇したことはあったのですが、(コロナ期間中だったこともあり)オンライン開催だったためオフラインでの登壇は初めてかつ、 今回のカンファレンスの中でトップバッターだったので緊張しました。 レガシーシステム にPHPStanを導入した事例についてお話させていただきました。 レガシーシステム を抱えるサービスでは同様の課題をかかえているようで、多くの方に興味を持っていただけたようでした。 数人の方から質問をいただき、導入の際の課題などを共有できたので良かったです。 レガシーとモダンなシステムが混在する開発環境を改善しよう report by id:rakuinoue speakerdeck.com 初めて登壇する機会を頂きました。ありがとうございます。 担当するプロダクトチーム内で行った開発環境改善に関するお話をさせていただきました。 途中、おいしい?音声トラブルもありましたが、無事に時間内で発表を終えることができて良かったなと思います。 もう少し PHP の話を濃くしたほうが良かったのかもと反省しております。 PHP8.2にバージョンアップしたら文字化けが発生して道頓堀に飛び込みたくなった話 report by id:nerobluebros fortee.jp speakerdeck.com 初登壇です。タイトルは採択されるために作ったもので、実際には飛び込んでいません w それはさておき、ゴリゴリの技術系のカンファレンスである一方、私の発表は運用・サポートの話が中心なので、 セッションに来てくださった人たちに「聴きたいのと違う」と刺さるか不安でした。 どれくらい刺さったかはわからないけれど、センションにたくさんの人が来てくださったし、 発表して一定の手応え、ウケもあったので、良かったかなと思っています。 また、セッション後の「Ask the speaker」で PHP のコミッタの方とお話する機会があり、 私たちがおこなった修正方法の是非やなぜ文字化けするか?が聞くことができたので、 登壇して貴重な体験ができたと思います。ありがとうございました! レガシーコードに潜む奇妙なコメント ~ 信じるか信じないかはあなた次第 ~ report by id:yamamuuu speakerdeck.com 私が普段開発をしているメール共有サービス メールディーラーはリリースから20年以上が経つサービスであり、奇妙なコメントが多数存在します。 本LTでは私が実際に遭遇した奇妙なコメントをご紹介し、コメントを信じることが如何に危険なことであるかを知っていただきました。 暇な人は見てみてください。 PHP8.1で、リソースがオブジェクトに!? ~マイナーリリースの変更がレガシープロダクトに与えた影響~ report by id:hirobex speakerdeck.com 登壇しました。 PHP8.0→8.1へバージョンアップした時、下位互換性のない変更により修正が発生した話をしました。 個人的にはウケていたと感じたのでよかったです。 まとめ 6年ぶりとなった PHPカンファレンス 関西でしたが参加者数431人と非常に大盛況でした! 来年の開催も今から楽しみですね! PHPerのためのコミュニティ PHPTechCafe ラク スでは PHP に特化したイベントを毎月開催しております。 その名も「PHPTechCafe」!! 次回は02/28(水)に『 PHPカンファレンス 関西2024を振り返る』 をテーマに開催します! まだまだ参加者を募集していますので、ぜひお気軽にご参加ください。 👉PHPerのための「 PHPカンファレンス 関西2024を振り返る」 PHP TechCafe rakus.connpass.com 最後までお読みいただきありがとうございました!
アバター
 弊社で毎月開催し、 PHP エンジニアの間で好評いただいている PHP TechCafe。2023年8月のイベントでは「PHP8.3の新機能」について語り合いました。弊社のメンバーが事前にまとめてきた情報にしたがって、他の参加者に意見を頂いて語り合いながら学びました。今回はその内容についてレポートします。 rakus.connpass.com PHP8.3 新機能について Marking overridden method オブジェクトを継承していることを示すattributeが追加 ※プロパティのオーバーライドは対象外 Type Class Constants class、interface、trait、およびenumの定数に型を設定できるようになった ※継承しているクラス定数の型を拡張することはできない。 mb_str_pad str_padのマルチバイト用関数が追加 Dynamic class constant fetch クラス定数を動的に指定することができるようになった Arbitrary static variable initializers static変数の初期化時に、固定値以外の変数や関数を渡せるようになった Readonly amendment readonlyプロパティをcloneするとき再初期化することが可能になった ※cloneメソッドでreadonlyを変更できるのは一回のみ PDO driver specific sub-classes PDOのサブクラスを追加 PDO::connect Randomizer Admditions Randomizerクラスに以下の関数が追加 getBytesFromString() getFloat() nextFloat() その他の関数追加修正 json_validate() range() mb_strimwidth() その他議題 定数NumberFormatter::TYPE_CURRENCYの削除 定数MT_RAND_PHPの削除 最後に PHP8.3 新機能について PHP8.3の新機能は弊社のメンバーが事前にHackMdの記事としてまとめています。 今回のイベントではこの記事に沿って新機能をみていきました。 hackmd.io すべての機能は今回確認することはできなかった為、 確認する場合はこちらの記事にてよろしくお願いいたします。 Marking overridden method オブジェクトを継承していることを示すattributeが追加 <?php class P { protected function p () : void {} } class C extends P { #[\Override] public function p () : void {} } このことにより、 ・インターフェースを実装してるのか、クラスを継承してオーバーライドしているのか明示 ・親クラスの シグニチャ が変わった場合で、意図しないオーバライドを防ぐ ができます。 メリット、用途が分かりやすく今回の新機能の中で一番注目が集まった機能です。 アトリビュート が追加されたのはPHP8.0からなので、その アトリビュート 機能を使った活用が早いとして 喜々として話されておりました。 コメントからは、「解析ツールを使って自動で付けて回ることが出来そう」と。 可読性、保守性の向上が期待できそうです。 ※プロパティのオーバーライドは対象外 プロパティのオーバーライドは、親クラスと子クラスで意味合いが変わることが多いので、 対象外となります。 Type Class Constants class、interface、trait、および enum の定数に型を設定できるようになった <?php enum E { const string TEST = "Test1" ; // string 型の指定が可能になりました } この議題では、 PHP で今まで出来ていなかったことに驚くかたもおられました。 ※継承しているクラス定数の型を拡張することはできない。 以下コード例で言いますと、 public const mixed C = 0; の部分です。 これは、int ⇒ mixed になっているためできません。 少しややこしいですが、 定義の範囲が広くなったらNG と認識して問題ないです。 コメントでは、子クラスで型を再定義するようなケースに突っ込みがありました。 なるべく避けましょう。 <?php trait T { public const ? array E = [] ; } class Test { use T; private const int A = 1 ; public const mixed B = 1 ; public const int C = 1 ; public const Foo | Stringable | null D = null ; // T::Eが再定義されたときに型を変更することはできないのでNG。 public const array E = [] ; } class Test2 extends Test { // private ⇒ public は 任意に型変更 OK public const string A = 'a' ; // mixed ⇒ int は OK public const int B = 0 ; // int ⇒ mixed は NG public const mixed C = 0 ; // since Foo&Stringable ⇒ Foo|Stringable は OK public const ( Foo & Stringable ) | null D = null ; } enum E { // 定数は共変のコンテキストを提供するのでOK public const static A = E :: Foo; case Foo; } class Foo implements Stringable { public function __toString () { return "" ; } } mb_str_pad str_padのマルチバイト用関数が追加 <?php // This will pad such that the string will become 10 bytes long. var_dump ( str_pad ( 'Français' , 10 , '_' , STR_PAD_RIGHT )) ; // BAD: string(10) "Français_" var_dump ( str_pad ( 'Français' , 10 , '_' , STR_PAD_LEFT )) ; // BAD: string(10) "_Français" var_dump ( str_pad ( 'Français' , 10 , '_' , STR_PAD_BOTH )) ; // BAD: string(10) "Français_" // This will pad such that the string will become 10 characters long, and in this case 11 bytes. var_dump ( mb_str_pad ( 'Français' , 10 , '_' , STR_PAD_RIGHT )) ; // GOOD: string(11) "Français__" var_dump ( mb_str_pad ( 'Français' , 10 , '_' , STR_PAD_LEFT )) ; // GOOD: string(11) "__Français" var_dump ( mb_str_pad ( 'Français' , 10 , '_' , STR_PAD_BOTH )) ; // GOOD: string(11) "_Français_" こちらの議題では、追加された内容よりも、 マルチバイトと PHP の話にスポットが当たっておりました。 というのも、マルチバイト文字と言えば、日本語のひらがな、カタカナ、そして漢字。 国内で苦しめられているエンジニアも少なくありません。 (議題でもマルチバイトの闇がぽつりと溢れだす場面が) しかし世界で見れば需要は少ないのか、 RFC では蔑ろにされるイメージがあり、 今回の追加は珍しいとの反応が。 RFC を確認すると著者はフランスの方で、 例として、フランス語、 ギリシャ 語、絵文字が記載されております。 欧州ではISO8859-1からISO8859-16まであり、 地域によって切替を行う方式なので、追加したくなったのかなと推測されておりました。 Dynamic class constant fetch クラス定数を動的に指定することができるようになった <?php class Foo { const BAR = 'bar' ; } $ bar = 'BAR' ; // PHP8.3 以降は以下の記述が可能 echo Foo :: { $ bar } ; // PHP8.2 までで上記と同様の動作を実現する方法 echo constant ( Foo :: class . '::' . $ bar ) ; 上記コードを一目見て、直感的な理解の難しさ故に「黒魔術に見える」との意見がありました。 echo Foo::{$bar}; ポイントはこの部分で、 文字列"BAR"が格納されている$barを使用し、Foo関数のBAR(文字列bar)を呼び出しております。 enum の使い勝手が向上することは良いですね。 Arbitrary static variable initializers static変数の初期化時に、固定値以外の変数や関数を渡せるようになった <?php function bar () { echo "bar() called \n " ; return 1 ; } function foo () { static $ i = bar () ; // ← 8.2まではこの書き方が出来なかった echo $ i ++ , " \n " ; } foo () ; // bar() called // 1 foo () ; // 2 foo () ; // 3 この議題で良い活用法について思案しており、 コメントの中で「キャッシュの初期値設定は楽になりそう」との意見がありました。 本題とは関係ありませんが、このように皆で活用法を考えて、 共有することができるのは、 PHP TechCafeの良いところですね。 Readonly amendment readonlyプロパティをcloneするとき再初期化することが可能になった cloneは インスタンス をコピーする関数です。 clone時に、readonlyのプロパティを一度だけ変更することが可能になります。 下記コード例で言いますと、 $this->bar = clone $this->bar が新機能にあたります。 <?php // __clone()の実行中のみ、readonlyプロパティを再初期化することができる class Foo { // コンストラクタ public function __construct ( public readonly DateTime $ bar , public readonly DateTime $ baz ) {} // clone public function __clone () { $ this -> bar = clone $ this -> bar; // OK $ this -> cloneBaz () ; } private function cloneBaz () { // __cloneから呼び出されている場合はreadonlyプロパティの変更がOK unset ( $ this -> baz ) ; } } $ foo = new Foo ( new DateTime () , new DateTime ()) ; $ foo2 = clone $ foo ; // エラーは発生しない。 // この場合、Foo2::$bar は2重にcloneされており、Foo2::$baz は初期化されない ※cloneメソッドでreadonlyを変更できるのは一回のみ 一回目は変更できるが、二回目はNGになります。 <?php class Test { public function __construct ( public readonly DateTime $ bar ){} public function __clone () { $ this -> bar = $ this -> bar; // OK $ this -> bar = clone $ this -> bar; // NG } } PDO driver specific sub-classes PDOのサブクラスを追加 各ドライバ固有のメソッドを持つPDO( PHP Data Objects)のサブクラスが追加されます。 <?php // MySQL $ pdoMySQL = new PdoMySql ( $ dsn ) ; $ pdoMySQL -> getWarningCount () ; // MySQL専用機能 // PostgreSQL $ pdoPgsql = new PdoPgsql ( $ dsn ) ; $ pdoMySQL -> getPid () ; // PostgreSQL専用機能 例として、 PostgreSQL にあるpidの取得が専用の関数により簡単になります。 専用の関数なので、これらの関数を使用した際はDB移行時に注意してください。 PDO::connect 特定のDBのサブクラスを取得する PDO::connect というファクトリメソッドが追加されます。 $dsn が PostgreSQL に接続したら、 PostgreSQL のPDO、 MySQL に接続したら MySQL のPDOが返却されます。 <?php class PDO { public static function connect ( string $ dsn [ , string $ username [ , string $ password [ , array $ options ]]]) { if ( connecting to SQLite DB ) { return new PdoSqlite ( ... ) ; } return new PDO ( ... ) ; } } サブクラスのコンスト ラク タを使って直接接続も可能。 $db = new PdoSqlite($dsn, $username, $password, $options); 議題として、何でもOKなファクトリメソッドを用意しているが、 専用のコンスト ラク タを用意されていると、そちらを使うのがメインになると思うのではとの意見がでました。 Randomizer Admditions Randomizerクラスに以下の関数が追加 getBytesFromString() 与えられた文字列、文字列長を参照してランダムに文字列を生成。 第一引数 $string :選択対象の文字列 第二引数 $length :返り値の文字列長 下記例では、半角小文字英数字の中から16桁のランダムな文字列を生成します。 <?php $ randomizer = new \Random\Randomizer () ; // ランダムなドメイン名 var_dump ( sprintf ( "%s.example.com" , $ randomizer -> getBytesFromString ( 'abcdefghijklmnopqrstuvwxyz0123456789' , 16 ) )) ; // string(28) "xfhnr0z6ok5fdlbz.example.com" こちらに対して、第一引数に 正規表現 は使用不可とのことで、惜しむ声が少しありました。 これからの改善に注目です。 getFloat() 引数$minと$maxの間の 浮動小数点数 を返す。 第一引数 $min :最小値 第二引数 $max :最大値 第三引数 $boundary : 区間 境界の指定 ※デフォルトは ClosedOpen \Random\IntervalBoundary::ClosedOpen : $min以上、 $maxより下 \Random\IntervalBoundary::ClosedClosed : $min以上、 $max以下 \Random\IntervalBoundary::OpenClosed : $minより上、 $max以下 \Random\IntervalBoundary::OpenOpen : $minより上、 $maxより下 <?php $ randomizer = new \Random\Randomizer () ; // 経緯度 var_dump ( sprintf ( "Lat: %+.6f Lng: %+.6f" , $ randomizer -> getFloat ( -90 , 90 , \Random\IntervalBoundary :: ClosedClosed ) , // 緯度は90/-90どちらも可 $ randomizer -> getFloat ( -180 , 180 , \Random\IntervalBoundary :: OpenClosed ) , // 経度は180はあるけど, -180はない )) ; // string(32) "Lat: -51.742529 Lng: +135.396328" こちらの用途については、次の引数の紹介も併せます。 nextFloat() 0~1の間でランダムな少数を出してくれます。 以下コードと同等の処理を実行。 getFloat(0, 1, \Random\IntervalBoundary::ClosedOpen) 内部実装がgetFloat()よりも整理されており、処理速度が速いとのこと。 0.0以上、 1.0より下 のランダムな少数を生成する場合はこっちを使うとよい。 この機能を作られた方はゲーム会社に勤めているとのことで納得の声があがりました。 業務アプリを作成する場合は乱数に頼る機会は少ないがゼロではない為、 こういった数値機能が豊富になると有り難いですね。 その他の関数追加修正 json_validate() 文字列が JSON の正しい形かどうかを判定します。 <?php json_validate ( '{ "test": { "foo": "bar" } }' ) ; // true json_validate ( '{ "": "": "" } }' ) ; // false 今まで、 JSON 形式のチェックは json_decode によるチェックを行っていたかと思いますが、 JSON の大きさによって大量のメモリを割り当てる必要がありました。 この関数を使用するとその心配がなくなります。 range() range関数に発生していた不自然な挙動が修正されます。 <?php var_dump ( range ( 0 , 3 , -1 )) ; // PHP8.2まで [0, 1, 2, 3] // PHP8.3以降 ValueError var_dump ( range ( '9' , 'A' )) ; // PHP8.2まで [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] // PHP8.3以降 [9, :, ;, <, =, >, ?, @, A] var_dump ( range ( '' , 0 )) ; // PHP8.2まで [0] // PHP8.3以降 [0] Warning: range(): Argument #1 ($start) must not be empty, casted to 0 こちら、誤った指定でエラーになるようになります。 var_dump(range('9', 'A')); は、 変更後も挙動が分かり難いですが、ASCIIコードの順になります。 現在Range関数を使用している場合は、不自然な挙動を前提に動いている可能性がないか注意が必要です。 コメントでは、 コードゴルフ *1 の選択肢が減りますね!とのことで笑いが起きました。 mb_strimwidth() 指定した幅で文字列を丸める関数ですが、負の値を入れることが出来てしまった問題が修正されます。 使用されている場合は、負の値が今まで入っていたが動いていた場合があるので注意が必要です。 その他議題 定数NumberFormatter::TYPE_CURRENCYの削除 フォーマッタの形式を指定する定数です。 通貨の値をフォーマッタ化する定数がありましたが、実装されないままだったので消すことになりました。 定数MT_RAND_ PHP の削除 PHP7.1で修正された乱数発生機問題の「互換性維持」手段が、今回でなくなることになります。 最後に PHP TechCafeでは PHP の機能をなぞるだけではなく、 新機能の活用法について皆で考えたり、 有識者 に機能が追加された背景まで語りつくしていただけました! そのため、すべての変更点をなぞることができませんでしが、 ある意味 PHP TechCafeの気軽さならではかと思います笑 「 PHP TechCafe」では今後も PHP に関する様々なテーマのイベントを企画していきます。 皆さまのご参加をお待ちしております。 *1 : 可能な限りもっとも短い ソースコード で記述することを競う
アバター
はじめに 新卒1年目のTKDSです! 先日,Go言語で json で返すレスポンスを作る際,ゼロ値の場合の項目の出し分けを行いたい場面がありました. そこで,encoding/ json でゼロ値の場合の項目の出し分けを行う方法を調査しました. はじめに 行いたいこと 1. 改変したいフィールドの型をany(interface{})にして,タグにomitemptyを指定する 2. encoding/json/v2 のomitzeroを使う. 3. MarshalJSON()メソッドを実装する. まとめ 行いたいこと profileがゼロ値の場合,responseの一部を改変し,profileを含まず出力します. {"id":1,"created_at":"2009-11-10T23:00:00Z","updated_at":"2009-11-10T23:00:00Z","profile":{"name":"TKD","age":"1000","email":"hogege@example.com"}} profileがゼロ値の場合 {"id":1,"created_at":"2009-11-10T23:00:00Z","updated_at":"2009-11-10T23:00:00Z"} 改変前のサンプルコードは こちら です. goの構造体として次のような形を持っています. type user struct { ID int64 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Profile profile `json:"profile"` } type profile struct { Name string `json:"name"` Age string `json:"age"` Email string `json:"email"` } user型の変数を定義する際,以下のコードのようにProfileを指定せずに変数の初期化を行うと,Profileフィールドは変数の型のゼロ値で初期化されます.stringの場合は ”” です. user := user{ ID: 1 , CreatedAt: time.Now(), UpdatedAt: time.Now(), } サンプルコードを実行してみると, fmt.Println("Profile.Name is zero: ", user.Profile.Name == "") でtrueが出力されているのがわかります. では,このコードを目的に合うように変更していきます. ここで json を改変する方法は3つ考えられます. 1. 改変したいフィールドの型をany(interface{})にして,タグにomitemptyを指定する 通常,struct {}の型情報で宣言されているポインタ型でないフィールドには,何も代入しない場合ゼロ値がセットされます. 一方,タグのomitemptyは nil である場合にキーを無視して構造体を json に変換します. そのため,このサンプルコードのProfileフィールドは json への変換時,キーが無視されることなく,セットされたゼロ値を含む json レスポンスになってしまいます. omitemptyが機能しないサンプルコード: https://go.dev/play/p/xJG-nHmNLB6 この処理はProfileに nil を代入することで防げます. profileの値が nil になることで,omitemptyの条件に一致するようになり, json の項目にProfileが含まれなくなります. しかし,Profile型の変数宣言では nil を代入できません. エラーになるサンプルコード: https://go.dev/play/p/SRyjrSaOTmz そこで, nil を代入できるようにProfileの型をany(interface{})にします. 修正したサンプルコード: https://go.dev/play/p/m_b-fa3iyhj type user struct { ID int64 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Profile any `json:"profile_at,omitempty"` } {"id":1,"created_at":"2009-11-10T23:00:00Z","updated_at":"2009-11-10T23:00:00Z"} 出力結果からprofileの項目が消えていることが確認できます. しかし,この方法には欠点があります. any型にはなんでも代入できてしまうため,実装するときに変数の型について自分がなんの型を扱っているか気をつける必要があります. これでは静的型付き言語であるgoを使うメリットが薄れてしまいます. 2. encoding/ json /v2 のomitzeroを使う. Goの実験的な実装であるencoding/ json /v2を使う方法です. ドキュメント サンプルコード profileを無視する機能を実現するのは構造体部分のタグです. type user struct { ID int64 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Profile profile `json:"profile,omitzero"` } omitzeroをつけることによって,ゼロ値の場合 json に含まれなくなります. この実装が実験的でなくなったら使いたい方法ですね. 3. MarshalJSON()メソッドを実装する. json .Marshal()で json に変換する構造体に MarshalJSONメソッド を実装することで独自のタグに基づいて JSON を生成するロジックを記述できます. MarshalJSONはMarshaler interfaceに定義されているメソッドです. ついでに,なぜMarchalJSONを実装するのか encoding/jsonパッケージ を見ながら調査してみました. json .Marshalの記述は src/encoding/json/encode.go にあります. まずpackageのはじめの32行目あたりに, // If an encountered value implements [Marshaler] // and is not a nil pointer, Marshal calls [Marshaler.MarshalJSON] // to produce JSON . とあり, nil でなく,MarshalJSONを実装しているとMarshalJSONが呼び出されることが記述されています. func Marshal(v any) ([] byte , error ) { e := newEncodeState() defer encodeStatePool.Put(e) err := e.marshal(v, encOpts{escapeHTML: true }) if err != nil { return nil , err } buf := append ([] byte ( nil ), e.Bytes()...) return buf, nil } marshalを実行するメソッドを持つ構造体を返すのは,newEncodeStateです. func newEncodeState() *encodeState { if v := encodeStatePool.Get(); v != nil { e := v.(*encodeState) e.Reset() if len (e.ptrSeen) > 0 { panic ( "ptrEncoder.encode should have emptied ptrSeen via defers" ) } e.ptrLevel = 0 return e } return &encodeState{ptrSeen: make ( map [any] struct {})} } encodeStateのreflectValueを見てみましょう. func (e *encodeState) reflectValue(v reflect.Value , opts encOpts) { valueEncoder(v)(e, v, opts) } typeEncoderを呼び出した戻り値を返しています. func valueEncoder(v reflect.Value ) encoderFunc { if !v.IsValid() { return invalidValueEncoder } return typeEncoder(v.Type()) } typeEncoderについてみてみましょう. typeEncoderは長いので省略します. 省略 // Compute the real encoder and replace the indirect func with it. f = newTypeEncoder(t, true ) wg.Done() encoderCache.Store(t, f) return f } typeEncoderは最終的に newTypeEncoder の戻り値を返しています. 次はnewTypeEncoderについてみてみましょう.こちらも長いので省略します. 中身をみるとわかりますが,marshalerという単語が変数名やコメントに散見されます. 本丸に近づいていそうです. marshalerEncoderという名前が複数あります. matshalerEncoderを返すためのif文があります. これはポインタであるかどうか,marshalerTypeのinterfaceを実装しているかどうかなどをチェックしているようです. 前述したコード冒頭のMarshalJSONが機能する条件と合致しそうです. addr, textなどの接頭辞がついてるmarshalerEncoderもありますが,シンプルな marshalerEncoder を見てみましょう. func marshalerEncoder(e *encodeState, v reflect.Value , opts encOpts) { if v.Kind() == reflect.Pointer && v.IsNil() { e.WriteString( "null" ) return } m, ok := v.Interface().(Marshaler) if !ok { e.WriteString( "null" ) return } b, err := m.MarshalJSON() if err == nil { e.Grow( len (b)) out := e.AvailableBuffer() out, err = appendCompact(out, b, opts.escapeHTML) e.Buffer.Write(out) } if err != nil { e. error (&MarshalerError{v.Type(), err, "MarshalJSON" }) } } メソッドの途中で, b, err := m.MarshalJSON() が呼ばれていることが確認できました. ここで,構造体に実装されたメソッドが実行され,独自のMarshalJSONが実行されます. なぜ,MarshalJSONを実装すると JSON のGoの構造体の間の マッピング が実行できるのか理解できました! 本題に戻り,MarshalJSONを実装してゼロ値のprofileを無視してみましょう. サンプルコード: https://go.dev/play/p/UkVe0N0iU2G 実装したメソッドは次のようになっています. func (u user) MarshalJSON() ([] byte , error ) { fmt.Println( "marchal" ) if u.Profile.Name == "" && u.Profile.Age == "" && u.Profile.Email == "" { return json.Marshal(& struct { ID int64 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` }{ ID: u.ID, CreatedAt: u.CreatedAt, UpdatedAt: u.UpdatedAt, }) } return json.Marshal(u) } 処理は単純で,ゼロ値だったらprofileを含まない構造体を返すだけです. 非常にシンプルにかけます. また,ゼロ値以外にも応用が効くので覚えておきたい方法です. まとめ 最後まで見ていただきありがとうございました! 今回は, goの json パッケージについて調べました. 本筋から逸れましたが,パッケージの中身まで調べてみるとなぜinterfaceを満たすだけで任意の処理ができるのか学ぶことができました. 今後も気になった処理やパッケージがあったら調べてみたいと思います.
アバター
はじめに こんにちは!新卒1年目のTKDSです! 今回はkindで任意のポートをローカルマシンのポートに マッピング する方法を紹介します. 実際にkindでclusterを作成して動作確認をしながら進めます. はじめに kindとは default 設定でのCluster構築 Cluster作成 deploymentとNodePortの作成 kindの設定ファイルの作成 設定したポートにアクセスする まとめ kindとは コンテナを使用して, kubernetes クラスタ ーを構築できるものです. githubのAbout には About Kubernetes IN Docker - local clusters for testing Kubernetes と書いてあるので,kindは Kubernetes IN Docker の略のようです. 先に結論から言うと,clusterの作成時に設定ファイルでポートを指定します. では,clusterの作成・ Kubernetes リソースの作成・動作確認・kindの設定し直しの順で行っていきます. default 設定でのCluster構築 まずはdefault設定でClusterを構築し, curl で通信してみます. Cluster作成 $ kind create cluster defaultの設定で,kindという名前のclusterが作成されます. 作成されたclusterを確認してみましょう. $ kind get clusters kind deploymentとNodePortの作成 このdeploymentの内容は kubernetesのドキュメント にあるものを使用しています. deploymentの作成 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 NodePortの作成 apiVersion: v1 kind: Service metadata: name: nginx-service spec: type: NodePort selector: app: nginx ports: - protocol: TCP port: 80 nodePort: 30007 リソースを作成します. deploymentとpodの状態を確認します. deploymentとdeploymentで指定していたpodがreplicasの数で起動しているのが確認できました. 次にNodePortの確認をします. NodePortが作成されていることが確認できます. マッピング されているポートの確認 NodePortでportを設定したので,アクセスしてみます. cluster-ipとそのポートを指定・ localhost とNodePortの指定,どちらを行ってもnginxにはアクセスできません. これは,nodePortで指定しているportがローカルマシンのportに マッピング されていないためです. 冒頭でも述べたようにkindで作るclusterはコンテナなので,コンテナのポートをローカルマシンに マッピング しなければなりません. まずは,稼働してるclusterについて見てみます. docker psでコンテナを確認することができます. PORTS の項で,ローカルのポートにコンテナのポートが マッピング されているのが確認できます. ここでは,NodePortで指定してる30007番のPortが マッピング されていないのがわかります. そのため,ローカルマシンからコンテナ内のnodePortにアクセスができなくなっています. では, yaml に書いたPortでアクセスできるようにしていきます. kindの設定ファイルの作成 kindはcluster作成時に設定を書いた yaml ファイルを指定することができます. まずは設定ファイルを作成しましょう. 設定ファイルからcluster作成 kindのフラグについて調べます $ kind create cluster --help 出力 Creates a local Kubernetes cluster using Docker container 'nodes' Usage: kind create cluster [flags] Flags: --config string path to a kind config file -h, --help help for cluster --image string node docker image to use for booting the cluster --kubeconfig string sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config -n, --name string cluster name, overrides KIND_CLUSTER_NAME, config (default kind) --retain retain nodes for debugging when cluster creation fails --wait duration wait for control plane node to be ready (default 0s) Global Flags: --loglevel string DEPRECATED: see -v instead -q, --quiet silence all stderr output -v, --verbosity int32 info log verbosity, higher value produces more output --config string path to a kind config file と書いてあります. まず設定ファイルを作成します. kindの設定ファイルについては,ドキュメントの Configuration に記述があります. 設定ファイルでkind上のコンテナのポート番号を containerPort に書き,hostPortにローカルマシンのポートを指定します. 設定ファイルは下記の通りです. kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraPortMappings: - containerPort: 30007 hostPort: 30007 listenAddress: 127.0.0.1 - role: worker では,設定ファイルを使って,clusterを作成しましょう. $ kind create cluster --config my-cluster.yaml docker psでマウントされているポートを見る $ docker ps ローカルマシンのポートにコンテナのポートが マッピング されているのが確認できました. 設定したポートにアクセスする もう一度,deploymentとserviceの作成をしておいてください. では,アクセスしてみましょう. 無事レスポンスを確認することができました. まとめ 今回は,kindでのport設定を紹介し,実際にレスポンスを確認するところまで行いました. ここまで見ていただきありがとうございました!
アバター
こんにちは、配配メール開発エンジニアのhiro_jiです。 突然ですが、負荷テストの進め方ってイメージできますか? ある程度経験があれば難なく進めることができると思いますが、そうでない場合はそもそも進め方のイメージが湧きづらいかと思います。 かくいう私も最初は何から手を付ければよいか分からなかった記憶があります。。。 そこで今回は負荷テスト初心者の方向けに、私の所属するチームで実施している手順を紹介します! 負荷テストとは? 負荷テストのフロー 全体像 方針検討 詳細計画 テスト準備 テスト実施 評価 分析・チューニング おわりに 負荷テストとは? 本題に入る前に、負荷テストとは何かについて軽く触れておきます。 負荷テストとは、特定の条件下でシステムやアプリが示す性能を評価、検証するものであり、運用中の障害を未然に防いだり、パフォーマンスの問題や ボトルネック を改善するために非常に重要な役割を持ちます。 負荷テストには、性能要件を満たしているか検証するための「ロードテスト」や、限界値を特定するための「ストレステスト」など、目的や観点に応じて様々な手法があり、計測方法や分析する指標がそれぞれが異なります。 (テスト手法の名称に関してはサービスや会社によってまちまちなようですが、本記事ではそれらの総称を「負荷テスト」として扱います) 本記事ではそれぞれの手法については触れませんが、「スムーズにテストを進める方法」という観点で参考になれば幸いです。 負荷テストのフロー 全体像 負荷テストフロー 私たちのチームではいくつかの工程に分けてテストを進めていて、 工程ごとにレビューを挟むことで余計な手戻り作業を削減しています。 方針検討 このフェーズでは以下のようなことを決めます。 テストを行う背景や目的 性能要件を満たしているかを確認したい?それともシステム限界値まで特定したい? どのような指標で評価するか 応答時間 エラー率 スループット サーバリソースの使用量 評価対象とする機能や処理の範囲 など ここの方針がずれていると、 無駄な作業や大幅なやり直し につながってしまいます。 例えば、限界値を特定したかったのに性能要件の範囲までの負荷しかかけれていなかったり、ある SQL の性能だけを計測したいのに機能全体も性能を計測したり... 効率的に進めるためにも、最低限上記のような内容は早い段階で明確にしておきましょう。 詳細計画 ここでは方針検討で決めた内容を元により詳細な内容を決めます。具体的には 、 使用する環境やサーバ構成 負荷の量 同時ユーザー数 リク エス ト数 レコード数やファイルサイズ エビデンス として残す情報 処理時間だけでよいのか?サーバリソースの状況も取得するのか? 操作手順(負荷をかけるシナリオ) 使用ツール 評価基準 などなど 決めることは多いですが、ここが曖昧だと実は想定する負荷をかけれていなかったなんてことになりかねないので、可能な限り詳細に記載することが大切です。 随分過去の話ですが、DBに用意するレコードに関してレコード数自体は想定通りだけど、その内訳に関して計画担当とデータ準備担当間で認識齟齬が生じていしまい、テストのやりなおしが発生したことがありました。 特にデータ量などを記載する際は、 数値だけでなくその数値とした根拠や内訳も明確に記載 し、チーム内の認識を合わせましょう。 テスト準備 詳細計画で決めた内容に沿って必要な環境、データや スクリプト などを準備します。 テスト用の環境 テストシナリオ DBに登録するレコードやリク エス トなどのデータ 負荷の計測用 スクリプト やツール 詳細計画に沿ってテスト準備をするだけといえばそれまでですが、個人的にはここが一番時間がかかるフェーズだと思います。 対象機能の理解や大量データの作成方法など、ある程度の前提知識やコツが求められることが多いです。詰まったら迷わずチーム内の 有識者 に聞いちゃいましょう。 また、次回以降の テストの効率と再現性向上のため に、データ作成や計測用に作成した スクリプト はどこかに残しておくことをおススメします。 テスト実施 準備ができたらいよいよテスト実施です。 テストの内容にもよりますが、大量データを扱う場合などは色々と注意が必要になります。 私たちのチームではトラブルや手戻りを未然に防ぐために、以下のような内容をチェックリスト化し、テスト実施前に毎回確認しています。 使用する環境が正しいか 共用の環境を利用する場合、事前に利用することを周知できているか リソース監視対象のサーバで、余計な処理が動いていないか 計測データにノイズが入らないか 外部サービスへ大量リク エス トを送るようなことはないか 送る必要がある場合は、許可を得ているか テスト手順が適切か 負荷テストはその性質上、他のテストに比べて様々なリスクがあります。 いきなり本番テストを行うのではなく、 まずはミニマムで実施する ことでテストの妥当性を確認しましょう。 評価 計測した結果が、詳細計画で決めた基準を満たしているかどうかを明確にします。 単純にOK、NGだけ記載するのではなく、その評価に至った根拠や補足情報を記載し、 結果の妥当性を示すこと が大切です。例えばサーバリソース(LAやメモリ)に関して、瞬間的な値のみで判断するのと、連続的な値も含めて判断するのとでは、大きく意味合いが異なると思います。 分析・チューニング 評価、分析の結果、基準を満たさない場合はアプリの改修もしくはインフラ側の拡張を検討する必要があります。 いきなりアプリ改修やインフラ側の拡張に走ってしまうと、全く改善効果が得られず無駄な対応に時間をかけてしまったなんてことになりかねません。 まずは ボトルネック の特定から進めることがチューニングの一番の近道です。 おわりに 本記事では私のチームで実践している負荷テスト手順についてご紹介させていただきまいた。 負荷テストと聞くと身構えてしまう方もいる多いかもしれませんが、 段階的にチーム内で認識を合わせて進める ことができていれば、なんてことないと思います。 今回は進めかたという点に絞ってご紹介しましたが、次回は具体的に使用している負荷計測ツールや分析方法についても紹介できればと思います!
アバター
概要 Prometheus未経験の非インフラエンジニアが、Grafanaでサー バモ ニタリングができるようになるまでの 軌跡を記録します。 とりあえず、モニタリングができることが目標なので運用面の考慮等はしていませんが、参考になれば幸 いです。 概要 きっかけ Prometheusとは Grafanaとは 今回構築する環境の構成 ①prometheus server ②exporter ③Grafana インストール 前提条件 ②exporterのインストール (1)ソースをダウンロード (2)ダウンロードしたソースを解凍する (3)node_exporterを起動する (4)node_exporterの起動確認 ①prometheus serverのインストール (1)ソースをダウンロード (2)ダウンロードしたソースを解凍する (3)Prometheusの設定ファイルを編集する (4)prometheus serverを起動する ③Grafanaのインストール (1)Grafanaのインストール (2)grafana-serverの起動と起動確認をする (3)Grafanaにブラウザでアクセスする (4)Data Sourceの設定 (5)dashboardの設定 やってよかったこと まとめ きっかけ 検証環境のとあるサーバが突然死しました。 なぜ死んだのかは分かりませんでした。 というのも、OSと必要最低限の ミドルウェア をインストールしただけのサーバだったのです。 これは良くないなと思い、サーバをモニタリングしたいなと思いました。 sysstatとか使えば良いのかもしれませんが、せっかくなのでPrometheusを使ってみることにしました。 Prometheusとは システムおよびサービスのモニタリングシステムです。 設定されたターゲットから定期的にデータを収集して 蓄積 可視化(Prometheusでの可視化は、今回は触れません) アラートの通知(今回は触れません) を行うことが可能です。 Grafanaとは Grafanaは、様々なデータソースから取得したデータを可視化するツールです。 Prometheus自体でも可視化は可能ですが、 アドホック な立ち位置で利用されるものだそうです。 可視化には、Grafanaを使うよう公式サイトに記載されています。 今回構築する環境の構成 ①prometheus server いわゆるPrometheusと呼ばれるものです(だと思っています)。 ②exporter 監視エージェントです(だと思っています)。 取得したい情報に応じて、exporterをインストールします。 主なexporterは、 Prometheusのマニュアル に記載されています。 ①prometheus server  は、  ②exporter  に対して定期的に情報を取得しに行き、データを蓄積します。 ③Grafana ③Grafana  は、  ①prometheus server  からデータを取得し、可視化を行います。 インストール 順番は前後しますが、 ②exporter ①prometheus server ③Grafana の順番でインストールします。 前提条件 OS: AlmaLinux 8.8 ②exporterのインストール 今回は、サーバの状況が知りたいので、 node_exporter をインストールします。 (1)ソースをダウンロード Prometheusのダウンロードページ のリンクから  node_exporter-1.7.0. linux - amd64 .tar.gz  をダウンロードしました。 wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz (2)ダウンロードしたソースを解凍する tar xzvf node_exporter-1.7.0.linux-amd64.tar.gz (3)node_exporterを起動する cd node_exporter-1.7.0.linux-amd64 ./node_exporter (4)node_exporterの起動確認 node_exporterが起動していることを確認します。 node_exporterのポート番号 9100 exporterのエンドポイント /metrics コマンド curl -s http://localhost:9100/metrics 実行結果 # HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 3.0043e-05 go_gc_duration_seconds{quantile="0.25"} 4.12e-05 go_gc_duration_seconds{quantile="0.5"} 4.6299e-05 ・ ・ ・ ①prometheus serverのインストール (1)ソースをダウンロード Prometheusのダウンロードページ のリンクから  prometheus-2.45.2. linux - amd64 .tar.gz  をダウンロードしました。 # wget https://github.com/prometheus/prometheus/releases/download/v2.45.2/prometheus-2.45.2.linux-amd64.tar.gz (2)ダウンロードしたソースを解凍する tar xzvf prometheus-2.45.2.linux-amd64.tar.gz (3)Prometheusの設定ファイルを編集する cd prometheus-2.45.2.linux-amd64 vi prometheus.yml prometheus.yml  の末尾に  node_exporter  の情報を追記します。 - job_name : "node_exporter" static_configs : - targets : [ "localhost:9100" ] (4)prometheus serverを起動する ./prometheus --config.file=prometheus.yml ③Grafanaのインストール Grafanaは、 cloud on-premises Enterprise OSS エディションがありますが、今回はon-premisesの OSS をインストールします。 (1)Grafanaのインストール GrafanaのOSSダウンロードページ を開き Red Hat, CentOS, RHEL, and Fedora  のコマンドを実行します。 今回は以下のコマンドを実行しました。 sudo dnf install -y https://dl.grafana.com/oss/release/grafana-10.3.1-1.x86_64.rpm (2)grafana-serverの起動と起動確認をする sudo systemctl start grafana-server sudo systemctl status grafana-server (3)Grafanaにブラウザでアクセスする http://<IPアドレス>:3000  にアクセスします。 Email or username admin Password admin でログインできます。 ※初回ログイン時にパスワードの変更を求められます。  Skipも可能です。 (4)Data Sourceの設定 画面左上のトグルメニューから Home Connections Data sources を選択します。 +Add new data source  をクリックして  Prometheus  を選択します。 Name: 任意の名前 HTTP Prometheus server URL:  http://<IPアドレス>:9090 を入力し、  Save & Test  をクリックします。 (5) dashboard の設定 Grafanaの dashboard の設定ですが、 テンプレート が公開されています。 今回は、 Node Exporter Full を使わせていただきました。 画面右上の +  をクリックして  Import dashboard  を選択します。 先程エクスポートしたテンプレートをアップロードします。 Options Name: 任意の名前 Unique identifier(UID): 任意の値 Prometheus: 先程追加した  data source  を選択 を設定し、 Import  をクリックします。 表示される ダッシュ ボードの一部ですが、テンプレートを取り込むだけで簡単にモニタリングが出来ています。 キャプチャには入っていませんが、 ディスク使用量 ディスクIO ロードアベレージ 等もグラフ化されています。 やってよかったこと サーバのモニタリングができるようになった 結構簡単にできた インストールが簡単 ダッシュ ボードの設定もテンプレートを利用するだけ 拡張性が高いことがわかった exporterを追加することで、別のモニタリングも可能 対象サーバの追加も容易にできそう ディスク枯渇を事前に検知できた 日々ディスク使用量が増加していることが分かった 作業ファイルの削除漏れを見つけ、定期的に削除するようにした 実行するJobの数を調整した メモリ使用量を確認し適切な実行数となるよう調整した まとめ 環境構築からモニタリングを行えるところまでを紹介しました。 今回は、1サーバで1項目だけのモニタリングを紹介しましたが、対象サーバやモニタリング項目の追加も 容易に行えますので、気になる方はぜひ試して頂ければと思います。
アバター
はじめに こんにちは。フロントエンド開発課に所属している新卒1年目のm_you_sanと申します。 今回はissue formsを使って GitHub Issuesのテンプレートを作成する方法について、紹介したいと思います。 はじめに issue formsとは?導入するメリットは? 作り方 まとめ issue formsとは?導入するメリットは? issue formsは、 GitHub 上で提供される GitHub Issuesに入力フォームを追加する機能です。 GitHub Issuesでは、デフォルトで大きめのテキストエリアが1つ提供されていますが、そのまま運用すると以下のような問題が起こります。 具体的に何を書いて良いのかわからない 人によって記載粒度にばらつきがある 欲しい情報が記載されていない場合がある 上記の問題は MarkDown 形式のテンプレートでも、ある程度改善することはできますが、記入者がテンプレートを無視して自由に記載できてしまうなどの欠点があります。 一方でissue formsでテンプレートを作ることで、小項目に分けることができるので、記入者がどこに何を書けば良いのかが明確になります。 また、 MarkDown 形式のテンプレートと異なり、記入者がテンプレートを無視することができないため、記入者による書き方のばらつきが起こりにくくなります。 加えて、条件付きではありますが、issue formsでは必須項目の指定ができるので、欲しい情報を記入者に入力させることが可能です。 作り方 今回は例として、バグ報告用と改善・リファクタ用のテンプレートを作成します。 まずプロジェクト直下に .github/ISSUE_TEMPLATE/ を作成し、その下に yaml ファイルを置きます。 //ディレクトリ構成 . ├── .github │ └── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── enhancement_request.yml │ ├── config.yml : 次に bug_report.yml を編集します。 # bug_report.yml name : バグ報告 description : 不具合報告はこちら title : 'Bug: ' body : - type : textarea id : overview attributes : label : 概要 description : | 不具合の概要を記載してください。 見た目に関係する場合は、スクリーンショット等を貼り付けてください。 validations : required : true - type : input id : environment attributes : label : 再現環境 description : | 再現環境を記載してください。 例: Chrome + Windows Edge + Windows Chrome + macOS Safari + macOS validations : required : true - type : textarea id : process attributes : label : 再現手順 description : 不具合を再現させる手順を記載してください。 value : | 1. 2. 3. ... validations : required : true - type : textarea id : supplement attributes : label : 補足 キーの詳細は以下の通りです。 キー 説明 必須 name テンプレートの名前。一意である必要があります。 〇 description テンプレートの説明。名前の下に表示されます。 〇 title タイトルに事前に入力されているテキスト。 × body フォーム全体の内容。 〇 type フォームの要素。 checkboxes、dropdown、input、 markdown 、textareaの中から指定できます。 〇 id フォーム要素の識別子。フォーム定義内では一意である必要があります。 × attributes フォーム要素のプロパティを定義するキーと値のペア。 〇 label フォームのラベル。 〇 description フォームの説明。ラベルの下に表示されます。 × value 事前にフォームに入力されているテキスト。 × validations フォームのバリデーションを定義するキーと値のペア。 × required trueにすると入力必須になります。 public リポジトリ の場合のみ有効になります。 × 同じように enhancement_request.yml も編集します。 # enhancement_request.yml name : 改善・リファクタ description : 改善・リファクタはこちら body : - type : textarea id : background attributes : label : 背景 description : どのような課題を抱えてissueを立てたのか、経緯や前提を記載してください。 validations : required : true - type : textarea id : merit attributes : label : メリット description : 改善することでどのようなメリットがあるのかを記載してください。 validations : required : true - type : textarea id : do attributes : label : やること description : このissueで取り組むことを記載してください。 validations : required : true - type : textarea id : supplement attributes : label : 補足 最後に config.yml を編集します。 この config.yml は、新しくissueを作成する際に表示されるテンプレート選択画面をカスタマイズするためのファイルです。 今回は blank_issues_enabled をfalseにすることで、issue formsで作成したテンプレートのみを選択できるようにしています。 # config.yml blank_issues_enabled : false push後、画面を確認して以下のようになっていれば完成です。 テンプレート選択画面 バグ報告用テンプレート 改善・リファクタ用テンプレート まとめ 今回は、issue formsで GitHub issuesのテンプレートを作成する方法について紹介させていただきました。 私が所属しているチームでは、実際にissue formsを運用しているのですが、デフォルトのテンプレートを使っていたときに比べて、issueの管理がしやすくなったので、気になった方は是非試してみてください。 参考資料 docs.github.com docs.github.com
アバター
ラク スでは多くの SaaS プロダクトを開発・運用しており、オンプレミスまたは クラウド を適切に選択してインフラ基盤を構築しています。 そのインフラを担うのが、 ラク スのインフラ開発部です。 今回はインフラ開発部のマネージャーが厳選した、インフラエンジニアにおすすめの書籍10選をご紹介します。 それぞれの書籍に推薦コメントを記載していますので、是非ご参考になさってください。 選定基準は以下の通りで、今後インフラを深く理解し実力をつけていきたい方にも最適です。是非ご覧ください。 「すぐに役に立つがすぐに廃れる知識ではなく、10年以上使える書籍」 「分かりやすい本ではなく、難解ではあるがきちんと原理・原則を学べる書籍」 目次 目次 Operating Systemを理解しよう 詳解 Linuxカーネル 第3版 DNS & BIND 第5版 トラブルシューティングを理解しよう 詳解 システム・パフォーマンス 第2版 パケットキャプチャ入門 LANアナライザWireshark活用術 第4版 分散システムを理解しよう 分散システム 第二版 Google File System セキュリティを理解しよう AWSではじめるクラウドセキュリティ: クラウドで学ぶセキュリティ設計/実装 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践 思考力を高めよう 失敗の科学 失敗から学習する組織、学習できない組織 なぜあの人の解決策はいつもうまくいくのか? おわりに Operating Systemを理解しよう 詳解 Linux カーネル 第3版 Daniel P. Bovet (著), Marco Cesati (著), 高橋 浩和 (監修, 読み手), 杉田 由美子 (翻訳), 清水 正明 (翻訳), 高杉 昌督 (翻訳), 平松 雅巳 (翻訳), 安井 隆宏 (翻訳) インフラ技術の基本知識である Linux Kernelの本質を学び取れる一冊。Kernelはどんな機能を持ち、管理、処理しているのか。OSコア機能であるKernelを知ることはインフラエンジニアとしての基本的素養といえます。 DNS & BIND 第5版 Cricket Liu (著), Paul Albitz (著), 小柏 伸夫 (翻訳) DNS に関係する機能全般の詳細解説書です。OS内部でも外部でも名前解決技術は深く広く利用されている。名前解決技術を抑えることはOSのみならずシステム全般の理解を深めることとイコール。 トラブルシューティング を理解しよう 詳解 システム・パフォーマンス 第2版 Brendan Gregg (著), 西脇 靖紘 (監修), 長尾 高弘 (翻訳) インフラだけではなく、SREでも求められるシステムレスポンスについての知識、調査、分析方法を網羅的に記載している良書です。 システムがスローダウンし原因不明の際には必ず目を通したい一冊。 パケットキャプチャ入門 LANアナライザ Wireshark 活用術 第4版 ネットワークパケット解析ツール Wireshark の利用法が記載されています。ネットワークありきのシステム運用においてトラブル解決にパケット取得、分析スキルは必須です。 tcpdump だけではなく wireshark を用いることで迅速かつより明快に分析が可能になります。 分散システムを理解しよう 分散システム 第二版 アンドリュー・S・タネンバウム (著), マールティ ン・ファン・ス ティー ン (著), Andrew S. Tanenbaum (著), Maarten van Steen (著), 水野 忠則 (翻訳), 佐藤 文明 (翻訳), 鈴木 健二 (翻訳), 竹中 友哉 (翻訳), 西山 智 (翻訳), 峰野 博史 (翻訳), 宮西 洋太郎 (翻訳) PublicCloudでは分散システムの利用が容易に可能になっていますが、これをオンプレで実現するためには分散システムの全体像、構成する機能群を学びとる必要があります。 自前のPrivateCloud構築、運用に対応できる知識をこの書籍で身につけることができます。 Google File System Sanjay Ghemawat, Howard Gobioff, and Shun-Tak Leung(著) 分散システムを体現したシステムでもあり、分散システムのコアシステムである Google File Systemの理解を深めることで分散システムを実践的に学び取ることが出来ます。難解に見えますが、 アメリ カの大学にて分散システムを扱う際の教材にもなっています。 https://web.mit.edu/6.033/www/papers/gfs-sosp2003.pdf セキュリティを理解しよう AWS ではじめる クラウド セキュリティ: クラウド で学ぶセキュリティ設計/実装 松本 照吾 (著), 桐谷 彰一 (著), 畠中 亮 (著), 前田 駿介 (著) AWS を題材にセキュリティの基本を学べる書籍で、「 クラウド における特徴ポイント」を学び取ることができます。 AWS はrootアカウントの設定に不備があると、乗っ取りや不正利用の被害にあい、数百万の利用料金を請求されるケースも多発しています。基礎 からし っかり学んでおく必要があります。 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性 が生まれる原理と対策の実践 徳丸 浩 (著) インフラ基盤だけではなく、アプリケーションの設計、実装不備によっても外部侵入は起こりえます。インフラエンジニアだからアプリ側の知識は不要というわけではなく、少なくとも設計時に指摘ができる程度の知識を身につけておくことは必要です。   思考力を高めよう 失敗の科学 失敗から学習する組織、学習できない組織 マシュー・サイド (著), 有枝 春 (翻訳) エンジニアリングにおいて設計、テスト、運用、あらゆる場面に失敗は紛れ込みます。その失敗にどう対処すべきか、他業種事例からも学び取れる一冊。 なぜ失敗するのか、なぜ失敗から学ぶ必要があるのかを具体例を挙げて記載している良書です。 なぜあの人の解決策はいつもうまくいくのか? 枝廣 淳子 (著), 小田 理一郎 (著) システム思考+問題解決を扱った書籍です。 ロジカルシンキング は一つの事象を一時点でスナップショットし、分析、比較します。しかし、実際のシステムはいくつもの事象の集合体であり、時間軸によっても変化していきます。システム思考を身につけることで一つの事象だけではなく、全体、フローの概念を加えてさらに高い次元で問題解決できる思考力を身につけることができます。 おわりに 今回は、原理原則をしっかり学ぶことができ、10年先にも活かせる知識が身につく書籍を中心に紹介しました。 ラク スでの実務体系や利用技術に沿って「Operating System」、「 トラブルシューティング 」、「分散システム」、「セキュリティ」の知識、そして「思考力」を高めるラインナップとなっていますので、通して読んでいただくことでインフラエンジニアとしての実力をバランスよく高められるのではないかと思います。 是非手にとっていただき、都度立ち返ってみていただければ幸いです。
アバター
はじめに こんにちは、Hiropyです。 今回は、 Java のComparatorについて簡単に解説できればと思います。 はじめに Comparatorとは? compareメソッドの使用方法 使用例 Comparableとの違い 主なメソッド comparing reversed naturalOrder・reverseOrder nullsFirst・nullsLast thenComparing まとめ Comparatorとは? Comparator は、「比較者」という和訳の通り オブジェクト同士の比較 を行うインタフェースで、主にList等の ソート (並べかえ)に使われます。 型パラメータTを持つ 関数型インタフェース で、抽象メソッドの compare (T o1, T o2)を実装することで大小比較ができるようになります。 後述する通り便利なメソッドが複数あり、毎回compareを実装する必要がないこともポイントです。 ※この記事には関数型インタフェースや ラムダ式 が特に説明なく登場します。よくわからない方はまずそちらから調べてみてください。 compareメソッドの使用方法 compare (T o1, T o2)は、 o1が小さい(ソートしたとき先に並ぶ)ときは返り値が負に、o1が大きい(後に並ぶ)ときは正に、等しいときは0 になるように実装します。 例えば、String型を文字数で比較(短いほうを「小さい」と判定)したい場合、実装は以下のようになります。 // s1のほうが短いときはs1.length() < s2.length()なので負の値を返す、s1のほうが長いときはその逆で正を返す Comparator<String> stringComparator = (String s1, String s2) -> s1.length() - s2.length(); より複雑な実装も可能です。 以下のComparator は、heightが小さいほうを「小さい」と判定し、heightが同じ場合は、weightが大きい方を「小さい」と判定する一風変わったものです。 class Person { public int height; public int weight; } Comparator<Person> personComparator = (p1, p2) -> { if (p1.height != p2.height) { return p1.height - p2.height; } return p2.weight - p1.weight; }; 使用例 配列やリストには、Comparatorを用いたソートを行うメソッドが用意されています。 以下に代表的なものを挙げますが、基本的にソート後の並びは 昇順 (小さい順)です。 (無論ソートメソッドの実装次第で並び順が降順やその他の順になっているものも存在し得ます) 以下の例では、先程のstringComparatorを用いてList を文字数の昇順で並べ替えています。 Comparator<String> stringComparator = (String s1, String s2) -> s1.length() - s2.length(); List<String> wordList = new ArrayList<>(Arrays.asList( "Today" , "is" , "a" , "good" , "day" )); System.out.println(wordList); // >[Today, is, a, good, day] // stream().sortedは元のリストを変更せずに新しいストリームを返す List<String> sortedList = wordList.stream().sorted(stringComparator).toList(); System.out.println(sortedList); // >[a, is, day, good, Today] // List.sortは元のリストを変更する wordList.sort(stringComparator); System.out.println(wordList); // >[a, is, day, good, Today] List<String> wordList2 = new ArrayList<>(Arrays.asList( "This" , "is" , "a" , "pen" )); // Collections.sortも元のリストを変更する Collections.sort(wordList2, stringComparator); System.out.println(wordList2); // >[a, is, pen, This] もちろん、1回しか使わないときはいちいちComparator変数を定義しなくてもソートする関数の引数内に記述すればOKです。 List<String> wordList = new ArrayList<>(Arrays.asList( "Today" , "is" , "a" , "good" , "day" )); List<String> sortedList = wordList.stream().sorted((String s1, String s2) -> s1.length() - s2.length()).toList(); // >[a, is, day, good, Today] Comparableとの違い Comparatorと似た名前の比較用インタフェースとして、 Comparable が存在します。 Comparableも型パラメータTを持つ関数型インタフェースで、実装時には compareTo (T o)を実装する必要があります。 このcompareToはクラス内蔵のComparatorのようなもので、 インスタンス の比較がしたいクラスであらかじめ比較方法を実装しておくものです。 Comparableを実装したオブジェクトをソートするときにメソッドを引数なしや引数nullで呼び出すと、メソッドにもよりますがcompareToを使用した昇順ソートがなされることが多いです。 なお、compareToの比較による順序付けのことを 自然順序付け といいます。 class Person implements Comparable<Person>{ public int age; public Person( int age) { this .age = age; } @Override // thisが小さいときは負の数を、thisが大きいときは正の数を、同じときは0を返すよう実装 public int compareTo(Person p) { return this .age - p.age; } } List<Person> people = new ArrayList<>(Arrays.asList( new Person( 10 ), new Person( 30 ), new Person( 20 ))); // stream.sortedでは引数なしで呼び出す List<Person> sortedPeople = people.stream().sorted().toList(); // List.sortでは引数nullで呼び出す people.sort( null ); // いずれも[10, 20, 30] Comparableは クラス内で一度実装すれば毎回ソートの際の比較方法を書かなくていい というメリットがあります。 一方でComparatorはソート時に毎回実装するので、 同じクラスでもその場に合わせた色々な基準でソートできる ソートする場所に比較方法を書くのでパッと見て何でソートするのかわかりやすい といった点がメリットです。 両方使用して、「Comparableを実装しておいて普段は自然順序付け、特別な並べかえ方法を使いたいときはComparator」といった運用方法も可能です。 ちなみに、 JDK に含まれているメジャーなクラスにも、Comparableを実装しているクラスは以下のように複数存在します。 IntegerやDouble, BigDecimal など数値系は、数値の昇順に並ぶ Stringは、辞書順の昇順に並ぶ LocalDateTimeなどの日付時刻系は、日付時刻の昇順に並ぶ 主なメソッド 前述の通りComparatorにはcompare以外にもstatic・defaultメソッドがいくつか用意されています。 これらを使用することで、より短く、見やすいコードで比較・ソートが行えます。 comparing Comparator. comparing (Function keyExtractor)では、引数に「Comparatorを実装したオブジェクトを返すメソッド」を実装したFunctionを入れることで、そのメソッドの返り値を自然順序付けで比較するComparatorが返されます。 オブジェクトの比較は、数値型(Integer, Doubleなど)の変数やlengthなど何らかの 数値 を使って行うことが多いかと思いますが、 この際compareを実装してComparatorを作る場合は同じメソッドの呼び出しを2回書くことになるほか、「どっちからどっちを引くと昇順だっけ?」と迷うことも少なくありません。 comparingを使用するとメソッドを1回書くだけで済むので、コーディングミスを減らすことが可能です。 Comparator<String> stringComparator = Comparator.comparing(s -> s.length()); // Comparator<String> stringComparator = (String s1, String s2) -> s1.length() - s2.length(); と同じ // メソッド参照を使用するとより簡潔に書ける Comparator<String> stringComparator2 =Comparator.comparing(String::length); 数値型に限らず、例えばStringを返すメソッドを入れた場合、辞書順で比較するComparatorになります。 class Person { public String name; public Person(String name) { this .name = name; } public String getName() { return name; } } List<Person> people = new ArrayList<>(Arrays.asList( new Person( "Hinata" ), new Person( "Emily" ), new Person( "Tsubasa" ))); List<Person> sortedList = people.stream().sorted(Comparator.comparing(Person::getName)).toList(); // > [Emily, Hinata, Tsubasa] また、引数を2つ取るcomparing(Function keyExtractor, Comparator keyComparator)もあり、この場合は自然順序付けではなく第2引数のComparatorで比較します。 reversed このメソッドを呼び出したComparatorの 逆順 で判定を行うComparatorを返します。 降順 でソートしたい場合や既存のComparatorの逆順でソートしたい場合、このメソッドを使うと便利です。 List<String> wordList = new ArrayList<>(Arrays.asList( "Today" , "is" , "a" , "good" , "day" )); List<String> sortedList = wordList.stream().sorted(Comparator.comparing(String::length).reversed()).toList(); // >[Today, good, day, is, a] naturalOrder・reverseOrder Comparator. naturalOrder ()は、Comparableを実装したオブジェクトについて、自然順序付けで比較をするComparatorを返します。 Comparator. reverseOrder ()は、Comparableを実装したオブジェクトについて、自然順序付けの 逆順 で比較をするComparatorを返します。 List<Integer> numberList = new ArrayList<>(Arrays.asList( 4 , 2 , 3 , 5 , 1 )); List<Integer> naturalSortedList = numberList.stream().sorted(Comparator.naturalOrder()).toList(); // > [1, 2, 3, 4, 5] // Comparableの項で書いた通り、以下のように書いても同じ結果になる // List<Integer> naturalSortedList = numberList.stream().sorted().toList(); List<Integer> reverseSortedList = numberList.stream().sorted(Comparator.reverseOrder()).toList(); // > [5, 4, 3, 2, 1] nullsFirst・nullsLast Comparator. nullsFirst (Comparator comparator)は、nullをnull以外より小さいとみなし、両方がnull以外なら引数のcomparatorによる比較を行うComparatorを返します。 つまり、ソート時にnullが先頭に来ることになるのです。 Comparatorを実装する際に比較対象にnullが入ってくると NullPointerException が発生する場合がありますが、 List<String> wordList = new ArrayList<>(Arrays.asList( "Today" , "is" , "a" , "good" , "day" , null )); List<String> sortedList = wordList.stream().sorted(Comparator.comparing(String::length)).toList(); // > NullPointerException nullsFirstを使用することで例外発生を防ぐことができます。 List<String> wordList = new ArrayList<>(Arrays.asList( "Today" , "is" , "a" , "good" , "day" , null )); List<String> sortedList = wordList.stream().sorted(Comparator.nullsFirst(Comparator.comparing(String::length))).toList(); // > [null, a, is, day, good, Today] nullsLastはその逆で、nullをnull以外より大きいとみなす、つまりソート時にnullが末尾に来るようなComparatorを返します。 thenComparing 複数条件でソート するときに使用するメソッドです。 Comparatorの後ろにつけて引数で比較条件を指定することで、そのComparatorが0を返したときに追加の条件で比較することができます。 引数にはComparatorの他、comparingと同様FunctionやFunction+Comparatorを入れることが可能です。 以下の例では、Stringのリストを文字数の昇順で、文字数が同じものについてはStringの自然順序付け(辞書順)で並べ替えています。 List<String> wordList = new ArrayList<>(Arrays.asList( "cat" , "dog" , "apple" , "banana" , "elephant" , "ant" , "zebra" )); List<String> sortedList = wordList.stream().sorted(Comparator.comparing(String::length).thenComparing(Comparator.naturalOrder())).toList(); // > [ant, cat, dog, apple, zebra, banana, elephant] まとめ Comparatorは Java でソートを行う際に非常に重要になるインターフェースです。 Webアプリなどではそもそも SQL のORDER BYでソートしているから Java でソートをする場面は必ずしも多くないかとは思います。 とはいえ Java 側での処理の途中でソートを行う処理もあったりするので、使いかたを覚えておいて損はないでしょう。 本記事がComparator、ひいてはソートの理解に少しでも役立てれば幸いです。
アバター
新卒1年目のTKDSです! 今回は、AIコーディング支援サービスである、 Amazon CodeWhispererの導入方法について記事を書きました。 個人での利用は無償ですので、自宅などでは費用をかけずにAIコーディング支援サービスを利用できて便利でした。 Amazon CodeWhispererの情報 導入の手順 1. 最新の AWS Toolkit プラグインをVS Codeにインストール 2. 認証 3. AWS Builder IDの作成 試す まとめ Amazon CodeWhispererの情報 公式サイト https://aws.amazon.com/jp/codewhisperer/ 公式サイトのインストール情報 https://aws.amazon.com/jp/codewhisperer/resources/#Getting_started 導入の手順 今回は VS Code にCodeWhispererを導入する手順をご紹介します。 1. 最新の AWS Toolkit プラグイン を VS Code にインストール VS Code 用の AWS Toolkitのサイト https://aws.amazon.com/jp/codewhisperer/resources/#Getting_started 拡張機能 のストアを開き、インストールを選択します。 2. 認証 インストール完了すると次の画面が表示されるので、CodeWhispererを選択します。 クリックすると以下の内容が展開されます。 「 Sign up or Sign in」を選択します。 ブラウザで認証と AWS Builder IDの作成に移ります。 3. AWS Builder IDの作成 Emailを使ってBuilder IDを作成します。 登録すると、メール認証が行われ、完了すると再度 VS Code の画面に遷移します。 試す 少しだけコード補完を試してみました。 コード補完が実際に働いていることが確認できました。 まとめ 今回はCodeWhispererを導入してみました。 GitHub Copilotと比べるとやや使いにくさを感じましたが、無償と考えると十分なのではないでしょうか。 ぜひ記事をご覧になった方はお試ししてみてください。
アバター
はじめに こんにちは。フロントエンド開発課に所属している新卒1年目のm_you_sanと申します。 今回はTypeScriptのinferについて紹介したいと思います。 はじめに inferとは? 具体的な使用例 関数の戻り値の型を推論する Promiseの内部の型を推論する 配列の中身を推論する 文字列リテラルと組み合わせる まとめ inferとは? inferは 型推論 する際に使われるキーワードで、 ジェネリクス 型と条件型(Conditional Types)と合わせて使われます。 inferを使うことで、関数の戻り値や配列の中身など、 ジェネリクス 型の内容によって変化する型情報をConditional Typesの条件分岐の中で推論することができます。 具体的な使用例 関数の戻り値の型を推論する inferは組み込み型のReturnTypeの内部で実は使われています。 型変数の R が、 型推論 が行われる部分です。 下記のコードでは、 ジェネリクス 型の T が関数である場合、その戻り値の型を R として推論し、その R がReturnTypeの結果として得られます。 関数が ジェネリクス 型である場合、戻り値は文字列や数値などの様々なパターンがありますが、inferを使うことで柔軟に型情報を取得することができます。 type ReturnType < T > = T extends ( ...args: any [] ) => infer R ? R : any ; const helloFn = () => "Hello World" ; const addtion = ( num1: number , num2: number ) => num1 + num2 ; const isEvenNumber = ( num: number ) => num % 2 === 0 ; //string型になる type HelloFnReturnType = ReturnType <typeof helloFn >; //number型になる type AddtionReturnType = ReturnType <typeof addtion >; //boolean型になる type IsEvenNumberReturnType = ReturnType <typeof isEvenNumber >; Promiseの内部の型を推論する inferを使うことで、Promise型が内包している型を推論することができます。 下記のコードでは、 ジェネリクス 型の T がPromise型であった場合、Promiseの型引数の部分を R として推論して、その R がExtractPromiseの結果として得られます。 type ExtractPromise < T > = T extends Promise < infer R > ? R : never ; //string型になる type SampleStringType = ExtractPromise < Promise < string >>; //number型になる type SampleNumberType = ExtractPromise < Promise < number >>; 配列の中身を推論する inferを使うことで、配列の中身を推論することもできます。 下記のコードのFisrtは、 ジェネリクス 型の T が配列の型だった場合、その配列の先頭の要素を R として推論します。 Array1の場合、先頭の要素以外(2と3)は ...any[] となって、1のみが R として推論されるため、結果としてArray1FirstTypeは1となります。 type Fisrt < T extends any [] > = T extends [ infer R , ... any []] ? R : never ; type Array1 = [ 1 , 2 , 3 ] ; type Array2 = [ "1" , 2 , 3 ] ; //1になる type Array1FirstType = Fisrt < Array1 >; //"1"になる type Array2FirstType = Fisrt < Array2 >; 文字列 リテラル と組み合わせる inferは文字列 リテラル と組み合わせることができます。 Replaceは文字列 S に含まれる文字列 T を U に置き換える型です。 具体的な処理の流れとしては、まず初めにstring型の S に T が含まれているかをConditional Typesを用いてチェックしています。 このとき、 T の前後の文字列をそれぞれ A 、 B として推論しています。 最後に T を U に置き換えて、推論された A と B を文字列 リテラル で結合させて、結果として返します。 下記の例では、 A は「NO」、 B は「NO LIFE」になり、 T の「MONEY」が U の「CAT」に置き換わるため、結果として「NO CAT NO LIFE」が得られます。 type Replace < S extends string , T extends string , U extends string > = S extends ` ${ infer A }${ T }${ infer B } ` ? ` ${ A }${ U }${ B } ` : never ; //NO CAT NO LIFEになる type replaced = Replace < " NO MONEY NO LIFE" , "MONEY" , "CAT" >; まとめ 今回はtypescriptのinferについて紹介させていただきました。 個人的には、関数の戻り値やPromiseの型引数など、型情報が不確定なものに対しても、inferを使うことで柔軟に型情報を取得できるところが魅力的に感じました。 inferの使い方について、更に深堀したい方は type-challenges に取り組んでみるのが良いかと思います。
アバター
こんにちは。フロントエンド開発課所属の koki _matsuraです。 今回はPlaywrightの コンポーネント テストについて個人的な意見を書いています。 目次は以下の通りになっています。 はじめに 導入方法 Playwright Component Test Runner のスゴい点 コンポーネントが実際にレンダリングされる 画面のサイズを指定できる タイムゾーンや言語を指定できる コンポーネントの振る舞いを見るテストに対応できる まとめ 参考 はじめに Playwrightは Microsoft が開発・メンテナンスしているCypress、Puppeteerなどと同じE2E自動テスト フレームワーク として有名です。 playwright.dev Chromium 、Edge、 Firefox などの複数のブラウザに対応しており、全てに単一の API で簡単にテストの実装が可能になっています。 目玉機能としては、コード生成が挙げられるのではないでしょうか。 コード生成とはユーザが画面を実際に触り、それがコードとして自動で反映されていく機能で手っ取り早くテストを開始するには最適な機能です。 他にも素晴らしい機能があるのですが、その中でも現在、実験的に試されているComponent Test Runnerがとても興味をそそられました。 そこで導入方法とPlaywright Component Test Runnerの個人的に凄いと思った点を紹介していきます。 導入方法 プロジェクトのトップで下記のコマンドを実行してください。通常のPlaywright導入コマンドにオプションでctをつけたような形になっています。 npm init playwright@latest -- --ct どの フレームワーク に向けて使うのかを聞かれるので、該当するものを選択してください。今回はReact18を選択します。 次はブラウザをインストールするかを聞かれますが、デフォルト(true)にします。 これで導入が完了しました。 プロジェクトの ディレクト リには3つのファイルが追加されていると思います。以下がそれぞれの簡単な役割となっています。 playwright / index.html テストの対象となる コンポーネント を レンダリング するためのhtmlファイル playwright / index. tsx index.htmlに対して、 スタイルシート の反映、テーマの適用などをするためのファイル playwright-ct.config.ts コンポーネント テストの設定を行うファイル テスト対象 ディレクト リを設定したり、 タイムアウト の設定、テストをするブラウザの設定が可能 テストファイルを作成すれば、 npm run test-ct でテストが実行できます。 Playwright Component Test Runner のスゴい点 コンポーネント が実際に レンダリング される 昨今の コンポーネント テストでよく使われているテストランナーはVitest・Jestではないでしょうか。そして、テストランナーとともに使われるJSDomなどはNode.js環境でDOMをエミュレートするためのライブラリであり実際のブラウザに レンダリング されているわけではありません。 ブラウザと完全に同じ レンダリング になるかわからないという問題があります。 しかし、Playwrightは index.html を通して、対象の コンポーネント が実際のブラウザに レンダリング されます。 つまり、ユーザの環境に限りなく近い状態でテストが可能となります。 また、副作用的なメリットとして今まではNode.js上でDOMをエミュレートしていたため、testing-libraryやuser-eventが必要になってましたが、それも要らなくなります。 Playwright Component Test Runnerのみで基本的なテストはできます。 以下は チェックボックス のテストを例に比較したものです。 内容自体は同じですが、簡潔に書けていることがわかります。 // @playwright/experimental-ct-reactを使わない例 import { AddressForm } from "../../Checkout/AddressForm" ; import { render } from "@testing-library/react" ; import { screen } from "@testing-library/dom" ; import { it , expect } from "vitest" ; import userEvent from "@testing-library/user-event" ; import "@testing-library/jest-dom/vitest" ; it ( "チェックボックスをクリックするとcheck状態が切り替わる" , async () => { render (< AddressForm / >); const user = userEvent.setup (); const checkbox = screen.getByRole ( "checkbox" ) as HTMLInputElement ; await user.click ( checkbox ); expect ( checkbox ) .toBeChecked (); } ); // @playwright/experimental-ct-reactを使う例 import { test , expect } from "@playwright/experimental-ct-react" ; import { AddressForm } from "../../Checkout/AddressForm" ; test ( "チェックボックスをクリックするとcheck状態が切り替わる" , async ( { mount , } ) => { const component = await mount (< AddressForm / >); const checkbox = component.getByRole ( "checkbox" ); await checkbox.click (); expect ( checkbox ) .toBeChecked (); } ); 画面のサイズを指定できる コンポーネント には画面のサイズによって見た目が変わるレスポンシブなものもよくあります。 このような コンポーネント をviewportを変えてテストするようなことはあまりないと思います。 そもそも、そのようなテストをVitest・Jestでするにはwindow.innerWidthやwindow.innerHeight、そしてresizeイベントなどで実装することになり非常に困難です。 だからと言って、E2Eテストで行うには適さないため、手動で確かめることが必要とされることが多いのでないでしょうか。 Playwright Component Testはそれを解決します。 方法は以下のようにviewportを設定するだけです。 テストファイルに書けば、個々に設定でき、 playwright-ct.config.ts に書けばテスト全体に設定可能です。 import { test } from '@playwright/experimental-ct-react' ; test.use ( { viewport: { width: 1280 , height: 720 } } ); 例えば、以下のような一定の幅になるとサイドバーが ハンバーガ ーメニューになる コンポーネント があります。 この コンポーネント に対し、次のようなテストで対応します。 test.use ( { viewport: { width: 599 , height: 720 } } ); test ( 'widthが599pxの時にハンバーガーメニューになっていること' , async ( { mount } ) => { const sideMenu = await mount (< SideMenu / >); const hamburgerButton = sideMenu.getByRole ( 'button' ); await expect ( hamburgerButton ) .toBeVisible (); } ) テストを実行するとwidth: 599px、height: 720pxで レンダリング されます。 サイドバーになることを確認する場合もviewportのサイズを変えてテストするだけ大丈夫です。 また、画面サイズではなく、 iOS などの様々なデ バイス を指定したい場合も playwright-ct.config.ts から変更できます。 タイムゾーン や言語を指定できる 時間によって見た目が変わったり、ブラウザの言語設定によってテキストが変わるような コンポーネント があると思います。 以下はブラウザの言語設定によってテキストが変わるサイドバーです。 これをVitest・Jestで見た目部分のテストをするとなると非常に困難で、手動で確認したり、VRT(Visual Regresion Test)で対応することになります。 Playwrightはこれにも対応しており、先ほど同様に タイムゾーン や言語を下記のようにテストファイルに書くことで設定できます。 test.use ( { locale: "en-GB" , timezoneId: "Europe/Paris" , } ); これでブラウザの読み込み時の言語や タイムゾーン が変わるのですが、例えば、多言語対応のためにi18nextなどのライブラリを使っている場合は上手くいきません。Providerがないため、Playwrightのブラウザでは以下のような レンダリング になります。 これへの対応方法は @playwright/experimental-ct-react をインストールした際に自動で作成される playwright/index.tsx にProviderを書きます。 import { beforeMount } from '@playwright/experimental-ct-react/hooks' ; import { I18nextProvider } from 'react-i18next' ; import i18n from '../src/i18n/configs' ; beforeMount (( { App } ) => { return ( < I18nextProvider i18n = { i18n } > < App / > < /I18nextProvider > ); } ); そして、 コンポーネント テストファイル側では特に気にせず、いつも通りのテストを書くだけです。 test.use ( { viewport: { width: 600 , height: 720 } , locale: 'ja' } ); test ( 'lang="ja"の時は日本語で表示されていること' , async ( { mount } ) => { const sideMenu = await mount (< SideMenu / >); const listRoot = sideMenu.locator ( '.MuiList-root' ); const inbox = await listRoot.locator ( 'li' ) .nth ( 0 ) .innerText (); const favorite = await listRoot.locator ( 'li' ) .nth ( 1 ) .innerText (); const trash = await listRoot.locator ( 'li' ) .nth ( 2 ) .innerText (); const sent = await listRoot.locator ( 'li' ) .nth ( 3 ) .innerText (); expect ( inbox ) .toBe ( '受信トレイ' ); expect ( favorite ) .toBe ( 'お気に入り' ); expect ( trash ) .toBe ( 'ゴミ箱' ); expect ( sent ) .toBe ( '送信済み' ); } ); Playwrightのブラウザでも問題なく日本語で レンダリング されていることが確認できます。 コンポーネント の振る舞いを見るテストに対応できる PlaywrightのようなE2Eテスト フレームワーク では当然なのですが、実際のブラウザに レンダリング するという特徴が非常に強力で、例で紹介したようなVitest・Jestでは対応が困難であった 画面サイズや タイムゾーン に依存する コンポーネント のテスト や今までは仕方なく 手動で確認していたテスト 、 E2Eのような 単体テスト コードの改善 などの コンポーネント の振る舞いを見るテストに最適です。 しかし、 コンポーネント 内部のロジック面のテストにおいては今までのようにVitest・Jestで対応するのが良いと思います。 まとめ 今回はPlaywright Component Test Runnerについて個人的に凄いと思った点について軽く紹介させていただきました。 既存のPlaywrightにmount関数を組み合わせたシンプルな形でありながら、今まではPage単位だったテストをComponent単位にすることを可能にしてくれました。 Vitest・Jestと少し比較するような書き方をしてしまいましたが、どちらかのみではなく両方 を上手く使い分けることでより コンポーネント の品質を高く保てるのではないかと考えています。 また、Playwright Component Test Runnerが登場したことにより、本来Vitest・Jestがどのようなテストに取り組むべきなのかがわかってきたような気もします。 まだ実験的機能ですが、正式に公開された際には コンポーネント テストに積極的に取り込んでいきます! 最後まで読んでいただきありがとうございます。 参考 playwright.dev
アバター