メドピア開発者ブログ

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

Vue Fes Japan 2024に参加しました!#vuefes

こんにちは。 メドピアの伏見 ( @fussy113 ) です。

2024年10月19日に大手町プレイス ホール&カンファレンスで開催された Vue Fes Japan 2024 に参加してきました! この記事では当日の様子やセッションの内容などをお届けします。

スポンサーとしての取り組み

メドピアはゴールドスポンサー、セッションルームネーミングライツスポンサーとして協賛いたしました。

スポンサーブース

握力で技術的負債を粉砕しよう!
『握力測定 in Vue Fes Japan 2024』

と題して、握力を測定していただきました。

"せっかくなので、両手測ってもいいですか?" という方や、"こんな筈ではない、もう一回!"と再挑戦しに来てくださる方もいて非常に盛り上がりました! スポンサーブース企画として、なかなかユニークだったのではないでしょうか。

TOP3は技術広報のXアカウントにて公開中です。

1位の72.1kg、すごいですね...!

セッションルームネーミングライツスポンサー

「メドピアトラック」という名前のルームを開設しておりました。 キーノートセッションなどもこの「メドピアトラック」で開催され、広いホールの席がほぼ満席で埋まるなど大変な盛り上がりを見せておりました!

セッション

私が参加したセッションのうち、いくつかを紹介させていただきます。

キーノート

vuefes.jp

トップバッターEvan Youさんによるキーノートセッション!

これまでのVueの話から始まり、最新動向の共有がされました。 v3.5のアップデートの話や、v3.6についての言及がありましたね。 v3.6ではSuspenseのstableや、Vapor Modeの試験的導入などを予定しているとのことで、楽しみです!

またViteなど、エコシステム周りの今後についても話がありました。 VoidZeroによって、今後のフロントエンドの開発体験がどう変わっていくのか。 私はエコシステム周りをあまり追えていなかったので、フロントの今後の変化にワクワクしました。

Vaporモードを大規模サービスに最速導入して学びを共有する

vuefes.jp

2024年10月現在、R&Dが進められているVueの新しいコンパイル戦略、Vapor Mode。 vuejs/vue-vaporリポジトリのplayground上に実際にプロダクトレベルのコードを入れて、Vapor Modeによる恩恵がどれだけ得られるかを検証したという内容のセッションでした。

R&Dということもあり、従来のVueのコンポーネントでは動かないところから試行錯誤して動くところまでやり切る、凄まじい熱量を感じました。 また、Vapor Modeをオンにした時、バンドルサイズ、初期描画、更新速度について良い結果が出た点がとても興味深かったです。

Chromeを利用した計測の方法もとても定量的でわかりやすく、今後のVapor Modeへの期待がとても高まりました!

Vue3の一歩踏み込んだパフォーマンスチューニング

vuefes.jp

APIから最大1,000件のデータを取得、リアクティブにデータを編集できるページのパフォーマンス改善をVueの機能を利用して行なっていったという内容のセッションでした。 Vue2、Vue3それぞれで取れるアプローチの方法を紹介してくださっていたのが印象的でした。すぐに取り入れることが出来そうです。

また、Vue3からはリアクティブな追跡について細かいチューニングが出来るようになったようで、そのTipsが多く紹介されました。 進化したリアクティブAPIVueUsev-memoなど、新しい機能に対してのキャッチアップの重要さを感じました。

アフターイベント

全てのセッション終了後は、アフターイベントが開催されました。

Vue.jsを愛する人たちが集まって大変な盛り上がりを見せていました。

オリジナルカクテル、会社の人と1杯ずついただきました

私も他社のエンジニアの方と情報交換したりしました。 お話ししてくださった方、ありがとうございました!

参加してのまとめ

"Fes =お祭りのように Vue.js を共に盛り上げ、共に学び、そしてなによりも共に楽しむ"が体現されたイベントだなと肌で感じることができました。 私自身は実務に活かせるような内容のセッションをメインに聴いたこともあり、とても学びもあって非常に良い時間でした。

来年の開催が楽しみですね、また参加するのが今から楽しみです!

Vue Fes Japan 2024 After Meetup やります!

STORES、MNTSQ、メドピアの3社でAfter Meetupを開催します! https://medpeer.connpass.com/event/331417/

イベントタイトル: 『Vue Fes Japan 2024 After Meetup』
開催日時: 2024/11/1(金) 19:00 〜 21:30
会場: メドピア株式会社
東京都中央区築地1-13-1 銀座松竹スクエア8階
※ オフライン開催

各社のエンジニアによるLTやパネルディスカッションを予定しています。 ご興味がある方はぜひご参加ください!
昨年の様子は こちら


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


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

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

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

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

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

監査ログの保管先をRDBからS3に移行する

こんにちは。サーバーサイドエンジニアの @atolix_です。

今回はメドピアで運用しているアプリケーションのkakariの監査ログをDB管理からS3管理に移行したので、その方法と手順について紹介したいと思います。

kakari.medpeer.jp

背景

従来kakariではAuditedを用いて、監査ログを専用のauditsテーブルに保管する処理を行っていました。

github.com

# application_record.rb

class ApplicationRecord < ActiveRecord::Base
  ...
  include Auditable
# auditable.rb

module Auditable
  extend ActiveSupport::Concern

  included do
    audited
    ...
  end

しかしレコードの変更の度にauditsテーブルへの書き込みが走る為、DBマイグレーションを行なった際に書き込みのロックが発生して、アプリケーションの動作に影響が出るといったインシデントが発生してしまいました。

今回はこの恒久対応としてDBへの書き込みを廃止してS3に監査ログを保管できるように変更を加えていきます。

実装概要

監査ログを通常のアプリケーションログと分別してS3に保管したいので、以下のような構成を組みます。

アプリケーションログの出力は従来通りCloudWatch Logsに送信、一方で監査ログはKinesis Firehoseを経由してS3に格納できるようにFireLensを間に置いて二箇所に振り分ける想定です。

AuditLoggable

今回は食べチョクさんが作成してくださったAuditLoggableをインストールして、監査ログをファイルに出力するように変更します。 tech.tabechoku.com

Auditedと同様にapplication_record.rbに追記をすることでレコードの変更を追跡することが出来ます。

# application_record.rb

class ApplicationRecord < ActiveRecord::Base
  include Auditable
  ...
  extend AuditLoggable::Extension
  log_audit

initializersでaudit.logの書き込み場所を指定します。

# config/initializers/audit_loggable.rb

AuditLoggable.configure do |config|
  if Rails.env.test? || Rails.env.development?
    config.auditing_enabled = false
  else
    config.audit_log_path = Rails.root.join("..", "..", "opt", "audit.log")
  end
end

DBへの書き込みをしていた時と同様のリアルタイム性を保つ為に、audit.logへの書き込みを標準出力に吐き出すようにシンボリックリンクを貼ります。

# Dockerfile
...
RUN ln -sf /dev/stdout /opt/audit.log

標準出力された監査ログを確認すると、以下のようなjsonで出力されました。

{
  "timestamp": "2024-08-06T18:00:59.981+09:00",
  "record": {
    "auditable": {
      "id": 1,
      "type": "ModelType"
    },
    "user": {
      "id": 1,
      "type": "Admin::Account"
    },
    "action": "update",
    "changes": "{\"name\":[\"xxx\",\"yyy\"]}",
    "remote_address": "xxx.xxx.xxx.x",
    "request_uuid": "xxxxxxxxxx"
  }
}

これで監査ログが標準出力に出るようになったので、現在Railsからはアプリケーションログと監査ログの2種類が混ざって出力されることになります。

次はFireLensを通して2種類のログを振り分けて送信できるようにインフラ構成を修正します。

FireLens

AWSではFireLensを使用することでコンテナのログルーティング設定が簡単に実装出来ます。

docs.aws.amazon.com

コンテナ定義内でRailsのログドライバーにFireLensを指定した後、カスタム設定のファイル(今回はfluent-bit/etc/extra.conf)と送信先のKinesis Firehoseを設定します。

# container_definition.json

 {
    "name": "rails",
     ...
    "logConfiguration": {
      "logDriver": "awsfirelens" # FireLensを指定
    },
...
},
...
{
    "essential": true,
    "image": "${fluentbit_image}",
    "name": "fluentbit",
    "firelensConfiguration": {
      "type": "fluentbit",
      "options": {
        "config-file-type": "file",
        "config-file-value": "/fluent-bit/etc/extra.conf"
      }
    },
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "${log_group}",
        "awslogs-region": "${region}",
        "awslogs-stream-prefix": "firelens"
      }
    },
    "environment": [ # 送信先を外部から指定できるようにロググループやリージョンの情報を環境変数に格納する
      {
        "name": "LOG_GROUP",
        "value": "${log_group}"
      },
      {
        "name": "REGION",
        "value": "${region}"
      },
      {
        "name": "TARGET_FIREHOSE",
        "value": "${firehose_name}"
      }
    ]
  }

次にログをCloudWatch LogsとKinesis Firehoseの二箇所に分岐して流せるようにFluent Bitの設定を加えていきます。 先にDockerfileに必要な設定ファイルを記述しておきます。

# Dockerfile

FROM amazon/aws-for-fluent-bit:2.32.2 

COPY extra.conf /fluent-bit/etc/extra.conf
COPY parsers.conf /fluent-bit/etc/parsers.conf
COPY categorize_logs.lua /fluent-bit/etc/categorize_logs.lua
COPY stream-processor.conf /fluent-bit/etc/stream-processor.conf
COPY output.conf /fluent-bit/etc/output.conf

Fluent Bit内の設定詳細

ここからはログの大まかな処理の流れを説明していきます。

最初にRailsコンテナから受け取るログはFireLensを通して*-firelens-*でタグ付けされるので、分かりやすくする為に一旦stream-processor.conf内でcombine.webにタグを集約させます。

# stream-processor.conf
[STREAM_TASK]
    Name web
    Exec CREATE STREAM web WITH (tag='combine.web') AS SELECT * FROM TAG:'*-firelens-*';

combine.webタグが付与されたログはjsonパースされます。

# parsers.conf
[PARSER]
    Name         json
    Format       json
    Time_Key time
    Time_Format %Y-%m-%dT%H:%M:%S.%L%z
    Time_Keep On
    Time_Offset +0900

後に再度stream-processor.confを通った時にタグを書き換えやすくするために、一度luaスクリプトで仮のタグ情報を付与します。

function categorize_logs(tag, timestamp, record)
  if record["record"] ~= nil then
    record["new_tag"] =  "audit"
  else
    record["new_tag"] =  "rails"
  end

  return 2, timestamp, record
end

stream-processor.conf内でアプリケーションログと監査ログを判別できるようにタグを書き換える処理を加えます。

# stream-processor.conf
...

[STREAM_TASK]
    Name  audit
    Exec  CREATE STREAM audit WITH (tag='logs.audit') AS SELECT * from TAG:'*combine.web*' WHERE new_tag = 'audit';

[STREAM_TASK]
    Name  rails
    Exec  CREATE STREAM rails WITH (tag='logs.rails') AS SELECT * from TAG:'*combine.web*' WHERE new_tag = 'rails';

output.confでは先ほどstream-processor.confで書き換えたタグを元に、従来のコンテナログと監査ログをそれぞれCloudWatch Logs, Firehoseに振り分けます。

# output.conf

# 通常のコンテナログは従来通り CloudWatch Logs に送信する
[OUTPUT]
    Name              cloudwatch_logs
    Match             logs.rails # stream-processor.confで書き換えたタグ
    region            ${REGION}
    log_group_name    ${LOG_GROUP}
    log_stream_prefix rails

# 監査ログは Kinesis Firehose に送信する
[OUTPUT]
    Name              kinesis_firehose
    Match             logs.audit
    region            ${REGION}
    delivery_stream   ${TARGET_FIREHOSE}

最終的なextra.confとログの流れは以下のようになります。

# extra.conf

[SERVICE]
    Parsers_file parsers.conf
    Streams_File stream-processor.conf

[FILTER]
    Name         parser
    Match        combine.web
    Key_Name     log
    Parser       json

[FILTER]
    Name         lua
    Match        combine.web
    script       categorize_logs.lua
    call         categorize_logs

@INCLUDE output.conf

Kinesis Firehose

FireLensからS3にログを送信する間にKinesis Firehoseを挟みます。

一応S3プラグインを使って直接バケットに監査ログを送信することも可能ですが、今回はFargateのような永続ディスクのない環境でFluent Bitを実行しているので、突然のコンテナの停止時に監査ログをロストしてしまう可能性が考えられます。

その為に分散バッファーとしてKinesis Firehoseを経由して継続的にログを送信できるように構成しています。

github.com

設定自体は監査ログを格納するS3バケットをdestinationに指定したシンプルな内容です。

resource "aws_kinesis_firehose_delivery_stream" "fluentbit" {
  ...
  destination = "extended_s3"

  extended_s3_configuration {
    bucket_arn  = aws_s3_bucket.audit_logs.arn
    buffering_size     = 10  # MB
    buffering_interval = 300 # seconds
    compression_format = "GZIP"
    custom_time_zone   = "Asia/Tokyo"
    ...
}

動作確認

該当のバケットを確認すると監査ログが蓄積されていることが分かります。

念の為1週間ほど従来のDBへの保管とS3への保管を並行で稼働させて、レコード数に差がないことまで確認出来たらDBへの書き込みを停止して完了です。

まとめ

FireLensとKinesis Firehoseを使うことで比較的簡単に監査ログの保存先をS3に移行することが出来ました。 S3に保管した監査ログはAthena等と組み合わせてクエリ検索出来るようにしておくと良さそうです。

一方でDBへの書き込みと比較すると監査ログのロストの確率は少しだけ上がるので、移行する際にはログの欠損が起きないか検証を十分にする必要があります。


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


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

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

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

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

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

メドピアはVue Fes Japan 2024にゴールドスポンサーとして協賛します!

こんにちは。 10月からメドピアのVPoEになりました保立 ( @purunkaoru ) です。

メドピアは2024年10月19日に大手町プレイス ホール&カンファレンスで開催される Vue Fes Japan 2024 にゴールドスポンサー、セッションルームネーミングライツスポンサーとして協賛、そしてブースの出展も行います!

ブース企画

今回のメドピアブースは

握力で技術的負債を粉砕しよう!
『握力測定 in Vue Fes Japan 2024』

と題し、ブースを訪問いただいた皆さまに、握力測定を行っていただきます。

握力測定に挑戦!

握力測定、最近してますか?
学生以来測ってないという方!めったにないチャンスです!
この機会にぜひ挑戦してみてください。

アンケートに答えてメドピアオリジナルグッズを手に入れよう!

測定結果をアンケートに入力していただくと、メドピア特製アクリルスタンドと大きなビニールバック(通称デカバック)をプレゼント。
また、アンケートに参加いただいた方の中から抽選で、3名の方に豪華景品をプレゼント!

  • アクリルスタンド

  • オリジナルデカバック

この場所でお待ちしています

メドピアは、会場1階 カンファレンス106+107でブース出展しております。

ブースには、メドピアカラーの濃い緑色のTシャツを着たメンバーが立っています。
お気軽に声をお掛けください!

当日皆様にお会いできるのを楽しみにしております!

今年もAfter Meetupを開催します!

STORES、MNTSQ、メドピアの3社でAfter Meetupを開催します! https://medpeer.connpass.com/event/331417/

イベントタイトル: 『Vue Fes Japan 2024 After Meetup』
開催日時: 2024/11/1(金) 19:00 〜 21:30
会場: メドピア株式会社
東京都中央区築地1-13-1 銀座松竹スクエア8階
※ オフライン開催

ご興味がある方はぜひご参加ください!
昨年の様子は こちら


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


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

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

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

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

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

小さくはじめる 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