Open Yoosuke opened 1 year ago
⑥_1 障害監視インフラの構成
アプリケーションログ -> Sentry -> Gmail
の流れで、異常系ログを送信することで監視を行う。
アプリケーションから Sentry.capture_message/2 をコールすることで アプリケーション -> Sentry
にログを送る。
Sentry のメール通知機能を用いて Sentry -> Gmail
通知を行う。
Sentryというエラー監視をはじめとする様々なトラッキング機能を提供する](https://sentry.io/welcome/)%E3%81%A8%E3%81%84%E3%81%86%E3%82%A8%E3%83%A9%E3%83%BC%E7%9B%A3%E8%A6%96%E3%82%92%E3%81%AF%E3%81%98%E3%82%81%E3%81%A8%E3%81%99%E3%82%8B%E6%A7%98%E3%80%85%E3%81%AA%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%E6%A9%9F%E8%83%BD%E3%82%92%E6%8F%90%E4%BE%9B%E3%81%99%E3%82%8B) SaaS を採用。
無料の Developer プランを使用。 1アカウント / 月5000ログ制限 / ログの保持期間30日 などの制限がある。
無料の Developer プランだと Gmail 通知は可能だが、 Sentry の Integration 機能を使って Slack などに通知するのは不可能(Teamプラン以上でなければならない)。
現状の要件では Gmail 通知までできればいいので問題ないが、Slack などの外部サービスへの通知を行いたい場合は Zapier や GAS などで自前で作りこむ、もしくは Team プラン以上にアップグレードして Integration 機能を使うことを検討。
アプリケーションではこちらのSentry公式のライブラリを使用
Phoenix アプリケーションで Sentry を導入し Gmail にメール通知するまでのフィジビリティ確認を行い、以下の記事にまとめた。
⑬_1 エラーハンドリングのグランドルール
エラーのおおまかな考え方は以下
Logger.error
は使わず Logger.warning
を使う)Logger.error
は使わず例外を投げる想定)準正常系のエラーを返す可能性があるContext
, Util
は以下の形式で値を返す
{:ok, any()} | {:error, error_type :: atom(), params :: any()}
後処理でのエラーなどはユーザーには正常なレスポンスを返しつつ、必要ならシステムにアラートを飛ばす
case File.rm(path) do
:ok -> :ok
{:error, reason} -> Logger.error("Failed message #{inspect(reason)}")
end
基本的には異常系のエラーが起こった場合は例外をraiseする or !
付きの関数を使う
エラーハンドリングは DmClusterWeb.ErrorHandler
で行う
lib/dm_cluster/exceptions.ex
のエラーをraiseする基本的にFallbackControllerで処理する
Controllerの実装時に DmClusterWeb.FallbackController
をaction fallbackに設定する
action_fallback(DmClusterWeb.FallbackController)
エラーレスポンスの内容は DmClusterWeb.FallbackController
に実装する
準正常系についてはテストを実装すること
(TJさん、tamanugiさん抜き出し後に差異があれば埋め合わせお願いします)
server/lib/dm_cluster_web/templates/error/error.html.heex
ステータスコード | エラーページ要否とページ内容の整理 |
---|---|
400 Bad Request | エラー通りで良い →タイトル「リクエストが正しくありません」 →メッセージ「リクエストが正しく無いため、アクセスできない状態です[br]お手数ですが、URLを見直してください」 →「/」に戻るボタン付き |
401 Unauthorized | ログインページにリダイレクトするのでエラーページ不要 |
402 Payment Required | 該当機能無し |
403 Forbidden | 404として扱う |
★404 Not Found | エラー通りで良い ※基本400と同じデザイン →タイトル「ページが見つかりません」 →メッセージ「大変申し訳ありません[br]お探しのページは見つかりませんでした」 →「/」に戻るボタン付き |
405 Method Not Allowed | エラー詳細を隠したいので400エラーと同じで良い |
406 Not Acceptable | エラー詳細を隠したいので400エラーと同じで良い |
400系の以降 | エラー詳細を隠したいので400エラーと同じで良い |
★500系全般 | エラー詳細を隠したいので単一メッセージで良い ※基本400と同じデザイン →タイトル「アクセスできない状態です」 →メッセージ「サーバーにアクセスが集中しているか、メンテナンスを実施中のため、一時的にアクセスできない状態です[br]申し訳ありませんが、少し時間を空けてから再度アクセスを行ってください」 →「/」に戻るボタン付き |
各フォーム入力時エラーは、各入力項目の直下にリアルタイム表示(LiveView通り)
<div class="mb-6">
<%= label f, :name, "企業名", class: "text-xs mb-0.5 block" %>
<%= text_input f, :name, class: "input" %>
<%= error_tag f, :name %>
</div>
on_mount
時のエラーのハンドリングは DmClusterWeb.ErrorHandler
で行うhandle_event/3
で rescue する必要はない) 個別にメッセージが必要な場合は try-rescue
で拾ってフラッシュメッセージを実装する
@impl true
def handle_event("search", params, socket) do
search_params =
params
|> Map.take(SearchCondition.string_keys())
|> Enum.reject(fn {_, v} -> is_nil(v) end)
|> Enum.reject(fn {_, v} -> v == "" end)
socket =
socket
|> assign(:open_sp_item_search, false)
rescue
_ ->
socket =
socket
|> put_flash(:error, "任意のメッセージ")
{:noreply, socket}
end
⑬_2 ログのグランドルール
DmCluster.Logger
を使うことalias DmCluster.Logger
Logger.error("Parameter type check error", params: params)
[API] dm-send [message] hogehoghogehogheo [error_code] 20001
hogehoghogehogheo
ログレベルの基準は以下のとおり
resource.type="k8s_container"
resource.labels.project_id="dm-cluster-dm-send-dev"
※環境ごとに値は異なる
⑬_3 Ecto DSLのグランドルール
from
で統一することselect:
を省略することfrom |> Repo.*
の形式にしパイプを使ってカッコの連続をしないことon:
は SQL風に記述すること 複数の連結条件はand
を使用すること駄目な例:fromを使っていない
def get_history_from_order_number!(order_number) do
History
|> where([h], h.order_number == ^order_number)
|> Repo.one!()
end
改善例
from(history in History,
where: history.order_number == ^order_number
)
|> Repo.one!
駄目な例:fromでないかつjoinの引数が増えてよろしくない
参考元 https://medium.com/flatiron-labs/how-to-compose-queries-in-ecto-b71311729dac
def with_transmission(query \\ Car, type) do
query
|> join(:left, [c], s in Specification, as: :specifications, on: c.specification_id == s.id)
|> join(:left, [c, specifications: s], t in Transmission, as: :transmissions, on: s.transmission_id == t.id)
|> where([c, transmissions: t], t.type == ^type)
end
駄目な例:fromでないかつjoinの引数を省略すると変数が暗黙になるため事故を起こしやすい
def with_transmission(query \\ Car, type) do
query
|> join(:left, [c], s in Specification, as: :specifications, on: c.specification_id == s.id)
|> join(:left, t in Transmission, as: :transmissions, on: s.transmission_id == t.id)
|> where( t.type == ^type)
end
join時のon:
の駄目な例:
複数の連結条件はand
を使用しないSQLと互換性がない書き方
from(history in History,
join: item in Item,
on: [id: history.item_id, company_id: history.company_id]
)
改善例
from(history in History,
join: item in Item,
on: history.item_id == item.id and history.company_id == item.company_id
)
(ymnさんリード)
⑬_4 テストのグランドルール
種別 | 説明 | 備考 |
---|---|---|
正常系 | ある操作がエラーを出さずに処理されることをテストする | HTTPにおけるステータスコード 2xx や 3xx のイメージ |
準正常系 | ある操作が想定済みのエラー(必須項目が入力されていない等)を出すことをテストする | HTTPにおけるステータスコード 4xx のイメージ |
異常系 | 入力やアプリ自体に問題がなくても出てしまうエラー(DBが落ちている等)がハンドリングされることをテストする | HTTPにおけるステータスコード 5xx のイメージ |
詳しくは以下のページを参照
https://wa3.i-3-i.info/diff610test.html
状況依存を再現するためモックを使うが、本プロジェクトでは2つのライブラリを利用する
ライブラリ | 利用シーン | ドキュメント |
---|---|---|
mock | モジュールの関数のうちいくつかをモック化できればいい場合 | https://hexdocs.pm/mock/Mock.html |
mox | モジュールを丸ごとモック化したい場合 | https://hexdocs.pm/mox/Mox.html |
モック化する範囲は最小限に抑えること
DmCluster.Histories.update_history_status/1 は内部で複数の処理を行うが、DBへの書き込みが失敗することをシミュレーションするには
Histories.update_history_status
が Postgrex.Error
を起こすHistories.update_history
が Postgrex.Error
を起こすRepo.update
が Postgrex.Error
を起こすといった具合に、モック化する範囲が最小限になるようにする
Phoenixには最初からテスト用DBを用意してテストを実行できる環境が整っているため、基本的にはPhoenixの作法に従う
テスト用DBに実際にデータを入れたり参照したりする
テスト用DBに実際にデータを入れたり参照したりする
mock を使って Postgrex.Error
が発生することをシミュレーションする(テスト用DBをダウンさせたりはしない)
実装は以下を参考
正常系/準正常系/異常系すべて、 mox を使ってファイル操作をシミュレーションする(GCPにはアクセスしない)
実装は以下を参考(Cloud Storageを使っている箇所のテストではないが、moxの使い方という点では参考になるはず)
正常系/準正常系/異常系すべて、 mox を使ってファイル操作をシミュレーションする(SSHやSCPは行わない)
実装は以下を参考
DmCluster.Mailer.deliver/1
でメール送信処理を行うが、 Swoosh.Adapters.Test を使うため、実際にメールが送信されることはない
DmCluster.Mailer.deliver/1
でメール送信処理を行う
DmCluster.Mailer.deliver/1
でメール送信処理を行う
mock を使って Swoosh.DeliveryError
が発生することをシミュレーションする?
※詳しい調査が必要だが、エラーを投げなくても {:error, ...}
を返せばいいだけ かもしれない
TODO
⑬_5 UI開発のグランドルール
enabled
のままとする
disabled
にしておく?「アイテム名は必須項目です」
→ 「必須項目です」 とする更新とサブミット両方
<.form let={f} for={@changeset} phx-change="validate" phx-submit="save">
フラッシュメッセージを表示する
変更項目がなしの場合はフラッシュメッセージを表示しない?
変更せずにUpdateを書けるとchangesetはvalidで変更されるがupdated_atは更新されずデータとしては変更前を維持することになる。 このときに「変更しました。」のようなフラッシュはどうするべきか?
page_size
|<
<
>
>|
) page
page と page_size が query param に定義されて、例:?page=1&page_size=10
注意点がある要素のみ
検索条件の種類は以下
from <= column <= to
)input type=text
LIKE
検索を行う-- アイテム名: アイテム で検索した場合
SELECT
...
WHERE
name LIKE '%アイテム%';
YYYY/MM/dd
YYYY/M/d
2022/02/29
2022/08/32
)2022/01/30
~ 2022/01/29
はNG)
0:00:00
toの場合は 23:59:59
に変換する
2022/01/30
→ 2022/01/30 0:00:00
(JST)2022/01/30
→ 2022/01/30 23:59:59
(JST)<=
と >= を組み合わせて使う
BETWEEN
句は使わない-- 検索条件が 登録日付 2022/01/30 ~ 2022/02/03 の場合
SELECT
...
WHERE
inserted_at >= '2022/01/29 15:00:00'
AND
inserted_at <= '2022/02/03 14:59:59'
ページネーション後に検索が行われる場合、検索後は1ページ目に戻る(= 検索後は常に1ページ目に戻る)
:index
にリダイレクト、例:<%= live_redirect "リセット" to: Routes.item_approval_index_path(DmClusterWeb.Endpoint, :index) %>
はい、URLパラメータを残す。
動作完了後。
例:
{:ok, _item_approvals} ->
{:noreply,
socket
|> put_flash(:info, "Item approvals updated successfully")
|> push_redirect(to: socket.assigns.return_to)}
→ 500 ページが表示される
→ フラッシュメッセージが表示される
UIの場合のエラーログメッセージはフォーマットしない
一覧
詳細
例
・送付状況 DmCluster.Formatters.HistoryFormatter
・日付 DmCluster.Formatters.DateTimeFormatter
共通関数実装例(共通関数側) https://github.com/karabiner-inc/dm_cluster/pull/322
共通関数適用例(画面側) https://github.com/karabiner-inc/dm_cluster/pull/333/files
更新日時 desc
order by updated_at desc;
また、updated_atが、PostgreSQLだと秒単位になり、順不同になるリスクがあるため、次点のorder byとしてid(降順、desc)を入れること。
方針:Server と連携が必要な部分は LiveView にする、UI(表示系)だけな部分は Alpine.js を利用する。
具体的な例:
alpine.js で @click イベントで select inputを実装した場合、併せて @click.outside イベントも指定し、フォーカスが外れたときに閉じるようにする
確認案
0.本ルールの利用サイクル フェーズ切り替え時には、本ルールを必ず全員で見返すこと フェーズ切り替え時に限らず、疑問を持ったときや、問い合わせ先が分からないときも、本ルールを必ず参照すること
1.判断基準 どんどんメンバー間でコミュニケーション取ってもらって、交流深めたり、連携してくださいませ(`・ω・´)ゞ 話の宛先や判断に困るときや、本ページに書かれていないことは、@Yoosuke、もしくは @piacere に相談してください 顧客折衝やお金周り、あと既存環境とかでも無い限り、@piacere に許可を取る必要は無く、各役割やメンバーとのコミュニケーションを取ることで解決を試みてください 自信が無い場合や、心配ある場合は、事前に @Yoosuke に共有いただくとグッドです 議論の場に、@Yoosuke, @piacerex を巻き込んでいただくのも、お気軽にどうぞ @Yoosukeは毎朝 08:00 ~ 08:30はFace to Face で相談できる時間としておきます。
定例で使っているZoomを、定例以外の個別メンバー間でご利用いただくのもOKです 利用時間帯がぶつかったときは、よしなに調整してください…あまりにぶつかるときは、別途追加発行もします
意見が割れて、収束がつきにくいとき等は、最終的に @piacere がジャッジを行うようにしますので、呼び出してください & ご安心ください(^o^)
言いにくいことがあっても、言わないと何も生まれません … 言って良い場なので、意見を出しましょう
基本設計レビュールール確定したら一旦クローズ
0.本ルールの利用サイクル
1.判断基準
2.開発進捗管理/確認
3.顧客調整
4.ドキュメントの扱い方
5.内部会議体
リーダーMTG(毎週 月12:00~13:00)
ランチMTG(毎週 火12:00~13:00)
6.インフラ/開発環境
7.Github運用/CI
PhNの開発ブランチの運用
develop -> staging -> masterブランチを運用する(従来運用に同じ)
PhN開発中の保守ブランチの運用
hotfix及びmaintenanceブランチを運用する。
開発時ルール
ローカルでのコミット時にCIが走る
developから作成したブランチでプルリクを出すこと(いかなる対象も直接push禁止)
例:git checkout -b feature-upload_csv-tj
※結合テストおよび総合テスト時に使用。開発/単体テストでは基本、使用しない
プルリク作成時にGithub Actionsで設定されたmix testが走る。レビュワーへのレビュー依頼はこのmix testにクリア後に依頼すること
全コード、レビュー必須なので、コーディングに自信無い方は、早めにプルリクを出すこと。
初心者向けトピック
コミットはCLI上から実施すると、CIのエラーメッセージを読みやすいので便利。
作業中のブランチをプッシュするときは下書きであることが伝わるようにする。 -例1 プルリク名の頭に[WIP]をつける -例2 Draft PRにしておく
レビュー
V字モデルの上工程(外部設計、方式設計、連結・総合テストケース)レビューは@takuto
下工程(詳細設計、CD/UT)に関してはサブチーム単位での相互レビューを基本とする。
@the_haigoさんチーム
@ymnさんチーム
8.リリース
9.UI設計/開発
9.1 画面開発流れ
9.1.1 画面開発のPR分け方
9.2 UI 開発の必要な資料
9.3 assets/node_modules のインストール方法
9.4 phx.gen.live 使い方
Table を作成しない
Table 作成ずみ(API 開発時作成した)
10.API設計/開発
11.DB設計/開発
12.利用バージョン
13.実装時ルール(コードレビュー観点/ポイントも)
利用LiveViewテンプレート形式
LiveViewのコアっぽいところを使う場合
ファイルアップロード
ログ閲覧
モジュールのネームスペース
■UI →UIページ名およびphx.gen.liveに従う →コンテキスト →phx.gen.live Posts Post posts phx.gen.live Writers Writer writers ※既存のUI向け関数が、「api」配下にいる…
■Util →lib/dm_cluster/utils →切り出したい人は切り出して →モジュール名は名詞で組むこと(動詞や動詞+erとしない) →長いモジュール名は避けること →カヲスったら、mokichi & TJペアがどうにかする(レビュー段階で)
Sentry.capture_message/2 を使うこと
後処理でのエラーなどはユーザーには正常なレスポンスを返しつつ、必要ならシステムにアラートを飛ばす
異常系の実装方針
基本的には異常系のエラーが起こった場合は例外をraiseする or
!
付きの関数を使う エラーハンドリングはDmClusterWeb.ErrorHandler
で行うlib/dm_cluster/exceptions.ex
のエラーをraiseする異常系のテストについて
Context
API
準正常系の実装方針(4xx系)
基本的にFallbackControllerで処理する
Controllerの実装時に
DmClusterWeb.FallbackController
をaction fallbackに設定するエラーレスポンスの内容は
DmClusterWeb.FallbackController
に実装する準正常系についてはテストを実装すること
異常系の実装方針(5xx系)
UI
(TJさん、tamanugiさん抜き出し後に差異があれば埋め合わせお願いします)
HTTPステータスコード毎のエラーページ
server/lib/dm_cluster_web/templates/error/error.html.heex
→タイトル「リクエストが正しくありません」
→メッセージ「リクエストが正しく無いため、アクセスできない状態です[br]お手数ですが、URLを見直してください」
→「/」に戻るボタン付き
→タイトル「ページが見つかりません」
→メッセージ「大変申し訳ありません[br]お探しのページは見つかりませんでした」
→「/」に戻るボタン付き
→タイトル「アクセスできない状態です」
→メッセージ「サーバーにアクセスが集中しているか、メンテナンスを実施中のため、一時的にアクセスできない状態です[br]申し訳ありませんが、少し時間を空けてから再度アクセスを行ってください」
→「/」に戻るボタン付き
準正常系の実装方針(4xx系) ※正常系メッセージ表示も包含する
各フォーム入力時エラーは、各入力項目の直下にリアルタイム表示(LiveView通り)
異常系の実装方針(5xx系)
on_mount
時のエラーのハンドリングはDmClusterWeb.ErrorHandler
で行うhandle_event/3
で rescue する必要はない)個別にメッセージが必要な場合は
try-rescue
で拾ってフラッシュメッセージを実装する