本番環境のSSMSでトランザクションキャンセルした際にやらかした話

この記事は Zenn にも同様の内容で投稿しております。

zenn.dev


先日、本番障害で任意のテーブルをロックしっぱなしにし、さらに業務影響を与えるという二次障害を起こしました。

この記事は将来の自分への記事でもあります。

今回の件で学んだことをまとめ、同じミスを繰り返さないようにしたいです。

経緯

某日、本番環境にて業務が停止するレベルの障害が発生しました。

本障害は3ヵ月前に起こっていましたが、久しぶりだったこともあり少し焦っていました。

しかし、本障害は対応手順書が存在し、手順書を見れば問題を解決出来るようになっていました。

私は手順書通りに実行しました。

詳細は社外秘としますが、概ね、以下のような流れです。

  1. SSMSで原因解消用のSQLupdate文を含む)を実行する
    • このクエリは状況によってクエリが終了しない(ずっと発行中になる)
  2. クエリが終了しない場合、クエリ発行を中断し、Windows Serviceの業務アプリを再起動する

今回のケースはクエリが終了しない場合であったため、SSMSの画面上からクエリ発行を中断し、Windows Serviceの再起動を試みました。

ここで、問題が発生します。

手順書上では、Windows Serviceは正常に再起動し、問題が解消する手順となっていました。

しかし、Windows Serviceは再起動時のデータベースアクセスにて、タイムアウトエラーを発生し、起動に失敗しました。

この時点で顧客から鬼電が掛かりまくっており、私はパニックになってしまいました。

何が起こったか

問題の発生点はここです。

SSMSの画面上からクエリ発行を中断し、

発行クエリにはupdate文が含まれています。

経緯では触れませんでしたが、update文を含んでいるため、トランザクションの明示的な開始もしていました。

SSMSではトランザクション処理実行中にクエリ発行を中断すると、中断したタイミングで処理が止まるため、トランザクションは開始されたままになります。

実験してみましょう。

適当にデータベースを作成し、以下のクエリを実行してみます。

use [TestDatabase]

begin transaction

select
    *
from
    [SampleTable]

update 
    [SampleTable]
set
    [StringValue] = 'ほげほげ'
where
    [IntValue] = 100

waitfor delay '00:00:10';

select
    *
from
    [SampleTable]

rollback transaction

重たい処理を表現するためにwaitfor delayで処理を待ちます。

この際にSSMS画面上のマークの「クエリ実行の取り消し」ボタンを押下します。

次にsys.dm_tran_locksテーブルを確認し、テーブルのロック状況を確認します。

[SampleTable]テーブルがロック状態にありますね。

つまり話を戻すと、update文による占有ロックが掛かったままになっていたため、Windows Serviceは再起動時にselect文すら発行出来ず、処理が落ちたということになります。

どうすべきだったか

SSMSでキャンセルした場合、エラーではないため try-catch では制御出来ません。

その他色々ググってみましたが、SQL側で制御する方法は私は見つけられませんでした。 *1

そのため、今回のケースでは以下の対策方法が考えられるのではないかと思います。

  1. 手順書に sys.dm_tran_locks テーブルを確認するクエリを実行する手順を追加する
    • 明示的に確認することを手順書に組み込むことで確実に防ぐ
    • クエリは事前に作成しておき、第三者のレビューを通しておく
  2. PowerShellスクリプトなど、プログラムにてタイムアウト時間を設定して実行する
    • Invoke-SqlCmd コマンド *2には -QueryTimeout というプロパティがあります
    • スクリプトは作成者以外によるテストをしておく

そもそも、人の手で作業をするからミスをするのであって、理想を言えば後者を採用するべきだと思います。

ですが、今回のケースのレベルであれば前者の手順書に明記でも十分回避可能かと思います。

私にはこれくらいしか思いつきませんでした。

もし、他にもっといい方法があればコメントを頂けると幸いです。

『Micro Frontends』という記事を読んだのでまとめる

最近、マイクロフロントエンドという技術を知っていろいろ調べていたら以下のような記事を見つけました。

martinfowler.com

マイクロフロントエンドとは何か、非常に分かりやすくまとまっています。

なので日本語で要約・加筆して後で読み返しやすくしていこうと思います。

マイクロフロントエンドとは

マイクロフロントエンドとは、 単体で実行可能な、サイトから切り出された特定のUI領域 であり、Webフロントエンドの新たなアーキテクチャです。

この特徴から以下のようなメリットがあります。

  • インクリメンタルアップグレード
  • シンプルで分離されたコードベース
  • 独立したデプロイ
  • 自立したチーム

インクリメンタルアップグレード

マイクロフロントエンドは単体で実行可能にするため、使用するフレームワーク、ライブラリが他のマイクロフロントエンドと共通ではありません。

このため、他のマイクロフロントエンドに左右されずに安定して依存するフレームワーク、ライブラリのバージョンをアップグレードすることが出来ます。

シンプルで分離されたコードベース

各マイクロフロントエンドのコードはモノリシックなフロントエンドコードに比べて格段にサイズが小さいです。

機能ごとにコンテキストを分けることで、開発時に認知しなければならない領域が小さくなり、開発者の負担が減ります。

独立したデプロイ

コンテキストごとに実行単位が分かれているため、独立してリリースすることが可能です。

独自のCI/CDパイプラインを構築し、他のマイクロフロントエンドのリリースサイクルを気にすることなくリリースすることが出来ます。

Independent Deployment
出典: Micro Frontends - martinfowler.com

自立したチーム

コードとリリースサイクルが分離されることで、チームも機能単位で分離することが出来ます。

機能ベースのチームは顧客に価値提供するための必要なすべてを保有しており、独立して活動することが出来ます。

マイクロフロントエンドの実装

マイクロフロントエンドはどのように実装するのか、以下の観点で説明していきます。

  • 統合アプローチ
  • スタイリング
  • 共有コンポーネントライブラリ
  • クロスアプリケーション通信
  • バックエンド通信
  • テスト

統合アプローチ

マイクロフロントエンドは以下の機能を持つコンテナアプリケーションと呼ばれる領域に展開されます。

  • ヘッダーやフッターなどの一般的なページ要素のレンダリング
  • 認証やナビゲーションなどの横断的関心事への対処
  • 各マイクロフロントエンドの統合とレイアウト・描画タイミングのコントロール

コンテナアプリケーションとマイクロフロントエンドはそれぞれ別のサーバーにホストされ、サーバーサイドインクルードなどの技術を用いて 、1つのサーバーが他のサーバーにリクエストを送信し、ページを構築します。

これは各マイクロフロントエンドの独立性を保つためです。

Server Side Include
出典: Micro Frontends - martinfowler.com

具体的なソースコード上での統合は以下のような手法があります。

  • npmパッケージとしてビルド時に統合する
  • iframeを介してランタイム時に統合する
  • JavaScript処理でランタイム時に統合する
  • Webコンポーネントを介してランタイム時に統合する

もっとも主流であるのは JavaScript処理でランタイム時に統合する 方法ですが、 統合プロセスにこだわりがない場合、 Webコンポーネントを活用してランタイム時に統合する 方法も良いとされています。

以下は 本家サイトにある実例 を少し弄ったものです。

<html>
  <head>
    <title>Feed me!</title>
  </head>
  <body>
    <h1>Welcome to Feed me!</h1>

    <!-- これらのバンドルは後続のJavaScriptによる統合により使用される -->
    <script src="https://browse.example.com/bundle.js"></script>
    <script src="https://order.example.com/bundle.js"></script>
    <script src="https://profile.example.com/bundle.js"></script>

    <div id="micro-frontend-root"></div>

    <!-- JavaScirpt処理での統合 -->
    <script type="text/javascript">
      // バンドルにより関数が取り込まれる
      const microFrontendsByRoute = {
        '/': window.renderBrowseRestaurants,
        '/order-food': window.renderOrderFood,
        '/user-profile': window.renderUserProfile,
      };
      const renderFunction = microFrontendsByRoute[window.location.pathname];

      // divタグへレンダリングする
      renderFunction('micro-frontend-root');
    </script>

    <!-- Webコンポーネントでの統合 -->
    <script type="text/javascript">
      // バンドルによりWebコンポーネントが取り込まれる
      const webComponentsByRoute = {
        '/': 'micro-frontend-browse-restaurants',
        '/order-food': 'micro-frontend-order-food',
        '/user-profile': 'micro-frontend-user-profile',
      };
      const webComponentType = webComponentsByRoute[window.location.pathname];

      // JavaScript関数を用いてコンポーネント要素を配置する
      const root = document.getElementById('micro-frontend-root');
      const webComponent = document.createElement(webComponentType);
      root.appendChild(webComponent);
    </script>
  </body>
</html>

スタイリング

従来のモノリシックなフロントエンドでは、CSSの課題をBEMなどの厳密な命名規則やSASSのようなCSSプリプロセッサで解決してきました。

しかし、マイクロフロントエンドを活用したアプローチでは、 CSSモジュール や様々な CSS in JSライブラリ の活用により、プログラムから適用します。

また、Webコンポーネントによるアプローチを活用している場合、 シャドウDOM によるスタイルの分離も活用されます。

共有コンポーネントライブラリ

サイト全体の視覚的な一貫性を維持するため、再利用可能なUIコンポーネントのライブラリを開発する手法があります。

ラベル、ボタン、自動入力のドロップダウン検索フィールドなどのよく使われるコンポーネントを共有ライブラリとしてまとめます。

ただし、これらの共有ライブラリは以下の課題があるため、アプリケーション全体が成熟してから作成するのが良いです。

  • アプリケーションの初期ではAPIのあるべき姿が安定しない
  • 一貫性のないコードの寄せ集めになりやすい
    • 明確な規則や技術的ビジョンがないため
  • 汎用的なライブラリにするための強力な技術スキルが必要
  • 多くのチーム間でのコラボレーションを促進するためのヒューマンスキルが必要

クロスアプリケーション通信

マイクロフロントエンドモジュール間でデータ通信をする方法は以下の3つあります。

  • カスタムイベントにより上位のモジュールへ渡す
  • 属性値として上位のモジュールから下位のモジュールへ渡す
  • アドレスバーを使用する

ここで注意すべきはどのアプローチでも 状態が共有されないようにする 必要があります。

また、データ通信の仕組みが壊れていないことを自動的に確認する方法についての検討が必要です。

機能テストの実装は1つのアプローチですが、実装と保守のコストの観点からテスト数は制限した方がよいです。

コンシューマー主導のコントラクト の実装により、 各マイクロフロントエンドがほかのマイクロフロントエンドに必要なものを指定出来るようにすることも、1つのアプローチです。

バックエンド通信

マイクロフロントエンドのバックエンド通信のパターンとして BFFパターン があります。

BFFパターン(Backend For Frontends)とは、複数のAPIの集約をになったり、 フロントエンドアプリケーションのためのビジネスロジックを実行したりするバックエンド実装のパターンです。

マイクロフロントエンドが通信するAPIが複数あったり、各APIの定義・実装が不安定であったりする場合にとても効果的なパターンです。

Backends For Frontend
出典: Micro Frontends - martinfowler.com

テスト

注意すべき点として、各マイクロフロントエンドとコンテナアプリケーションの統合テストがあります。

CypressやSeleniumなどのe2eテストライブラリ・ツールにより実施出来ます。

しかし、決して網羅しようとせず、単体テストでチェックできない観点に対して実施します。

コンテナアプリケーションでマイクロフロントエンドが描画されているか確認するために、 ハードコードされたタイトル文字列が存在することをアサーションする

マイクロフロントエンドをまたがるシナリオのテストもフロントエンドの統合の検証に焦点を当て、 ユニットテストでカバーされている内容など、必要以上にテストしません。

欠点

マイクロフロントエンドも、他のアーキテクチャ同様、そのメリットはトレードオフです。

それは次のような欠点があります。

  • ペイロードサイズ
  • 環境の違い
  • 運用とガバナンスの複雑さ

ペイロードサイズ

独立して構築されたJavaScriptバンドルは一般的な依存関係の重複を引き起こします。

例えば、すべてのマイクロフロントエンドにReactの独自コピーが含まれる場合、ユーザーにはReactをn回ダウンロードさせることになります。

都市部よりはるかに遅いネットワーク環境で実行される場合、ダウンロードサイズを気にする必要があります。

しかしこれは、初期ページロードとの引き換えとなります。

初期ロードで一度に必要なファイルを一括ダウンロードするモノリシックなフロントエンドに比べ、 必要なタイミングで段階的にJavaScriptバンドルをダウンロードするマイクロフロントエンドは読み込みが速くなる可能性があります。

環境の違い

マイクロフロントエンドはコンテナアプリケーションではなく、空白ページでスタンドアロン実行が出来る場合があります。

コンテナアプリケーションにレガシーコードが残されているなど、技術的な難がある場合に有効な開発手段です。

しかし、同時に本番環境とスタンドアロン実行環境に大きな差がある場合にリスクが伴います。

ここでの対策は他の状況と同様、可能な限り本番環境に似た環境へ定期的にデプロイし、統合時の問題を早くキャッチすることです。

運用とガバナンスの複雑さ

純粋に1つの大きなものを小さい複数のものに分割して管理するため、管理するものが増えます。

より多くのリポジトリ、ツール、ビルド/デプロイパイプライン、サーバー、ドメインなど、考慮するものがたくさんあります。

  • 統一されたインフラストラクチャの用意
  • 開発・テスト・リリースプロセスの統合
  • ツール・プラクティスの統合
  • 品質ガバナンスの確保

これらの考慮を実現していくために必要な技術的・組織的な成熟度があるかどうかを検討する必要性があります。

所感

今回はマイクロフロントエンドという新しいアーキテクチャについてキャッチアップしました。

アプリケーションが解決すべき問題は段々複雑化し、それに伴ってアーキテクチャも複雑化していくのを感じ取りました。

如何に大きな問題を小さく扱うかという、マイクロフロントエンドのヒントは私が普段携わっているWindowsアプリケーションにも応用が利くかなと感じました。

今回は省略しましたが、元記事には具体的な実装の説明もあるのでご一読されることをおすすめします。

『merpay Tech Talk ~ 伝わる技術文書の書き方 ~』に参加したので学んだことをまとめる

『merpay Tech Talk ~ 伝わる技術文書の書き方 ~』という、メルペイ主催の技術イベントがあったので学んだことをまとめたいと思います。

mercari.connpass.com

イベントの内容

以下のような内容になっていました。

  1. オープニングトーク
  2. 柴田芳樹 さん(メルペイ Software Engineer)発表
    • 『merpay Tech Talk ~ 伝わる技術文書の書き方 ~』
  3. ひつじ (mhidaka) さん(メルペイ Experts team)発表
    • 『今すぐテクニカルライティングが必要になったときの転ばない杖』
  4. tenntenn さん(メルペイ Experts team)発表
    • 『実践!テクニカルライティング』
  5. わいわい質問タイム
    • 発表者同士の質問やイベント参加者からの質問とその回答
  6. 公開 処刑 レビュータイム

※資料公開があったらあとでリンク付けます。

学んだこと

技術文章の分類

文章には以下の分類がある。(by 柴田さん)

  • 理解した技術のまとめ
  • 新たな技術のまとめ
  • 自分で考えた手法・技術のまとめ
  • プロセス(プラクティス)の改善・導入
  • 自分の考え(心得など)を伝えたもの
  • 書籍紹介

特に理解した技術のまとめでは以下のように細分化される。

  • 多くの人にとっては既知かもだが、自分にとっては新たな理解であるもの
  • 技術は新しくないが、多くの人がきちんと理解してないかもしれないもの

文章を構成する時の注意点

  • 導入文があると読者にメンタルモデルが出来るので読みやすくなる
    • 「はじめに」、「目標規定文」、「想定読者の説明」など
  • 文章を書く時のフレームワーク(柴田さん作)
    • 技術系
      1. 技術を導入する前の課題の説明
      2. その課題を解決するために導入した技術の概要
      3. どのように導入したかの説明
      4. 導入後の課題の解決状況
      5. 残された課題と今後の対応予定
    • 改善活動系
      1. あるべき姿の説明
      2. 現状把握と分析
      3. 対策の説明と実施
      4. 対策の効果の説明
      5. 残された課題と今後の対応
  • 文章書いたら読んでもらえる思ってはいけない
    • 「読者は筆者が思った通りには行動しない」
    • 面白いと思ってもらえるように努力する
    • イラストや表を使って読者をひきつける努力をする
  • 結論は先に説明する
  • 不要な情報は削減する
  • 結論に意見を書く
    • 意見を書くことで追体験につながる
  • 文章の流れ
    1. 文章の目的を考える
      • 目標規定文を記載する
    2. 見出しをざっくり考える
      • 章レベルで
    3. ネタを厳選する
      • 考えた見出しに合うコード片などを用意する
    4. 見出しをしっかり考える
      • 節レベルで主張したいことを簡単に書き下す
    5. ネタが見出しに合っているか考える
      • コードに過不足がないかチェック!
    6. 書く
  • うまく書けない場合
    • うまく書けない項目は飛ばす
      • 飛ばすと不要だったってなる場合も
    • 雑でもいいので箇条書きする
      • コードでもいい
    • 誰かに話してみる
    • いったん諦める
      • 別の作業をする
  • めっちゃ書いちゃう場合
    • 一旦最後まで書いてみる
    • 冗長な表現がないか見直す
    • バックアップを取った上で思い切って消しちゃう
    • コードを短くする
      • 文脈で分かるところや例外処理は省略
  • 「まずは」みたいな表現は書き始めなどで自身がない時に書きがち
    • 書いてもいいけど、あとで見直して消す

文章の表現に関する注意点

  • 表記ゆれは避ける
  • 漢字表記とひらがな表記の使い分け
    • 迷ったら用語辞典で調べる
      • 朝日新聞の用語の手引
      • 読売新聞 用字用語の手引
      • 講談社校閲局編 日本語の正しい表記と用語の辞典
      • NHK 漢字表記辞典
  • 正しい日本語を使用する
    • NHKことばのハンドブックを参考にする
  • 文章の見やすさに気を付ける
    • 半角丸括弧 () ではなく、全角丸括弧 () を使うなど
  • 冗長な表現は避ける
    • 例1:「出来るのであれば」→「出来れば」
    • 例2:「することなく」→「せずに」
    • 例3:「行うことが出来ます」→「行えます」
  • 誤字脱字は避ける
  • 技術用語は正しい用語を使用する
    • チーム固有の用語ではないか?
    • 英語表記である必要性のないものはないか?
      • 例:「microservice」→「マイクロサービス」
  • 正確性と分かりやすさのバランスを取る
    • 正確でも情報量が多いと分かりにくくなる
  • 主語は省かない
    • タイトルを書いた後が省略されがち
    • 筆者が自明だと考えているとき、読者は置き去りになる
  • 受動態は避ける
    • 受動態は主体が曖昧になる
  • 誤解が発生しない表現にする
    • 比喩表現は伝わりにくい
    • 弱い動詞、複数の意味のある動詞を使用しない
  • 事実は可能な限り簡潔に書く
    • 事実と意見を混同しない
  • 意見は誰のものか明らかにする
    • 意見は事実の裏付けをもって主張する
  • 口語と文語を使い分ける
  • 1文は簡潔に書く
    • 主語と述語で文章が成り立っているか?
  • 論拠をあげる
    • 論拠は自分の主張が尤もことを補う武器
  • 正しい情報を伝える
    • 推測や感想を混ぜない
  • 意見は「私は~と思う」で表現する
  • 参考文献はアクセス日時なども併せて記述する
  • 固有名詞は正式名称を用いる
  • ローマ数字と漢数字の使い分け
    • 数えられるものは数字、数えられないものは漢数字で表現する
      • 例:りんご3つ、三角形
  • 最近の流行りは1段落2~3行
  • 「~と思います」ではなく、言い切ることで共感されやすい

文章をレビューするときの注意点

  • 自己レビューする時
    • 自分で読んで理解できますか?
    • 正しい用語を使っていますか?
    • 技術的に間違っていませんか?
  • チーム内レビュー
    • 自己レビューより見落としに気付ける
      • しかし、チーム固有の技術的説明など第三者視点でレビュー出来ない可能性がある
    • 三者レビューして貰うまでに軽微な部分を見て貰う感じ
  • 三者レビュー
    • 扱ってる技術をよく知らない人からのレビュー
      • 読んで理解できる内容か確認してもらう
    • 扱ってる技術をよく知っている人からのレビュー
      • 技術的に間違っていないか確認してもらう
  • 耳で聞いてみる
    • OSの読み上げ機能を使ってみる
    • 耳で聞いてみて頭に入ってこない文章は読んでも分からない
  • 自動化ツールを使ってみる
  • Google ドキュメントを使う
    • コメント機能の活用
    • バージョン機能の活用
  • 自分の癖みたいな表現は自己レビューが困難
    • 他人に見て貰う
    • スクリプトで表記ゆれなどを検知する
    • 書き味として残してみる

所感

『『『文章書いたら読んでもらえる思ってはいけない』』』

これがめちゃめちゃ痛いですね。終わってからずっと頭に残っています。

これ、技術ブログや書籍執筆に限らず普段のメールやチャットでの会話でも同じことが言えるのではないかと思いました。

それから今回のイベントの発表者の方々は国語的なテクニックを書籍などから学び、それを何度も実践するということとLintツールなどを駆使して出来ないことは割り切るという使い分けが上手く出来る人達なんだなと感じました。

textlint や RedPen 何かはさっそく使ってみたいですね。

C# 9.0 の record 型 と DDD の値オブジェクトについて

.NET 5 のリリースにより、 C# 9.0 が使えるようになりました。

C# 9.0 の新機能の中で注目を浴びている機能として record 型があります。

この record 型がドメイン駆動設計の値オブジェクトの設計に有用だという知見を得たので、この知見について記したいと思います。

record 型とは?

record 型の詳細は以下にあります。

docs.microsoft.com

record 型を使用することでイミュータブルなオブジェクトを容易に作ることが出来るようになります。

例えば、今までであれば以下の様に実装していたクラスが...

public class Phone
{
    public string AreaCode { get; }
    public string CityCode { get; }
    public string SubscriberNumber { get; }

    public Phone(string areaCode, string cityCode, string subscriberNUmber) =>
        (AreaCode, CityCode, SubscriberNumber) = (areaCode, cityCode, subscriberNUmber);

    public static bool operator ==(Phone left, Phone right) =>
        left.AreaCode == right.AreaCode &&
        left.CityCode == right.CityCode &&
        left.SubscriberNumber == right.SubscriberNumber;

    public static bool operator !=(Phone left, Phone right) =>
        left.AreaCode != right.AreaCode ||
        left.CityCode != right.CityCode ||
        left.SubscriberNumber != right.SubscriberNumber;

    public override int GetHashCode() =>
        new[] { AreaCode, CityCode, SubscriberNumber }
            .Select(x => x is not null ? x.GetHashCode() : 0)
            .Aggregate((x, y) => x ^ y);

    public override bool Equals(object obj) =>
        obj is Phone other &&
        Equals(other.AreaCode, AreaCode) &&
        Equals(other.CityCode, CityCode) &&
        Equals(other.SubscriberNumber, SubscriberNumber);

    public override string ToString() =>
        $"{AreaCode}-{CityCode}-{SubscriberNumber}";
}

以下の様になります。

public record Phone(
    string AreaCode,
    string CityCode,
    string SubscriberNumber
)
{
    public override string ToString() =>
        $"{AreaCode}-{CityCode}-{SubscriberNumber}";
}

主に以下の機能が自動で実装されるようになると公式には書いてあります。

  • 値ベースの等価比較のためのメソッド
  • GetHashCode() のオーバーライド
  • コピーメンバーとクローンメンバー
  • PrintMembers および ToString()

難しいことが書いてありますが、簡単に言うと上記で示した class の実装サンプルのメソッドは operator 含め、すべて自動で実装されます。

上記の record 型のサンプルで ToString() メソッドのオーバーライドの実装を残した通り、メソッドも実装出来ます。

ちなみに、自動実装される ToString() メソッドは以下の様に出力されるようです。

"Phone { AreaCode = 080, CityCode = 1234, SubscriberNumber = 5678 }"

今のところ、使いどころが分からないですね。

record 型と値オブジェクト

先日、Twitter で以下の様なことを教えて貰いました。

エリックエヴァンスのドメイン駆動設計 では、 ドメインの概念のうち「ライフサイクルがあり、識別子によって管理するもの」をエンティティ、「値は不変で、識別子によって管理しないもの」を値オブジェクトと呼んでいます。

前節で示した class のサンプルコードはこの値オブジェクトの実装例になります。

値は不変のため、プロパティが get のみであり、ID オブジェクトを保有していません。

また、クラスは参照型であるため、デフォルトでは Equals() メソッドは参照同士の比較をします。

そのため、== オペレーターや != オペレーターの実装や Equals() メソッドのオーバーライドが必要になります。

ですが、 record 型を使用すればこれらがすべて自動で実装されるため、書くべきコードが減ってすっきりします。

コードが減れば確率的にバグが減るので安心ですね。

所感

ドメイン駆動設計に取り組んで以来、今まであまり値オブジェクトの定義をしてきませんでした。

今まで実装してきたコードの中には実は値オブジェクトになりうる概念もあったかもしれません。

今回の record 型の追加をきっかけに、値オブジェクトを見つけてみる意識をしていきたいと思いました。

Excel を読み取るコマンドレットを見つけたので SQL Server にデータを入れる PowerShell スクリプトを書いてみた。

こんなものを見つけました。

www.powershellgallery.com

Excel を読み取れる PowerShell のコマンドレットです。

思いつくことは 1 つですね?

そう、Excel でテストデータを作成し、データベースに Insert するスクリプトの作成です。

なので、試しに書いてみました。

Import-Excel コマンドレットの使い方

使い方は簡単で、こんな Excel シートに対しては、

f:id:neko3cs:20200913230900p:plain

以下の通り、ファイル名とシート名を指定して値を取得出来ます。

PS> Import-Excel -Path .\TestData.xlsx -WorksheetName "PriceOfFruits"

実行結果は以下の通りです。

f:id:neko3cs:20200913230642p:plain

ちなみに、シート名を指定せずに実行した場合は1番目のシートが取得対象シートになります。

こんな風にテーブルの開始セルが A1 から始まっていなくても、

f:id:neko3cs:20200913231004p:plain

以下のように、開始行と開始列を指定してあげれば取得出来ます。

PS> Import-Excel -Path .\TestData.xlsx -WorksheetName "AgeOfEmployee" -StartRow 4 -StartColumn 2

f:id:neko3cs:20200913234044p:plain

検証環境構築

以下のようなスキームのテーブルを雑に用意します。

f:id:neko3cs:20200913224851p:plain

よく使われそうな数字、文字列、時間、真偽値、あと、バイナリーも一応用意しました。

テストデータとなる Excel は以下の通りです。*1

f:id:neko3cs:20200913224932p:plain

データベースのテーブルに合わせて Excel にテーブルを用意します。

今回は面倒くさいのでバイナリーには NULL を入れます。

スクリプトの実装

Excel からデータを読み取るスクリプトは以下の感じで実装しました。

#!pwsh

param(
    [string]$Path,
    [string]$WorkSheetName
)

$SampleTable = Import-Excel $Path `
    -WorksheetName $WorkSheetName

foreach ($Record in $SampleTable) {
    sqlcmd `
        -S localhost `
        -E `
        -Q "insert into [TestDatabase].[dbo].[SampleTable] values ('$($Record.IntValue)','$($Record.StringValue)','$($Record.DateTimeValue)', '$($Record.BooleanValue)', $($Record.BinaryValue));"
}

動作確認程度なので結構雑に書いてます。

重要なのは ' の有無だと思います。

NULL の場合は ' を外さないとエラーか nvarchar とかの場合は文字列として入ってしまうので if 文とかで分岐させる必要がありますね。

varbinary とかも OPENROWSET 関数とかでバイナリー値を読み取って渡したりする必要があると思うので少し工夫が必要です。

それ以外の型の場合は ' を付けちゃえばインサート出来そうです。

実行結果

スクリプトを実行してみた結果は以下の通りです。

f:id:neko3cs:20200913231733p:plain

スクリプトは正常に実行されています。

f:id:neko3cs:20200913230206p:plain

テーブルにもきちんと値が登録されました。

今回は 1 テーブルしか使いませんでしたが、シート名をテーブル物理名とかにしておけば foreach 回して複数テーブルにも対応出来そうですね。

また、sqlcmdSQL を発行するスクリプトを書きましたが、SQL ファイルを出力する PowerShell スクリプトを作ってもいいかもしれません。

汎用的なスクリプトが作れたら私の PowerShell スクリプト保管リポジトリ にでもおいておこうと思います。


2020.09.18 追記

汎用的なスクリプト一応作ったので貼っておきます。

varbinary に対応してないなど、完璧ではないですが、 identity_insert に対応させてあったりある程度使えると思います。

ご参考までにご活用ください。

github.com

*1:テストデータを Excel で作る場合はセルの型を文字列にしておくことを強く推奨します。時間とかが化けるので...。