Andrioidアプリ広告におけるメディエーション機能について

はじめに

ジモティーでAndroidとiOSの開発をしている3年目の坂本です。

最近はマユリカというお笑いコンビのマユリカのうなげろりん!!というラジオを聴くのにハマっています。

特に#36の回、めちゃくちゃ笑いました。ポッドキャストで聴けるので気になった方はぜひ!

さて、今回はジモティーAdsについて書きたいと思います。

ジモティーAdsについて

2023年11月にジモティーAdsをリリースしました🎉

ジモティー独自のデータを活用することで、より高い広告効果の提供を目指しています。

ジモティーアプリで今まで表示していたAdMob広告とジモティーAdsを共存させるために、AdMobのメディエーション機能を採用しています。

メディエーションについて

メディエーションについて、以下のように紹介されています。

メディエーションは、アプリでの広告配信に使用する広告ソースを 1 か所で管理できる機能です。
メディエーションを使用すると、届いた広告リクエストを複数の広告ソースに送信し、使用可能かつ最適な広告ソースを確実に見つけて広告を掲載できます。

つまり、AdMobにリクエストを投げたらいろんな広告ソースの中から最適な広告を返してくれるので、広告掲載率と収益を向上が期待できるというわけです。

その最適な広告ソースの選ばれ方は以下の2種類あります。

  • 入札
  • ウォーターフォール

入札

入札は、リクエストを受け取るとリアルタイムで入札オークションが行われ、見事オークションを落札した広告が選ばれるみたいです。

ウォーターフォール

ウォーターフォールは、リクエストを受けると全ての広告のeCPMが再計算され、最上位のネットワークから順に紹介が行われ、リクエストに一致する広告があれば配信されます。

以下の例だと、eCPMが4ドルのネットワークDが選ばれ、リクエストに一致した広告があればネットワークDが配信されます。

一致する広告がなければ、次に高い3ドルのAdMobネットワークの広告が配信されます。

メディエーションにジモティーAdsを組み込む

今回はその中にジモティーAdsを組み込みます。

ジモティーAds単体だと3.5$ですが、メディエーション機能を使うことでネットワークDの4$の広告を表示できるようになります。

https://support.google.com/admob/answer/13420272?hl=ja

実装

カスタムイベントを作成

まずはカスタムイベントを管理画面で作成します。(詳しくはこちらJmtyCustomEventなどカスタムイベントアダプタを実装するクラスの名前を決めます。

そのクラスの完全修飾名を管理画面のClass Nameに設定します。 → 例: com.google.ads.mediation.sample.customevent.HogeHogeCustomEvent

アダプターを初期化する

Google Mobile Ads SDK が初期化されると、アプリ用に設定されたすべてのサードパーティ製アダプタとカスタムイベントに対してinitialize()が呼び出されるそうです。

今回作成したJmtyCustomEventでもinitialize()メソッドが走るので、初期化完了のメソッドを呼び出します。

class JmtyCustomEvent : Adapter() {

    override fun initialize(
        context: Context,
        initializationCompleteCallback: InitializationCompleteCallback,
        mediationConfigurations: MutableList<MediationConfiguration>,
    ) {
        Log.d(TAG, "initialize: JmtyAdsMediation")
        initializationCompleteCallback.onInitializationSucceeded()
    }
}

バージョン番号の報告

カスタムイベントを作成したら、カスタムイベントアダプタ自体のバージョンと、サードパーティSDKのバージョンの両方をGoogle Mobile Ads SDKに報告する必要があります。

今回ジモティーAdsの部分はSDK化していないため1.0.0にしています。

具体的な実装は省略しますが、気になる方は以下のドキュメントに実装コードがありますのでご確認ください。 https://developers.google.com/admob/android/custom-events/setup#report_version_numbers

リクエスト

今まで通りロードメソッドを呼ぶ

リクエストの仕方は今まで通りのloadAdメソッドを呼ぶだけです。

        adView.loadAd(request)

例えば以下のようなウォーターフォールの順番の場合、ネットワークDの広告があればそれがAdViewとして表示され、JmtyCustomEventは起動しません。

ネットワークDがない場合、ジモティーAdsが選ばれJmtyCustomEventloadBannerAdメソッドが呼び出されます。

class JmtyCustomEvent : Adapter() {

    private lateinit var bannerLoader: JmtyCustomEventLoader

    override fun initialize(
        context: Context,
        initializationCompleteCallback: InitializationCompleteCallback,
        mediationConfigurations: MutableList<MediationConfiguration>,
    ) {
        Log.d(TAG, "initialize: JmtyAdsMediation")
        initializationCompleteCallback.onInitializationSucceeded()
    }

    override fun loadBannerAd(
        adConfiguration: MediationBannerAdConfiguration,
        callback: MediationAdLoadCallback<MediationBannerAd, MediationBannerAdCallback>,
    ) {
        bannerLoader = JmtyCustomEventLoader(adConfiguration, callback)
        bannerLoader.loadAd()
    }
}

JmtyCustomEventLoaderでは、実際にジモティーAdsのサーバーに広告のリクエストを行います。

class JmtyCustomEventLoader(
    private val mediationBannerAdConfiguration: MediationBannerAdConfiguration,
    private val mediationAdLoadCallback: MediationAdLoadCallback<MediationBannerAd, MediationBannerAdCallback>,
) : JmtyAdListener, MediationBannerAd {

    private lateinit var adView: JmtyAdView

    fun loadAd() {
         // ジモティーAdsのリクエスト
         // Viewの生成
    }

    override fun getView(): View {
        return adView.getView()
    }
}

リクエスト時のパラメータ

ジモティーAdsのリクエスト時にパラメータを渡したいが、リクエスト時にはJmtyCustomEventのアダプターに直接渡すことができません。

この解決策としてAdRequest.addNetworkExtrasBundleを使います。

リクエストする箇所で、Bundleに値を詰めてアダプタークラスを指定してAdRequestに渡します。

        val jmtyAdRequestBundle = Bundle().apply {
                putInt("id", 1)
        }
        val request = AdRequest.Builder()
                .addNetworkExtrasBundle(
                    JmtyCustomEvent::class.java,
                    jmtyAdRequestBundle,
                )
                .build()
        adView.loadAd(request)

指定したJmtyCustomEventのアダプターのMediationBannerAdConfiguration.mediationExtrasから取り出すことができます。

class JmtyCustomEventLoader(
    private val mediationBannerAdConfiguration: MediationBannerAdConfiguration,
    private val mediationAdLoadCallback: MediationAdLoadCallback<MediationBannerAd, MediationBannerAdCallback>,
) : JmtyAdListener, MediationBannerAd {
    fun loadAd() {
        val id = mediationBannerAdConfiguration.mediationExtras.getInt("id", -1)
    }
}

ジモティーAdsがない場合

mediationAdLoadCallback.onFailureを呼び出します。

class JmtyCustomEventLoader(
    private val mediationBannerAdConfiguration: MediationBannerAdConfiguration,
    private val mediationAdLoadCallback: MediationAdLoadCallback<MediationBannerAd, MediationBannerAdCallback>,
) : JmtyAdListener, MediationBannerAd {

    private lateinit var adView: JmtyAdView

    fun loadAd() {
         // ジモティーAdsのリクエスト

        // 広告がない時
        val adError = AdError(JmtyAdErrorCode.NO_FILL.errorCode, code.getErrorMessage(), ERROR_DOMAIN)
        mediationAdLoadCallback.onFailure(adError)
        return
    }
}

ジモティーAdsがなかったことが通知され、次のネットワークへリクエストが移り、AdMobネットワークがあればその広告がAdViewで表示されます。

Viewの生成

サーバーから受け取った広告の情報を使ってViewを作成しています。

作成したViewはMediationBannerAdgetView()メソッドに渡します。これでAdViewでジモティーAdsを表示できるようになります。

class JmtyCustomEventLoader(
    private val mediationBannerAdConfiguration: MediationBannerAdConfiguration,
    private val mediationAdLoadCallback: MediationAdLoadCallback<MediationBannerAd, MediationBannerAdCallback>,
) : JmtyAdListener, MediationBannerAd {

    private lateinit var adView: JmtyAdView

    fun loadAd() {
         // ジモティーAdsのリクエスト
         // Viewの生成
    }

    override fun getView(): View {
        return adView.getView()
    }
}

最後に

今回はリリースしたジモティーAdsをAdMobメディエーションに組み込んだ経緯やその方法について紹介させていただきました。

今後はジモティーAdsのCPMをあげていって配信比率を増やしていけるように、改善していければなと思います。


弊社では一緒にプロダクトを改善していただける仲間を探しています!

こちらでお気軽にお声がけください!

ネイティブアプリエンジニアの採用って難しいですよね。。。

ジモティーのウェブチームについてお話ししたいです

挑戦するデザイナーの舞台:ジモティーでの働き方と魅力

地域のあらゆる情報を可視化し、マッチングすることで必要なモノや情報を融通しあい、持続可能で豊かな社会作りを目指しているジモティー。プロダクトだけでなく、デザイナーも枠にとらわれない挑戦を絶えず行っています。

ジモティーのデザイナーはどんな働き方をしているのか、どんな経験を得られるのか。今回は人事がインタビューし、株式会社ジモティー、ユーザーグロース部の部長としてプロダクト開発の責任者(デザイナー組織の責任者も兼任)を務めている梅田さんと、デザイナーとして活躍されている鈴木さんに、UIUXの視点でジモティーでデザイナーとして働く魅力について語ってもらいました。

 

梅田晃啓

ユーザーグロース部 部長 兼 プロダクトマネージャー

ナイル株式会社に新卒入社し、Webコンサルティング事業部に配属。スタートアップから大企業まで幅広い規模・業種のSEO・コンテンツマーケティングを担当。2017年より株式会社エス・エム・エスに入社し、全社SEO推進グループ長、介護転職サービスマーケグループ長兼PdMを担当。2022年よりジモティーに入社し、現在はユーザーグロース部部長として、Web・Appのプロダクト責任者として従事。

 

鈴木健吾

ユーザーグロース部 デザイナー

ミツエーリンクス株式会社に新卒入社し、デジタルコンテンツグループに配属。バナーやLP作成、大規模サイトのリニューアルを担当。2017年より、客先に常駐し、よりスピード感を持って制作業務、サイト更新を担当。2020年よりジモティーに入社し、現在はユーザーグロース部のデザイナーとして、Web・Appのデザイン業務を担当。

 

「枠にとらわれない」デザイナー像

 

――ジモティー内でのデザイナーの役割について教えてください。

鈴木: UIUX改善に関わるデザイン及びディレクション業務を担当しています。アプリのUIデザインから、Webページの企画・UIデザインまで、幅広いデザインを担当しています。デザイン以外にも、仮説やデータに基づいたユーザーストーリーの考案や、施策や機能の企画立案なども業務として行っています。

 

――鈴木さんの経歴についてお伺いしてもいいですか。

鈴木: 理系の大学でプロダクトデザインを学んでいました。リアルなプロダクトのデザイン、工業デザインですね。学んでいくことがサービスデザインとかユーザー体験に広がって、UXになったり、その中でUIも学習しました。 大学卒業後は、ウェブの制作会社に新卒入社し、デザインとフロントエンドの基礎を学んだ後、客先に出向いて、LPやバナー、ウェブページの運用などを行っていました。もうクリエイティブばかりでしたね。

梅田: どういうことを学びたくてジモティーに転職しようと思ったんですか。

鈴木: 一つのサービスをもっと深掘りしたいっていう思いがあったところですね。ウェブ制作会社に入った時は、ただひたすらにスキルが欲しい、もっとスキルを伸ばしたいという思いで入社しました。いろんな会社の案件を運用していく中で、一つのサービスを上流下流通してやりたいと強く感じました。その結果、ジモティーを選びました。

 

――ジモティーと前職とのUIUXに関する考え方の違いは何ですか。

鈴木: 一番大きいのは枠にとらわれない点ですね。前職の制作会社に在籍していた時は、ある程度決まった内容に磨きをかけるっていう感じでした。ジモティーの場合は、枠はなく、場合によっては枠を壊しつつ磨き込む時もあって。すごく根本的に違うと感じました。

梅田: 具体的には、どのような点で枠にとらわれないと感じましたか?

鈴木: ジモティーでデザインを考える際、単純な一文脈だけの、綺麗なUIの話だけではダメで、このデザインは売上にどう影響するのかとか、その先の展開を見据えてやるべきなのかとか、費用対効果に合うのかとか、そもそもやる必要があるのかとか。多角的な検証が求められるので、持てる引き出しをフル活用しています。

梅田: それはジモティーで働く中で学べたのでしょうか?

鈴木: はい。ジモティーでは、そもそも論からROIや売上の観点だったりとか、本当に上流からやる経験があるところが特殊ですよね。しかも、その場数がめちゃくちゃあるところが、ユニークなポイントだと感じています。

 

――実際の具体的な事例があれば教えてください。

鈴木: 決済機能周りの改修の案件。直近だと、すごく深く考えつつ、いろんな意見をまとめました。

依頼自体はPdMの方からあり、手を動かしながら、いかに使いやすく、そして既存の画面と違和感なく使えるかをエンジニアやディレクターと議論しました。作り込んでいく過程で、デザイナーチームで相談して、色々なフィードバックをもらったり、他社の事例を調べながら、どういう風にやってるんだろう、といったところを調べながら進めましたね。

 

梅田: 確かに、すごく緊急でやらなくちゃいけないというよりかは、デザイナーが主体的に考えた案件でしたね。ポンってお題だけ渡されて、ざっくりのイメージがある中で、考え直したりとか、他社比較したり。デザインとしての王道のフローをたどりながら、かつ中で相当議論したプロジェクトでしたね。 これからリリースされて、大きく数字に跳ね返ってくるといいですよね。

鈴木: そうですね。その機能を使って、例えば他のキャンペーンをやるとか。また、他の画面上で押し出していく動線を作るとか、いろんな派生の仕方はあるのかなと思っています。

 

ジモティーのデザイナーチームとしての成長

 

――プロダクトにおける課題感はありますか?

梅田: WebのPC・SP、iOS・Androidのネイティブアプリ、はたまたジモスポと、4つのデバイスとリアル店舗でデザインを整備しなくてはいけない中で、どうしても手が回っていない部分があると、部としても自覚しています。

鈴木: そうですね。プロジェクトの優先順位や、ROIの観点からどうしても着手が劣後してしまっている部分は多くあります。社内だけでなく、ユーザーからの意見も取り入れつつ、チームとして体制を作っていって、ジモティーのUIUXをよりモダンなものに整えていけたら、と考えています。

 

――さらに、チームとしてここを伸ばしたいというスキル等ありますか。

梅田: プロジェクトで上流から下流までやるのと同じくらい、プロダクト全体のクオリティを維持し、メンテナンス・改善していくデザインシステムは重要だと思ってます。デザインチームでは、その運用と改善も進めてもらっていますね。

鈴木: そうですね、とても重視しています。改善に関しては、他社さんの大規模サービスのデザインシステムの事例を知ることを大事にしていますね。また、マテリアルデザインガイドラインを手本としているので、その理解は、もっと学んでいかなくてはいけないし、深めていかなくてはと思ってます。

ちなみに、ジモティーではデザインツールはFigmaを使っています。Figmaってすごく魅力的で、どんどんアップデートが入り新しい機能が実装されるので、キャッチアップを丁寧にやって、自分の中に蓄積していくことも理想ですね。

 

――チーム内での学習や、最新トレンドをキャッチアップしていますか

鈴木: はい。具体的な方法としては、Figma関連のtwitterやインスタをフォローするとか。トレンドを逃さないようにしますね。情報が出てきた際に、朝会でチーム内でフラットに「新しい機能出ましたよね!」のような形で話し合うようにしています。

あとは、朝会をデザイナーチームとして独立してやって、その中で日々共有してます。ほかにも、Slackで気になるニュース等を意見交換しながら、最新トレンドを取り入れて共有して可視化しています。言語化するところは、ツールでキャッチアップする意味でも、意識的に機会を作るようにしていますね。

 

大胆かつロジカル、チャレンジングな環境

 

――ゼロベースで考えることと、デザインシステムに沿った運用は、どのように両立していますか。

鈴木: ゼロベースで考える施策の時は、上流の情報設計時はデザインシステムは、逆に考えないようにしてます。そこを意識してしまうと、枠を超えたアイディアが出にくくなってしまうので、考え方を使い分けるようにしていますね。

その後、要件が固まってきた時に、作法に乗っ取ると、どういう風に実現できるかを考え、そのタイミングでようやくデザインシステムを考慮して、施策ベースで考えてきた矛盾を整理してあげる。そうやって、矛盾をなくしていく中で、反対にデザインシステムの修正箇所も明確になって、磨きをかけることが運用観点では重要だと考えています。今後どうしていきたいかと言うと、施策は施策でガシガシ進めて、その中でデザインシステムを、今のプロダクトに沿うように整えていく、という改善活動をしていきたいです。

梅田: 理想と現実みたいな話が多分あると思っていて、理想は全部デザインシステムに組み込まれていて、そこから毎回施策に応じて、UIが出来上がっていることだと思います。ただ現実はそうもいかず、状況に応じて変えていかなくてはいけないし、それに合わせてデザインシステムも変えていかなくてはいけない、というイメージですよね。

鈴木: ジモティーとしてすごく大切にしていることは、ユーザーにとっての課題を解決できることだと思うんですよね。ユーザーが、手軽に投稿できたりとか、欲しいものがゲットできたりだとか。そういうところの体験が何より重要で、デザインは体験ありきで考えるべきことだと思います。ユーザーのことを第一に考えるっていう理想系と、現実問題のデザインシステムというところを、常に理想と現実を行ったり来たりしながらバージョンアップしていく。ジモティーのデザインチームでは、この点をとても重視しています。

 

――最後に、ジモティーのデザイナーに向いている人について教えてください

 

鈴木: ロジカルかつ大胆に動ける人は向いてるかもしれないですね。ロジカルさだけでなく、思いきって「こうすればもっといいんじゃない?」といったアイディアを思い浮かべて、実行することができる人。ただ一方で、細かい作業も根気強くやれる方だとより良いと思いますね。やっぱりデザインシステム周りで、既存画面を粘り強くみていくことがあるので、その両方を持っている方は非常に向いているかなと。

梅田: まさにジモティーのバリューでいうところの「ジモティズム」を、デザイン面において行っているような人ですよね。ずっと「本質的な成果へのこだわり」ばかり追いかけてアクションを恐れたり、細かいことをやってる人ではなく、「圧倒的な当事者意識」を持って、大胆に極端なパターンを試してみるような「大胆な挑戦」を行える方のイメージですね。スピード感を必要とする環境だけれども、それを楽しめる人はきっと合ってると思います。

鈴木: ジモティーのすごくいいポイントは、チャレンジングなことをこの規模のサービスで行えることですね。メンバーが上流から下流までできる点がすごく大事だし、デザイナーとして培うべきスキルのキャッチアップや、トレンドキャッチもできる環境が、チームとしても会社としても整っているところが大きいです。デザインシステムを運用、構築しながら、さらにゼロベースで考えていく。大変かもしれないけど、責任のある仕事ができる舞台が整っているのが、ジモティーのデザイナーのポジションであり、環境だと思います。

 

ジモティーエンジニアのビジネスとの関わり

お久しぶりです。 バックエンドのチームで活動している阿部和貴と申します。

前回投稿からおよそ2年、前回の時にはAndroidエンジニアとして活動していましたが、1年ほど前にバックエンドのチームへコンバートし現在ではRuby on Rails を中心としてバックエンドの領域で活動しています。

思えば、iOSエンジニアとして入社してさまざまな領域での経験をさせてもらえていますね。

今回は、少し毛色を変えてジモティーエンジニアの現在のビジネスとの関わりについてお話ししていきたいと思います。

こちらの記事でビジネスと近い距離で要件や仕様に関われるというお話をしていました。

現在ではエンジニアもビジネス側としてプロダクトの成長のために企画のフェーズから担当するようになりました。

その様子を自分の経験談を交えながら話していければと思います。

エンジニアとビジネスの関わり方

先で記載した通り、現在ジモティーのエンジニアも企画フェーズからプロジェクトに入りプロダクトをどのような方向に開発・変更していくべきか、分析・議論し日々業務にあたっています。

以前の記事で弊社の開発フローは大きく次のような流れがあるとお伝えしました。

  1. ビジネス要件定義
  2. システム要件定義
  3. 開発
  4. テスト
  5. リリース

また、 分析のデータ出しはエンジニアの得意分野なので、ビジネス要件定義からエンジニアも関わっていることをお伝えしていました。

現在もそこは変わらないのですが、このビジネス要件も含めエンジニアが主担当として案件を進めていく様に変化してきました。

そのため、分析・議論だけでなく画面のワイヤーをFigmaで作成して、自分でディレクションを行い、実装するエンジニアも存在します。

このようにエンジニアの活躍できる場が広がっているのが現在のジモティーです。

ジモティーのビジネス領域

現在のジモティーではユーザーグロース(UG)とマネタイズでチームが分かれており、ここにエンジニアも半々で所属して各Gで企画から携わっています。

  • UG:サービス成長のため、ユーザーが使いやすいサービズにするためのグループ
  • マネタイズ:事業を推進するために売り上げを構築するために動いているグループ

私はマネタイズの方に所属しております。

マネタイズグループでは、ジモティーの売り上げ構成としては以下の4つになっておりそれぞれの売り上げを伸ばす施策の検討を行なっています。

  • 広告:アドネットワークを利用した広告をコンテンツ内に掲載。第三者配信型と自社広告型の2種類がある。
  • DB連携(成果報酬) :他メディアとデータを連携し送客。
  • 機能課金:投稿を目立たせる機能を販売
  • その他売上:ネット決済時に発生する手数料課金、ジモスポでの商品売上及び業務受託売上、HRでの応募課金等がある

売り上げ構成など詳しく知りたい方は決算説明資料にも記載されていますので参照ください。

この中で私はDB連携を担当しており、売り上げ向上のため日々試行錯誤中です。

私が担当していること

私が企画側も担当していることはお伝えしていますが、それだけではイメージしづらいと思いますので 具体的にどの様な働き方をしているかお伝えしようと思います。

ジモティーでは11カテゴリーの投稿が存在しております。

その中で5カテゴリーにおいて、様々な企業のデータを連携し、投稿を作成しております。

この連携のことを弊社では「DB連携」と呼んでいます。

この連携データのコンディションを確認し連携企業に対してできるだけ効果を返すために基盤の整理・ジモティーユーザーに人気のデータの特性を分析などを中心に私は業務にあたっています。

以下に普段の業務の一例を示しています。

業務 詳細
KPIのためのモニタリング基盤の作成 連携しているデータのコンディションを確認するためのモニタリングを整備してマーケティングでよく使われる指標であるimp*1 ,CT*2, CV*3をモニタリング可能にした
連携投稿の状況をモニタリング 実際にどんな連携データが存在しているのか、KPIに大きな変動はないのかをモニタリングする。悪化している場合は、各部署と連携しつつ調査を進める。
連携データ、CVデータ、ローデータを分析 連携データの中で実際にユーザーがCVしたデータから人気のデータの特性を調査し、 コンテンツの配置を検討する。
施策立案 分析したデータを元に課題になっている部分の仮説をたて、検証する。その中で効果がありそうなものに優先度を振り分ける
例) xxxカテゴリの連携投稿はimp数は十分に出ているはずだが、CTRが低いのでimpされてもユーザーマッチしない連携投稿が閲覧されやすくなっているのではないか?
要件定義 実際に課題は何か?システムで何を実現したいのか?を定めてKPIの目標値を決める。画面は改修するのであればモックを作成
例) CV数を向上させることを目標として、閲覧されやすい箇所にユーザ志向に合わせた案件を配信する。これにより、CTR *4が上昇することが見込まれるため、結果としてCV数向上を実現できる。実際に実施するかどうかは費用対効果を見積もってROIから判断する。
設計・システム開発 実際に施策を実現するためにシステムをどう改修すればいいのか検討・実装(開発者の皆様には馴染み深いフェーズ)
施策振り返り リリースしたロジックの効果を確認し、思った効果が出ていなければ切り戻し、原因を調査。主に作成したモニタリング基盤でKPIを確認する

ざっくりと上記を繰り返しながら連携してくださっている企業へ効果を返せるように改善を進めています。

面白さ or 苦労した

ビジネス側の企画から参加することで、今までのエンジニアリングとは異なるやりがいもみえるようになってきました。

  • 実際に売り上げに直結することで今まで以上にメディアのコンディションに敏感になる
  • 実際に自分が要件定義から実装まで上流から下流まで担当できる
    • スピード感のあるPCDAを回すことでき、企画に必要なスキルをエンジニアでも身につけることができる
    • ドメイン知識が深まることで設計の方向性が明確になり、エンジニアスキルの方も同時に成長できる
    • 今まで諦められていた機能もエンジニアが主導だからこそ、さっと実装してプロダクトの成長に貢献できる

一方で、エンジニアリングという領域から外に出ることでその分苦労したこともあります。

  • ビジネス知識0の状態からチャレンジ
    • そもそも売り上げを上げることをKGIとするときに、何に対して効果がある案件が良さそうなのか判断がつかないため効果がある案件を進めることができない
      • ドメイン知識が豊富な方に壁打ちをお願いすることで思考が整理されていき、自走することができるようになってきました。
  • まだまだ課題がたくさんある領域だった
    • 広告業界でよく目にするCTR、CVRをモニタリングできない状態だったので施策の効果がわかりづらい状態でした。実際に設計から入りモニタリング可能な状態にするという面白さを経験できました。

この苦労も一人ではなく伴走してくださったり、サポートしてくださる環境があったからこそ成長を感じられています。

最後に

このようにビジネス側にも密接に関われるエンジニアが多数在籍しており、日々プロダクトを成長させて行くために領域関係なく試行錯誤を繰り返しています。

もし、エンジニアの方でもビジネスと近い距離で仕事をしてみたいと思ってくださったり、熱量があるエンジニアを募集しています!

一緒に課題を解決してくれる仲間としてジョインしてくださる方を待っています!

また、自分が担当しているDB連携領域でも随時連携してくださる企業を募集しております。

直近3ヶ月でも興味を持っていただいていた企業様とも連携を行い、連携データを随時配信しております。

もし興味をお持ちの企業様がいらっしゃいましたら、ご連絡お待ちしております。


弊社では一緒にプロダクトを改善していただける仲間を探しています!

こちらでお気軽にお声がけください!

ネイティブアプリエンジニアの採用って難しいですよね。。。

ジモティーのウェブチームについてお話ししたいです

*1:Impressionの略で広告が表示された回数。ここではリスト形式で表示された回数としている

*2:Click Throughの略。広告が何回クリックされたかの回数

*3:Conversionの略で、コンバージョンした数。連携先へ送客できた数としている

*4:「Click Through Rate」の略。ユーザーに広告が表示された回数(impression数)のうち、広告がクリックされた回数の割合のこと。

Terraformプロジェクトの構造を考える

こんにちは! ジモティーにてインフラ開発・運用を担当している斎藤です。

ジモティーでは主にAWSを活用してシステムを作っているのですが、分析基盤としてBigQueryを利用するなど、一部の用途でGoogle Cloudも活用してきました。

今回、BigQueryにリアルタイムでログを蓄積していく必要があり、APIサーバなどのコンピューティングリソースもGoogle Cloudに構築する必要性が出てきました。

Google Cloudでインフラを管理するなら将来的な拡張も見据えてやはりTerraformか、、、? という考えに至り、実際にTerraformプロジェクトを1から立ち上げてみましたので、そのときに考えたことを共有したいと思います。

Terraformってなに?

パブリッククラウドのリソース(サーバ、ネットワーク、ストレージ等々)をコードで管理するためのIaCツールです。

Hashicorp社製で、HCLという独自言語によってインフラを宣言することができます。

例えば以下のような記述をすると、Google Cloud上にCloud Storageバケットをひとつ定義できます。

resource "google_storage_bucket" "static_contents_bucket" {
  name                        = "jmty-static-contents"
  location                    = "ap-northeast1"
  uniform_bucket_level_access = true
}

IaCツールにおいてはAWSだとCloud Formationが有名ですね。

最近はCloud Formationをより抽象化してより開発者がコーディングに集中できるようなAWS CDK(Cloud Development Kit)というものもあります。

TerraformがAWSのそれらと違うのは、プロバイダというライブラリを入手することで多種多様なインフラをコード化対象にできる点です。

AWS、Azure、Google Cloudなどの主要パブリッククラウドだけでなく、GitHubやHerokuなどのSaaS、PaaSに対してもプロバイダが提供されています。

提供されているプロバイダはこちらから一覧することが可能です。

プロジェクト構造

さて、Terraformでインフラを管理することを決めたのはいいのですが、まず何をすればいいのか。。。

Terraformプロジェクトの構成の仕方は基本的に自由です。拡張子を .tf にさえしておけば、どこでどんなリソースを定義することもできます。

ただしプラクティスは既に無数に存在します。

なんならGoogle自身がTerraformでインフラを管理する際のプラクティスも提供してくれています。

重要な点は一つのディレクトリが「モジュール」として扱われる点です。

基本的にデプロイはモジュール単位で行われ、モジュールの中で別のモジュールを呼び出すこともできます。

このような特徴から、Terraformプロジェクトでは以下のようなプロジェクト構造を採用しました。

結果、プロジェクトは大きくわけて modules ディレクトリと environments ディレクトリを持つ形に落ち着きました。

.
├── README.md
├── environments → ★各環境ごとに設定値を格納したファイル群
│   ├── global → ●環境間共有リソース
│   │   ├── common
|   |   ...
│   ├── prod → ●本番環境
│   │   ├── api_server
|   |   ...
│   └── stg → ●検証環境
│       ├── api_server
|       ...
└── modules → ★各システムごとのリソース定義ファイル群(モジュール)
    ├── api_server
    │   ├── enable_apis.tf
    |   ...
   ...

以降で modules ディレクトリと environments ディレクトリについて説明します。

modulesディレクトリ

modules ディレクトリには各環境を構成するシステム単位毎に、Google Cloudリソース群がモジュールとして定義されています。

例えば api_server モジュールにはAPIサーバとそれが動くために必要なインフラ、 management_console モジュールには管理画面のインフラを定義するためのリソース群が定義されています。

木構造で表すと、以下のような構造になっています。

modules/
├── api_server
│   ├── enable_apis.tf
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   └── variables.tf
├── management_console
│   ├── enable_apis.tf
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   └── variables.tf
...
└── networking
    ├── enable_apis.tf
    ├── main.tf
    ├── outputs.tf
    ├── provider.tf
    └── variables.tf

各モジュールは以下のようなファイル群で構成するようにしました。(各モジュールによっては、利用しないファイルも存在します)

  • enable_apis.tf
    • モジュールの実行に必要なGoogle CloudのAPIの有効化を行う
  • main.tf
    • モジュールの実行によって作成したいGoogle Cloudリソースを定義する
  • outputs.tf
    • モジュールの出力を定義する。モジュール実行の戻り値(出力インタフェース)として機能する
  • provider.tf
    • モジュールで利用するTerraformプロバイダを宣言する
  • variables.tf
    • モジュールで利用する変数を定義する。モジュール実行の引数(入力インタフェース)として機能する

environmentsディレクトリ

modules ディレクトリでは環境固有の設定値を引数にしたモジュールを定義していました。environments ディレクトリでは modules で定義したモジュールに対して、各環境の設定値を入力として与えモジュールをインスタンス化します。

結果としてGoogle Cloudにリソースをデプロイすることができます。

stg 環境、つまり検証環境を例にとると以下のような構成になっており、各モジュール名のディレクトリで実際にモジュールがインスタンス化されるようになっています。

environments/stg/
├── api_server
│   ├── backend.tf
│   ├── data.tf
│   ├── main.tf
│   ├── outputs.tf
│   ├── terraform.tfvars
│   └── variables.tf
├── common
│   ├── backend.tf
│   ├── outputs.tf
│   ├── terraform.tfvars
│   └── variables.tf
├── management_console
│   ├── backend.tf
│   ├── data.tf
│   ├── main.tf
│   ├── outputs.tf
│   ├── terraform.tfvars
│   └── variables.tf
...
└── networking
    ├── backend.tf
    ├── data.tf
    ├── main.tf
    └── outputs.tf

各モジュールは以下のような構成を取るようにしました。

  • backend.tf
    • Terraformステートを格納するTerraformバックエンドを宣言する
  • data.tf
    • 依存する別モジュールを宣言する。例えばサーバをデプロイするためにnetwork名が必要になるため、 networking モジュールに依存するなど。ここで定義したモジュールの出力を使って main.tf でさらにインフラを定義する
  • main.tf
    • モジュールをインスタンス化する宣言をする
  • outputs.tf
    • モジュールの出力を all というキーに全て詰め込む宣言をする。これにより外部モジュール側の data.tf で利用宣言すると、もれなくこのモジュールの出力を利用させることができる
  • terraform.tfvars
    • variables.tf に実際に渡す値を書く
  • variables.tf
    • モジュールで利用されている変数を定義しておく
      • モジュール側と環境側で二重管理になってしまっているが、モジュール側の variables.tf はあくまで環境に依存せず必須にしたい変数を必須宣言しておき、環境によっては任意にしたい変数にはデフォルト値を付与して任意宣言としてある。これにより、モジュール側は必須パラメータだけを最低限定義すればよく、環境によるチューニングは任意パラメータで実施することが可能になる

commonモジュール

各環境には modules ディレクトリにはないモジュールである common モジュールが定義されています。

これは特殊なモジュールで main.tf を持たないモジュールです。

つまり common モジュールをインスタンス化することによってGoogle Cloud上にリソースがデプロイされることはなく、Terraformステートだけがデプロイされることになります。

commonモジュールの役割は、各システム間で値を共有することです。

commonモジュールの出力にあらかじめ環境内の各システム間で共有したい値(環境名など)をあたえておきます。

これにより各モジュールは common モジュールに依存すればそれらにアクセスすることができるようになり、先の役割を全うするようになっています。

global環境

environments ディレクトリには global 環境が定義されています。

これは各環境に直接属さない、環境間共有のリソースを定義する環境として利用されます。

例えばIAMを管理する project_members モジュールなどがこの環境でインスタンス化されます。

構成自体は他の環境と全く同様です。

最終的なデプロイ方法

結果として、以下のように modulesenvironments が宣言され、Google Cloudリソースが生成されることになります。

実際に terraform apply を実行するのはデプロイしたい環境のシステム単位ごとに行うようにします。

まとめ

この記事ではTerraformを使ってプロジェクトを構成する際に弊社で考えたことを書きました。

以下が肝だったのではないかと考えていますが、この考え方はデプロイ先がGoogle Cloudに限らず、適用できる考え方かと思います。

これからTerraformプロジェクトを立ち上げようとする方の参考になれば幸いです。

  • モジュールには環境固有の設定値を入れないことで、環境の複製を容易に、かつ環境差異が生じにくいようにした
  • モジュールの入力(variable) と出力(output) を意識しモジュールに対するインタフェースと捉えることで依存関係を明確にし、かつ一方向にのみ依存するように整理した

今回の共有は以上です。今後もテックブログを定期的に更新し、ジモティーの開発事例やプラクティスを共有していきたいと思います。

次回もまたお楽しみに!


弊社では一緒にプロダクトを改善していただける仲間を探しています!

こちらでお気軽にお声がけください!

ネイティブアプリエンジニアの採用って難しいですよね。。。

ジモティーのウェブチームについてお話ししたいです

Androidアプリのマルチモジュール化

はじめに

初めまして、ジモティーで Android アプリを担当している谷です。

最近マイクラにハマってしまい、やることが無限すぎて困っています。

今回は弊社の Android アプリをマルチモジュール化したのでそのお話をしたいと思います。

導入の背景

まずは弊社の Android アプリの現状の構成について説明します。

弊社の Android アプリは MVVM + CleanArchitecture を取り入れ、3層のレイヤードアーキテクチャで構成しています。

  • Presentation 層
  • Domain 層
  • Data 層 (Infra層)

上記を1つのモジュール内で管理しており、各層はパッケージで切り分けているため、参照の方向に強制力はありませんでした。

なので、実装担当者が参照の方向に気をつけながら実装する必要があり、本来の目的以外のところに注意を払う必要があり、厳しいさを感じてました。

そこで、マルチモジュールを取り入れることで、アーキテクチャをモジュールで表現することができるようになると考えて、上記課題を解決できるのではないかと思い導入を決めました。

また、各モジュールの依存関係を整理していくことで、ビルド時間の短縮の短縮も見込める可能性があるため、その点でも開発生産性の向上に期待できそうというのも決め手の一つとなりました。

分割方針

長い歴史のあるアプリで、コードベースも巨大なため、1回で理想的な形に変更できないと判断しました。

そこで、以下の点を考慮して方針を決めました。

  • 学習コストを低くする
  • 今後より改善しやすい構造であること

特に 学習コストを低くする という点を意識するために、現状のアーキテクチャ構造に近くなるようにモジュールを分割する形としました。

イメージ図

パッケージ構成

最終的なパッケージ構成は以下です。

モジュールには * をつけています。

(data パッケージ内の entity は CleanArchitecture 上の entity ではなく、 Gson によるレスポンスのマッピングを行うクラスを格納するモジュールとしての命名となっています。命名ミスっているので本当は変更したいところですが。。。)

├── app*
├── data
│   ├── repositoryimpl*
│   ├── data*
│   ├── entity*
│   ├── mapper*
│   ├── network*
│   └── database*
├── domain
│   ├── repository*
│   ├── model*
│   └── usecase*
└── utility*

解説

全てを説明するのは難しいため、ポイント部分の解説です。

  • 基本構成は app / domain / data としました。

  • app モジュールには Application クラスが入っており、全てのモジュールを参照する形になっています。

    • 本来、Application クラスを分離するべきですが、Application クラスを一時的なデータの保存場所として使っており、修正するには時間がかかりそうだったためこの形にしました。
  • utility モジュールは全モジュールが共通で使う、以下のようなクラスを格納しています。

    • util
    • helper
    • constant (定数などを定義しているクラス)

マルチモジュール化の結果

  • ⭕️アーキテクチャを仕組みで表現できるようになった

    • 適切な依存関係を設定することができるようになったため、アーキテクチャの強制力を働かせて開発を進めることができるようになり、実装者が意識しないといけない部分が減ったため、開発生産性が向上した。
  • ❌一方で、ビルド時間の短縮に対しては効果が薄かった。

    • フルビルド時のビルド時間が若干増加してしまいました。

工夫した点

  • マルチモジュール化によりどのくらい改善が図れたかを調べるため、gradle-profilerを使用して計測を行うようにしました。

    • マルチモジュール修正頻度よりも多く計測を行う必要はなさそうと判断したため、計測頻度は1週間に1回行うようにしました。
  • マルチモジュールの移行率を計測することでモチベーションを上げるようにしました。

    • 最初はちょっとずつそれぞれのモジュールへ移行していたのですが、数が膨大だったため、少しずつでも移行されている実感を得られないと続かないだろうと考えて計測を行うようにしました。 (チームの方にも手伝っていただき、移行が加速しました!)
  • ConventionPlugin導入

    • モジュールを作成する中で同じプラグインやライブラリを記述していく必要があり、それぞれのライブラリの管理に関してのオーバーヘッドが発生することが想像されたので、共通で使える形にしたいと考えて導入しました。
  • モジュールの作成手順の作成

    • 今回のマルチモジュール化では、学習コストを低くするというのも大事な要素だったので、モジュールの手順書を作成して、チームに展開後、作成した手順書を使ってハンズオンを行いました。

残課題

  • Application クラスを application モジュールとして切り出すことができなかったので分離を進める。
    • 画面上で生成したインスタンスの一時保存クラスとして Application クラスを使用してしまっているため、修正工数がかなりかかってしまうのと、安全にマルチモジュール化が行えないと判断して、app モジュールはそのままにするという形にしました。

最後に

今回はジモティーの Android アプリのマルチモジュール化について紹介させていただきました!

移行したばかりでまだまだこれからという状況ですが、日々改善していければと思います!


弊社では一緒にプロダクトを改善していただける仲間を探しています!

こちらでお気軽にお声がけください!

ネイティブアプリエンジニアの採用って難しいですよね。。。

ジモティーのウェブチームについてお話ししたいです

【iOS】広告の事前読み込みチャレンジ

はじめに

どうも。

ジモティーでiOSアプリ開発チームのマネージャーをやらせてもらっている、ていです。

前回の記事で弊社のiOSアプリにアーキテクチャを導入した過程の計画編をお届けしたので順番的には導入編をお届けするのが筋かと思いますが、気分が乗らないので少し前に広告の取得表示改善に取り組んだ話をしようと思います。

広告の課題

弊社の広告は所謂アドネットワークから配信される広告をSDKを通じてアプリに表示させるよくある手法を取ってるのですが、課題はたくさんあります。

私自身ジモティーに入社して7年目を迎えておりますが、この7年は広告(SDK)との戦いの歴史だと言っても過言ではないと自分では思ってます。

たくさんある課題の一つに広告の表示が遅く、ユーザさんが広告を目にする前にスクロールやページ遷移で広告表示画面から離れてしまうというものがありました。

そもそも見栄えが悪いのもありますし、弊社としても売上の機会損失につながっている状況です。

事前読み込み方式のチャレンジ

広告はリクエストを送った後、Biddingと呼ばれる入札が行われてその時点で収益効果の高い広告を返す仕組みになっている関係上リクエストからレスポンスまで一定の時間がかかってしまい、さらに(Admobの場合)カスタムイベントをたくさん設定しているとその分さらに時間がかかるということがあるので、どうしてもページ表示から広告表示までタイムラグが発生してしまいます。

弊社のアプリは特にそのタイムラグが大きく、レスポンスが返ってくるまでは広告表示枠が白板状態でぽっかりと空いてしまい非常に見栄えが悪い状態になっています。

そこで以下のような考え方で改修を行なってみることになりました。

1. 現在の広告取得後の表示タイミングで次に表示する予定の広告を取得しておいて、端末内に保存する

2. 次の広告表示タイミングでまず事前取得した広告を表示し、さらにその次に表示する予定の広告を取得しておいて端末内に保存する

3. こうしておけば初回リリース以降は(広告取得失敗がない限り)常にほぼ待ち時間0で広告を表示することが可能になる。

実際のソース(一部抜粋)

キャッシュクラス

class AdCacheHelper {
    private static var preloadAd: PreloadAd?

    static func getAdView() -> GADBannerView? {
        Self.checkAdGetTime()
        let localData = Self.preloadAd
        Self.removeAdView()
        return localData?.adView
    }

    static func setAdView(_ adView: GADBannerView) {
        Self.preloadAd = PreloadAd(createdAt: Date(), adView: adView)
    }

    // 取得から1時間経過した事前読み込み広告は破棄
    private static func checkAdGetTime() {
        if let preloadAd = self.preloadAd {
            let adLimitDate = Calendar.current.date(byAdding: .hour, value: 1, to: preloadAd.createdAt)!
            if adLimitDate < Date() {
                Self.removeAdView()
            }
        }
    }

    private static func removeAdView() {
        Self.preloadAd = nil
    }
}

struct PreloadAd {
    let createdAt: Date
    let adView: GADBannerView
}

呼び出しがわ

    public func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
        if let adCacheableViewController = self.rootViewController as? AdLoadable {
            switch self.loadStatus {
            // 事前取得表示済みの場合と再取得後はキャッシュ保存してリフレッシュ待機状態にする
            case .preLoaded, .reRequested:
                AdCacheHelper.setAdView(bannerView)
                self.loadStatus = .refreshing
            // 初回起動時 or 破棄されてる場合 or 異常として広告がない場合は取得した広告を表示して事前読み込み用の広告を再取得
            case .firstLoading:
                adCacheableViewController.loadAd(bannerView: bannerView, admobUnitId: self.admobUnitId)
                self.loadStatus = .reRequested
                self.requestAds()
            // リフレッシュはそのまま表示するのみ
            case .refreshing:
                adCacheableViewController.loadAd(bannerView: bannerView, admobUnitId: self.admobUnitId)
            }
        }
    }

※1 実装に関する指摘コメント歓迎です!

※2 ただしあまりに厳しい言葉だと実装者のメンタルに響きますのでお手柔らかにお願いします。

※3 実装に関してzaimさんのブログを勝手に参考にさせていただきました。

結果

結果としては見栄えの部分ではある程度改善されたのですが、数字上の効果はあまり改善が見られませんでした。

仮説としてはそもそも広告の取得が遅すぎて、事前読み込み完了前にページ遷移などで広告リクエストが破棄されてしまいいつまで経っても事前広告がキャッシュされないパターンが多いのでは?というものが有力そうです。

最後に

この経験を踏まえ今後は事前読み込み方式を一度に複数読み込んでおくとか、そもそもBiddingの速度を上げられないか?など多岐にわたる改善方法を検討して進めていく予定です。

広告を扱われていて速度改善で良い成果を挙げられた方などいらっしゃいましたら、是非お知恵をお貸しいただけると幸いです。

またはジョインしていただき一緒にジモティープロダクトを改善していただけるとよりハッピーなので、よろしくお願いします。


弊社では一緒にプロダクトを改善していただける仲間を探しています!

こちらでお気軽にお声がけください!

ネイティブアプリエンジニアの採用って難しいですよね。。。

ジモティーのウェブチームについてお話ししたいです

CodeBuild始めました

ジモティーでインフラとバックエンドを担当している鈴木です。最近は貝出汁ラーメンをよく食べてます。美味しい。

ジモティーにCodeBuildを導入しましたので、背景や工夫した点などを紹介します。

CodeBuildとは

AWS CodeBuildは、AWSが提供するフルマネージド型のビルドサービスです。このサービスを利用することで、ソースコードのビルド、テストの実行など、ビルドプロセスを自動化することができます。 サーバーレスなサービスであり、ビルド実行の度に新たな環境が自動的に作成されるため、ユーザーは事前にビルドサーバーを準備しておく必要がありません。

背景

ジモティーでは、Rails、Next.jsといったアプリケーションサーバーがECS Fargate上で稼働しており、FargateにデプロイするDockerイメージは、元々はJenkinsを使用してビルドしていました。

しかし、Jenkins上で同時に複数のビルドを行うと、メモリ不足に陥るケースがありました。

さらに、新たな問題として、RailsやNext.jsのコンテナをARMアーキテクチャに置き換えることを計画していました。これにはARM向けのイメージをビルドする必要がありますが、イメージのビルドは一般的にホスト環境のCPUアーキテクチャに依存します(※)。弊社のJenkinsサーバーはx86アーキテクチャのインスタンスで構築されているため、ARM向けのイメージをビルドすることができませんでした。

こういった一連の課題を解決するため、ジモティーではCodeBuildの導入を決定しました。これにより、ホスト環境に依存せずに、各アーキテクチャのイメージをビルドできるようになります。また、CodeBuildはビルドごとに独立した環境で実行されるため、ビルドの同時実行によるメモリ不足といったリソースの問題も解決できます。

※ Docker Buildxプラグインを使うことでマルチアーキテクチャでのビルドが可能ですが、今回はリソース問題も解決できるCodeBuildの導入を選択しました。

工夫した点

Jenkins環境でDockerイメージをビルドしていた際には、Dockerビルドの結果がキャッシュとしてローカルに保存され、次に同じイメージをビルドする際にビルド時間を短縮することができていました。

一方、AWS CodeBuildはビルドごとに新しい環境を作成し、ビルドが完了するとその環境は破棄されます。そのため、ビルド間でイメージキャッシュを共有することができません。

キャッシュによるビルド時間の短縮はデプロイプロセスの効率において重要だったため、ECRに保存している前回のイメージを --cache-from オプションで指定することでキャッシュを有効にしました。

# 前回のイメージをECRからpull
- docker pull XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY_NAME}:latest

# 前回のイメージを --cache-from オプションで指定
- docker build -t ${REPOSITORY_NAME} . --cache-from XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/${REPOSITORY_NAME}:latest 

この方法では、前回のイメージをECRからpullするため、通信のオーバーヘッドは発生しますが、このユースケースではビルド時間の短縮幅に比べるとはるかに小さいものだっため許容しました。

まとめ

  • ジモティーのアプリケーションサーバーはFargateで構築されており、デプロイするDockerイメージをJenkins上でビルドしていた
  • 以下の課題をCodeBuild導入により解決した
    • ビルドの同時実行によるリソースの枯渇
    • ビルドがホスト環境のCPUアーキテクチャに依存するためARM用のイメージビルドが難しい
  • CodeBuildではローカルのイメージキャッシュを使えないというデメリットがあるがECRからpullしたイメージをキャッシュ指定することで解決できた(pullの通信によるオーバーヘッドは発生する)

この記事では、ジモティーにおけるCodeBuild導入の背景やそのメリット、そして工夫した点について解説しました。CodeBuildを活用することで、様々なアーキテクチャに対応したビルド環境を効率的に構築することができます。今後もテックブログでジモティーの開発事例や取り組みをご紹介していきますので、どうぞお楽しみに!


弊社では一緒にプロダクトを改善していただける仲間を探しています!

こちらでお気軽にお声がけください!

ネイティブアプリエンジニアの採用って難しいですよね。。。

ジモティーのウェブチームについてお話ししたいです