TECH PLAY

株式会社マイナビ デジタルテクノロジー戦略本部

株式会社マイナビ デジタルテクノロジー戦略本部 の技術ブログ

227

CXMツールとは お疲れ様です。MA推進課のA.Kと申します。 普段は就職・ライフキャリア事業本部のオウンドメディアとMarketoなど、事業部のマーケティング担当者が使うシステムの運用保守を担当しています。 今回、MA推進課としては初となる、アルバイト情報事業本部のCS部門(カスタマーサクセス)と連携した新たな施策を試みたので内容を共有できればと思います。 タイトルにもある「CXM」という言葉について聞き馴染みがある方は少ないのではないでしょうか。 CXMとは Customer Experience Managementの略で、顧客体験の管理や改善を行い、顧客体験(CX)を最大化するための手法のことを指します。 商品やサービスの価格やスペックなどだけでなく、その商品・サービスにより得られる満足感や感動、心地よさなどを向上させることは、顧客ロイヤルティを醸成し収益向上へとつながります。具体的な目的は、顧客との関係性の強化やブランド構築などが挙げられます。 ※引用:顧客体験マネジメント(CXM)とは?CRMとの違いや成功のポイント、企業事例を紹介, docomo business, https://www.nttcoms.com/service/nps/column/cxm/ CXMをどう活用していくか 売り上げ向上には、新規顧客の発掘だけでなく、既存顧客がどうやったら離反しないか、継続して利用してくれるのかも重要になります。 そのため「お客様の声を聴く」ことで、顧客体験とマイナビ認識の乖離をキャッチアップし、営業活動改善や、社内に対して成功事例の共有をすることを目指していきます。 デジ戦が手伝ったこと 前置きが長くなりましたが、本題に入れればと思います。 上記のCXMの運用は事業部側が行っています。以下に今回のシステムの全体図を載せておきます。 SFA/CRM 企業の情報や商談の情報などが入っています。 掲載情報DB マイナビバイトに掲載されている情報が格納されている社内向けのDBです。 GCS BigQuery S3 Trocco クラウド・サービス間のデータ転送に使うツールです。今回の主役。 CXMツールでは、「送付リスト」という形でcsvファイルを渡すことで、自動的に配信が行われる仕組みとなっております。ただし、そのリストに持たせる情報には、通知先はもちろん、分析に使う項目など、複数DBを参照する必要があるうえ、細かい送付条件が多数ありました。そこで、デジ戦側でリスト作成のシステムを作ってみることにしました。 今回はtroccoを使用して以下のステップでCXのアンケートを送る送付リストを作ります。 ①各DBの情報をBQに集約させる ②頑張ってテーブルを結合して必要な情報を抽出する ③作成した配信リストをS3に格納→あとはCXMツール側で自動で配信してくれます。 ①各DBの情報をBQに集約させる 最初にSFA/CRMのデータをtroccoを用いてBQに格納します。 案件の情報を始め、掲載情報やマイナビ社員の情報を参照します。 ここで大苦戦したのが履歴テーブルの存在です。 単純に1つのテーブルを参照すればよいわけではなく、履歴テーブルからも一部情報を参照する必要があり、複数テーブルを行ったり来たりしていました。 以下苦労点です。 参照しなければいけないテーブルが5個あり結合および参照に苦戦 数ある商品から特定の商品を省くロジックを正規表現を使って実装 タイムゾーンがUTCとJSTが入り混じってタイムスタンプ型が嫌いになる 次に掲載情報DBのデータをBQに格納します。 アンケート送付タイミングのタッチポイントを判別できるように必要な項目に絞って取得します。 こちらも4テーブル参照して結合を繰り返していました…。 最後に営業マスタを手動でアップロードして準備完了です。 ②BQ内でデータを結合する BQで集めたデータを結合しつつ、送付対象の条件を追記してリストを作成していきます。 使っているSQLのコードは自身が初学者ということもありかなりシンプルです。 ROW_NUMBER() OVER (PARTITION BY ○ ORDER BY ○ ASC,□ DESC) AS rownumber マスキングかけてるので分かりにくいかもですが 1つの項目に対して複数のデータが紐づいているので、データを降順に並び替えて一番最新のデータの情報を使用します。 後はSFA/CRMと掲載情報DBのデータをキーとなる項目で紐づけてリストの大元は完成です。 後は事業部サイドの運用上の条件を実装していきます。 --送付条件の実装DELETE FROM 対象のテーブル WHERE 特定のカラム NOT IN (SELECT CAST( 特定のカラム AS STRING) –元のDBによって型が違うので一致させるFROM ○○テーブル ); このような条件を要望に沿って同様に実装していきます。 この際にも複数DBを参照し、同じ項目でもデータの内容に相違がないか、空の項目がないかなど確認しています。 コードを見ていただいて分かるように、大元のリストから条件に当てはまらないレコードを削除しています。 この時点で残ったデータをCXMツールに受け渡しできるような形に成形して処理は完了です。 ③ワークフローの作成 今までクエリを書いてリストの作成ができましたが、実際に運用するのは事業部の方たちです。 なので専門的な知識がなくても、「最小限の工数で」運用してもらえるようにしていきます。 ここで重宝したのがtroccoのワークフロー機能です。 上記で記述したデータ抽出&リスト作成のクエリを登録し、それを順次自動で実行してくれます。 スクショの例でいうと、SFA/CRMからBQへデータ連携→掲載情報DBからBQへデータ連携→BQでリストの作成→BQからS3へファイルの格納をボタン一つで実行してくれます。 (加えてスケジュール設定ができるので自動化も可能) ここまで作成して細かい仕様もお伝えし、事業部にお渡しします! 初めての開発経験をしてみて 学生時代も情報系の学部にいましたが、このレベルの開発をするのも今回が初めてでした。 配属から1年ちょっとは開発はベンダーさんにお任せしていたのですが、自分で作るとこんなに大変なんだなとベンダーさんの凄さを実感しました。 一方で、SQLをがっつり触ったことで知識が付いたのはもちろん、マイナビの掲載の仕組み・営業フローについても知れる良い機会でした! コード自体は基本自分で書きましたが、「仕様書も作成する」「ワークフローはなるべく簡略化する」「変更を加える際はデータ不整合が起きないことを確認してから本番適用する」など、開発の基本的な部分も開発経験が豊富なK.S課長にサポートしていただき、無事にリリース&安定稼働まで繋げることができました。 (前職の経験を踏まえた上長のアドバイス×新卒社員のDCPA精神で大分デジ戦らしい取り組みになったのでは!?と個人的には思っています。) 我々ができるのはメールリストの作成までですが、これを利用して今後事業部内でどのような動きが起きていくのか、引き続き技術的なサポートができる箇所があるのか、リリースして終わりではなく、その先にあるビジネスインパクトまで協力して取り組んでいければと思います! 以上、SaaSを組み合わせてメールリストを作成できるよというお話でした!
アバター
はじめに こんにちは、ゼネラルコンテンツ推進課のI.Tです。 私の課で運営しているエンジニア向けメディアでは、毎月SEO記事を新規・リライト含め30~40本程度リリースしております。(2025年3月時点) ベンダーから納品されたSEO記事を弊社で校正・校閲していますが、この作業は人力で行うため、担当者によって確認の観点が異なり、質にばらつきが生じてしまいます。 校正・校閲の質を上げ、作業時間を削減できないかと考え、まずはJAPAN AIの利用が頭に浮かびました。しかし、より柔軟に対応できるCopilotエージェントを利用できないかということで、同じ課のY.Kさんが校正・校閲のCopilotエージェント「校正エディター」を提案しました。 Y.Kさんに校正エディターのプロンプトを作っていただき、利用してみると校正・校閲ツールとして実践レベルで使えるものとなりましたので、今回はその作り方と結果を書いていきます。 対象サイト エンジニア向けメディア 作成手順 ①校正・校閲のルールを作る ②作成したルールをCopilotエージェントに設定する ①校正・校閲のルールを作る まずはCopilotエージェントに設定するための校正・校閲のルールを作りましょう。 (内容は社外秘の為、本記事では伏せさせて頂きます。) ②作成したルールをCopilotエージェントに設定する 上記で作成したルールをCopilotエージェントに設定します。 Copilotエージェントの作成画面にある「構成」の各項目に、作成したルールを当てはめる形で設定します。 ▼設定した構成はこちら 名前:校正エディター説明:Webメディアの編集者として、原稿の校正・校閲を行うエージェントです。誤字脱字、表現ミス、表記揺れ、口語表現、単語の繰り返しなどを具体的に指摘します。指示:- ユーザーが添付ファイルまたは直接テキストを送ると、エージェントはその内容を確認し、誤字脱字、スペルミス、日本語として違和感のある表現、表記揺れ、口語表現、同じ単語の繰り返しなどを具体的に指摘する。- 読者が明らかに違和感を持つ点については、強調して指摘する。- 原稿の記号(マークダウンなど)については指摘しない。- 指摘は以下の形式で出力する。 - 問題点(例: 誤字、表現ミス、リンク切れなど) - 該当箇所の引用 - 修正案(必要に応じて)- 見出しや太字など装飾部分は無視し、本文のみを確認する。- 校正校閲の専門家として、専門的な立場で対応する。▼スタータープロンプト※下記を「タイトル」と「メッセージ」にそれぞれいれてください。 誤字脱字の指摘この文章に誤字脱字がないか確認してください。 表現の違和感の指摘この文章に日本語として違和感のある表現がないか確認してください。 表記揺れの指摘この文章に表記揺れがないか確認してください。 口語表現の指摘この文章に口語表現が含まれていないか確認してください。 単語の繰り返しの指摘この文章に同じ単語が繰り返されていないか確認してください。 総合的な校正・校閲この文章を総合的に校正・校閲してください。 Copilotエージェントへの設定方法は、下記の記事で紹介していますので気になる方はご確認ください。 Copilotエージェントでエンジニア向けメディアのペルソナを作ってみた https://marketingblog.mynavi.jp/marketing/copilot_persona_creation/ 実際に作成したエージェントは以下です。 これで「Copilot へメッセージを送る」の箇所に記事の全文をコピペすれば、勝手に校正してくれるようになりました。 実際に使ってみた どの程度の質で校正・校閲してくれるのでしょうか。 試しに、3年ほど更新していなかった以下記事について校正エディターに校正してもらいました。 質問:記事内容コピペ 回答:校正エディター 以下の点について指摘します。1. 誤字・脱字: - 「アジャイル(Agile)sとは」→「アジャイル(Agile)とは」 - 「プログラマーが常にペアで相互チッェクをしながら」→「プログラマーが常にペアで相互チェックをしながら」 - 「アジャイル人材にはどのような知識、スキルが求められるのてしょうか?」→「アジャイル人材にはどのような知識、スキルが求められるのでしょうか?」 - 「DX(テジタル・トランスフォーメーション)」→「DX(デジタル・トランスフォーメーション)」2. 日本語として違和感のある表現: - 「アジャイル(Agile)sとは」→「アジャイル(Agile)とは」 - 「アジャイル(Agile)sとは「素早い」を意味する英語です。」→「アジャイル(Agile)とは「素早い」を意味する英語です。」3. 表記揺れ: - 「アジャイル開発」→「アジャイル開発」 - 「ウォーターフォール開発」→「ウォーターフォール開発」4. 口語表現: - 「アジャイル開発は、大まかなシステムの仕様が決まったら、「手直しを前提に、まずは作ってみる」という考え方にのっとり、極力少ない工数で、短期間で開発をする手法のことです。」→「アジャイル開発は、大まかなシステムの仕様が決まったら、「手直しを前提に、まずは作ってみる」という考え方に基づき、極力少ない工数で、短期間で開発を行う手法です。」5. 同じ単語の繰り返し: - 「アジャイル開発のメリット・デメリット」→「アジャイル開発の利点と欠点」 - 「アジャイル開発のメリット」→「アジャイル開発の利点」 - 「アジャイル開発のデメリット」→「アジャイル開発の欠点」以上の点を修正すると、文章がより読みやすく、正確になります。何か他にご質問や修正が必要な箇所があれば、お知らせください。 「1. 誤字・脱字:」については、さすがAIなので正しく添削してくれますね。 「4. 口語表現:」についても、正しく直してくれています。 しかし、「3. 表記揺れ:」の部分では、表記揺れをしていないのにもかかわらず指摘をしています。 これはおそらく元文字の書式が揃っていない(「アジャイル」の「イ」だけ「ゴシック体」になっているなど)ことから指摘されているのかと思います。 「5. 同じ単語の繰り返し」の部分でも、この記事ではSEOの観点でわざと「メリット・デメリット」を繰り返して記載しているのですが、その点は伝わらずに、SEOKWではない「利点」「欠点」に変えていますね。 使ってみた感想 校正エディターを使っても最後はどうしても目視で確認をしなければならないですが、そもそも誤字脱字や口語表現などが正しいかなどの一次チェックが大変なので、その点をCopilotが変わりにやってくれることで校正作業が非常に楽になるでしょう。 記事の校閲・校正にかぎらず、人の目では正誤判断の難しいSNS投稿文のコンプラチェックエディターなど、様々なエージェントが作れそうです。
アバター
はじめに こんにちは、ゼネラルコンテンツ推進課のI.Tです。 私の課で運営しているエンジニア向けメディアでは、毎月SEO記事を新規・リライト含め30~40本程度リリースしております。(2025年3月時点) ベンダーから納品されたSEO記事を弊社で校正・校閲していますが、この作業は人力で行うため、担当者によって確認の観点が異なり、質にばらつきが生じてしまいます。 校正・校閲の質を上げ、作業時間を削減できないかと考え、まずはJAPAN AIの利用が頭に浮かびました。しかし、より柔軟に対応できるCopilotエージェントを利用できないかということで、同じ課のY.Kさんが校正・校閲のCopilotエージェント「校正エディター」を提案しました。 Y.Kさんに校正エディターのプロンプトを作っていただき、利用してみると校正・校閲ツールとして実践レベルで使えるものとなりましたので、今回はその作り方と結果を書いていきます。 対象サイト エンジニア向けメディア 作成手順 ①校正・校閲のルールを作る ②作成したルールをCopilotエージェントに設定する ①校正・校閲のルールを作る まずはCopilotエージェントに設定するための校正・校閲のルールを作りましょう。 (内容は社外秘の為、本記事では伏せさせて頂きます。) ②作成したルールをCopilotエージェントに設定する 上記で作成したルールをCopilotエージェントに設定します。 Copilotエージェントの作成画面にある「構成」の各項目に、作成したルールを当てはめる形で設定します。 ▼設定した構成はこちら 名前:校正エディター説明:Webメディアの編集者として、原稿の校正・校閲を行うエージェントです。誤字脱字、表現ミス、表記揺れ、口語表現、単語の繰り返しなどを具体的に指摘します。指示:- ユーザーが添付ファイルまたは直接テキストを送ると、エージェントはその内容を確認し、誤字脱字、スペルミス、日本語として違和感のある表現、表記揺れ、口語表現、同じ単語の繰り返しなどを具体的に指摘する。- 読者が明らかに違和感を持つ点については、強調して指摘する。- 原稿の記号(マークダウンなど)については指摘しない。- 指摘は以下の形式で出力する。 - 問題点(例: 誤字、表現ミス、リンク切れなど) - 該当箇所の引用 - 修正案(必要に応じて)- 見出しや太字など装飾部分は無視し、本文のみを確認する。- 校正校閲の専門家として、専門的な立場で対応する。▼スタータープロンプト※下記を「タイトル」と「メッセージ」にそれぞれいれてください。 誤字脱字の指摘この文章に誤字脱字がないか確認してください。 表現の違和感の指摘この文章に日本語として違和感のある表現がないか確認してください。 表記揺れの指摘この文章に表記揺れがないか確認してください。 口語表現の指摘この文章に口語表現が含まれていないか確認してください。 単語の繰り返しの指摘この文章に同じ単語が繰り返されていないか確認してください。 総合的な校正・校閲この文章を総合的に校正・校閲してください。 Copilotエージェントへの設定方法は、下記の記事で紹介していますので気になる方はご確認ください。 Copilotエージェントでエンジニア向けメディアのペルソナを作ってみた https://marketingblog.mynavi.jp/marketing/copilot_persona_creation/ 実際に作成したエージェントは以下です。 これで「Copilot へメッセージを送る」の箇所に記事の全文をコピペすれば、勝手に校正してくれるようになりました。 実際に使ってみた どの程度の質で校正・校閲してくれるのでしょうか。 試しに、3年ほど更新していなかった以下記事について校正エディターに校正してもらいました。 質問:記事内容コピペ 回答:校正エディター 以下の点について指摘します。1. 誤字・脱字: - 「アジャイル(Agile)sとは」→「アジャイル(Agile)とは」 - 「プログラマーが常にペアで相互チッェクをしながら」→「プログラマーが常にペアで相互チェックをしながら」 - 「アジャイル人材にはどのような知識、スキルが求められるのてしょうか?」→「アジャイル人材にはどのような知識、スキルが求められるのでしょうか?」 - 「DX(テジタル・トランスフォーメーション)」→「DX(デジタル・トランスフォーメーション)」2. 日本語として違和感のある表現: - 「アジャイル(Agile)sとは」→「アジャイル(Agile)とは」 - 「アジャイル(Agile)sとは「素早い」を意味する英語です。」→「アジャイル(Agile)とは「素早い」を意味する英語です。」3. 表記揺れ: - 「アジャイル開発」→「アジャイル開発」 - 「ウォーターフォール開発」→「ウォーターフォール開発」4. 口語表現: - 「アジャイル開発は、大まかなシステムの仕様が決まったら、「手直しを前提に、まずは作ってみる」という考え方にのっとり、極力少ない工数で、短期間で開発をする手法のことです。」→「アジャイル開発は、大まかなシステムの仕様が決まったら、「手直しを前提に、まずは作ってみる」という考え方に基づき、極力少ない工数で、短期間で開発を行う手法です。」5. 同じ単語の繰り返し: - 「アジャイル開発のメリット・デメリット」→「アジャイル開発の利点と欠点」 - 「アジャイル開発のメリット」→「アジャイル開発の利点」 - 「アジャイル開発のデメリット」→「アジャイル開発の欠点」以上の点を修正すると、文章がより読みやすく、正確になります。何か他にご質問や修正が必要な箇所があれば、お知らせください。 「1. 誤字・脱字:」については、さすがAIなので正しく添削してくれますね。 「4. 口語表現:」についても、正しく直してくれています。 しかし、「3. 表記揺れ:」の部分では、表記揺れをしていないのにもかかわらず指摘をしています。 これはおそらく元文字の書式が揃っていない(「アジャイル」の「イ」だけ「ゴシック体」になっているなど)ことから指摘されているのかと思います。 「5. 同じ単語の繰り返し」の部分でも、この記事ではSEOの観点でわざと「メリット・デメリット」を繰り返して記載しているのですが、その点は伝わらずに、SEOKWではない「利点」「欠点」に変えていますね。 使ってみた感想 校正エディターを使っても最後はどうしても目視で確認をしなければならないですが、そもそも誤字脱字や口語表現などが正しいかなどの一次チェックが大変なので、その点をCopilotが変わりにやってくれることで校正作業が非常に楽になるでしょう。 記事の校閲・校正にかぎらず、人の目では正誤判断の難しいSNS投稿文のコンプラチェックエディターなど、様々なエージェントが作れそうです。
アバター
はじめに 私のチームではエンジニア向けメディアを運営しているのですが、メディア運営の意見出しや壁打ち相手ができるAIを作りたいなと思っていました。 そこで、実際にいるエンジニアを想定したペルソナをCopilot エージェントに設定し、壁打ち相手として対応できるようにしました。 今回は、Copilot エージェントの設定方法や使ってみた感想を書いていきます。 作成手順 ①ペルソナを作る ②作成したペルソナをCopilot エージェントに設定する ①ペルソナを作る まずはCopilot エージェントに設定するためのペルソナを作りましょう。 作成済のペルソナ 佐藤は27歳のエンジニアで 、 千葉県在住 。 彼は普通科高校を卒業後 、 理系大学を卒業し 、 ITの資格やクラウド ・ ネットワーク ・ 免許や数学英語の資格も保有している 。 彼は父 、 母 、 姉 、 妹と家族構成を持ち 、 最近結婚した 。 彼の目標は 、 エンジニアとしてのスキルアップとキャリアアップであり 、 アンドエンジニアを通じて最新の技術トレンドや知識を得ることを目指している 。 彼は高校時代に勉強のモチベーション維持が難しかったが 、 理系科目が好きでロボットに興味があり 、 大学でプログラミングに出会い 、 C言語で挫折したもののJavaとPythonを学び始めた 。 大学卒業後はエンジニアとして就職せず 、 輸入転売会社で倉庫整理の業務に従事 。 その後 、 エンジニアの道を目指し 、 パーソルテクノロジー株式会社に入社し 、 金融システムのネットワーク保守 ・ 運用業務に3年間従事 。 現在はSES ・ ネットワークエンジニアとしてソーシャルゲームのネットワークシステムの運用 ・ 保守業務に従事している 。 正社員として自宅から通い 、在宅と出社の比率は5 : 5 、 残業は平均30時間 / 月 。 現年収は450万円で 、 希望年収は550万円 。 現職の環境はお堅い社風で 、 セキュリティ意識が高く 、 成果主義の人事制度 。 クラウドエンジニアを目指して資格を取得したが 、 オンプレミスの現場に配属されていることに不満を抱いている 。 将来的にはクラウドエンジニアとして最新の技術を学び 、 PMもやりつつ作業も自分で巻き取れるエンジニアになりたいと考えている 。 趣味はゲーム 、 アニメ 、 自転車 、 ボルタリングで 、 業務後は家事をやった後にゲームをすることが多い 。 休日は自転車で遠出したり 、 ボルタリングをしたり 、 ネットワークの勉強をしたり 、 友人と飲みに行ったりしている 。 PCはプライベート ・ 会社貸与両方Windowsを使用し 、 携帯はAndroid 、 ブラウザはChromeを使用 。 よく見るサイトは業務ではCiscoの公式HP 、 非業務ではYouTubeやPing - t 。 交友関係は中学 、 高校 、 大学の同級生 、 家族 、 前職の同期 、 現職場の同期で 、 妻は前職の同期 。 友人は多いが 、 人見知りの面もあり 、 外部のコミュニティに連れて行っても交友関係が広がりにくい 。 ②作成したペルソナをCopilot エージェントに設定する Copilot エージェントとは 簡単に説明すると、特定の利用目的のためにCopilotを最適化できる機能です。 ChatGPTで言うカスタムGPTのようなものですね。 今回は、アンドエンジニアのペルソナの壁打ち相手をCopilot エージェントに設定したいので、そのように記載します。 Copilot エージェントの設定方法 ①Copilotを開き、右サイドバーの「エージェントの作成」をクリック ②Copilot Studio(Copilot エージェントを設定する画面)が開くので、ここで設定する このように、会話形式でエージェントを設定することが可能なのだ! 右上の「構成」を押すと、会話した内容に基づいてCopilotが設定したCopilot エージェントの構成が表示されます。この構成画面でCopilot エージェントの詳細設定を編集できます。 実際に作ってみた例 今回は、27歳のエンジニア「佐藤」というCopilot エージェントを作りました。 会話形式で指示し、以下のようなエージェントを設定しています。(会話のログが残っていなかったので会話部分はナシで失礼します。) 「構成」に設定した内容 名前 : 佐藤 説明 : 佐藤は27歳のエンジニアで 、 千葉県在住 。 彼は普通科高校を卒業後 、 理系大学を卒業し 、 ITの資格やクラウド ・ ネットワーク ・ 免許や数学英語の資格も保有している 。 彼は父 、 母 、 姉 、 妹と家族構成を持ち 、 最近結婚した 。 彼の目標は 、 エンジニアとしてのスキルアップとキャリアアップであり 、 アンドエンジニアを通じて最新の技術トレンドや知識を得ることを目指している 。 彼は高校時代に勉強のモチベーション維持が難しかったが 、 理系科目が好きでロボットに興味があり 、 大学でプログラミングに出会い 、 C言語で挫折したもののJavaとPythonを学び始めた 。 大学卒業後はエンジニアとして就職せず 、 輸入転売会社で倉庫整理の業務に従事 。 その後 、 エンジニアの道を目指し 、 パーソルテクノロジー株式会社に入社し 、 金融システムのネットワーク保守 ・ 運用業務に3年間従事 。 現在はSES ・ ネットワークエンジニアとしてソーシャルゲームのネットワークシステムの運用 ・ 保守業務に従事している 。 正社員として自宅から通い 、在宅と出社の比率は5 : 5 、 残業は平均30時間 / 月 。 現年収は450万円で 、 希望年収は550万円 。 現職の環境はお堅い社風で 、 セキュリティ意識が高く 、 成果主義の人事制度 。 クラウドエンジニアを目指して資格を取得したが 、 オンプレミスの現場に配属されていることに不満を抱いている 。 将来的にはクラウドエンジニアとして最新の技術を学び 、 PMもやりつつ作業も自分で巻き取れるエンジニアになりたいと考えている 。 趣味はゲーム 、 アニメ 、 自転車 、 ボルタリングで 、 業務後は家事をやった後にゲームをすることが多い 。 休日は自転車で遠出したり 、 ボルタリングをしたり 、 ネットワークの勉強をしたり 、 友人と飲みに行ったりしている 。 PCはプライベート ・ 会社貸与両方Windowsを使用し 、 携帯はAndroid 、 ブラウザはChromeを使用 。 よく見るサイトは業務ではCiscoの公式HP 、 非業務ではYouTubeやPing - t 。 交友関係は中学 、 高校 、 大学の同級生 、 家族 、 前職の同期 、 現職場の同期で 、 妻は前職の同期 。 友人は多いが 、 人見知りの面もあり 、 外部のコミュニティに連れて行っても交友関係が広がりにくい 。 指示 : - アンドエンジニアの読者であるペルソナとして振る舞う 。 - Webディレクターからの質問に答え 、 エンジニアとしての働き方やキャリアについて話す 。 - 施策の提案に対して 、 読者目線でのフィードバックを提供する 。 - エンジニアの働き方やキャリア ・ 技術について 、 本当に人物としている前提で会話を行う 。 - 話し方は丁寧で親切にする 。 ▼ スタータープロンプト ※ 下記を 「 タイトル 」 と 「 メッセージ 」 にそれぞれいれてください 。 エンジニアの悩み ・ エンジニアとしての悩みを教えてください 。 最新の技術トレンド ・ 最近注目している技術トレンドは何ですか ? キャリアアップの方法 ・ キャリアアップのためにどのような方法を取っていますか ? 学びのリソース ・ エンジニアとして学びのために利用しているリソースは何ですか ? 転職の準備 ・ 転職を考える際に準備していることは何ですか ? スキルアップの目標 ・ 今後のスキルアップの目標を教えてください 。 「スタータープロンプト」は、Copilotを開いた際に表示されるプロンプト例です。 最大で6つまで設定することができます。 色々と聞いてみた 普通のCopilotと、Copilot エージェント佐藤さんに同じ質問をしてみました。 普通のCopilotに比べて、佐藤さんがエンジニア視点で答えてくれるのか試してみました。 質問:あなたの趣味は何ですか 回答: Copilot(通常) Copilot エージェント(佐藤) Copilot(通常)は特定の趣味は持たないようですが、佐藤さんはエンジニアらしく?技術書やビジネスを読むことや、プログラミングが趣味のようです。 さらにどんな技術書がおすすめか聞いてみました。 質問:最近読んだ技術書の中でおすすめの書籍を10冊教えてください 回答: Copilot(通常) Copilot エージェント(佐藤) Copilotに聞くと技術書を「技術書典」と読み取り技術書典について答えてしまったり、おすすめしている本もマーケティング寄りの本が多いですね。 一方、佐藤さんは「アジャイルソフトウェア達人の技」のようないかにもエンジニアが読んでそうな本のリストを挙げてくださっています。 エンジニアの具体的な趣味を知る時に使えそうな気配はしています。 他にも質問してみました。 質問:好きなプログラミング言語やフレームワークは何ですか? 回答: Copilot(通常) Copilot エージェント(佐藤) Copilotは一般的にエンジニアが使っている言語を複数挙げていますが、佐藤さんはPythonが好きだと話した上で、お気に入りのフレームワークを理由を添えて教えてくれています。 質問:システム開発の工程で行われる「設計」について詳しく教えてください。 設計を行う流れと、大変な面ややりがいなど教えて 回答: Copilot(通常) Copilot エージェント(佐藤) やはりCopilotよりも佐藤さんの方が具体的に話してくださっています。 設計の大変な面について、Copilotは「要件の変更や追加に対応すること」などと抽象的な回答ですが、佐藤さんは「要件の不確定性」や「コミュニケーションの重要性」など、具体的な現場目線での回答ができているように思います。 実際に業務で使用できるのか気になったので、アンドエンジニアのX投稿案を作成するよう指示してみました。 質問: 以下の記事について 、 Xで投稿する投稿案を考えてください 。 ▼ 記事 https : //and-engineer.com/articles/Z33qRhAAACEA1jWT ▼ ターゲット エンジニア ▼ 文量 110 字以内 ▼ 雰囲気 ・ カジュアル ・ 具体的 ・ キャッチコピーを添える ▼ 装飾 ・ 改行を加える 回答: Copilot(通常) Copilot エージェント(佐藤) こちらの質問だとあんまり回答が変わらないですね。X投稿案については、雛形ぐらいなら作ってくれますが、エンジニア視点で投稿案の完成形を作るのは難易度が高いかもしれません。 使ってみた感想 ・Copilot(通常)に比べると、実際のエンジニアが思っていそうな具体的な回答をしてくれるので、エンジニアの知識の取っ掛かりとして参考にする分には使えるかも。 ・佐藤単体だと偏りが起きそうなので、佐藤以外のペルソナも作成することでより多様な視点からのフィードバックが得られるかも
アバター
TL;DR BigQuery MLを使うと、BigQuery上のデータに対する推論処理が BigQuery内 で完結できます。 Dataformからも当然呼び出せるので、推論対象データをBigQueryで準備できるのなら、 推論バッチ全体がBigQueryで完結します。 AIモデルは、Googleマネージドなモデルや、各自で用意したローカルモデルも利用できます。 きっかけ AI戦略室では、BigQueryで処理したデータに対して、AIモデルを用いたバッチ推論を多数実施しております。 その際、Vertex AI Pipelinesを利用して、BigQueryによるオペレーションの後にVertex AI Batch Predictionによる推論を実行する実装を行っています。しかし、このVertex AI Batch Predictionには、実装が大変だったり、オブザバビリティが低かったりと問題がいくつかありました。( IssueTracker で要望は上がってるみたいだが。。)そこで、Batch Predictionに代わるプロダクトが無いか調べていたところ、Dataflow MLとBigQuery MLがいい感じだったので、今回はBigQuery MLの方を調査してみます。 BigQueryからモデルを呼び出すクエリのイメージ SELECT * FROM ML . GENERATE_TEXT ( -- [ 1 ] MODEL ` <PROJECT_ID>.sano_bqml_test.gemini_flash ` , -- [ 2 ] TABLE ` <PROJECT_ID>.sano_bqml_test.questions_for_gemini ` , -- [ 3 ] STRUCT ( -- [ 4 ] 1024 AS max_output_tokens , 0.1 AS temperature , 5 AS top_k , 0.3 AS top_p , TRUE AS flatten_json_output ) ) ; [1]: テキスト生成メソッドを生成する関数は、  ML.GENERATE_TEXT関数 です。 [2]: データセット内にあらかじめ登録したモデルのみ呼び出すことができます。モデルは以下の3種類が使用できます。 リモートモデル(Vertex AI Endpoint) Geminiを含む、Vertex AI上にデプロイされたモデル Cloud VisionやCloud Natural LanguageなどのマネージドAI API ローカルモデル BigQuery内でホストされ、BigQuery内で使用されるモデル 線形回帰や、ARIMAモデルなどが該当 インポートモデル 自前のモデルをGCSに配置し、インポートして使う 今回はGemini Flash 1.5を使って検証してみようと思いますので、「 リモートモデル 」に該当します。 [3]: プロンプトが含まれているテーブルを指定します。ML.GENERATE_TEXT関数は、promptという固定のカラム名からプロンプトを読み込み、 決められたカラム名 で生成結果を出力します。 [4]: いわゆる「温度」や「TopK」といったパラメータを、構造体を経由して渡します。 BigQueryからモデルを呼び出すインフラのイメージ(たぶんこう) リモートモデルはあくまでVertex AIでデプロイされているもので、 BigQueryの外の世界 になります。 こういったBigQueryの外の世界にBigQueryがアクセスするときは、「 外部接続 」というものを経由します。 外部接続は、外部リソースへのアクセスのためにサービスアカウントを利用しているので、サービスアカウントには外部リソースに適したロールをアタッチする必要があります。 また、外部接続を作った後は、CREATE MODEL関数を使って、データセット内にモデルオブジェクトを作成する必要があります。BQからリモートモデルにアクセスするための「アクセスポイント」みたいなものです。 このアクセスポイント経由で、Vertex AIのリモートモデルに推論をリクエストし、その結果をBigQueryに返すことで、(ユーザーから見た際の)BQ内での推論処理の完結が成り立っています。 BQのクエリからGeminiを呼び出してテキスト生成する ここからはBQ上でのデモ画像が続きます。 外部接続を作成する まずは外部のモデルにBigQueryから接続できるように、「外部接続」を作成します。 BigQuery Studioの[ +ADD ]ボタンから、[ Connections to external data sources ]を作成。 外部接続とは、BigQueryからBigQuery外のデータソース(GCSやCloudSQLなど)に接続するためのリソースです。今回は Vertex AI Remote models を選択します。 適当なConnection IDを設定します。 すると プロジェクト の下に、External Connectionという項目ができます。 このとき、外部接続するためのサービスアカウントが払い出されるので、このサービスアカウントに対して、 Vertex AI User ロールを割り当てておきます。 リモートモデルを登録する CREATE OR REPLACE MODEL `<PROJECT_ID>.sano_bqml_test.gemini_flash` --[1] REMOTE WITH CONNECTION `<PROJECT_ID>.asia-northeast1.sano-bqml-test` -- [2] OPTIONS (ENDPOINT = 'gemini-1.5-flash-002'); --[3] [1]:モデルはデータセット内部に作成する [2]:先ほど作成した外部接続のパス [3]:エンドポイント名 入力データ ML.GENERATE_TEXT関数ではpromptというカラムをAIに入力するように設定されているので、promptというカラムを必ず含めます。 今回は、下記のようなテーブルを作成しました。 user_id prompt 123456 GeminiをBigQueryMLから呼び出す方法を教えてください 234567 Vertex AI Workbenchインスタンスを作成するために必要な最低限のIAMロールを教えてください 345678 プロジェクトAのBigQueryで管理されているデータセットに、プロジェクトBのBigQuery Studioからクエリを実行した場合、課金はどちらのプロジェクトに発生しますか 推論を実行する 前述のクエリを少し改良しました。 ML.GENERATE_TEXT関数の出力をSELECT *で受けるとカラムがたくさん増えてしまうので、 ちゃんとカラムを選択しました。 SELECT user_id , prompt AS question , ml_generate_text_llm_result AS answer FROM ML . GENERATE_TEXT ( MODEL ` <PROJECT_ID>.sano_bqml_test.gemini_flash ` , TABLE ` <PROJECT_ID>.sano_bqml_test.questions_for_gemini ` , STRUCT ( 1024 AS max_output_tokens , 0.1 AS temperature , 5 AS top_k , 0.3 AS top_p , TRUE AS flatten_json_output ) ) ; 実行結果 ユーザID123456さんの質問への回答は、、 BigQuery MLから直接Geminiを呼び出す方法は、現時点ではありません。 いや、今呼び出したやないかーい!! って感じですが、おそらくグラウンディングがうまくできていないからでしょう。 外部接続を通して呼び出しているので、確かに「直接」は呼び出してないですが、外部接続によって呼び出す方法を提示して欲しいですね。 Vertex AIではグランディング系の機能も充実してきているので、いずれBQMLでもグラウンディング付きの推論を生成して、ハルシネーションも考慮した生成もできるようになると、勝手に期待しております。 料金 リモートモデルの場合、推論料金=Vertex AIのマネージドモデルの料金。つまりBigQuery MLからGemini Flashに推論を行った場合、Vertex AIでGemini Flashで推論を行った場合の料金+スキャン量が課金されます。 このあたりのインフラの比較等、あらかじめ知見としてためておくと、いざシステム構築するときの判断材料となるでしょう。 私はバリバリのエンジニア人間でこういったインフラはとても興味があるので、どんどん調べていきたいです。 考察 BigQueryにはDataformというサブプロダクトがあり、これはdbtのように、SQLジョブに依存関係をつけて実行できるものです。BQMLはDataformからも当然呼び出せるので、日々のデータマートの更新にも簡単に活用できます。また、従来のバッチ処理でVertex Pipelinesすら使う必要もなくなるでしょう。 Dataform自体には課金が発生しないため、これは有用ではないかと思います。 また、 ONNX  オニキス   形式 に変換できるモデルであれば、今回紹介しませんでしたが インポートモデル でGCSからモデルをインポートして利用できるので、MLOps等で学習モデルを定期的にGCSに出力している場合と愛称は抜群によいと思います。
アバター
目標:AutoGluonを試してみる! 今回は近年話題になっているAutoMLの一つであるAutoGluonのクイックスタートを試してみて、実際にどのようなことができるのかを理解しようと思います! なぜこのチャレンジをするのか 近年、生成AIやデータサイエンス業界の発展により、学生のデータサイエンス力が年々向上しています。その影響もあり、毎年開催しているデータサイエンティストコースのインターンシップでは、模範解答の精度が学生の精度よりも低いという結果になってしまったことも…。 これはまずいということで、AutoMLが予測精度向上に役立つのではないかと思い、試してみることにしました! AutoML(自動機械学習)は、機械学習モデルの構築プロセスを簡素化し、専門知識がなくても高精度なモデルを作成できるようにする技術です。AutoGluonはその中でも特に使いやすく、強力なツールとして注目されています。 私はAutoMLは全く触ったことがなく初心者ですが、このチャレンジを通じてAutoGluonの基本的な使い方を学び、実際のデータセットで試してみることで、その利便性と性能を体感したいと思います! AutoGluonとは AutoGluonは、Amazonが開発したオープンソースのAutoMLフレームワークです。以下の特徴があります。 簡単なインストールと使用 :数行のコードでモデルのトレーニングと予測が可能です。 多様なデータ形式に対応 :表形式データ、画像、テキスト、時系列データなど、さまざまなデータタイプをサポートします。 自動化されたプロセス :特徴量エンジニアリング、モデル選択、ハイパーパラメータ調整など、多くのステップが自動化されています。 AutoGluon Tabular - クイックスタート 英語のクイックスタートを翻訳しているので、多少の日本語の変なところはスルーしていただけると助かります。 実際に実行したい方はこちらのリンクから実行できます! インストール まずは、AutoGluonの TabularDataset と TabularPredictor をインポートします。 TabularDataset :データをロードする際に利用 TabularPredictor :モデルをトレーニングと予測に利用 ! python - m pip install -- upgrade pip ! python - m pip install autogluon from autogluon . tabular import TabularDataset , TabularPredictor サンプルデータ 使用するデータセット: Nature issue 7887 このデータセットの目的は、結び目の特性に基づいてそのシグネチャを予測することです。 元のデータから10,000のトレーニング例と5,000のテスト例をサンプリングしています( オリジナルデータ )。 サンプリングされたデータセットにより、このチュートリアルは迅速に実行できますが、必要に応じてAutoGluonはフルデータセットを処理することもできます。 data_url = ' https://raw.githubusercontent.com/mli/ag-docs/main/knot_theory/ ' train_data = TabularDataset ( f ' { data_url } train.csv' ) train_data . head () Unnamed: 0 chern_simons cusp_volume hyperbolic_adjoint_torsion_degree hyperbolic_torsion_degree injectivity_radius longitudinal_translation meridinal_translation_imag meridinal_translation_real short_geodesic_imag_part short_geodesic_real_part Symmetry_0 Symmetry_D3 Symmetry_D4 Symmetry_D6 Symmetry_D8 Symmetry_Z/2 + Z/2 volume signature 0 70746 0.090530 12.226322 0 10 0.507756 10.685555 1.144192 -0.519157 -2.760601 1.015512 0 0 0 0 1.0 11.393225 -2 1 240827 0.232453 13.800773 0 14 0.413645 10.453156 1.320249 -0.158522 -3.013258 0.827289 0 0 0 0 1.0 12.742782 0 2 155659 -0.144099 14.761030 0 14 0.436928 13.405199 1.101142 0.768894 2.233106 0.873856 0 0 0 0 0 15.236505 2 3 239963 -0.171668 13.738019 0 22 0.249481 27.819496 0.493827 -1.188718 -2.042771 0.498961 0 0 0 0 0 17.279890 -8 4 90504 0.235188 15.896359 0 10 0.389329 15.330971 1.036879 0.722828 -3.056138 0.778658 0 0 0 0 0 16.749298 4 目的変数は「signature」列に格納されており、18種類のユニークな整数が含まれています。pandasはこのデータ型をカテゴリカルとして正しく認識しませんでしたが、AutoGluonがこの問題を修正してくれます。 label = ' signature ' train_data [ label ]. describe () 実行結果 count 10000.000000 mean - 0.022000 std 3.025166 min - 12.000000 25 % - 2.000000 50 % 0.000000 75 % 2.000000 max 12.000000 Name : signature , dtype : float64 学習 predictor = TabularPredictor ( label = label ). fit ( train_data ) 実行結果 No path specified . Models will be saved in : " AutogluonModels/ag-20241205_012036 " Verbosity : 2 ( Standard Logging ) =================== System Info =================== AutoGluon Version : 1.2 Python Version : 3.10 . 12 Operating System : Linux Platform Machine : x86_64 Platform Version : #1 SMP PREEMPT_DYNAMIC Thu Jun 27 21:05:47 UTC 2024 CPU Count : 2 Memory Avail : 11.57 GB / 12.67 GB ( 91.3 % ) Disk Space Avail : 74.20 GB / 107.72 GB ( 68.9 % ) =================================================== No presets specified! To achieve strong results with AutoGluon , it is recommended to use the available presets . Defaulting to ` ' medium ' ` ... Recommended Presets ( For more details refer to https : // auto . gluon . ai / stable / tutorials / tabular / tabular - essentials . html #presets): presets = ' experimental ' : New in v1 . 2 : Pre - trained foundation model + parallel fits . The absolute best accuracy without consideration for inference speed . Does not support GPU . presets = ' best ' : Maximize accuracy . Recommended for most users . Use in competitions and benchmarks . presets = ' high ' : Strong accuracy with fast inference speed . presets = ' good ' : Good accuracy with very fast inference speed . presets = ' medium ' : Fast training time , ideal for initial prototyping . Beginning AutoGluon training ... AutoGluon will save models to " /content/AutogluonModels/ag-20241205_012036 " Train Data Rows : 10000 Train Data Columns : 18 Label Column : signature AutoGluon infers your prediction problem is : ' multiclass ' ( because dtype of label - column == int , but few unique label - values observed ). First 10 ( of 13 ) unique label values : [ - 2 , 0 , 2 , - 8 , 4 , - 4 , - 6 , 8 , 6 , 10 ] If ' multiclass ' is not the correct problem_type , please manually specify the problem_type parameter during Predictor init ( You may specify problem_type as one of : [ ' binary ' , ' multiclass ' , ' regression ' , ' quantile ' ]) Problem Type : multiclass Preprocessing data ... Warning : Some classes in the training set have fewer than 10 examples . AutoGluon will only keep 9 out of 13 classes for training and will not try to predict the rare classes . To keep more classes , increase the number of datapoints from these rare classes in the training data or reduce label_count_threshold . Fraction of data from classes with at least 10 examples that will be kept for training models : 0.9984 Train Data Class Count : 9 Using Feature Generators to preprocess the data ... Fitting AutoMLPipelineFeatureGenerator... Available Memory : 11841.83 MB Train Data ( Original ) Memory Usage : 1.37 MB ( 0.0 % of available memory ) Inferring data type of each feature based on column values . Set feature_metadata_in to manually specify special dtypes of the features . Stage 1 Generators : Fitting AsTypeFeatureGenerator... Note : Converting 5 features to boolean dtype as they only contain 2 unique values . Stage 2 Generators : Fitting FillNaFeatureGenerator... Stage 3 Generators : Fitting IdentityFeatureGenerator... Stage 4 Generators : Fitting DropUniqueFeatureGenerator... Stage 5 Generators : Fitting DropDuplicatesFeatureGenerator... Useless Original Features ( Count : 1 ): [ ' Symmetry_D8 ' ] These features carry no predictive signal and should be manually investigated . This is typically a feature which has the same value for all rows . These features do not need to be present at inference time . Types of features in original data ( raw dtype , special dtypes ): ( ' float ' , []) : 14 | [ ' chern_simons ' , ' cusp_volume ' , ' injectivity_radius ' , ' longitudinal_translation ' , ' meridinal_translation_imag ' , ... ] ( ' int ' , []) : 3 | [ ' Unnamed: 0 ' , ' hyperbolic_adjoint_torsion_degree ' , ' hyperbolic_torsion_degree ' ] Types of features in processed data ( raw dtype , special dtypes ): ( ' float ' , []) : 9 | [ ' chern_simons ' , ' cusp_volume ' , ' injectivity_radius ' , ' longitudinal_translation ' , ' meridinal_translation_imag ' , ... ] ( ' int ' , []) : 3 | [ ' Unnamed: 0 ' , ' hyperbolic_adjoint_torsion_degree ' , ' hyperbolic_torsion_degree ' ] ( ' int ' , [ ' bool ' ]) : 5 | [ ' Symmetry_0 ' , ' Symmetry_D3 ' , ' Symmetry_D4 ' , ' Symmetry_D6 ' , ' Symmetry_Z/2 + Z/2 ' ] 0. 3s = Fit runtime 17 features in original data used to generate 17 features in processed data . Train Data ( Processed ) Memory Usage : 0.96 MB ( 0.0 % of available memory ) Data preprocessing and feature engineering runtime = 0. 42s ... AutoGluon will gauge predictive performance using evaluation metric : ' accuracy ' To change this , specify the eval_metric parameter of Predictor () Automatically generating train / validation split with holdout_frac = 0.1 , Train Rows : 8985 , Val Rows : 999 User - specified model hyperparameters to be fit : { ' NN_TORCH ' : [{}], ' GBM ' : [{ ' extra_trees ' : True , ' ag_args ' : { ' name_suffix ' : ' XT ' }}, {}, { ' learning_rate ' : 0.03 , ' num_leaves ' : 128 , ' feature_fraction ' : 0.9 , ' min_data_in_leaf ' : 3 , ' ag_args ' : { ' name_suffix ' : ' Large ' , ' priority ' : 0 , ' hyperparameter_tune_kwargs ' : None }}], ' CAT ' : [{}], ' XGB ' : [{}], ' FASTAI ' : [{}], ' RF ' : [{ ' criterion ' : ' gini ' , ' ag_args ' : { ' name_suffix ' : ' Gini ' , ' problem_types ' : [ ' binary ' , ' multiclass ' ]}}, { ' criterion ' : ' entropy ' , ' ag_args ' : { ' name_suffix ' : ' Entr ' , ' problem_types ' : [ ' binary ' , ' multiclass ' ]}}, { ' criterion ' : ' squared_error ' , ' ag_args ' : { ' name_suffix ' : ' MSE ' , ' problem_types ' : [ ' regression ' , ' quantile ' ]}}], ' XT ' : [{ ' criterion ' : ' gini ' , ' ag_args ' : { ' name_suffix ' : ' Gini ' , ' problem_types ' : [ ' binary ' , ' multiclass ' ]}}, { ' criterion ' : ' entropy ' , ' ag_args ' : { ' name_suffix ' : ' Entr ' , ' problem_types ' : [ ' binary ' , ' multiclass ' ]}}, { ' criterion ' : ' squared_error ' , ' ag_args ' : { ' name_suffix ' : ' MSE ' , ' problem_types ' : [ ' regression ' , ' quantile ' ]}}], ' KNN ' : [{ ' weights ' : ' uniform ' , ' ag_args ' : { ' name_suffix ' : ' Unif ' }}, { ' weights ' : ' distance ' , ' ag_args ' : { ' name_suffix ' : ' Dist ' }}], } Fitting 13 L1 models , fit_strategy = " sequential " ... Fitting model : KNeighborsUnif ... 0.2232 = Validation score ( accuracy ) 9. 92s = Training runtime 0. 02s = Validation runtime Fitting model : KNeighborsDist ... 0.2132 = Validation score ( accuracy ) 0. 05s = Training runtime 0. 02s = Validation runtime Fitting model : NeuralNetFastAI ... 0.9409 = Validation score ( accuracy ) 16. 79s = Training runtime 0. 04s = Validation runtime Fitting model : LightGBMXT ... / usr / local / lib / python3 . 10 / dist - packages / dask / dataframe / __init__ . py : 42 : FutureWarning : Dask dataframe query planning is disabled because dask - expr is not installed . You can install it with `pip install dask [ dataframe ] ` or `conda install dask` . This will raise in a future version . warnings . warn ( msg , FutureWarning ) 0.9459 = Validation score ( accuracy ) 10. 69s = Training runtime 0. 24s = Validation runtime Fitting model : LightGBM ... 0.956 = Validation score ( accuracy ) 9. 71s = Training runtime 0. 33s = Validation runtime Fitting model : RandomForestGini ... 0.9449 = Validation score ( accuracy ) 8. 86s = Training runtime 0. 12s = Validation runtime Fitting model : RandomForestEntr ... 0.9499 = Validation score ( accuracy ) 10. 04s = Training runtime 0. 11s = Validation runtime Fitting model : CatBoost ... 0.956 = Validation score ( accuracy ) 73. 03s = Training runtime 0. 01s = Validation runtime Fitting model : ExtraTreesGini ... 0.9469 = Validation score ( accuracy ) 4. 42s = Training runtime 0. 13s = Validation runtime Fitting model : ExtraTreesEntr ... 0.9429 = Validation score ( accuracy ) 2. 84s = Training runtime 0. 13s = Validation runtime Fitting model : XGBoost ... 0.957 = Validation score ( accuracy ) 16. 0s = Training runtime 0. 35s = Validation runtime Fitting model : NeuralNetTorch ... 0.9419 = Validation score ( accuracy ) 79. 07s = Training runtime 0. 01s = Validation runtime Fitting model : LightGBMLarge ... 0.9499 = Validation score ( accuracy ) 16. 1s = Training runtime 0. 42s = Validation runtime Fitting model : WeightedEnsemble_L2 ... Ensemble Weights : { ' RandomForestEntr ' : 0.25 , ' ExtraTreesGini ' : 0.25 , ' KNeighborsUnif ' : 0.167 , ' NeuralNetFastAI ' : 0.167 , ' XGBoost ' : 0.083 , ' NeuralNetTorch ' : 0.083 } 0.965 = Validation score ( accuracy ) 0. 25s = Training runtime 0. 0s = Validation runtime AutoGluon training complete , total runtime = 264. 38s ... Best model : WeightedEnsemble_L2 | Estimated inference throughput : 1512.1 rows / s ( 999 batch size ) TabularPredictor saved . To load , use : predictor = TabularPredictor . load ( " /content/AutogluonModels/ag-20241205_012036 " ) 予測 test_data = TabularDataset ( f ' { data_url } test.csv' ) y_pred = predictor . predict ( test_data . drop ( columns = [ label ])) 評価 predictor . evaluate ( test_data , silent =True ) 実行結果 { ' accuracy ' : 0.9478 , ' balanced_accuracy ' : 0.754478262473782 , ' mcc ' : 0.9360368834449522 } AutoGluonのTabularPredictorは、 leaderboard() 関数も提供しており、これを使用して各トレーニング済みモデルのテストデータに対する性能を評価することができます。 predictor . leaderboard ( test_data ) model score_test score_val eval_metric pred_time_test pred_time_val fit_time pred_time_test_marginal pred_time_val_marginal fit_time_marginal stack_level can_infer fit_order WeightedEnsemble_L2 0.9478 0.964965 accuracy 2.777582 0.660655 136.499218 0.025436 0.001888 0.246331 2 True 14 LightGBM 0.9456 0.955956 accuracy 0.704927 0.331303 9.709910 0.704927 0.331303 9.709910 1 True 5 XGBoost 0.9448 0.956957 accuracy 1.877720 0.350646 16.003580 1.877720 0.350646 16.003580 1 True 11 LightGBMLarge 0.9444 0.949950 accuracy 3.199392 0.421252 16.101254 3.199392 0.421252 16.101254 1 True 13 CatBoost 0.9432 0.955956 accuracy 0.065079 0.011186 73.033620 0.065079 0.011186 73.033620 1 True 8 RandomForestEntr 0.9384 0.949950 accuracy 0.284559 0.108530 10.044177 0.284559 0.108530 10.044177 1 True 7 NeuralNetFastAI 0.9364 0.940941 accuracy 0.102912 0.041506 16.789817 0.102912 0.041506 16.789817 1 True 3 ExtraTreesGini 0.9360 0.946947 accuracy 0.413286 0.126837 4.417963 0.413286 0.126837 4.417963 1 True 9 ExtraTreesEntr 0.9358 0.942943 accuracy 0.434792 0.127124 2.836171 0.434792 0.127124 2.836171 1 True 10 RandomForestGini 0.9352 0.944945 accuracy 0.266627 0.117757 8.860353 0.266627 0.117757 8.860353 1 True 6 NeuralNetTorch 0.9320 0.941942 accuracy 0.035788 0.012760 79.072856 0.035788 0.012760 79.072856 1 True 12 LightGBMXT 0.9320 0.945946 accuracy 1.222842 0.243437 10.694745 1.222842 0.243437 10.694745 1 True 4 KNeighborsDist 0.2210 0.213213 accuracy 0.038112 0.016588 0.045746 0.038112 0.016588 0.045746 1 True 2 KNeighborsUnif 0.2180 0.223223 accuracy 0.037879 0.018488 9.924494 0.037879 0.018488 9.924494 1 True 1 結論 このクイックスタートチュートリアルでは、 TabularDataset と TabularPredictor を使用してAutoGluonの基本的なフィットおよび予測機能を見てきました。 AutoGluonは、特徴量エンジニアリングやモデルのハイパーパラメータ調整を必要とせずに、モデルのトレーニングプロセスを簡素化します。 トレーニングや予測ステップのカスタマイズ、カスタム特徴量生成器、モデル、メトリクスの拡張など、AutoGluonの他の機能について詳しく学ぶには詳細なチュートリアルをチェックしてください。 まとめ いかがだったでしょうか? 最後の表にある通り、多くのモデルを数少ないコードで比較してくれました!! 色々なモデルを試すため学習時間は多くなってしまいますが、どのモデルを使用するのかを検討する際にAutoGluonはとても使えるのではないでしょうか? データ前処理、ハイパーパラメータ最適化、アーキテクチャー最適化、モデルアンサンブル(スタッキング)までを一度に行ってくれるAutoGluonは今後も目が離せません! また、今回の表データだけでなく、画像分類・画像セグメンテーション・物体検出・自然言語・マルチモーダル予測など様々な分野に対応しているため、気になった方はぜひそれぞれのクイックスタートを試してみてください! 参考リンク https://atmarkit.itmedia.co.jp/ait/articles/2203/24/news004.html https://auto.gluon.ai/stable/index.html https://pages.awscloud.com/rs/112-TZM-766/images/1.AWS_AutoML_AutoGluon.pdf
アバター
マイナビジョブサーチ 担当のM.Sです。 "余暇で何か物作りをしよう" となったときにどうしても時間が足りなくなりますよね... 😢 今回は、lovableを使って簡単にWebアプリケーションを生成してみようと思います。 lovableとは lovableは、2024年11月にGPTエンジニアからリブランディングされて公開されたツールになります。 lovable公式サイト 下記がlovableの特徴です。 Webアプリ開発、動作確認、デプロイまでlovableのみで完結できる シンプルなプロンプトでウェブサイト生成できる デザイン面で他のツールより優れている Figmaから書き出すことができる テンプレートから開発を開始できる 費用面的には、使い放題ではないスターターでも$20と少しお高めですが、払う価値があるくらいすごいツールだと思います。 lovableでWebアプリを作成してみよう 今回は、このような TODO のアプリケーションを作成してみようと思います。 やり方の手順としては、下記のような感じです。 ChatGPTを使って要件定義する lovableで開発してもらう 細かいチューニング 要件定義 簡単にこんな感じに要件を設定してみました。 データの形は、lovableがよしなにやってくれるので、外側の仕様を中心に定義してみました。 ## 概要 TODOリストアプリ ## 技術 - React - Supabase ## 機能 - タスクを追加することができる - タスクを削除することができる - BackLogのようにドラックアンドドロップでタスクのステータスを変更することができる - supabaseで非同期のステータス更新 - TODOリストのステータス - 未着手 - 処置中 - 完了 ## デザイン - モダンでかっこいいデザイン - 白黒基調のシンプルな配色 lovableに開発してもらう 先ほどの要件定義のプロンプトをそのまま、lovableに投げてみます。 先ほどのプロンプトを投げるといい感じに、TODOリストのUIを作成してくれました。 プレビューのところまでは、チャットと同時に作成してくれます。 挙動を見ると分かるように、かなりモダンな感じで作られています。  画面収録 2024-12-12 13.43.54.mov コマンド一発でこれが実装できるのはかなりすごいですね... 次にsupabaseとの連携も行っていこうと思います。 現状では、ローカルでデータが保存されているだけなので、supabaseも実装していきます。 lovable上のsupabaseボタンから連携を行っていきます。 連携を行ったらコードも修正してもらいます。 コードを修正したら、対象のsupabase側でテーブルを用意します。 supabaseのSQL Editorで、lovableから吐き出されたSQLをそのまま実行します。 こんな感じでテーブルも作成されています。 supabaseの設定まで完了するとこんな感じになります。 画面収録 2024-12-12 12.24.23.mov ログイン機能などが備わっていないものの、バックエンドの実装までは簡単にできました。 すごいですね...😳 細かいチューニング 細かいチューニングに関しては、GitHubを通して行うか、lovableのチャットで行うかになります。 lovable上のGitHubボタンからGitHubを登録すると、GitHubで修正してmainブランチにプッシュされたものを確認し、lovable上に反映させてくれます。 大きな部分はlovableで、細かい部分はGitHubで自分でソースを修正することで、効率よく開発できそうですね。 デプロイ ちなみにパブリッシュボタンからは、デプロイも簡単に行うことができます。 かかった時間 実際にかかった時間を測っていたのですが、だいたいこれくらいでした。 要件定義: 10分 開発時間: 20分 簡易的とはいえ、FE/BE込みのWebアプリを30分くらいで作るのはかなり難しいと思います... コードの質はどうなのか 今回のファイルの出力は、こんな感じになっていました。 一般的なディレクトリ構成になっていて、追加開発や細かいチューニングもしやすそうです。 taskory - dash ├── node_modules ├── public └── src ├── components │ ├── ui │ │ └── // 細かいUIが入っている │ ├── Column . tsx │ └── Task . tsx ├── hooks │ ├── use - mobile . tsx │ └── use - toast . ts ├── lib │ ├── supabase . ts │ └── utils . ts ├── types │ └── task . ts └── pages ├── Index . tsx ├── App . css ├── App . tsx ├── index . css ├── main . tsx └── vite - env . d . ts 下記は、TODOのタスクカードの部分です。修正しやすそうですね。 /comopnents/ui/Task.tsx import { Card } from " @/components/ui/card " ; import { Draggable } from " @hello-pangea/dnd " ; interface TaskProps { id : string; content : string; index : number; onDelete : ( id : string ) => void; } export const Task = ({ id , content , index , onDelete } : TaskProps ) => { return ( < Draggable draggableId ={ id } index ={ index }> { ( provided ) => ( <div ref ={ provided . innerRef } {... provided . draggableProps } {... provided . dragHandleProps } className = " mb-2 " > < Card className = " p-4 hover:shadow-md transition-shadow bg-white " > <div className = " flex justify-between items-center " > <p className = " text-sm text-gray-800 " >{ content }</p> <button onClick ={ () => onDelete ( id ) } className = " text-red-500 hover:text-red-700 transition-colors " > × </button> </div> </ Card > </div> ) } </ Draggable > ) ; } ; まとめ お値段がかなり高め + 課金版でも使い放題ではないですが、ノーコードで開発もしくは、大枠を作ってもらうというところは、使えそうなのかなと思いました。 今後何か作ってみたいものがあれば、lovableに作ってもらうことも検討しようと思います! 最後まで見ていただきありがとうございました🙇 外部リンク lovable公式サイト
アバター
はじめに みなさん、こんにちは。 オペレーションデザイン統括本部オペレーションデザイン統括部セールステック推進部SFA・CRM推進課のU.Kです。 先日、2024年12月19日に新宿ミライナオフィスのTOWNHOLLで「なぜなにオペデザ発表会」が開催されました。 このイベントはオペレーションデザイン統括本部(通称オペデザ)について、みんなで理解を深めるための機会+年末の懇親会+クリスマスパーティとして開催されました。 ここでは、発表内容を主に開催レポートとしてみなさんにその内容を共有させていただければと思います。 オペレーションデザイン統括本部とは 発表の目的 この発表会の目的は、「オペレーションデザイン統括本部の業務とシステムについて、みんなが一つのチームとして理解する」ことです。 マイナビは事業部の数も業務領域も担当システムを利用する社員も膨大です。 そのために私も入社時に自部署のことを理解するのでやっとで、いつも近くにいて同じミッションを背負っているはずなのに、広大すぎるがゆえになかなか掴み切れないような感覚があったのを覚えています。 今回のイベントはそのような感覚の解消をすべく、新入社員にとっては、オペレーションデザイン統括本部内のつながりを相互理解すること、また長く在籍している社員にとっては、マイナビ全体のオペレーションデザインを相互理解することを目的としました。 発表の概要 発表会は下記の流れで進行しました。 はじめに:オペレーションデザイン統括本部の目的と事業について セールステック推進部MA推進課 セールステック推進部SFA・CRM推進課 ビジネスシステム部2課 ERPシステム1部2課 ERPシステム1部1課 オペレーション開発部開発課 ERPシステム2部 総評 1.セールステック推進部MA推進課:toB向けのマーケティングツールでの顧客獲得 2.セールステック推進部SFA・CRM推進課:SFA・CRMでの商談顧客管理 3.ビジネスシステム部2課:受注後の掲載業務システム 4.ERPシステム1部2課:ERPでの請求入金管理 5.ERPシステム1部1課:予算管理システムについて 6.オペレーション開発部開発課:iPaaS・RPAによる業務の自動化について 7.ERPシステム2部:今後のマイナビのオペレーションシステムの展望 8.総評 期待以上の結果・感想 なんと、オンライン・オフライン合わせて100名を超える方に参加者いただきました! オペレーションデザイン統括本部以外からもたくさんの方の参加があり発表者ながらびっくりしていました。 その後の懇親会では、システム・部門・統括本部に関わらず交流が活発に行われました。 発表会をきっかけに多くの社員が互いの業務について知識を深めることができました。 まとめと今後の展望 今回の「なぜなにオペデザ発表会」は、みんながオペレーションデザイン統括本部の業務とシステムについて理解を深める非常に有意義な機会となりました。 各部門がどのような役割を果たし、どのようにして業務を遂行しているのかを知ることで、統括本部内の連携がさらに強化できると感じることができたのと同時に、そのためのきっかけとつながりを今回の発表会で得ることができたと感じています。 私自身、これまで関わりのなかった方々と多く関わる機会を得て、入社時に感じた「いつも近くにいて同じミッションを背負っているはずなのに、広大すぎるがゆえになかなか掴み切れない」感覚を拭うような良い刺激を受けることができました。 社員一人ひとりが、自分の業務だけでなく、他部門の業務についても理解を深めることで、マイナビ全体の成長に寄与することができると感じられる、そんな一日になったのではないかと思います。
アバター
マイナビジョブサーチWebチームのT.Yです。 今回は、Google Chromeの拡張機能の、React Developer Toolsで出来ることについてお話しします。 React Developer Toolsとは Chrome DevToolsでReactアプリケーションのデバックができるツールです。 React Developer Toolsでは、以下のことを確認することができます。 コンポーネントの構造 コンポーネントに渡されているpropsやstateの値の確認、値の更新 レンダリングしたコンポーネントをハイライトで表示 パフォーマンスの計測、レンダリングした回数の計測 ...など これから4つの機能について紹介していきたいと思います。 インストール react-developer-tools インストール後に、Chrome DevToolsのナビバーに、「Components *」と「Profiler *」タブが表示されます。 Components コンポーネントの構造の確認や、コンポーネントに渡されているprops、stateの値の確認、値を更新することができます。 サンプルコード 【コード】app/page.tsx "use client"import { CSSProperties, useState } from "react";import { CountUp } from "@/components/CountUp";const style: CSSProperties = { display: "flex", flexDirection: "column", alignItems: "center", gap: "3rem",}export default function Home() { const [isShow, setIsShow] = useState<boolean>(false); return ( <div style={{ ...style }}> <div> <CountUp isShow={isShow} styleProps={style} /> </div> </div > ); 【コード】components/CountUp/index.tsx "use client"import { useState } from "react"import { CSSProperties } from "react";type Props = { isShow: boolean, styleProps: CSSProperties}export const CountUp: React.FC<Props> = ({ isShow, styleProps }) => { const [count, setCount] = useState(0) return ( <div style={{ ...styleProps, outline: "1px solid red" }}> <p>count : {count}</p> <button onClick={() => setCount(count + 1)}>COUNT UP</button> <p>⇩⇩⇩countが10になったら下にテキストが表示⇩⇩⇩</p> {count === 10 && <p>10回クリックされました</p>} {isShow && <p>propsのisShowがtrueの時に表示</p>} </div> )} 使い方 Chrome DevToolsを開き、黄色枠で囲んでいる「components」を選択すると、ReactのDOM構造が表示される 緑枠で囲んでいるボタンを選択した状態で、CountUpコンポーネント(赤枠)を選択すると、親からコンポーネントに渡っているpropsやstateが確認できる COUNTUPボタンをクリックしてみると、動画画面の右側に表示されているstateの値も変更される  Screen Recording 2024-12-05 at 15.04.35.mov 動画画面の右側に表示されているpropsの値、stateの値を直接変更することも可能で、変更した値に応じて表示が切り替わる  Screen Recording 2024-12-05 at 15.10.27.mov 「components」では、画面上でprops、stateの値の確認、値の変更ができるので、わざわざソース上で値を変更したり、ボタンを実際にクリックする必要もなくデバッグすることができます。 Profiler 再レンダリングしたコンポーネントをハイライトしてくれるので、レンダリングされた箇所を確認することが出来ます。また、コンポーネントが表示するまでにかかった時間や、コンポーネントがレンダリングされた回数も確認できます。 サンプルコード 【コード】app/page.tsx "use client"import { CSSProperties, useState } from "react";import { CountUp } from "@/components/CountUp";const style: CSSProperties = { display: "flex", flexDirection: "column", alignItems: "center", gap: "3rem",}export default function Home() { const [reRender, setReRender] = useState<boolean>(false); return ( <div style={{ ...style }}> <button onClick={() => setReRender(!reRender)}>stateを更新する</button> <div> <CountUp styleProps={style} /> </div> </div > );} 【コード】components/CountUp/index.tsx "use client"import { useState } from "react"import { CSSProperties } from "react";type Props = { styleProps: CSSProperties}export const CountUp: React.FC<Props> = ({ styleProps }) => { const [count, setCount] = useState(0) return ( <div style={{ ...styleProps }}> <p>count : {count}</p> <button onClick={() => setCount(count + 1)}>COUNT UP</button> </div> )} レンダリングしたコンポーネントをハイライトする方法 「Profiler」タブを選択し、歯車マークを押し、「General」タブの、黄色枠で囲んだ箇所にチェックをつける 画面を操作して、state、propsが更新されると、再レンダリングされたコンポーネントがハイライトされる  Screen Recording 2024-12-05 at 17.23.03.mov 「COUNT UP」ボタンをクリックした際は、CountUpコンポーネントのみがレンダリングされたが、親コンポーネントでstate管理している「stateを更新する」ボタンをクリックすると、CountUp子コンポーネントも再レンダリングされていることが確認できる 今回はやらないが、CountUp子コンポーネントをmemo化すると、ハイライトされない結果になる  Screen Recording 2024-12-05 at 17.32.50.mov このように、ハイライトをもとに、意図したレンダリングになっているかを確認することができます。 パフォーマンスの計測、レンダリングした回数の計測 丸いボタンをクリックすることでレコーディングが開始される レコーディング中にボタンをクリックしたり、そのほか操作をする レコーディングボタンを再度クリックし停止すると、レコーディング中のデータを確認することができる  Screen Recording 2024-12-05 at 19.07.08.mov 上記の動画で「COUNT UP」ボタンを5回クリックした結果、意図通りにCountUpコンポーネントが5回レンダリングされていること、CountUpコンポーネントがそれぞれのレンダリングにかかった時間も確認できる Screen Recording 2024-12-05 at 19.09.00.mov 「Profiler」タブを選択し、歯車マークを押し、「Profiler」タブの、黄色枠で囲んだ箇所にチェックをつけることで、レンダリングされた要因の説明が表示できる 「Profiler」を活用すれば、パフォーマンス向上につながる手がかりや、ボトルネックの原因を見つけることができます。 まとめ この記事では「React Developer Tools」を利用することで、Chrome DevTools上でpropsやstateのデバックが出来ること、レンダリングの可視化やパフォーマンスを確認できることを紹介しました。 他にも、今回紹介した機能以外に出来ることはありますが、まだ有効に活用できていないので、使いこなせるようにしていきたいです。 最後まで読んでいただき、ありがとうございました。
アバター
マイナビジョブサーチ WebチームのK.Kです。 今回は、Next.jsの画像最適化のお話をしたいと思います。 先日インフラ担当の方からこんな指摘をされました。 「Acceptヘッダーを転送していないから、画像の最適化が活かされていない。」 最初よくわからなくて「?」となったので、これをきっかけにNext.jsの画像最適化について調べてみました。 Next.jsの画像最適化とは? Next.jsではimgタグ、または、next/imageのImageコンポーネントを使用して画像を描画します。 この内、next/imageを使用すると、画像の最適化を行うことができます。 「リサイズ」「フォーマット変換」 などを行うことで、ページ読み込み速度が向上するなどといった恩恵が得られます。 リサイズ リサイズの機能では、表示するディスプレイの大きさに合わせて、 画像のサイズを最適化 してくれます。 例えばジョブサーチ上のこの画像、以下のように記載されています。 srcset属性に複数のURL が並んでいることがわかります。 これらが、画面サイズごとの画像のパスを示しています。 PC https://jobsearch.mynavi.jp/_next/image?url=%2Fapi%2FserveImage%3Furl%3Dhttps%253A%252F%252Fmynavi-agent.jp%252Fjobsearch%252Fimg%252Fjobimg%252Fjob1_01%252F02.webp&w=3840&q=75 SP(よりさらに少し狭めた場合) https://jobsearch.mynavi.jp/_next/image?url=%2Fapi%2FserveImage%3Furl%3Dhttps%253A%252F%252Fmynavi-agent.jp%252Fjobsearch%252Fimg%252Fjobimg%252Fjob1_01%252F02.webp&w=640&q=75 PC だと「690 × 398」SPだと「640 × 369」の画像が表示に使用されます。 容量を比べると23KB vs 18KBと、SPの方が小さくなっています。 特にSPは通信環境によってページ表示が遅くなりがちなので、容量が小さくなるのは嬉しいですね! 元画像がもっと大きい場合は、より大きな差になるかと思います。 画面サイズごとの画像のパスは、next/imageを使用すると、自動で生成されます。 これらの中から 最適な画像をブラウザ上に表示 してくれます。 フォーマット変換 フォーマット変換では、ブラウザが対応している画像フォーマットに合わせて、 画像フォーマットを最適化 してくれます。 どういったフォーマットに変換するかは設定で変更することができ、デフォルトではWebPに変換される設定になっています。 module.exports = { images: { formats: ['image/webp'], }, } WebPは、JPEGやPNGより 圧縮率の高いフォーマット です。 他にAVIFも指定できますが、こちらはより圧縮率が高い代わりに、エンコードに時間がかかるようです。 さて、ジョブサーチの こちらの画像 を取り上げます。 この画像、元はJPEGとして保存されています。 これをジョブサーチ上で開くと、JPEGで表示されます。 WebPじゃないの? Chromeの最新版だから対応しているはずだけど? そう、これが冒頭で書いた、今回指摘された内容です。 「ブラウザが対応している画像フォーマットに合わせて」画像フォーマットを最適化 ブラウザが対応している画像フォーマット、これは何で判断しているのでしょうか? それが、 Acceptヘッダー なんです。 ↓ Acceptヘッダーとは? HTTP の Accept リクエストヘッダーは、クライアントが理解できるコンテンツタイプを MIME タイプで伝えます。 参照元: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Accept 実際にChromeでリクエストヘッダーのAcceptを見ると 「image/webp」の記載 があります。 Next.jsではこの値を見て、WebPで返すかどうかを判断しているわけですね。 公式ドキュメントにも以下の記載がありました。 Good to know: ~ 省略 ~ If you self-host with a Proxy/CDN in front of Next.js, you must configure the Proxy to forward the Accept header. 参照元: https://nextjs.org/docs/app/api-reference/components/image#formats ジョブサーチでは、 Next.jsの動作環境にAcceptヘッダーを転送していなかった ため、WebPのブラウザがフォーマットに対応しているか判定できず、そのままのJPEGフォーマットで返していたようです。 それでは、実際にAcceptヘッダーを転送して、フォーマットの変換をするとどうなるのか、検証環境で動作させてみました。 すると、 元画像(JPEG, 2000×1333):120KB Acceptヘッダーなし(JPEG, 916×611):41KB Acceptヘッダーあり(WebP, 916×611):20KB ということで、 画像の容量が半分 になりました!(圧縮されすぎでは?) 見た目上は変化がわからないのに、これはすごいですね! 最後に next/imageで画像の最適化をすることで、ページの読み込みが早くなったり、他にも視覚的な安定性が得られたりといったメリットがあります。 SEOにおいてもメリットがあるので、せっかくNext.jsを使用しているなら、動作しているのかちゃんと確認しないとなと思いました。 参考 https://nextjs.org/docs/app/building-your-application/optimizing/images https://nextjs.org/docs/app/api-reference/components/image#formats https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Accept
アバター
はじめに マイナビジョブサーチのWebチームのT.Yです。 みなさんは何のエディタを使っていますか? 私はVsCodeを使っているのですが、もう少し使いやすくならないかな〜、見やすくならないかな〜、と思ったことはないでしょうか? 思ったことがあるそんなあなたに、悩みを解決してくれるかもしれない設定をいくつか紹介します。 タブを多段表示する 設定:ワークベンチ>エディターの管理:Wrap Tabsにチェック setting.json:"workbench.editor.wrapTabs": true 新規タブを常に新しいタブとして表示 設定:ワークベンチ>エディターの管理:Enable Previewのチェックを解除 setting.json: "workbench.editor.enablePreview": false ファイルのインデントの表示幅を変更 設定:ワークベンチ>外観>Tree:Indent(デフォルト8) setting.json: "workbench.tree.indent": 20 ファイルのインデントにガイド線を表示 設定:ワークベンチ>外観>Tree:Render Indent Guides setting.json: "workbench.tree.renderIndentGuides": "always" ファイルのインデントのガイド線の色を変更 設定:ワークベンチ>外観> Color Customizations(setting.jsonで変更) setting.json:"workbench.colorCustomizations": { "tree.indentGuidesStroke": "#555555" // Default is #555555. }, フォルダを常に階層で表示する 設定:機能>エクスプローラー>Compact Foldersのチェックを解除 setting.json: "explorer.compactFolders": false 行番号の色と、カーソル行の行番号の色を変更 設定:ワークベンチ>外観> Color Customizations(setting.jsonで変更) setting.json:"workbench.colorCustomizations": { "editorLineNumber.foreground": "#7bff00", // 行番号 "editorLineNumber.activeForeground": "#ff0000", // カーソル行の行番号 }, 最後に いかがでしたでしょうか? 他にもたくさんの設定があるので、他の設定も色々と試してみてより良い開発環境にしてみてください。
アバター
今回はAPIのモックを用いたユニットテストとE2Eテストについて実際のコードを使いながら紹介しようと思います。 モックを用いたユニットテストの概要 モックとは まずモックとは何かについて説明します。 テストしたい関数が他のクラスに依存していることはよくあると思います。 例えば、SNSのとあるユーザーの投稿を取得するAPIのサービスクラスは投稿が公開か未公開か確認のために投稿のレポジトリクラスに依存し、またユーザーの存在を確認するためにユーザーのレポジトリクラスにも依存しています。 この状況下において、モックを使わずにUTを実装すると、ユーザーのレポジトリクラスのUTが失敗した場合、サービスクラスのUTも失敗しているということになります。そのため、原因特定に時間がかかります。 モックを使って実装すると、依存しているクラスや関数が想定通りの挙動をするように設定できるため、ユーザーのレポジトリクラスのUTが失敗した場合でも、サービスクラスのUTは成功します。そのため、瞬時にユーザーのレポジトリクラスのみでバグが生じていることがわかります。 つまり、モックとはUTの責任範囲を明確にし、 UTを実装しやすくする存在です。 依存注入 ただ、注意しなくてはいけないのは UTの対象関数の内、UTが制御できるのは対象関数の呼び方のみであるということです。 つまり、モックを使ってUTを制御するためには、モックするクラスを関数またはクラスの引数に設定する必要があります。 そのため、対象関数またはクラスの引数はクラスを注入できるように実装する必要があります。 これを 依存注入 と呼びます。 モックを用いたUTの実例 モックや依存注入について説明が終わったため、実際のコードを使って説明したいと思います。 今回はSNSのとあるユーザーの投稿を取得するAPIとそのUTコードを実装しました。 コントローラー コントローラーのコードは以下のようになっています。 get_post_info 関数の引数にサービスクラスを依存注入しています。 / controllers / get_post_controller . py < code >from dependency_injector . wiring import Provide , inject from fastapi import APIRouter , Depends from app . api_schemas . get_post_schema import ( GetPostRequest , GetPostResponse , GetPostSchema ) from app . core . container import Container from app . services . get_post_service import GetPostService router = APIRouter () @ router . get ( " /posts/ {post_id} " ) @ inject def get_post_info ( get_post_request : GetPostRequest = Depends (), service : GetPostService = Depends ( Provide [ Container . get_post_service ]), ) -> GetPostResponse : if post := service . get_post_info ( get_post_request . post_id , get_post_request . user_id ): return GetPostResponse ( result =True , post = GetPostSchema ( title = post . title , description = post . description ), ) return GetPostResponse ( result =False , post =None ) </ code > これに対するUTコードは以下のようになっています。 mock_get_post_service 関数でサービスのモックを作成し、各テストケースで利用しています。 コントローラーの get_post_info 関数の中で使うサービスのメソッドの返り値をモックで設定することで関数内の条件分岐を制御しています。 / tests / controllers / test_get_post_controller . pyfrom unittest . mock import MagicMock import pytest import requests from app . api_schemas . get_post_schema import ( GetPostRequest , GetPostResponse , GetPostSchema ) from app . controllers . get_post_controller import get_post_info from app . models . post import PostTable from app . models . user import UserTable from tests . base_test import BaseTest @ pytest . fixture () def mock_get_post_service (): return MagicMock () def test_get_post_succeeds ( mock_get_post_service ): mock_get_post_service . get_post_info . return_value = ( PostTable . test_public_post_by_user1_data () ) request = GetPostRequest ( post_id = 1 , user_id = 1 ) response = get_post_info ( get_post_request = request , service = mock_get_post_service ) assert response == GetPostResponse ( result =True , post = GetPostSchema ( title = PostTable . test_public_post_by_user1_data (). title , description = PostTable . test_public_post_by_user1_data (). description , ), ) def test_get_post_fails ( mock_get_post_service ): mock_get_post_service . get_post_info . return_value = None request = GetPostRequest ( post_id = 1 , user_id = 1 ) response = get_post_info ( get_post_request = request , service = mock_get_post_service ) assert response == GetPostResponse ( result =False , post =None ) サービス サービスのコードは以下のようになっています。 サービスはクラスにまとめているため、クラスの __init__ 関数で依存するユーザーレポジトリクラスと投稿レポジトリクラスを注入しています。 これによってクラス内の関数のインスタンスから依存先を利用できるようになっています。 / services / get_post_service . py < code >from app . models . post import PostTable from app . repositories . post_repository import PostRepository from app . repositories . user_repository import UserRepository class GetPostService : def __init__ ( self , post_repository : PostRepository , user_repository : UserRepository ): self . post_repository = post_repository self . user_repository = user_repository def get_post_info ( self , post_id , user_id ) -> PostTable : if not self . user_repository . get_user ( user_id ): return None post = self . post_repository . get_post ( post_id ) if not post or not self . __is_visible ( post , user_id ): return None return post def __is_visible ( self , post : PostTable , user_id ) -> bool : if post . user_id == user_id : return True elif not post . is_private : return True else : return False </ code > これに対するUTコードは以下のようになっています。 get_post_service 関数で依存するユーザーレポジトリクラスと投稿レポジトリクラスをモックしています。 サービスの get_post_info 関数の中で使うレポジトリのメソッドの返り値をモックで設定することで関数内の条件分岐を制御しています。 tests / services / test_get_post_service . py < code >from datetime import datetime from unittest . mock import MagicMock import pytest from app . models . post import PostTable from app . models . user import UserTable from app . services . get_post_service import GetPostService @ pytest . fixture () def get_post_service (): return GetPostService ( post_repository = MagicMock (), user_repository = MagicMock ()) def test_non_existing_user ( get_post_service ): non_existing_user_id = 1 get_post_service . user_repository . get_user . return_value = None assert get_post_service . get_post_info ( 1 , non_existing_user_id ) == None def test_non_existing_post ( get_post_service ): non_existing_post_id = 1 get_post_service . user_repository . get_user . return_value = ( UserTable . test_not_login_user1_data () ) get_post_service . post_repository . get_post . return_value = None assert ( get_post_service . get_post_info ( non_existing_post_id , UserTable . test_not_login_user1_data (). id ) == None ) def test_get_private_post_from_non_author ( get_post_service ): get_post_service . user_repository . get_user . return_value = ( UserTable . test_not_login_user1_data () ) get_post_service . post_repository . get_post . return_value = ( PostTable . test_private_post_by_user1_data () ) assert ( get_post_service . get_post_info ( UserTable . test_not_login_user1_data (). id , PostTable . test_private_post_by_user1_data (). id , ) == None ) def test_get_private_post_from_author ( get_post_service ): get_post_service . user_repository . get_user . return_value = ( UserTable . test_not_login_user1_data () ) get_post_service . post_repository . get_post . return_value = ( PostTable . test_private_post_by_user1_data () ) assert ( get_post_service . get_post_info ( PostTable . test_private_post_by_user1_data (). id , UserTable . test_not_login_user1_data (). id , ) == get_post_service . post_repository . get_post . return_value ) def test_get_public_post_from_non_author ( get_post_service ): get_post_service . user_repository . get_user . return_value = ( UserTable . test_login_user2_data () ) get_post_service . post_repository . get_post . return_value = ( PostTable . test_public_post_by_user1_data () ) assert ( get_post_service . get_post_info ( PostTable . test_public_post_by_user1_data (). id , UserTable . test_login_user2_data (). id , ) == get_post_service . post_repository . get_post . return_value ) </ code > レポジトリ レポジトリのコードは以下のようになっています。 レポジトリはクラスにまとめているため、クラスの __init__ 関数で依存するDBを注入しています。 DBを注入することで開発環境のDBとは別のDBにデータを入れることができるため、開発環境のDBに影響を与えずに済みます。 これによってクラス内の関数のインスタンスから依存先を利用できるようになっています。 / repositories / post_repository . py < code >from pydantic import BaseModel from sqlalchemy . orm import Session from app . models . post import PostTable from app . models . user import UserTable class PostRepository : def __init__ ( self , db : Session ): self . db = db def get_post ( self , post_id ) -> PostTable : return ( self . db . query ( PostTable ) . join ( UserTable , UserTable . id == PostTable . user_id ) . filter ( PostTable . id == post_id ) . first () ) </ code > これに対するUTコードは以下のようになっています。 レポジトリはAPIの最奥層であるため、何もモックせずに実際にテスト用DBにデータを入れた上でUTを書いています。 テスト用DBとアプリ用DBの切り替えは base_test.py で行っていますが、ここでは省略します。 / tests / repositories / test_post_repository . py < code >from app . helpers . helper import get_datetime_now_db_format from app . models . post import PostTable from app . models . user import UserTable from app . repositories . post_repository import PostRepository from tests . base_test import BaseTest class TestPostRepository ( BaseTest ): @ classmethod def _initialize_repository ( cls ): cls . post_repository = PostRepository ( cls . db ) @ classmethod def _insert_data ( cls ): cls . db . add_all ( [ PostTable . test_public_post_by_user1_data (), UserTable . test_not_login_user1_data (), ] ) cls . db . commit () @ classmethod def test_get_existing_post ( cls ): response = cls . post_repository . get_post ( PostTable . test_public_post_by_user1_data (). id ) assert response . id == PostTable . test_public_post_by_user1_data (). id assert response . title == PostTable . test_public_post_by_user1_data (). title assert ( response . description == PostTable . test_public_post_by_user1_data (). description ) assert response . user_id == PostTable . test_public_post_by_user1_data (). user_id assert ( response . is_private == PostTable . test_public_post_by_user1_data (). is_private ) @ classmethod def test_get_non_existing_post ( cls ): non_existing_post_id = 2 response = cls . post_repository . get_post ( non_existing_post_id ) assert response == None </ code > E2Eテスト E2Eテストとはシステム全体をテストするものです。 E2Eテストを実行することで関数間の値の受け渡しが正常であることを担保し、UTのみではカバーできないところをカバーし、バグが発生する可能性を下げることができます。 このレポジトリはAPIしか作成していないため、フロントエンドの挙動までは確認しません。 ここでは特定のパスにリクエストが来てからレスポンスが返されるまでの一連の動作を確認します。 そのため、ここでも実際にデータを入れます。 / tests / controllers / test_get_post_controller . py < code >from unittest . mock import MagicMock import pytest import requests from app . api_schemas . get_post_schema import ( GetPostRequest , GetPostResponse , GetPostSchema ) from app . controllers . get_post_controller import get_post_info from app . models . post import PostTable from app . models . user import UserTable from tests . base_test import BaseTest class TestGetPostController ( BaseTest ): @ classmethod def _insert_data ( cls ): cls . db . add_all ( [ UserTable . test_not_login_user1_data (), PostTable . test_public_post_by_user1_data (), ] ) cls . db . commit () @ classmethod def test_e2e ( cls ): post_id = str ( PostTable . test_public_post_by_user1_data (). id ) user_id = str ( UserTable . test_not_login_user1_data (). id ) response = cls . client . get ( " /posts/ " + post_id , params = { " post_id " : post_id , " user_id " : user_id } ) assert response . status_code == 200 assert response . json () == { " result " : True , " post " : { " title " : PostTable . test_public_post_by_user1_data (). title , " description " : PostTable . test_public_post_by_user1_data (). description , }, } </ code > 最後に APIのモックを用いたユニットテストは、自分が書いたコードが仕様を正しく反映していることを迅速に確認するための非常に効率的な手法です。 一定期間が経過して仕様を忘れてしまった場合でも、仕様がコードとして明確に表現されているため、再確認が容易になります。 また、E2Eテストはユニットテストだけでは見落としがちな、全体の動作を確認するのに非常に有効です。 これにより、実際の動作環境での問題を早期に発見し、修正することができます。 ぜひ、これらのテスト手法を取り入れて、効率的にAPI開発を進めていただければと思います。 今後も、テストの重要性を意識しながら、より良いソフトウェアを作り上げていきましょう。
アバター
今回は、GoogleChromeの拡張機能をサクッと作ってみようと思います。 概要 Google Chromeの拡張機能を作成する際には、主に3つの重要要素があります。 ポップアップ(popup) バックグラウンドスクリプト(background) コンテントスクリプト(content script) ポップアップ ポップアップは、ユーザーがChromeの拡張機能アイコンをクリックしたときに表示されるものです。 参考: DeepL コンテントスクリプト コンテントスクリプトは、ユーザーが閲覧しているウェブページに直接挿入されるJavaScriptです。ページのDOMを操作したり、ページからデータを取得したりするために使用されます。 参考: Dimentions (青い線 と 19px*214pxのツールチップ) バックグラウンドスクリプト バックグラウンドスクリプトは、拡張機能の背後で動作し、イベント駆動型で特定のイベントが発生したときにのみ起動します。バックグラウンドスクリプトは、APIへのアクセスやデータの管理、他のスクリプトとの通信を担当します。 コンテントスクリプトを発火させる時にも使用していたりします。 必要なファイル この後のチュートリアルでは、 chrome-extension-v3-starter  を参考にして作成していきます。 このリポジトリのファイルを参考に細くしてみると大体こんな感じです。 my-chrome-extension/├── manifest.json // ---------------- 拡張機能の概要やファイル定義を書く├── logo/ // ------------------------ アイコン画像│ └── (ロゴ画像ファイルなど)├── popup/ // ----------------------- ポップアップを書く│ ├── index.html│ └── style.css├── foreground.js // ---------------- コンテンツスクリプトを書く└── service-worker.js // ------------ バックグラウンドスクリプトを書く チュートリアル (スクリーンショット撮れる拡張機能) ポップアップボタンまたは、ショートカットキーでスクリーンショットを取れる拡張機能を作成してみようと思います。  画面収録 2024-12-03 9.55.40.mov ※開発のための準備に関しては割愛します。( こちら を参照してください。) ①コマンドを実行して、スクリーンショットのバイナリをコンソールに出力させる manifest.json にコマンドを追加する バックグラウンドスクリプト (service-worker.js) にて、画面キャプチャを書く manifest.json には、コマンドを追加するのと、スクリーンショットをするために、permissionを更新しています。①~③で更新が必要な箇所をまとめて追加していきます。気になる人は、 公式ドキュメント などを参照してみてください。 manifest.json { "manifest_version": 3, "name": "Chrome Extension v3 Starter", "description": "A minimal example of a chrome extension using manifest v3", "version": "0.0.1", "icons": { "16": "logo/logo-16.png", "48": "logo/logo-48.png", "128": "logo/logo-128.png" }, "options_page": "settings/settings.html", "action": { "default_title": "Chrome Addon v3 Starter", "default_popup": "popup/popup.html" }, "permissions": ["tabs", "commands", "activeTab"], // update permission "host_permissions": ["*://*/*"], "background": { "service_worker": "service-worker.js" }, "content_scripts": [ { "js": ["foreground.js"], "matches": ["https://*/*"] // update url pattern } ], // ↓ add commands "commands": { "take-screenshot": { "suggested_key": { "default": "Ctrl+Shift+F", "mac": "Command+Shift+F" }, "description": "Take a screenshot of the current page" } // ↑ add commands }} バックグラウンドスクリプト(service-worker.js)も修正します。manifest.json で定義した、"take-screenshot"を確認して、スクリーンショットを行い、バイナリを出力しています。 service-worker.js // add ↓ chrome.commands.onCommand.addListener((command) => { if (command === "take-screenshot") { chrome.tabs.captureVisibleTab(null, {}, (image) => { console.log(image); }); } }); // add ↑ こんな感じになります。 ※コマンドがうまく反映されない人は、 ショートカットの設定 から設定してみてください。 ② コンテンツスクリプトを使用して、開いているWebサイトで、スクリーンショットを表示させる コンテンツスクリプトにて、messageAPIを用いて、スクリーンショットを発火させる。 発火したスクリーンショットをWebサイトにレンダリングする。 先ほど、バックグラウンドスクリプトの、take-screenshotコマンド実行時の関数を修正します。 内容としては、スクリーンショットを行い、そのデータをsendMessageで渡そうとしています。 service-worker.js // update ↓ chrome.commands.onCommand.addListener(async (command) => { if (command === "take-screenshot") { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" }, (dataUrl) => { if (chrome.runtime.lastError) { console.error("Error capturing screenshot:", chrome.runtime.lastError); return; } console.log("Screenshot captured, sending data URL"); chrome.tabs.sendMessage(tab.id, { type: "screenshot", dataUrl }); }); } }); // update ↑ sendMessageで渡ってきたものを検知して、コンテンツスクリプト(foreground.js)にて、イベントを発火させています。 表示しているWebサイトの左下に撮ったスクリーンショットを同じものをレンダリングしています。 foreground.js // add ↓ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { console.log("Received message in foreground script", message); if (message.type === "screenshot") { const img = document.createElement("img"); img.src = message.dataUrl; img.onload = () => console.log("Image loaded successfully"); img.onerror = (e) => console.error("Error loading image:", e); img.style.position = "fixed"; img.style.bottom = "10px"; img.style.left = "10px"; img.style.width = "400px"; img.style.height = "300px"; img.style.zIndex = "10000"; img.style.border = "2px solid #000"; document.body.appendChild(img); } }); // add ↑ コマンドを実行すると、下記のよう左側に表示されます。 ③ポップアップから実装したスクリーンショットの機能を発火させる ポップアップのHTMLとJSを修正 バックグラウンドスクリプトで、スクリーンショットの機能を発火させるように設定 ポップアップ用のHTMLを更新します。JSのインポートとボタンの追加を行っています。 popup/popup.html <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <link rel="stylesheet" href="popup.css"> <title>Chrome Addon v3: popup</title> <script src="/popup/popup.js"></script> <!-- add import --> </head><body> <button id="take-screenshot">キャプチャする</button> <!-- add button --> </body></html> 新しくJSを追加します。追加したボタンにクリックイベントを付与して、take-screenshot のメッセージを送るように設定します。 popup/popup.js // ↓ add click event document.addEventListener('DOMContentLoaded', function () { document.getElementById('take-screenshot').addEventListener('click', () => { console.log('take-screenshot'); chrome.runtime.sendMessage({ name: 'take-screenshot' }); });}); バックグラウンドスクリプトで、新しく関数を追加します。take-screenshot のメッセージを感知して、スクリーンショット機能を発火させています。(中身の処理は②で書いた関数とほとんど同じ) service-worker.js // ↓ add chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.name === "take-screenshot") { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); chrome.tabs.captureVisibleTab(tab.windowId, { format: "png" }, (dataUrl) => { if (chrome.runtime.lastError) { console.error("Error capturing screenshot:", chrome.runtime.lastError.message); return; } console.log("Screenshot captured, sending data URL"); chrome.tabs.sendMessage(tab.id, { type: "screenshot", dataUrl }); }); } }); // ↑ add chrome.commands.onCommand.addListener(async (command) => { ... こんな感じで実行すると、ポップアップからもスクリーンショット機能を発火することができるようになります。 画面収録 2024-12-03 9.55.40.mov チュートリアルは以上です。 まとめ 概念を理解すると意外と簡単に作れるので、今後業務効率化できそうなところがあれば、自作してみようと思います。 最後まで見ていただきありがとうございました🙇
アバター
はじめに 皆さんこんにちは! ラスベガスで開催された AWS の re:Invent にマイナビからも 4 名のエンジニアで現地参戦してきました! re:Invent がどういったイベントなのか、Keynote から SWAG、認定者ラウンジまで、実際に参加してみたレポートをご紹介します。 AWS re:Invent とは? AWS re:Invent  は Amazon Web Service(AWS)社が年に一度開催する、AWS 最大規模の技術カンファレンスです。2012 年から開催されており、今年で 13 回目の開催となります。 多くの参加者が注目する Keynote(基調講演)から、2,000 以上の技術セッションへの参加、Expo(展示会)などさまざまなプログラムが用意されており、毎年イベント中に多くの新サービスや、新機能が発表されます。 イベント概要 開催日程:12 月 2 日(月)- 12 月 6 日(木)の 5 日間 開催地 :アメリカ ネバダ州 ラスベガス 参加者 :現地参加者:約 60,000 名(うち日本からの参加者:約 2,000 名) 参加費用:$2,099 会場 引用: https://reinvent.awsevents.com/experience/plan-your-trip/ キャンパス(会場)はラスベガスの中心地のホテルで、全部で 6 つあります。 会場のホテルがかなり大きいため、ホテル間の移動は無料のシャトルバスまたはモノレールを使います。 基本的にはシャトルバス、人が多く集まるイベントが開催されるタイミングでの移動はモノレールでの移動をオススメします。 プログラム ここからは re:Invent で実際に体験したプログラムを紹介していきます。 Keynote(基調講演) EXPO(展示会) 技術セッション SWAG AWS 認定者ラウンジ Keynote(基調講演) Keynote とは、AWS のトップリーダが今年注目の新サービスや新機能についての発表を行う講演で、開催期間中は毎日行われます。 オンラインでの視聴も可能で、動画配信もされています。 Keynote現地参加レポート 今回はその中でも最注目だった Day2 の Keynote、AWS の CEO である Matt Garman の講演に参加しました。 Matt Garman は EC2 の最初のプロダクトマネージャーで、24 年の 6 月に AWS の新 CEO に就任したため、今回が初の Keynoteとなります。 朝 8:00 から始まる講演でしたが、会場に入りたかったので1 時間半ほど前から並びました。 メンバーと 6:00 に集合で MGM からローカルバスに乗り(シャトルバスが出ていない時間)移動。 6:45 に講演会場の Venetian に到着して待ち列へ。 7:00 には入場規制されていたので、開始 1 時間前には並んでいないと会場には入れないようです。 早朝とは思えない人の多さ 7:15 すぎにメイン会場へ。ここもとにかく広かったです。 運よく先頭の方に並んでいたので、会場の真ん中のあたりに着席できました。 ノリノリの DJ がいる会場 講演開始の 8:00 になるとオープニングムービーが流れた後、AWS の CEO である Matt Garman 氏が登場し、会場の盛り上がりも最高潮に! 登場の瞬間を激写することに成功 この後 3 時間ほどかけて、たくさんの新サービスや新機能が発表され、発表のたびに拍手喝采が起こっていました。 Amazon EC2 Trn2 インスタンス/ Trn2 UltraServers Amazon S3 関連 Amazon S3 Tables Amazon S3 Metadata Amazon Aurora DSQL Amazon DynamoDB global tables Amazon EKS Auto Mode Amazon EventBridge と AWS StepFunctions のプライベート接続 Amazon Nova Amazon Bedrock 関連アップデート Amazon Q 関連アップデート etc... Amazon の CEO 兼社長である Andy Jassy も登壇 早朝から 3 時間の講演はかなりハードでしたが、AWS のスケールの大きさや影響力を肌で感じることができました。 EXPO(展示会) EXPO とはいわゆる企業展示会場のことで、AWS のエキスパートやパートナー企業の方と直接お話ししたり、最新の情報収集をすることができます。 この会場もとにかく広いです。日本の企業もちらほら見かけました。すべてのブースを回ろうと思うと数時間かかるのではないでしょうか... EXPO は Day 2 の 16 時からの開場で、初日はフードやドリンクの提供もあり、ちょっとしたパーティーのようでした。 EXPO 会場の様子 企業ブースがたくさん 気になる企業でサービスについて詳しく話を聞くのもよし、いつもお世話になっている企業のブースに行ってグッズをもらうのもよしです。 頂いたぬいぐるみ自慢 技術セッション イベント中には3,000 以上の技術セッションが開催されます。 セッションタイプや対象業種/業界、レベルが分かれており、予約または事前に並んで(Walk-Up Seats)参加します。 セッションタイプ 主要なセッションについて説明します。 Breakout Session 1 時間の講義形式で特定のトピックについて深掘りする。のちにオンデマンドで動画配信あり Workshop 2 時間の実践型セッション。個人やグループでのハンズオン Chalk Talk 10~15 分の講義の後、45~50 分の質疑応答を行う。少人数での開催 Builders Session 1 時間の小グループセッション。AWS エキスパートとのハンズオン GameDay ゲーム形式のセッション。 レベル 100 レベル(入門)から 400 レベル(エキスパート)まで難易度が分かれているので、自分のスキルレベルや興味に合わせてセッションを選択できます。 参加してみて セッション選びが難しい かなりの数なので、開催場所やスケジュールを考慮しながら自分に合ったセッションを選択するのが難しかったです。 事前にセッションカタログの絞り込み機能を活用しつつ、タイトルだけではなく詳細まで確認して、よくよく検討しておけば良かったなぁと反省しました。 予約なしでも意外といける 人気そうなセッションでも、30〜45 分前に並んでいれば予約なしで入れました。 予約いっぱいでも、直前に席が空いて予約できたりもしたので、諦めずにチェックするのも一つの手です! 予約すべきセッション Breakout Session は配信があるので、それ以外のグループワークや質疑応答ができるセッションに参加するのがおすすめです。 特に GameDay のようなゲーム形式のセッションは、すぐ予約が埋まってしまうので、参加したい場合はセッション予約開始時にすぐ予約するのが良いです。(私はぼけっとしていて予約できなかった) SWAG SWAGとは企業のオリジナルグッズのことです。 re:Invent 参加者全員がもらえる AWS のグッズから、EXPO に出展している企業のグッズまで様々なアイテムが手に入ります。 AWS の認定を取得している人だけがもらえるグッズもありました。 SWAG 受け取り会場 戦利品のご紹介 企業ごとに特色がありますね。 AWS パーカーや T シャツはもらってそのまま着ている人が結構いました。 持って帰るのが大変な量 認定者ラウンジ 認定者ラウンジとは、AWS 認定資格取得者だけが入ることのできるラウンジです。 資格を 1 つでも保有していると入ることができます。 入場するためには、受付で Credly での認定バッジを提示する必要があるので、あらかじめ準備しておくのが良いと思います。 ラウンジの中には作業スペースがあり、ドリンクや軽食が提供されています。 私がステッカーをもらったり撮影したりと喜んでいる隣で、みなさん黙々と作業されてました。 照明がオシャレ 広々とした作業スペースで黙々と作業する人々 おわりに AWS re:Invent 2024 参加レポート、いかがだったでしょうか。 なんとなくでも雰囲気を感じ取っていただけたら幸いです。 私自身、かなり開発や語学学習のモチベーションが上がりました。 興味のある方はぜひ来年参加してみてください! 参加メンバーと会場にあった大きな黒板の前でパシャリ 📷✨ マイナビロゴはどこにあるでしょう?👀 イベントURL: AWS re:Invent 2025 | December 1 – 5, 2025
アバター
バケットくんとは バケットくん(正確な日本語訳不明・英語: Buckets)は、S3のマスコットキャラクターです。 AWS Storage Blog の下部に紹介文が記載されています。 re:Inventに参加しないと知りえない情報を集めてみました。 身体的特徴 バケットくんが羽織っているダウンにはS3テーブルとS3メタデータという今回発表された機能のワッペンのようなものが付いており、AWS社内で予め準備されていたことが伺えます。 後頭部に存在する取っ手のような部分を動かそうとしている人を見かけましたが、動かせず固定されていました。触られたことに気づいたようでしたが、バケットくんには首が存在しないため後ろを確認するのに少しあたふたしている様子でした。 生息域 毎年開催されるre:Inventのイベント会場内に出現されることが知られています。今回マイナビのエンジニアはEXPO会場・KeyNoteの会場近くにいるところを目撃しました。また、Wynnホテルなど、会場内の他の場所で目撃したという情報も耳にしました。 EXPOのAWSブースの方に、バケットくんに会ったことがあるか聞いたところ、今回は3日間ブースに立っているが見たことが無いと言っていました。バケットくんに会いたい方は、能動的に探すことが求められそうです。 バケットくんの周りには必ず人間が2名程度付き添い、一緒に行動をしていました。 性格 気さくな性格。女性と撮影する際は膝をつくことも。 撮影用にダンスもしてくれます。大きめの上半身とは裏腹に、意外と身軽です。 関連情報 マイナビで入手することはできませんでしたが、re:Invent2日目までは、バケットくんに会うとステッカーをもらえたらしいです。そのため、ステッカーを集めたい人は早めにバケットくんを探し始めたほうがよいです。 EXPOのAWSブースでは、バケットくんの亜種?のステッカーが貰えました。 最後に マイナビ社内向けに本記事を公開したところ、なんとお世話になっているAWSの担当者さまに「 AWS社内に展開したい 」とお声がけをいただきました!バケットくんは、多くの人に愛されるキャラクターのようです。
アバター
はじめに 皆さん、こんにちは!デジタルテクノロジー戦略本部(以下デジ戦)のT.Uです。2024年11月にAI・ディープラーニングに関する資格「JDLA Deep Learning for GENERAL(通称:G検定)」を受験しましたので、今回は学習方法や受験の内容についてお伝えできればと思います。少しでも参考になれば嬉しいです。 資格取得にあたり、マイナビのデジ戦社員は、会社の制度を利用することで勉強~受験までを実質無料でできます。(マイナビ&デジ戦最高!) 資格・試験の概要 資格・試験名 JDLA Deep Learning for GENERAL(G検定) 資格・試験の説明 G検定とは、一般社団法人日本ディープラーニング協会(JDLA)が実施する、AI・ディープラーニングの活⽤リテラシー習得のための検定試験です。(公式ページより) 試験日 2024年11月09日(土) 試験の形式 CBT(自宅受験可)形式 120分160問 受験に際して 2024年第6回の試験からシラバスと問題数に変更があります。 ネット上には変更前の情報が多いためご注意ください! 資格取得の背景 マイナビバイトではGoogleのDeep learningやAIの技術を取り入れようという取り組みを進めています。 そのため技術の背景や歴史を知ることで、より業務理解を深めることを目的に取得しました。 学習方法 使用した教材 Udemy デジ戦の社員が無料で利用できるオンライン学習ツールです! 「【全200問の模擬試験付き】G検定に合格するための集中講義!人工知能(AI)について体系的に学ぶ(初心者向け)」 公式テキスト 「深層学習教科書 ディープラーニング G検定(ジェネラリスト)公式テキスト 第3版」 公式問題集 「徹底攻略ディープラーニングG検定ジェネラリスト問題集 第3版」 オンライン問題集 「 Study-AI 」 学習期間 約3か月(30時間程度) 学習スケジュール 最初の1か月半で公式本、Udemyをざっと見る。 とりあえず見る。何となく見る。興味を持って見る。何はともあれ見る。 残りの1か月半で問題集をとりあえず解いてみる。 間違った箇所は「×」、何となくしか分からなかった箇所は「△」を付けておき、その部分は解説→(それでも意味が分からない場合は)公式本を読む。 土日など時間を取れるときに模擬問題をやってみる。 私は問題集の模擬問題をやりましたが、Udemy、Study-AIでも良いと思います。 まずは時間を気にせず解いてみる→時間を計って解く。 時間以内に解けるように頑張る。 試験は120分で160問(単純計算で1問当たり45秒)なので、時間配分や120分160問の恐ろしさを身体で理解する。 学び・気づき 「実質無料」で受験可能!?とは 結論、 勉強はデジ戦の「書籍購入補助制度」で購入した書籍とUdemy 受験は自宅なので交通費不要 受験料はマイナビの「IT・ビジネス資格取得支援」制度を利用する ことで、資格取得に関わる費用が基本的にかからなくなります! 資格取得支援制度に関しては、合否に関わらず受験資格ごとに1回は支給されるため、安心してチャレンジできますね。 p.s. 今回紹介しているオンライン問題集のStudy-AIについても、現在はβ版ということで、無料で利用することができました。(会員登録は必要) 勉強に関しては範囲が広く、量が多いです。(公式本は448ページ、Udemyも13時間くらいあります。) 全てを真剣に調べながら進めると試験に間に合わないため、「こういうものなんだ」程度で一旦納得し、興味がある部分だけ詳しく調べるという進め方をしました。 試験に関しては、PCを使った自宅受験ができます。 試験内容は、どれだけすぐ解ける問題に回答して、次に行けるかが合格のカギだと思います。 問題文がかなり長いものもあるので、読解力も併せて必要な気がします。 かくいう私も全て解き終わった時には、残り時間が15秒でした。(見直しほぼできず ..> <.. ) 一応ですが、全く分からない問題は「A」といったルールを事前に決めておきました。 結果についてはその場では分からず、受験後3~4週間でメールが届きます。 私の場合は、2024年11月25日(月)に合否メールが届きました。 メールには「合否結果」「総受験者数と合格者数」「各分野別の得点率」等が記載されています。 合格証やオープンバッチなどの発行は、もう少しかかるようです。(2024/11/26時点) 資格取得支援制度で必要になる受験料の領収書に関しては、合否メールとは別で届きます。 ▼2024/12/10 追記 合格証書も無事届きました。 さいごに 今後もし取得を目指している方がいれば、 内容も多く大変な資格かと思いますが、合格率は高い資格ですので合格目指して頑張ってください!
アバター
はじめに デジタルテクノロジー戦略本部とは 2022年10月、事業部門ごとに点在していたITとデジタルマーケティングの担当部署を1つに統合する形で『デジタルテクノロジー戦略本部(以下、デジ戦)』が誕生しました。 それまではドメインごとにサービスの開発・運用・マーケティングを行うという縦割りの構造で事業を展開していましたが、それだけでは予測不能と言われるこれからの時代に対応することは困難です。そこで、データやシステム、人材などアセットの最適化を図り、より大きなスケールでイノベーションを推し進めていく組織が作られました。 デジコネとは デジコネとは、デジ戦に所属するメンバーの中で、 「他のチームの人が何をしているのか分からない」 「もっと他チームのマーケターと交流を図りたい・・・」 といった悩みを持った方や、組織自体への課題に対し、マーケター間で交流を図る機会の1つとして発足したプロジェクトです! 詳しくはこちらの記事をご確認ください! ###card_post_id=1068### 内容:第6回開催内容に関して テーマ:「マイナビが競合に打ち勝つためのマーケターの心得とは?」 登壇者:ノバセル株式会社 田部 正樹様  ノバセル株式会社 は、「マーケティングの民主化」をビジョンに掲げ、「指名検索」を重要指標とし、当社が独自に開発した「ノバセル for TV」や「ノバセル for デジタル」などの効果分析ツールや、第三者目線で評価・改善を行う「オーディットサービス」など、オンオフ横断で多様なマーケティングサービスを展開しています。 マーケティングの定義 マーケティングの基礎的な考え方や重要なポイントをご紹介いただきました マーケティングとは、商品を売ることではなく、売れる仕組みを作ること。 具体的には、プロダクト、価格、場所、プロモーションの4つの要素(4P)を組み合わせて、顧客に選ばれるための戦略を立てることを指す。 マーケティングの目的は顧客に選ばれて当たり前の状態を作ることであり、これがマーケティングの本質 だと解説いただきました。 また、他社にはない独自の特徴(POD: Point of Difference)を見つけ、それを強調することが重要であることや、カテゴリー内でのポジショニング(カテゴリーエントリーポイント)を確立することが重要であるという内容に関しても講義いただきました。 グループワーク 勉強会後半では、自分たちのサービスに関するマーケティング戦略を考えるワークショップに取り組みました。各グループは、サービスの存在理由(Why)、対象顧客(Who)、強み(What)、価値(Value)等を明確にするためのフレームワークを使用しました。 田部様より、複数サービスにおけるマーケティング戦略の貴重なフィードバックをいただき、大変有意義なワークショップとなりました。 感想 事後アンケートではこんな感想をいただきました! 「再認識させられること、ハッとさせられること、どちらも盛り沢山で有意義でした」 「事業が成長する過程や、自社の訴求ポイントを整理するための手法を学ぶことができた」 「同じサービスのメンバーで、今改めてマーケティングの話を聞き、ブランドについて話し合う。とても貴重な機会になりました」 感想にもある通り、改めてフレームワークを活用し、サービス内のメンバーと時間とって話し合うことで再認識できたことや気づきを得ることができたという意見が多かったです。今後も皆様の業務に役立つよう、デジコネの開催を進めてまいります。 過去回はこちらから! 第1回 「Z世代の心を掴むマーケティングとは?」 第2回 「Meta/マーケ担当が抑える最新トレンドとは?」  第3回「ADKマーケティング・ソリューションズ/明日から認知マーケターになれる?TVCMプランニングのすべて」 第4回「株式会社イー・エージェンシー/データの力を引き出す!GA・Looker Studio・BigQueryの基礎知識」 第5回「株式会社FaberCompany/LP大解剖!CVR改善!LPのPDCAの回し方
アバター
はじめに お疲れ様です。 Webマーケティング課のY.Dと申します。 専門はGA4などのマーケティング分析基盤を中心に仕事しています。 約一年ほど、マイナビが出資しているインドネシアの「企業×フリーランサー」のマッチングサービス「Sribu」に対して、私含めたデジ戦プロモーション部隊の3人でマーケティングサポートを行って参りました。 https://www.sribu.com/en ★買収時のニュースなど https://www.nikkei.com/compass/content/PRTKDB000001540_000002955/preview https://www.mynavi.jp/news/2021/12/post_32688.html サポート内容 Sribuマーケティング支援の詳細内容は以下です。 ①週一の定例会議 Sribu×グローバル経営×デジ戦で会議を行います。 ここではマイナビインドネシアが通訳となり英語と日本語が入り混じる会議となっています。(英語勉強しろ) ②広告戦略の立案 Sribuではオンライン広告でGoogle広告とMeta広告を運用しており、適切なキャンペーン構造や投資金額を立案します。Sribu側CMOと打ち合わせをしながら最適な広告効果を狙います。 ③計測設定 GA4やGTMなどの計測に関与するツールに関して技術的な支援をします。マイナビでも起きている数値が取れない・間違っているみたいな問題の解消をめざします。 なぜ出張したのか?【目的など】 オンラインでの会話を重ねてきた本件でしたが以下の理由で今回の話へとつながりました。 つまり、「直接会話をするため」ということです。 これは外国人だからというわけではないと思うのですが、実際に会ったこともない人・リアルでの交流がない人からの意見は基本素直に受け取ってもらえません。 人に話を聞いてもらうためには、まずは信頼関係を構築しコミュニケーションを深めることが最優先であると今回学びました。 出張場所(ジャカルタ,Sribuオフィスなど) 羽田空港からジャカルタまでは片道7時間半で、赤道直下の南国でした。日本よりも直射日光が鋭く、冷房の効きもより強いものでした。穏やかな時間がゆったり流れている雰囲気でした! ★渋滞 なんと・・・インドネシアには 信号がありません。 あまりも交通量が多く、雰囲気で横切ったり、合流したりしています。 ★辛い 食べ物がすべて辛いです。下記は適当に入った街のごはん屋さん。 伝統的な「ナシゴレン」などもスパイスの効いた激辛料理です。(個人的には) ※水道水が飲めない(というかアブナイ)ため氷などには注意が必要 ★Sribuオフィス ベンチャーキャピタルを感じるオフィスでした。 出張スケジュール 全体で3泊4日の出張スケジュールは以下です。 10月30日(水) 移動日なので割愛。 10月31日:木曜日 Ryan(CEO)挨拶:9:00-9:30 Alex(CMO)挨拶:9:30-10:00 マーケチームとのMTG①:10:00-12:00 自己紹介、質疑応答、問題点の整理、解決策のディスカッション [宿題] 自己紹介スライド(簡易)+Sribuへの質問を準備 一緒にランチ:12:00-13:00 各自手を動かす作業時間(都度質問対応・モニタリング):13:00-15:00 Staffinc訪問(webマーケチームとの情報交換):16:00-17:00 Sribuではこんなことやっているという共有 Staffincはどんな感じで運用しているのかのヒアリング 11月1日:金曜日 マーケチームとのMTG②:9:00-10:00 昨日の振り返り、今日のアクション明確化 各自手を動かす(都度質問対応・モニタリング):10:00-12:00 一緒にランチ:12:00-13:00 マーケチームとのMTG③:13:00-15:00 改めての問題整理、解決策のディスカッション、帰国後のそれぞれのアクション明確化 CEO,CMOへの成果報告:15:00-16:00 マイナビインドネシア訪問:16:30-17:30 11月2日(土) 帰宅日なので割愛。 出張での成果 課題の再発見 :直接の対話を通じて、Sribuの計測や広告キャンペーン設計の不整合、予算配分の課題など、遠隔では把握しにくかった運用上の問題を特定できました。 集中した支援による意思決定への貢献 :2日間にわたりデジ戦側の知見を活用し、P-MAXキャンペーンや計測精度の改善策を提案し、Sribu側の意思決定プロセスに貢献できました。 戦略転換の合意形成 :繁忙期戦略として、CPA悪化を一時的に許容してCV数の最大化を目指す方向へ転換する方針を共有し、獲得広告の予算の合意を得ました。 基本的には初日にヒアリングを行い、2日目に最終成果としてデジ戦チームからの提案を行いました。 以下の資料が最終的なアウトプットと言えます。 ①ヒアリングまとめ ②改善提案(※抜粋) 上記とても真面目に書いておりますが、一番は「会話ができたこと」が大きいです。 最終日には集合写真を撮りました。言葉の壁はありましたが、お互いの信頼関係を築く機会となったと感じております。 感じたこと 今回の出張は3人で行きましたので、それぞれの感想を載せておきます! 日本の基準よりも細かい設定確認などがされておらず、現地で会話しながら事実確認をしないと現状を把握することが難しい。特に計測に関しては基礎的なパラメータの整理など日本では共通ルールとなっている部分も個人(企業)独自のルールとなっていたりして会話しながらすり合わせないと修正案を出せないと感じた。 広告配信において、これまで改善インパクトが大きいであろう提案したものの、内容のすべてが反映されているわけではなかった。今回の出張の間に、各キャンペーンの配信目的や意図、改善アクションにつなげる際にどこが障害になっているのかなど、現地で直接コミュニケーションをとることで、どのように提案したら納得してもらえるか考えることができた。 デジ戦管轄のサービスは既にサービスとして成り立っているものがほとんどだと思いますが、今回の支援はまだ小さなサービスをいかに成長させるかという視点で、WEB広告施策も本当にゼロからの取り組みとなったので、戦略的マーケティングの重要性を改めて知るいい機会になりました。たった「10万円」の投資でサービスを大きく改善できる可能性を持っているということ、マーケティングサイドの目標未達が致命傷になり得ることなど、マーケターとして常に感じなくてはいけない「責任」の重さを実感しました。 まとめ 海外でビジネスの経験ができたことは非常に視野が広がる有難い経験でした。 また、英語勉強を本格的にしないとまずいなと言う感覚になりました。AIでの同時通訳も技術的にはありますが、よりリアルタイムでラフにコミュニケーションを取ることが如何に重要か気づきを得られたと思っております。 グローバル経営企画×デジ戦との連携強化は全社にも大きなメリットがあると思います。 今回の出張をきっかけで新たな東南アジア企業のマーケティング支援の話も複数上がったとのことで、こういった案件が各所で起こっている状態が起きればデジ戦グローバル化も現実になるかも・・・? 読んでいただきありがとうございました。
アバター
はじめに デジタルテクノロジー戦略本部とは 2022年10月、事業部門ごとに点在していたITとデジタルマーケティングの担当部署を1つに統合する形で『デジタルテクノロジー戦略本部(以下、デジ戦)』が誕生しました。 それまではドメインごとにサービスの開発・運用・マーケティングを行うという縦割りの構造で事業を展開していましたが、それだけでは予測不能と言われるこれからの時代に対応することは困難です。そこで、データやシステム、人材などアセットの最適化を図り、より大きなスケールでイノベーションを推し進めていく組織が作られました。 デジコネとは デジコネとは、デジ戦に所属するメンバーの中で、 「他のチームの人が何をしているのか分からない」 「もっと他チームのマーケターと交流を図りたい・・・」 といった悩みを持った方や、組織自体への課題に対し、マーケター間で交流を図る機会の1つとして発足したプロジェクトです! 詳しくはこちらの記事をご確認ください! ###card_post_id=1068### 内容:第5回開催内容に関して テーマ:「LPのPDCAの回し方」 登壇者:株式会社FaberCompany 岩本 庸佑様   ============ 株式会社FaberCompanyにてCROコンサルタントをされている岩本様をお招きし、LP制作にお けるPDCAサイクルの回し方について詳しくお話しいただきました。 Web広告におけるLPの重要性 LPはユーザーの検討フェーズに合わせた訴求やストーリー設計を行い、ターゲットを絞ったコンバージョン率最適化(CRO)を実現するための重要なページです。ここではFaberCompany様のサイトを例に、WEBサイトとLPの使い分けについて詳しく解説いただきました。 LP検証と分析の基本的な流れ 月並みですがPDCAを継続的に回すことが基本! 今回は②~③を重点的に解説いただきました。 現状把握から打ち手設計 気づきの抽出 現状把握のための手法を詳しくご紹介いただきました。下記から1つ又は複数の手法で「気づき(課題要素)」を抽出します。 数値的なアプローチ:GA探索レポートによる把握 簡易ユーザーテスト:身近な人にCV手前までを試して貰い、横から見る ヒートマップ分析:スクロール(ページの精読率)状況、クリック箇所 差分分析:競合他社との差分分析からテスト要素抽出 打ち手の設計 抽出した気づきを施策まで落とし込むプロセスをご紹介いただきました。最初はこのプロセスを丁寧に実施することが推奨とのことでした。 ヒートマップ分析を実践! マイナビサービスのLPを、ヒートマップを活用して上記プロセスを実践しました。短時間ではありましたが、多くの施策案が出され、岩本様から貴重なフィードバックもいただき、大変有意義なワークショップとなりました。 ▼実際にワークの中で出た気づき~打ち手 感想 事後アンケートではこんな感想をいただきました! 「まさにLP制作における成果について、課内で苦戦している最中だったのでとても参考になりました。」 「終了後同じチームになった方と各サービスのLP状況などをお聞きできて参考になりました。」 感想にもある通り、各サービスのLP運用状況を知るきっかけとなり、具体的な代理店へのオーダーにつながったというお話も伺いました!今後も皆様の業務に役立つよう、デジコネの改善を進めてまいります。 第1回 「Z世代の心を掴むマーケティングとは?」 第2回 「Meta/マーケ担当が抑える最新トレンドとは?」  第3回「ADKマーケティング・ソリューションズ/明日から認知マーケターになれる?TVCMプランニングのすべて」 第4回「株式会社イー・エージェンシー/データの力を引き出す!GA・Looker Studio・BigQueryの基礎知識」
アバター
はじめに はじめまして!23卒のエンジニアのR.Aといいます。 現在は主に社内システムのデバイス管理ツールの開発を行なっています。 現在はバックエンド側はGoを用いてレイヤードアーキテクチャとDDD(ドメイン駆動設計)に沿って開発を進めています。 開発していく中でレイヤードアーキテクチャやクリーンアーキテクチャなどは綺麗に書けるもののコードの量が多く、時間が取られてしまうという問題点がありました。 その問題を解決するために定型的なコードは自動生成したほうが業務効率化につながると思いコードを生成できるようにしました。 使ったライブラリとしては以下のものになります。 jennifer 概要 自動生成の概要としては以下のようになります。 プロジェクトのdomain配下に対象のentity構造体を作成しておく generator配下のmodelディレクトリに対象のmodelの構造体を配置する(sqlboilerだとスキーマからmodelが自動生成されるの楽です) make generate-backend-base-code Entity=”任意のエンティティ名” Model=”任意のモデル名” というコマンドを実行 interfase, di, repository, usecaseのコードが自動的に生成される レコードの新規作成関数、更新関数が自動生成される 以下が動画になります。 0:04くらいにコマンドを実行しています。 自動生成の中身の説明 各フォルダにソースコードを生成してくれる素となるものが入っています。 generator ┣ di ┃ ┗ gen_di.go // di生成 ┣ handler ┃ ┗ gen_handler.go // handler生成 ┣ models ┃ ┣ admin.go // 生成したいモデル ┣ repository ┃ ┣ gen_repo_impl.go // レポジトリ実体生成 ┃ ┗ gen_repo_interface.go // レポジトリインターフェース生成 ┣ usecase ┃ ┗ gen_usease.go // ユースケース生成 ┗ generator.go // main関数 コード生成の部分を全て書くと長くなってしまうので生成するときに実行されるmain関数と、ユースケースの生成部分について書きます。 generator.go(main関数) 以下のmain関数を実行することで各フォルダの関数を実行し、生成するという形をとっています。 func main() { // コマンドライン引数を定義 entity := flag.String("e", "", "Entity name") model := flag.String("m", "", "Model name") // コマンドライン引数を解析 flag.Parse() // エンティティ名を取得 e := *entity m := *model fileName := toSnakeCase(e) // handler作成 genhandler.GenHandler(e, fileName) // di作成 gendi.GenDi(e, fileName) // usecase作成 genusecase.GenUsecase(e, fileName) // repositoryinterface 作成 genrepository.GenRepoInterface(e, fileName) // repositoryimpl作成 genrepository.GenRepoImpl(e, m, fileName) } gen_usease.go (ユースケース生成) ここでモデル名とエンティティ名を使い、ユースケースのコードを生成しています。 難しそうに見えますがコードの文字を羅列していく感覚に近いので、ライブラリの仕様を理解してしまえばそこまで難しくはないです。 package genusecase import ( "strings" "github.com/dave/jennifer/jen" ) func GenUsecase(e string, fileName string) { // Usecase interface E := strings.Title(e) // エンティティ名の先頭を大文字に abbrUsecase := strings.ToLower(string(e[0])) + "u" abbrRepository := strings.ToLower(string(e[0])) + "r" f := jen.NewFile("usecase") f.ImportName("main/domain/entity", "entity") f.ImportName("main/domain/repository", "repository") f.Type().Id(E+"UseCase").Interface( jen.Id("Add"+E).Params(jen.Id(e).Op("*").Qual("main/domain/entity", E)).Error(), jen.Id("Update"+E).Params(jen.Id(e).Op("*").Qual("main/domain/entity", E)).Error(), ) f.Type().Id(E + "UseCaseImpl").Struct( jen.Id(E+"Repository").Qual("main/domain/repository", E+"Repository"), ) f.Func().Id("New"+E+"UseCaseImpl").Params(jen.Id(abbrRepository).Qual("main/domain/repository", E+"Repository")).Id(E + "UseCase").Block( jen.Return(jen.Op("&").Id(E+"UseCaseImpl").Values(jen.Dict{ jen.Id(E + "Repository"): jen.Id(abbrRepository), })), ) f.Line() f.Func().Params(jen.Id(abbrUsecase).Id(E + "UseCaseImpl")).Id("Add"+E).Params(jen.Id(e).Op("*").Qual("main/domain/entity", E)).Error().Block( jen.List(jen.Id("err")).Op(":=").Id(abbrUsecase + "." + E + "Repository").Dot("Add" + E).Call(jen.Id(e)), jen.If(jen.Id("err").Op("!=").Nil()).Block( jen.Return(jen.Id("err")), ), jen.Return(jen.Nil()), ) f.Line() f.Func().Params(jen.Id(abbrUsecase).Id(E + "UseCaseImpl")).Id("Update"+E).Params(jen.Id(e).Op("*").Qual("main/domain/entity", E)).Error().Block( jen.List(jen.Id("err")).Op(":=").Id(abbrUsecase + "." + E + "Repository").Dot("Update" + E).Call(jen.Id(e)), jen.If(jen.Id("err").Op("!=").Nil()).Block( jen.Return(jen.Id("err")), ), jen.Return(jen.Nil()), ) // Save to file f.Save("../application/usecase/" + fileName + ".go") } コマンド実行後に生成されるコードは以下のものになります。 package usecase import ( "main/domain/entity" "main/domain/repository" ) type MemberUseCase interface { AddMember(Member *entity.Member) error UpdateMember(Member *entity.Member) error } type MemberUseCaseImpl struct { MemberRepository repository.MemberRepository } func NewMemberUseCaseImpl(mr repository.MemberRepository) MemberUseCase { return &MemberUseCaseImpl{MemberRepository: mr} } func (mu MemberUseCaseImpl) AddMember(Member *entity.Member) error { err := mu.MemberRepository.AddMember(Member) if err != nil { return err } return nil } func (mu MemberUseCaseImpl) UpdateMember(Member *entity.Member) error { err := mu.MemberRepository.UpdateMember(Member) if err != nil { return err } return nil } 工夫した点 インフラ層でエンティティからDBモデルに変換するための関数が必要でした(sqlboilerのmodel特有の型があるため) この関数の中にはフィールド30個ほど存在するものがあり書くのが大変であったため、この関数を生成できるようにしました。 該当の関数は以下のような変換関数です。 func ToDbModelMember(mri *entity.Member) *models.Member { return &models.Member{ Email: mri.Email, EntryTime: mri.EntryTime, Kana: mri.Kana, MemberID: mri.MemberID, Name: mri.Name, Post: mri.Post, UpdateTime: mri.UpdateTime, } } 上記のコードを生成するために以下の手順を立てました。 ASTを使いmodelの構造体の型情報をループさせentityと比較する 比較した上でentityをどの型に変換したらいいか分岐させる 上記の情報をもとに関数を生成する この手順をもとに以下のコードを作成しました。 models, _ := FindModelStruct(fileName, modelName) // ここでmodelを取得 dict := make(map[string]jen.Code) // ここでフィールド名をkey 変換するentityをvalueとして作成 pk := "none" for _, field := range models.Fields.List { // modelの構造体をループさせます name := field.Names[0] if strings.Contains(name.Name, "ID") && strings.Contains(name.Name, E) { pk = name.Name // 主キー取得 } // ここでtypeごとに変換関数を記述させるようにしています // sqlboilerで使われているnull.stringなどを変換できるようにしています switch typeName := fmt.Sprintf("%s", field.Type); typeName { case "&{null String}": dict[name.Name] = jen.Qual("github.com/volatiletech/null", "StringFrom").Call(jen.Id(abbrRepository + "." + name.Name)) case "&{null Int}": dict[name.Name] = jen.Qual("github.com/volatiletech/null", "IntFrom").Call(jen.Id(abbrRepository + "." + name.Name)) case "&{null Time}": dict[name.Name] = jen.Qual("github.com/volatiletech/null", "TimeFrom").Call(jen.Id(abbrRepository + "." + name.Name)) default: // その他の型の場合の処理 dict[name.Name] = jen.Id(abbrRepository + "." + name.Name) } } stmt := jen.Dict{} for k, v := range dict { stmt[jen.Id(k)] = v } // model関数生成 f.Func().Id("ToDbModel" + E).Params(jen.Id(abbrRepository).Op("*").Id("entity." + E)).Op("*").Qual("main/infrastructure/postgres/sqlboiler", modelName).Block( jen.Return( jen.Op("&").Qual("main/infrastructure/postgres/sqlboiler", modelName).Values(stmt), ), ) model構造体取得関数 上記のFindModelStruct(modelの構造体を取得関数)について解説していきます。 ファイルをパースして Goの抽象構文木(AST) という標準ライブラリを使用し、取得しています。 func FindModelStruct(fileName, modelName string) (*ast.StructType, error) { fset := token.NewFileSet() node, err := parser.ParseFile(fset, "./models/"+fileName+".go", nil, parser.ParseComments) if err != nil { log.Println(err) return nil, err } var model *ast.StructType ast.Inspect(node, func(n ast.Node) bool { switch t := n.(type) { case *ast.TypeSpec: if t.Name.Name == modelName { if x, ok := t.Type.(*ast.StructType); ok { // modelの構造体を取得 model = x return false // 構造体が見つかったら探索を終了します } } } return true }) if model == nil { return nil, fmt.Errorf("%s struct not found", modelName) } return model, nil } 今後と課題 新規追加と更新処理しかないのでCRUD処理全てを生成できるようにしたい また、不要なものが生成されてしまう場合があるのでカスタムして必要なものだけ生成できた方がいい プロジェクトに応じて関数名など微妙に変えないといけないので、プロジェクトに応じた対応が必要 統一化するなどをした方がいい entityからmodelに変換する関数部分がsqlboilerに依存してしまっている、また今回のようにDBモデルとエンティティが似ているものにしか使用できない DB駆動設計となっているので改良が必要 最後に 自動生成を自ら提案してプロジェクトの業務効率化に貢献できたことは大きな経験となりました。 自主的に行動して、開発スピードを上げていくような工夫をこれからも続けていきたいです。 参考にした記事 https://qiita.com/nghrsss/items/e6c9c95db19640f0f654 https://qiita.com/po3rin/items/a19d96d29284108ad442
アバター