メドピア開発者ブログ

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

監査ログの保管先を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

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