メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

小さくはじめる OKR

集合知プラットフォーム事業部・開発部の榎本です。

前回の記事はフロントエンドエンジニアの小林さんによる『小さくはじめる Vue の Composable』でした。

今回は小さくはじめるシリーズ第二弾ということで、今期開発部でOKRを導入してみて、それがいい感じにワークしたので紹介したいと思います。

OKR導入前の課題

私たちの開発部を含む組織図は下図のようになっていました。

flowchart TD
    事業部 --> 開発部
    事業部 --> A部
    事業部 --> B部
    事業部 --> ...

1つの大きな事業部があり、その事業部を構成するユニットの1つとして開発部がある形です。

事業部単位および部署単位でそれぞれ目標を設定しています。しかし、1つ大きな問題がありました。

それは、事業部目標と開発部目標が関連していないことです。つまり、事業部は事業部として達成したい目標がある一方、開発部は開発部として「(事業部とは関係のない)開発部がやりたいこと」をベースに立てた目標が掲げられていました。

その結果、以下の問題が生じていました。

  • 事業部目標と開発部目標がリンクしていない
  • 開発部目標が単なる開発チームのToDoになっている(ToBeではない)

このような状況だったため、私自身も日々の業務をしながら「同じ事業部なんだけど、他の部署とはどことなく違う方向を向いて仕事をしているな〜」という違和感が付いて回っていました。

会社という1つの船は同じ方向(目標)に向かって走るべき

OKRを導入

上記の課題を解決するために、開発部でOKRを導入しました。OKRを導入すれば、「事業部目標と開発部目標が関連しない」問題を解決できると考えたからです。

本来、OKRは組織全体で導入するべきものです。しかし組織全体としては現状運用しているMBOの目標設定があり、その制度はすぐには変えることができなそうだったので、開発部のみで試験的にOKRを導入・運用してみることにしました。

OKRをどう決めるか?

チームのOKRは、そのチームの上位チームのOKRから決めるべきとされています。『Google re:Work - ガイド: OKRを設定する』 には下記のように書かれています。

最初に組織の目標を表明しておくと、チームや個人がそれを考慮して自分たちの目標を設定できます。この方法なら、組織全体の OKR に整合性をもたせることができます。

(中略)

ただしチームの OKR は、組織の OKR の少なくとも 1 つには関係している必要があります。

この「組織の目標」を「事業部の目標」、「組織のOKR」を「事業部のミッション」に置き換えて開発部のOKRを考えてみることにしました。

OKR研修を実施

OKRを決める前に、まずはOKRという目標設定のフレームワークの理解を深める必要があります。

Google re:Work のOKRガイドや、社内のOKR経験者へのヒアリング、OKRに関する書籍を参考に資料にまとめ、マネージャー陣に共有しました。

この時使った資料は、公開用に内容をアップデートして、speakerdeckで公開していますので、よろしければご活用ください。

OKR基本のキ / OKR Basics - Speaker Deck

OKRの決め方

マネージャー陣でディスカッション

OKRの基礎知識をインストールできたら、いざOKRについてのディスカッションです。

開発部のマネージャー陣を招集し、下記の順番でOKRのディスカッションを進めました。

  1. Objective決め
    • 事業部目標をベースにブレスト
    • Objectiveの決定(3つ)
  2. Key Results(以下「KR」と表記します)決め
    • 1で決めたObjectiveを順番にKRをブレスト
    • KRの決定(1つの Objective 毎に約3つずつ)
  3. OKR運用方法決め
    • チームとしてどのようにOKRを運用していくかを決定

マネージャー陣でOKRをディスカッションしている様子

時間はめっちゃかかる

サラッと書きましたが、このディスカッションはとても時間を要しました。参加メンバー全員初めてのOKR、ということもあってか約4時間のミーティングを三回、計12時間以上ディスカッションしました。

大変骨が折れる作業ではありましたが、以下の理由から時間をかけただけの価値は十分にあったと考えています。

  • 事業部目標とリンクする目標設定ができた
  • 今期、何にフォーカスすべきか?」のコンセンサスが得られた
  • 腹落ちするObjectiveワクワクするObjectiveを設定できた
  • ディスカッションを通してマネージャー同士の相互理解が進み、目線を合わせることができた

工夫した点

KRオーナーを決める

良いOKRを決めたとしても、それを推進する旗振り役がいなければ、なかなか前には進みません。

私たちはそれぞれのKR毎にオーナーを決め、担当者にオーナーシップをもって達成率の向上にコミットしてもらいました。オーナーは個人の場合もありますし、複数名の場合もありますし、チーム名の場合もあります。

オーナーは具体的には下記のことをやってもらいました。

  • KR達成のために、関係者を巻き込み、まとめ上げる
  • KRの達成率の管理
  • 四半期毎の振り返りの実施

また、オーナーにとって下記のような良い副作用もありました。

  • 旗振り役になってもらった人の中には、チームをリードした経験が少ないエンジニアもいたが、OKRのリード経験を通して成長に繋がった
  • KRの達成のためにチームを跨いだ活動も増え、チームを超えた交流の機会になった

週定例で進捗を追う

言うまでもなく、OKRは立てて終わりではなく、達成に向けて動き続けなければなりません。

私たちの現在のObjectiveは何で、Key Results の最新の進捗状況をどれくらいなのかを週次の開発部の定例で確認するようにしました。これによって、メンバー各人がOKRに自覚的になり、達成に向けた動きを加速させることが出来たと感じています。

決まったOKRは一枚のスプレッドシートにまとめて公開し、進捗を誰からも一目瞭然にする

OKRの振り返り

OKRを半年間やってみて、振り返りを実施しました。その結果を一部抜粋して共有します。

よかったこと

多くの開発部メンバーから「やってよかった」「来期もOKRを続けたい」という声を得ることが出来ました。

他にも以下のポジティブな感想を得ることが出来ました。

  • 運用改善KRを推進したチームが運用チームにアンケートを取ったところ、「運用改善の効果を感じられたか?」「来期もこの取り組みを続けていきたい思うか?」という質問に対して、運用チームほぼ全員からポジティブな結果を得られた
  • ストレッチ目標を設定することで、自分のキャパシティを超えた活動まで可能になった
  • オーナーの中には、楽しそうにオーナーを勤めてくれる人もいて頼もしかった
  • 今までよりも費用対効果の高いタスクに優先的に取り組むことができた
  • 同じ事業部の別の部署にもOKR研修を実施し「OKRとは何ぞや」について理解してもらえた
  • 同じKRを追っているメンバーでモブプログラミング・モブレビューを実施し、知見を共有できた

課題に感じたこと

一方で振り返って課題に感じたことも出てきました。

  • 推進力がオーナーの力に依存してしまう
  • 数値に目が行きすぎて、目的を見失うケースが一部あった
  • もっと適切なKR成果指標があるのに、最初に定めたKRで硬直化してしまっていた
  • 「OKRツリー」という言葉があるが、必ずしも全てがツリー状には紐づかない

今後改善したいこと

上課題も踏まえて、次回OKRをもっと上手に回すために、下記の点に気を付けたいと思っています。

  • 上手な進め方をしているKRオーナーをモデルケースとして、ノウハウを横展開する
  • オブザーバーとしてマネージャー陣もOKRのミーティングに参加し、きちんと目的を失わないようにガイドする
    • 「Objective達成のためのKey Result」という意識を持つことが大事
  • KRの成果指標・数値は(適切な理由があれば)変更可能なことをオーナーに伝える
  • 「OKRをツリー状にすること」に拘らないようにする

OKRを成功させるためのポイント

実際にOKRを半年間運用していみて感じる、OKRを成功させるためのポイントは下記だと考えます。

  • 前提として上位組織(今回でいうと事業部)のObjectiveを明確にする
  • 皆が率先して「やりたい!」と思えるようなチャレンジングでワクワクするObjectiveを設定する
  • OKRを全体公開し、定期的に進捗をチェックし、必要であれば見直し・振り返りを行う
  • マネージャー・リーダーがOKRを理解し、達成に向けてコミットする
  • OKRをそのまま従業員評価ツールに使わない
    • 評価を上げるためのいわゆる「数値ハック」を防止
  • ムーンショットを目指すというOKRの考え方を理解し、ストレッチした目標をきちんと設定すること
    • MBOに慣れてしまった脳だと、達成可能な範囲内の数値目標を置いてしまいがち

MBO vs OKR

さいごに

今回は開発部で小さく始めたOKRの例を紹介させていただきました。

OKRは基本的に全社レベルで導入する目標設定フレームワークですが、部単位でOKRを設定しても十分にワークする手応えを得られました。

皆様の目標設定の一助になれば幸いです。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

小さくはじめる Vue の Composable

こんにちは。フロントエンドエンジニアの小林和弘 @kzhrk0430 です。

今日は、Vue の機能のひとつである Composable を導入してみた体験談をシェアしようと思います。Vue を使っている方にはおなじみの機能かもしれませんが、僕が所属するチームでは Composable があまり積極的に利用されていない状況だったので Composable を小規模に導入したお話をします。

Composable とは

Vue における Composable の基本的な概念と役割について説明します。

Vue 2 では機能ごとのオプション(data, methods, computed, etc…)を宣言する Options API という書き方が使われていました。この書き方は Vue の機能の関心事をまとめる役割を果たしていました。

export default {
    data() {},
    computed: {},
    methods: {},
    mounted() {}
    // ...他のオプション
}

Vue 2.7 以降、Options API とは異なる新しい Vue の書き方として Composition API が組み込まれました。Composition API では Options API で宣言していた各オプションが関数として実装されています。

Composable はこの Composition API を活用して、Options API では実現できなかった Vue の処理をまとめて再利用するための関数です。

なぜ Composable が利用されていなかったのか

チーム内で Composable があまり積極的に利用されていなかった理由としては、

  • 各画面が独自性の高い機能を持っていたため、処理を共通化する必要性が低かったこと
  • 画面を跨ぐ機能を持つものはコンポーネント(Atomic Design の organisms)としてきれいに切り出されていたこと

が考えられます。

他には Composable を多用することでコンポーネント間で共通化する必要がない処理まで外部ファイル化されて、Vue が持っている良さが損なわれてしまうことを懸念していました。

ここでいう Vue が持っている良さというのは単一ファイルコンポーネント(SFC: Signle File Component)と呼ばれる独自のファイル形式です。

Vue は *.vue という特殊なファイル形式をもっています。.vue ファイル内で script, template, style タグでそれぞれ JavaScript, HTML, CSS を管理することができます。

Composable を徹底しすぎると共通利用されない処理が SFC の外側に定義されて、SFC で処理が一覧化できていた vue ファイルの良さが損なわれると考えています。

すでに reactive で関心事をまとめていた

Composable を導入する前に、チーム内では Vue の reactive 関数を利用してコンポーネント内の関心事をまとめるという取り組みを行っていました。

reactive 関数は引数に渡した Object をリアクティブオブジェクトとして扱うことができます。これを利用して、コンポーネント内の機能をリアクティブオブジェクトに集約していました。

具体的にどのようなことをやっていたか説明します。

下記のキャプチャは実際に kakari というかかりつけ薬局を支援する SaaS で利用されている、薬局の臨時休業・臨時営業時間を設定するモーダルのコンポーネントです。

kakari で実際に使われている薬局の臨時休業・臨時営業時間を設定するモーダル

このコンポーネントでは 3 つのリアクティブオブジェクトを作成しています。

  • 営業時間の表示を管理する openingHour
  • 処方せん受付時間を管理する prescriptionReceivableHour
  • チャット受付時間を管理する chatReceivableHour

リアクティブオブジェクトは各時間の状態管理とイベントハンドラ管理を行っています。

営業時間表示コンポーネント、処方せん受付時間コンポーネント、チャット受付時間コンポーネントを作成して関心事を切り出すという方法もありますが、下記の理由からひとつのコンポーネント内で reactive で状態管理をしています。

  • コンポーネント化しても、親コンポーネント側で子コンポーネントの状態管理が必要
  • 他のコンポーネントで利用されるような汎用的なコンポーネントではない

リアクティブオブジェクトに関心事がまとまるのでコードの見通しはよくなりました。しかし、reactive 関数をあまり利用しない方がよいというのが昨今の Vue の流れです。

ref vs reactive

Vue Fes Japan 2023 のイベントの Vue.js クリニックで Vue の作者である Evan You が refreactive のどちらを使うのが良いのかという質問に対して、基本的には ref を使って欲しいという回答をしていました。

reactive の難点としては、reactive 関数の返り値を代入した変数がリアクティブオブジェクトなのか通常の Object なのか判別できないということが上げられます。

Composable で関心事をまとめる

Composable の話に戻ります。

reactive のリアクティブオブジェクトで関心事をまとめていましたが Object と判別がつかないという問題があったため、Composable でその役割を代替することにしました。

具体的な例を出します。

下記は Nuxt のページコンポーネント内で表示するフォームコンポーネントの内容を切り替えるための Composable です。

<script setup>
import { computed, ref } from 'vue';

function useForm() {
  const formType = ref<'auth' | 'message' | 'reply' | 'complete'>('auth');

  const isAuth = computed(() => formType.value === 'auth');
  const isMessage = computed(() => formType.value === 'message');
  const isReply = computed(() => formType.value === 'reply');
  const isComplete = computed(() => formType.value === 'complete');

  function moveToMessage() {
    formType.value = 'message';
  }
  function moveToReply() {
    formType.value = 'reply';
  }
  function moveToComplete() {
    formType.value = 'complete';
  }

  return {
    isAuth,
    isMessage,
    isReply,
    isComplete,
    moveToMessage,
    moveToReply,
    moveToComplete,
  };
}

// 中略: Composable 以外のコンポーネントの処理

const {
  isAuth,
  isMessage,
  isReply,
  isComplete,
  moveToMessage,
  moveToReply,
  moveToComplete,
} = useForm();
</script>

フォームコンポーネントの切り替えに利用している computed やメソッドが Compoasble にグルーピングされています。Composable 内でのみ参照している formType も外部に露出しなくなっています。

標準的な Composable の使い方であればuseForm 関数を外部ファイル化しますが、共通化する必要性がないものなのでページコンポーネントの script setup 内で useForm 関数を定義しています。

今回の例は独自性の高い機能なので Composable を共通化することは難しいですが、関心事をまとめた Composable を作成しておくことで将来的にその関心事を他のコンポーネントで流用したくなったときに関数を切り出すだけですぐにロジックを共通利用できます。

まとめ

関心事をまとめるという目的で Composable を小規模に導入してみたというお話でした。

Composable の主な利用方法は、サードパーティー JavaScript やライブラリ、Web API の処理を外部化して Vue コンポーネントのテストを書きやすくしたり、まとめたロジックを再利用することだと思っています。

今回紹介した関心事をまとめるという目的で Composable を利用するという話は公式ドキュメントにもコード整理のためのコンポーザブル抽出として説明がされていますが、SFC の良さを活かすために script setup 内で Composable を定義しているのが今回のお話でした。

なぜここまで関心事をまとめることに熱心になるのか考えてみましたが、Vue の公式ドキュメントの Composition API と Options API のトレードオフの話を読んでしっくりきました。

Options API は各オプションでコンポーネントの機能をグルーピングしてくれていましたが、Composition API では関数を宣言するだけの、いわば通常の JavaScript を書くときと同じようにコンポーネントの処理を書くようになりました。

Options API では定められたコードパターンによって秩序が保たれていましたが、Composition API にはその秩序がないため、reactive や Composable を活用して秩序をつくり出している状態であると想像しています。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。

ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note

style.medpeer.co.jp

Amazon CloudFront環境におけるクライアントIPアドレスについて 〜CloudFront-Viewer-Addressの紹介〜

こんにちは。サーバーサイドエンジニアの三村(@t_mimura39)です。

本日はRuby・Railsの話に限定せず、Amazon CloudFront を利用している方に役立つ情報をご提供します。

目次

はじめに

弊社は基本的にAWS上にRailsアプリケーションを構築しているため、CDNが必要になるとまず選択肢として挙がるのが 「Amazon CloudFront(以下CloudFront)」です。
「CloudFront」を最前列に配置して、その後ろに「ALB」と「Railsアプリケーションが稼働するECS」を置くような構成が主流です。

この構成の場合一つ困ることがあります。 それは「RailsアプリケーションからクライアントのIPアドレスを取得できない」という点です。

Railsアプリケーションへのリクエストの途中にプロキシやロードバランサーが挟まるとそれらのIPアドレスを「クライアントのIPアドレス」とRailsが誤認してしまうためです。

この話はCloudFrontやRailsに限定されたものではなく、古くからWeb業界で認識されている内容かと思います。
「古くからWeb業界で認識されている」ということは回避方法があります。

まずは従来の回避方法について簡単にご紹介します。

「X-Forwarded-For」を活用する方法

「X-Forwarded-For(以下XFF)」とはクライアントIP アドレスを特定するために利用されるHTTPヘッダーです。

developer.mozilla.org

以下のような構文で経路上のIPアドレスがすべて含まれます。

X-Forwarded-For: <client>, <proxy1>, <proxy2>

基本的にはXFFの先頭のIPアドレスが「クライアントのIPアドレス」となるわけですが、本ヘッダーは容易に改ざん可能(≒ IPスプーフィングリスクがある)なため一工夫必要です。
本件については以下のブログ記事が詳しいため詳細はこちらをご参照ください。

mrk21.hatenablog.com www.m3tech.blog

誤解を恐れずに簡単に説明すると「CloudFrontを含む信頼できるIPアドレスの一覧を取得しXFFからそれらを間引いた結果の末尾のIPアドレスをクライアントのIPアドレスとみなす」といった仕組みになります。

この仕組みの実装例として以下のようなGemがあります。

もちろんこれらのGemを使わずとも自前でCloudFrontのIPアドレスを取得・管理し、nginxなどでヘッダーを調整するようなアプローチもあるかと思います。

実装方法はともかく、この仕組みでクライアントの安全にIPアドレスを取得することが可能になります。
古くから伝わっている手法のためセキュリティ的なリスクは低いですが、以下のような問題点があります。

  • 背景も含め若干複雑な仕組みとなってしまう
  • (実装方法によっては)「CloudFrontのIPアドレスが更新された際に即時反映されず正しくクライアントのIPアドレスが取得できない」のリスクがある

「CloudFront-Viewer-Address」を活用する方法

上記XFFの問題を解決するための手法の一つとして、「CloudFront-Viewer-Address」をご紹介します。
「CloudFront-Viewer-Address」は2021年頃提供された機能で特別新しい情報ではないのですが、弊社内ではあまり認識されていなかったため改めて今回取り上げてみました。

詳細は以下のブログ記事がとても詳しいので是非ご参照ください。CloudFrontの設定方法についても詳細に書かれていたためとても参考になりました。

dev.classmethod.jp

簡単に説明しますとCloudFrontを経由するアクセスに対して、以下のような構文でCloudFrontのViewer(クライアント)のIPアドレスとポート番号をヘッダーに付け加えてくれる機能です。

CloudFront-Viewer-Address: <IPアドレス>:<ポート>

# IPv4 IPアドレス=192.0.2.0, ポート=46532
CloudFront-Viewer-Address: 192.0.2.0:46532  

# IPv6 IPアドレス=2001:DB8:0:0:8:800:200C:417A, ポート=46532
CloudFront-Viewer-Address: 2001:DB8:0:0:8:800:200C:417A:46532

本ヘッダーを活用することで、従来の手法(上記XFFの手法)よりもシンプルで確実にクライアントのIPアドレスを取得することができます。 一点注意点として、「CloudFront-Viewer-Address」はIPアドレスだけでなくポート番号が末尾に付与されるためここは微調整が必要です。

Railsエンジニアへ

ここからはRailsに限定されたお話です。
上記の通り「CloudFront-Viewer-Address」ヘッダーを利用することでクライアントのIPアドレスが取得できるのですが、いざRailsでリクエストヘッダーを参照するとなると少し面倒です。

正確にいうと、「単純にリクエストヘッダーを参照するだけ」であればこのように簡単に実装できます。

class ApplicationController
  def 本当のリモートIP
    if headers[:HTTP_CLOUDFRONT_VIEWER_ADDRESS].present?
      headers[:HTTP_CLOUDFRONT_VIEWER_ADDRESS].remove(/:\d+\z/)
    end
  end
end

しかし、従来の request.remote_ip のI/Fを維持するにはRack層で処理する必要がありそうです。 (モンキーパッチを当てたりすればどうとにでもなりますが、ここは健全なアプローチで話を進めます。)

というわけで、あらかじめ用意しておいたものがこちらになります。 github.com

本Gemをインストールするだけで、自動的に「CloudFront-Viewer-Address」を参照し request.remote_ip でクライアントのIPアドレスが取得できるようになります。
専用のRackミドルウェア( ActionPack::CloudfrontViewerAddress::RemoteIp )を作成し、自動的に ActionDispatch::RemoteIp の後ろにinsertするような作りとなっています。

注意点

本Gemは「CloudFront-Viewer-Address」の値を信頼して実装しています。
Railsに対しての全てのリクエストがCloudFrontを経由していれば本ヘッダーは改ざんされることがないため安全です。

しかし、CloudFrontを経由しないアクセスを受け付けている場合は、任意のIPアドレスを「CloudFront-Viewer-Address」に指定することができてしまうためIPスプーフィングのリスクがあります
各々のインフラ構成やIPアドレスの利用方法に応じて、ご利用の判断をしてください。

おまけ

CloudFrontには「CloudFront-Viewer-Address」の他にも便利なリクエストヘッダーを付与する機能があります。

例えばIPアドレスを元に「緯度・経度」「国名・都市名」なんかをCloudFrontが算出し、「CloudFront-Viewer-Address」と同じような感じでヘッダーに付与してくれるようなものです。
詳細はAWSの公式ドキュメントをご参照ください。 docs.aws.amazon.com

自前でGeo情報に問い合わせていたような処理を置き換えられることが期待できますね。

参考資料


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

EM目線で見たiOSDC/DroidKaigi

モバイル開発グループのリーダーを務める小林(@imk2o)です。 今年もiOSDCとDroidKaigiに参加してきました! 例年と異なり、今年はモバイル開発グループのマネジメントを行う立場で参加したこともあり、いつもとは異なる目線でカンファレンス参加への意義や、今後のモバイルアプリエンジニアのキャリアについて考えてみました。

開発コミュニティに関わること

まずこういったカンファレンスを「情報収集を行うための場」という見方をしていませんか? もちろん新しい技術や知見を獲得できる機会であることは間違いありませんが、もっと大切なことがあります。

それは他のエンジニアたちと直接意見交換することだと私は思います。 (これはiOSDC主宰の長谷川さんもよく仰っている言葉ですよね)

社内のエンジニア同士はもちろん、できれば社外のエンジニアとも積極的なコミュニケーションを取ってもらいたいなと思います。 現地にいくと、オンライン上では明かされない話や内情など、共感できる内容が聞けちゃうことが多いです。 企業ブースにいるエンジニアの方だけでなく、同じテーブルに座った方や待機列の後ろの方に「ちょっと話しませんか?」みたいに声をかけるだけで、実は皆さんけっこう会話してくれるんですよね(今回も何度かそういう機会がありました...優しい世界だ)。

アプリエンジニアのキャリア

キャリアについてはエンジニア共通の課題と言えるかもしれないですが、アプリエンジニアにはどんなキャリアがあるのかという点は、私もメンバーと会話するときによく悩んでいるところです。

そんな中DroidKaigiでのkonifarさんのセッションがまさにドンピシャのテーマだったので紹介しておきます。 Androidエンジニアに限らず、あらゆるエンジニアにとって参考になるのではないかと思います。 www.youtube.com

一度、自分のこれまでの経験を棚卸ししてみるとよさそうですね。 その中で自分の強みとか、伸ばしたいスキルを見つけ磨いておくと、必ずどこかで生かせる機会があるのではないでしょうか。 特にアプリエンジニアは、他ポジションのエンジニアやデザイナー、営業・企画チームなど様々な人と関わる機会が多いので、円滑にコミュニケーションするための知識やベーススキルを身につけ、守備範囲を広げやすい職種かなと思っています。

アプリエンジニアを持つマネージャの方へ

もしアプリエンジニアがiOSDC/DroidKaigiのようなカンファレンスの現地参加に消極的であるなら、ぜひ背中を押してあげてください。目前の仕事を一旦棚上げしてでも、行ってもらう価値はあると思います。 「楽しんでこい!」と送り出すとはいえ、マネージャとしては何かしらの成長・成果は期待したくなるでしょう。 一例ですが、

  • エンジニアコミュニティの輪を広げたか
  • 技術ブログに書く
  • 社内勉強会や報告会を行う
  • 業務に生かせる技術やソリューションの提案
  • 今後発表やLT登壇し自己成長やプレゼンスを上げる

などについて1 on 1で会話してみてもよいかなと思います。

ではまた来年、カンファレンスでお会いしましょう!


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

DroidKaigi 2024に参加してきました!

はじめに

こんにちは!メドピアにてモバイルアプリエンジニアをしている佐藤です。
今年のDroidKaigiに、弊社はサポーターとして協賛し、総勢3名のモバイルアプリエンジニアがオフラインにて参加しました。
実際に足を運んだセッションを中心に、DroidKaigi 2024の参加レポートをお届けいたします。

セッションについて

Android ViewからJetpack Composeへ 〜Jetpack Compose移行のすゝめ〜

syarihuさんによるセッションです。

youtu.be

このセッションではJetpack Composeへの移行の進め方が中心のお話となります。
弊社では複数のスマホアプリをリリースしておりますが、ほとんどのアプリがAndroidViewで実装されている為、今後移行を進めていく際の参考になる内容がてんこ盛りでした。
すでに動いているアプリではFullCompose化を目指すのはデグレのリスク面から腰が重くなってしまう部分がありますが、UIの描画だけをCompose化する方法や実例を基にした移行のお話などがあり、弊社アプリでも移行を進める際の戦略に取り入れて進めていきたいと思いました。

2024年のナビゲーション・フォーカス対応:Composeでキーボード・ナビゲーションをサポートしよう

Tiphaine (ティフェン)さんによるセッションです。

youtu.be

ナビゲーション・フォーカスの説明から実装方法、デバッグのやり方について触れられていました。
私はナビゲーション・フォーカス対応の実装経験がなかった為、とても興味深く勉強になる内容でした。
知識として知っているというだけでもアプリ改善の際の提案の幅が広がるので、本セッションを通じてAndroidアプリエンジニアとして一段階成長することが出来たと思います。

また、余談にはなりますが本セッションのスピーカーであるTiphaineさんは弊社の元社員であり、MedPeer時代には多くのことを学ばせていただきました。
セッション後には弊社モバイルアプリエンジニアと記念撮影も行い、ブログへの掲載も快諾してくださいました。
Tiphaineさんの多大なるご厚情に心より感謝申し上げます。

要約:
あざす〜!!!!!!!!!!!!!!!
今度みんなで飲みに行きましょ〜🍻

Tiphaineさん(写真中央)と弊社MBアプリエンジニアの王(写真左)と佐藤(写真右)

KSPの導入・移行を前向きに検討しよう!

shxun6934さんによるセッションです。

youtu.be

KSPとkaptの説明、移行のメリットなどについて触れられていました。
弊社のアプリの中にはDataBidingを多用しているアプリもあり、DataBindingはKSPに非対応であることからKSPとkaptを併用するとビルド時間が遅くなるというのはまさに直面しそうな内容だった為、本セッションで予め知れて良かったです。
今後K2への移行に伴いKSPとkapt周りは触れる部分になってきますので、このセッションの内容はタイムリーでとても参考になりました。

電池寿命を考えた位置情報の監視方法を考える(Geofence)

はるちろさんによるセッションです。

youtu.be

Geofenceの説明、自作のGeofenceとGMSの比較などについて触れられていました。
Geofenceの機能を扱うアプリはあまり多くはないと思いますが、弊社のリリースしているアプリの中にはGeofenceを用いているアプリもあった為とても親和性を感じるセッションでした。
私自身もGeofence周りのコードに触れたことはありますが、私が1から開発したのではなく前任者から引き継いだアプリの為Geofenceの理解は浅瀬だった為、本セッションを通じて理解を深められ本当に良かったです。
余談ですがこのセッションを聞いた当日にGeofenceに関連するissueが立てられた為、「楽しい〜!」ってなりながら対応を進められました!笑

仕組みから理解する!Composeプレビューを様々なバリエーションでスクリーンショットテストしよう

Sumio Toyama (sumio_tym)さんによるセッションです。

speakerdeck.com

各ライブラリの説明やテストの実装方法について触れられていました。
コードを中心にお話があり、本セッションを通じて実装方法が参考になるのはもちろんのこと、スクリーンショットテストへの理解もとても深まる内容でした。
本セッションのお話でもあったような特定のバリエーションにおけるUIのバグ周りはまさに後回しにしがちだったりナイトモード自体にまだ対応していなかったりだった為、今後取り入れていきたい内容が満載のセッションでした。

アプリをリリースできる状態に保ったまま段階的にリファクタリングするための戦略と戦術

Yuki Anzaiさんによるセッションです。

youtu.be

リファクタリングを進める上での戦略から実際にどのようにリファクタリングを行うかをコードを交えてのお話でした。
参考になるリファクタリング例がてんこ盛りで勉強になったのはもちろんのこと、品質を維持したままリファクタリングを進める上での戦略がとてもわかりやすく説明されておりました。
特に戦略①であった環境を整えるの部分については改めてとても大切だと実感しましたので、弊社モバイルアプリエンジニア内においても周知を行いたいと思います。

Jetpack ComposeにおけるShared Element Transitionsの実例と導入方法 またその仕組み

hyogaさんによるセッションです。

youtu.be

Shared Element Transitionsの説明から実装方法についてのお話でした。
Shared Element Transitionsへの理解が深まったのはもちろんのこと、サンプルアプリも素晴らしくこんな素敵なアプリのコードがGitHub上で公開されていることに感謝が堪えません。
特に遷移アニメーション中のHorizontalPagerのスクロールを無効にするバグ対応の部分は結構な躓きポイントだと思います。もし私が実装していたら余裕で頭を悩ませていた自信があります笑
これらの解消方法含め知ることが出来てとても面白いセッションでした。

分析に裏打ちされたアプリウィジェット開発 - Jetpack Glanceとともに

Miyabi Goujiさん、Yuri Oguraさんによるセッションです。

youtu.be

ウィジェットに関する説明、ウィジェットを設置することによるKPIへの貢献や効果検証、実装方法などについて触れられていました。
個人的にはこのセッションが一番面白かったです。
特にデータ分析周りにも触れられており、ウィジェットが事業に対してどのように貢献し、そして認知度の少ないウィジェット機能をどのように訴求したかについても話されており、本セッションの内容はモバイルアプリエンジニアだけでなくアプリに携わるチームメンバーに対しても知っておいてもらいたい内容でした。
YouTubeにアップロードされた後は早速社内のチームメンバーにもシェアしました!

2024年最新版!Android開発で役立つ生成AI徹底比較

Nishimyさん、wiroha(ゐろは)さんによるセッションです。

youtu.be

Gemini、GitHub Copilot、ChatGPTの比較から活用事例について触れられていました。
生成AIは今では開発においてなくてはならないツールの一つであり、弊社においてもCopilotは希望するエンジニア全員に、ChatGPTも社内版ChatGPTの構築が行われエンジニア以外にも多くのメンバーが活用しております。
各生成AIの実務レベルでの活用例を本セッションで知ることが出来、弊社においてもより一層生成AIの活用を促進していきたいと思える素晴らしいセッションでした。

社内版ChatGPTの構築に関しては記事も公開されていますので、興味のある方はご参照ください。

tech.medpeer.co.jp

使って知るCustomLayout. vs DailyScheduler

Saiki Iijimaさんによるセッションです。

youtu.be

CustomLayoutに関する説明からDailySchedulerの実装方法について触れられていました。
ゴリゴリのJetpackComposeセッションでCustomLayoutに関する理解が深まったのはもちろんのこと、8:28辺りにある各関数の使い分けが出来るフローチャートはわかりやすくて最高です。
レイアウト周りは触れることの多い箇所なので、本セッションを理解することでレイアウト構築における選択肢の幅が広がる素晴らしいセッションでした。

AndroidアプリのUIバリエーションをあの手この手で確認する

Nozomi Takumaさんによるセッションです。

youtu.be

タイトル通りUIバリエーションの確認に関する様々な説明について触れられていました。
多くの確認方法の説明がありとても勉強になりました。特にフォルダブル端末の確認方法については疎い部分がありましたので、フォルダブル端末の確認やVirtual sensors活用例の部分は本当に助かる情報でした。

実践!難読化ガイド

みっちゃんさんによるセッションです。

youtu.be

タイトル通り丸々難読化に関するお話です。
難読化周りはたまにしか触れないので若干苦手意識があったのですが、このセッションのお陰で難読化面白い!となりました。
特に原理原則に基づいて立ち向かおう!のお話はコアな内容でありながらもとてもわかりやすくまとめられておりめちゃくちゃ面白かったです。

余談にはなりますが弊社では定期的に勉強会を実施しているのですが、現在はOWASP Mobile Top 10に関する勉強会を実施しており、このセッションを聞く2日前に M7: Insufficient Binary Protection Threat Agents の章に関する勉強会を実施してリバースエンジニアリングに関して語った後でしたのでより一層難読化セッションが楽しめました。
難読化のセッションと合わせてOWASP Mobile Top 10のこちらの章を読むことでより一層理解が深まると思います。

スポンサーブースについて

スポンサーブースにも多くの人がいて賑わいを見せており、すべては回りきれませんでしたが私も多くのスポンサーブースに足を運ばせていただきました!
ブースに足を運ぶことで各社に対する理解が深まり、ブースへ足を運んだことをきっかけに数多くのアプリをインストールしました!
話を聞いた後に実際にその会社のアプリのUI/UXに触れるのはリアルで交流した後ということもありめちゃくちゃ面白いです。

話を聞いてサービス面で特に面白かったのはMagicPodさんです。
一部弊社のサービスでもコア機能となる箇所はEspressoを用いてUIテストの導入を進めたりもしましたが、コア機能ゆえに変更が入り壊れることが多々ありメンテナンスには苦労しているのですが、何とMagicPodさんではAIが自動でスクリプト修正を行なってくれるとのこと!!!
UIテストの導入コストが下がるだけでなくメンテナンスコストも下げられる素敵なサービスだと思いました!

magicpod.com

また、ノベルティも多くいただきました!
どれも素敵なノベルティばかりでしたが、個人的に特に嬉しかったのがタイミーさんとnewmoさんのノベルティです。

タイミーさんのブースではガチャを回しタンブラーが当たったのですが、飲み物を美味しく飲むのはもちろん、ロゴがとても可愛いのでMTGでちょっとした雑談の際のネタにもなりそうなので重宝しております。

newmoさんではフェイスタオルをいただきました。私は趣味で毎週スーパー銭湯に通い月一ペースで遠征も行なっており、新しい銭湯施設に行った際は温泉タオルを収集している位温泉タオルが好きなので、このノベルティは本当に嬉しかったです。
このフェイスタオルを持ってDroidKaigiの翌日にスーパー銭湯で早速活用させていただきました!
切実に販売もして欲しい…!笑

最後に

DroidKaigi 2024はセッションによる勉強、ブースでの交流と得ることが多くとても有意義な大会でした!
このような素敵な大会を提供してくださったDroidKaigi運営の方々・スピーカーの皆様・多くの企業・DroidKaigiに関わるすべての方々に感謝を申し上げます。
また来年も参加できることを楽しみにしております!

弊社MBアプリエンジニア小林(写真右)と佐藤(写真左)


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

YJIT有効化後にUnicornワーカーを増やした場合の各メトリクスの推移について

はじめに

こんにちは。サーバーサイドエンジニアの冨家(@asahi05020934)です。現在は、全国の医師が経験やナレッジを 「集合知」として共有し合う医師専用コミュニティサイト「MedPeer」の開発を行っています。

Ruby 3.2からYJITが実用段階になりました。「MedPeer」でもパフォーマンスを改善するためにYJITを有効化することになりました。

本番環境でYJITを有効化後、ECSサービスのMemory Utilizationに余裕があったので、ECSサービスのUnicornワーカー数を増やすことを検討しました。 しかし、YJIT有効化後のUnicornワーカー数とメトリクスに関してまとめられた記事が少なく、どのように推移していくのか予想を立てるのは簡単ではありませんでした。YJIT有効化後にUnicornワーカー数を増やすと各メトリクスはどのように増減していくのでしょうか。

そこで、今回は「MedPeer」の本番環境でYJIT有効化後にUnicornワーカー数を増やしていった場合の各メトリクスの増減を明らかにしていきます。

計測した環境

以下の環境をもとに計測を行いました。

  • ECSタスク数: 8
  • Rubyのバージョン: 3.3
  • Railsのバージョン: 7.0.8.4
  • ECSタスクが使用するメモリ量: 8192MiB
  • ECSタスクが使用するCPUユニット数: 4096

手順

以下の手順に沿って計測していきました。

  1. Unicornワーカー数が96の日の0時0分から23時59分までのメトリクスを計測する
  2. Unicornワーカー数を96から104に増やす
  3. Unicornワーカー数が104の日の0時0分から23時59分までのメトリクスを計測する
  4. Unicornワーカー数を104から112に増やす
  5. Unicornワーカー数が112の日の0時0分から23時59分までのメトリクスを計測する
  6. 1と3と5の計測した結果を比較する

それぞれの計測日を「MedPeer」のサービスの1つである「Web講演会」の全配信の日程に合わせることで、リクエスト数やリクエストの内容に大きな差異が起きないように工夫しました。

計測すると、Unicornワーカー数112の時点でECSサービスのMemory Utilization Maxの最大値が84.23%まで上昇しました。 これ以上Unicornワーカー数を増やすと本番環境がダウンする可能性を危惧し、Unicornワーカー数を112まで検証することにしました。

結果

表1は、Unicornワーカー数96、Unicornワーカー数104、Unicornワーカー数112の各メトリクスの平均値を表したものです。

Unicornワーカー数96 Unicornワーカー数104 Unicornワーカー数112
ECSサービスのCPU Utilization Avgの平均(%) 5.19 3.33 5.38
ECSサービスのMemory Utilization Avgの平均(%) 35.10 35.50 36.70
ALBのResponse Time p50の平均(ms) 50.00 51.00 48.00
ALBのResponse Time p90の平均(ms) 193.00 210.00 210.00
ALBのResponse Time p95の平均(ms) 317.00 380.00 327.00
ALBのResponse Time p99の平均(ms) 643.00 735.00 647.00
RDSのCPU Utilizationの平均(%) 5.32 4.15 5.87
レイテンシー p50の平均(ms)   41.10 40.80 39.90
レイテンシー p75の平均(ms)   68.40 68.00 68.50
レイテンシー p90の平均(ms)   150.30 159.00 154.00
レイテンシー p95の平均(ms)   255.50 289.30 253.10

表1 Unicornワーカー数96、Unicornワーカー数104、Unicornワーカー数112の各メトリクスの平均値

表2は、表1をもとに各メトリクスの変化率を計算したものです。

96から104の変化率(%) 104から112の変化率(%)
ECSサービスのCPU Utilization Avgの平均 -35.84 61.56
ECSサービスのMemory Utilization Avgの平均 1.14 3.38
ALBのResponse Time p50の平均 2.00 -5.88
ALBのResponse Time p90の平均 8.81 0.00
ALBのResponse Time p95の平均 19.87 -13.95
ALBのResponse Time p99の平均 14.31 -11.97
RDSのCPU Utilizationの平均 -21.99 41.45
レイテンシー p50の平均   -0.73 -2.21
レイテンシー p75の平均   -0.58 0.74
レイテンシー p90の平均   5.79 -3.14
レイテンシー p95の平均   13.23 -12.51

表2 Unicornワーカー数96、Unicornワーカー数104、Unicornワーカー数112の各メトリクスの変化率

考察

ECSサービスのCPU Utilization Avgの平均

計測結果

96から104の変化率は-35.84%であるのに対し、104から112の変化率は61.56%であることがわかりました。このことから、ワーカー数を上げることでECSサービスのCPU Utilization Avgは上がるとは限らないことがわかりました。

考察

直感に反する結果となりました。リクエストの負荷自体が少ない場合、ワーカーを増やしても処理するタスクが少ないため、CPU使用率はあまり上がらなかった可能性があると考えました。

ECSサービスのMemory Utilization Avgの平均

計測結果

ワーカー数を増やすたびに35.10%、35.50%、36.70%と増えています。このことから、ワーカー数を増やす程ECSサービスのメモリがより使われていくことがわかりました。

考察

直感通りの結果になりました。YJITはメタデータにもメモリを使用します。そのため、ワーカーを増やしてメタデータが増えたことで、メモリ使用量が増えることが考えられます。

ALBのResponse Time

計測結果

p50の平均はワーカー数を増やす度に50.00ms、51.00ms、48.00msとレスポンス速度が速くなっていることがわかりました。しかし、p90の平均、p95の平均、p99の平均はワーカー数を上げる度に速度が上がっていないことがわかりました。このことから、ワーカー数を上げることで、基本的なレスポンス速度は速くなるが、遅めのレスポンスは速度が速くなるとは限らないことがわかりました。

考察

p50の平均以外は、直感に反する結果となりました。p90以上ではネットワークの遅延が影響し、一部のリクエストが遅くなる可能性があると考えました。

RDSのCPU Utilizationの平均

計測結果

96から104の変化率は-21.99%であるのに対して、104から112の変化率は41.45%であることがわかりました。このことから、ワーカー数を上げることで、RDSのCPU Utilizationの平均は上がるとは限らないことがわかりました。

考察

直感に反する結果となりました。ワーカー数を増やしても、発行されるクエリがシンプルで、CPUを大量に使用しない場合、RDSのCPU利用率は上がらなかった可能性があると考えました。

レイテンシー

計測結果

p50の平均は、ワーカー数を上げるたびに41.10ms、40.80ms、39.90msと速くなっていることがわかりました。しかし、p75の平均、p90の平均、p95の平均はワーカー数を上げる度に速度が速くなっていないことがわかりました。このことから、ワーカー数を上げることで基本的なレイテンシーは速くなるが、遅めのレイテンシーは速くなるとは限らないことがわかりました。

考察

p50の平均以外は直感に反する結果となりました。高パーセンタイルのレイテンシーには、ネットワークの遅延や一時的な輻輳が影響することがあるので、順当に速くなっていかなかった可能性があると考えました。

結論

以上、「MedPeer」の本番環境でYJIT有効化後にUnicornワーカー数を増やした場合の各メトリクスの増減を明らかにしていきました。その結果、ワーカー数を増やすことで、「ECSサービスのMemory Utilization Avgの平均」は順当に増えていき、「ALBのResponse Time p50の平均」と「レイテンシー p50の平均」は順当に速くなることがわかりました。これは、Unicornワーカー数を増やすことでアプリケーションのメモリ使用量は増えていき、基本的な速度は改善していくということでしょう。

しかし、他のメトリクスはワーカーを増やすことによって順当に変化していきませんでした。より正確な原因については明らかになっていないので、今後の課題にしたいと思います。

最後に

今回の記事の他にもYJITに関連がある記事を執筆しているので、興味がある方はぜひ読んでみてください。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

iOSDC Japan 2024に参加しました

みなさん、こんにちは!アプリエンジニアのオウです。

先日、iOSDC Japan 2024に参加してきました!今年、メドピアはシルバースポンサーとしてiOSDC Japanをサポートいたしました。会場はたくさんの参加者で賑わい、とても充実した時間を過ごすことができました。いくつか印象に残ったセッションを紹介したいと思います。

Appleウォレット / Googleウォレットに チケットを保存する方法 speakerdeck.com

このセッションでは、Apple WalletとGoogle Walletにパスを保存する方法についての説明です。実は今回のiOSDCで初めてチケットをApple Walletに保存したんですが、流れがスムーズで印象的でした。普段はWallet関連の開発には関わらないんですが、これを機にちょっと調べてみました。iOSでパスを新規作成するのはシンプルなんですが、更新フローになるとWeb APIやプッシュ通知を使うので結構複雑。今後のプロジェクトで使えたらいいなと思っています。

メインスレッドをブロックさせないためのSwift Concurrencyクイズ speakerdeck.com

このセッション、個人的には結構面白かったです。Swift Concurrencyのクイズを通して実践的な知識が身につく感じがしました。これまで意識していなかったんですが、SwiftのバージョンによってConcurrencyのルールが変わることもあるんですね。ActorとかGlobal Actor周りのルールは覚えなくても、コードを書くときにメインスレッドをブロックしないよう意識することが大事だと思います。ちなみに、5問中4問正解しました!!

Meet BrowserEngineKit speakerdeck.com

BrowserEngineKitについてのセッションでした。これはiOS17.4から使えるようになるみたいで、実際のプロジェクトで利用できるようになるのは少し先ですが、カスタマイズ性が高いので今後使ってみたいなと思っています。ただ、EUの制約に対する対応が必要だったりと、ストア公開にはいくつかハードルがあるみたいです。セキュリティ面の制限も厳しそうです。XPC frameworkも興味深いです。シンプルなAPIインターフェイスでプロセス間の安全なデータやり取りができるのが特徴です。

StoreKit2によるiOSのアプリ内課金のリニューアル speakerdeck.com

StoreKitからStoreKit2に移行する話についてのセッションでした。自分もいくつかのプロジェクトでStoreKitを使っているんですが、トランザクション処理が結構面倒だなと感じていました。StoreKit2ではトランザクション処理が簡単になり、非同期コードもより直感的に書けるようになっているのがいいですね。StoreKit Testingや返金対応も便利になっているので、今後のプロジェクトでも活用したいです。

すべてのヘルスケアデータを紐解く speakerdeck.com

iOS 17からヘルスケアデータをエクスポートできるようになったということで、どんなデータが取れるのかを紹介するセッションでした。普段あまり触らない分野なので、細かくデータの中身を説明してもらえて勉強になりました。そもそもCDAという標準規格があるのも知らなかったです。医療業界でこういったデータが活用される場面が増えるのかもしれません。

リョムキャットのパーフェクトSwiftネーミング教室 speakerdeck.com

このセッションでは、Swiftにおける命名規則についてのベストプラクティスを学びました。個人開発だとあまり気にしないかもしれませんが、チーム開発では読みやすくて保守しやすいコードを書くための命名ルールが本当に重要だなと感じました。

所感

今年のiOSDCは9年目の開催ということで、運営もとてもスムーズで、快適に参加することができました。会場の配置や時間管理、会場の気温など、細部にまで気を配った運営には感心しました。

イベント中に他の参加者や企業の方々と交流することで、普段は知ることのできない知識を得ることができ、とても有意義な時間を過ごせました。特にiOS関連の深い話題や、日常業務ではなかなか触れない技術について学べたのが大きな収穫でした。

ただ、残念だったのはAIに関する話題があまり取り上げられなかったことです。私は、今後のアプリ開発においてAIが非常に大きな影響を与えると考えています。AI技術の進展により、私たちの開発プロセスや提供するユーザー体験がどう変わっていくのか、自分自身でもリサーチしてみようと思います。

今回のiOSDCで得た新しい技術や他の開発者とのつながりは、今後の仕事にも役立つと感じました。来年もまた参加できることを楽しみにしています。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp