調べ物した結果

現役SEが仕事と直接関係ないことを調べた結果とか感想とか

SS-MIX2のクラウドにかかわる調査を読んで①


目次

www.mhlw.go.jp

SSMIX-2のクラウド検証が厚労省の方で行われたようで、資料を読んでみました。
以下のでは5つの検証が行われていて、

【抜粋-SS-MIX2ストレージのクラウド化に関わる調査一式 報告書-】
実証① SS-MIX2 ストレージのクラウド保存、相手地域の情報を含む情報閲覧
・ 地域医療連携ネットワークもしくは医療機関にて作成・保存された SS-MIX2 データをクラウドに確実にアップロードできることを確認す
る。また、差分データのアップデート方法を含めて、各医療機関のデータが確実にクラウドにアップロードされることを検証する。
・保健医療記録共有サービスの共通のビューワ(以下、「共通ビューワ」)で、クラウドに保存された SS-MIX2 データ、及びクラウドにア
ップロードされていない各拠点の SS-MIX2 データを参照してシームレスに患者情報を閲覧できることを検証する。
実証② データ保存におけるセキュリティ
クラウドに SS-MIX2 データを保存する際に、医療機関ごとのデータ保存領域を論理的に分離し、他の拠点からのアクセスを制限でき
ることを検証する。
実証③ データ形式の変換
 最大で全国民のデータをクラウドに蓄積している前提のもと、SS-MIX2 データを RDB、NoSQL のデータベース形式に変換、保存し、
共通ビューワから閲覧することをシナリオとして比較検証を行う。
 他の医療機関や他の患者データにアクセスできないよう、SS-MIX2 データを安全に保存するために求められるテーブル構造を検討す
る。
RDB、NoSQL に保存したデータに、複数の条件を用いてクエリーを行い、レスポンス速度を比較する。
実証④ 保健医療記録共有サービスでの名寄せについての考察
 患者基本情報の名寄せについて、世帯単位の被保険者番号、個人単位の被保険者番号の2パターンで技術検証を行い、名寄せ
の効率性を机上検討する。
名寄せが完了するまでの代替案もしくは推奨案を机上検討する。
実証⑤ 患者ポータルにおける同意管理
 SS-MIX2 データに含まれる患者情報の閲覧において、各患者のデータにアクセスする際に必要となる同意管理を行うための方法を
机上検討する。特に、患者ポータルにて同意/同意撤回を行う際の技術面/運用面での課題、要件について検討する。

今回はそのうちの①検証のアップロードのところまで読んだのでメモ的に雑感を書いていきます。

5年間分のSS-MIX2データのシステム案。

SS-MIX2 ストレージの保存、情報の閲覧

SS-MIX2のビューア側は見たことがないので、イメージしかないですが。(フリーのものがあれば試してみることはできそう)
まぁ、ビューア側は要件にそったディレクトリ構成なら問題はないのではないだろうか。というのが感想。

ストレージの選抜

AWS では大きく2つのストレージがあると切り分けていた。
ブロックストレージ(EBS)とオブジェクトストレージ(S3)
要件的にS3に軍配があがるだろうなぁと思ってはいたが、そうなったらしい。
主要な要因としては「要領に対するコストメリット」であろう。

【抜粋-SS-MIX2ストレージのクラウド化に関わる調査一式 報告書-】
全国の医療機関で作成される SS-MIX2 データをすべてクラウドにアップロードする場合、「表 3 ストレージに保存されるデータ件数の見
積もり」に記載の値より、5 年間の保存に必要となるデータ量は 922TB となる。

てな具合でSS-MIX2のデータ保存にはいかれた容量が必要になる。容量に関するバリューではS3だろうし、EBSには容量の限界もある。
診療データはどうしても「ずいぶんアクセスしていないデータ」ができがちだが
S3ならGlacier やら、S3のオプションやらでアクセス頻度に応じてさらにコストをえぐることもできそう。ということでS3だろうなぁ。
SS-MIX2では以下のようなフォルダ構成をとる必要があるが・・・
gyazo.com

論理的にはS3もフォルダ構成をサポートしているので、S3で問題ないだろう。
某所では「幻想」であるとされていますが。。。とにかく上記の形式で置くこと、ビューアが読み取れるところに意味があるので「論理的」であっても問題はないはずです。
dev.classmethod.jp

締めくくりとしては以下のように書かれている

【抜粋-SS-MIX2ストレージのクラウド化に関わる調査一式 報告書-】
まず、ブロックストレージについて、「表 2 ストレージの特徴」に記載の通り、ブロックストレージには容量制限があるため、必要な容量を一
括で確保することができない。AWS の場合、1つのブロックストレージとして確保できる容量が最大 16TB に制限されるとともに、アカウント
あたり最大 300TB に制限されるため、複数のアカウントを管理し、各アカウントに複数のブロックストレージを作成して必要な容量を確保す
る必要がある。また、データ容量922TB は保存期間を 5 年間とした場合の試算であるが、保存期間の増加や、SS-MIX2 ストレージに保
存されるデータが増加するような仕様改定が発生した場合に、ブロックストレージでは必要な容量を追加確保することが困難になる可能性
が残る。
最終報告書
~平成 30 年度 SS-MIX2 ストレージのクラウド化に関わる調査一式~
14 / 149
一方、オブジェクトストレージは、必要な容量を一括で確保することが可能なうえ、可用性、耐久性、拡張性で優位であり、かつ、コスト
比較においては圧倒的に優位である。SS-MIX2 データは頻繁に書き換えが発生するデータではないので、ブロックストレージのもつ更新頻
度が高いデータの保存に向いているという特徴にも該当しないため、積極的にブロックストレージを選択する理由もない。
以上より、本実証ではオブジェクトストレージを採用して実証を行うこととする。

なげーわい。容量についてはS3のほうが柔軟だし、特にEBS使う目的もないからS3ね。ということと解釈しました。
ということでストレージは「S3」に決定です。

クラウドへの転送方式

さぁ、ストレージは「S3」決まったわけだけど、オンプレに抱えているSS-MIX2データをどうやってアップロードしようかね。
という話。
ここでも大きく2つに切り分けて検証をしている。
・「同期方式」
・「ゲートウェイ方式」
正直、いまいちわかってはいないんだけど。アップロードに関しては伝送経路を保護しないといけない。
要は第3者がデータを盗み見れてはいけない。また確実にアップロードできないといけない(破損してり、失敗は許されない)という要件がついてくる。
そのうえで、一番費用の負担が少ないもの。。。という形で選抜していったように読み取れた。
gyazo.com
みたいな感じでS3も構築していくわけなので、よそ様のものが見れると色々とまずい。クラウドにはつきものの懸案点でもあると思う。

クラウドとしてのベストプラクティス、費用、技術的な課題などを加味して検討。AWSツヨツヨオジサンたちの目から見たらどう映るのか気にはなるところだが・・・

【抜粋-SS-MIX2ストレージのクラウド化に関わる調査一式 報告書-】
この構成は、「表 6 SS-MIX2 ファイルを各拠点からクラウドに転送する方式案ゲートウェイ方式案 2-2」の構成に AWS 特有の制限事
項を回避するために一部変更を加えたものになっている。AWS では、オブジェクトストレージ(Amazon S3)への接続を VPC からの接
続に限定する場合で、かつ、各拠点からクラウドに Internet VPN を使用してアクセスする場合、各拠点からオブジェクトストレージに直接
アクセスすることができない。この制限を回避して各拠点からオブジェクトストレージに至るネットワーク経路を疎通させることを目的として VPC
内にプロキシ(パケット転送装置)を設置した構成を最終的な技術検証の構成とした。

gyazo.com

みたいな形に落ち着いたらしい。ここで上げている「VPC->S3がだめよ」
というのがなんのことかよくわからなかったが・・・
dev.classmethod.jp
こちらの方法2のようなことを言っているのではないかと推測しています。ざっと記事を読ましていただく限り、
StrageGatewayだと高すぎるし、同期処理は採用方式が「ゲートウェイ」といっているので違うだろうしの消去法で「方法2」かな。というところ。
全然別のはなしですけど、S3ってあんがいセキュアに経路を構築するのが面倒な感じなんですね・・・S3そのものは安全でも経路の防護が面倒。

要件まとめ

最後のページの方に要件がまとまってました。気になるところだけつまむと
アップロード要件案
・少なくとも 1.9 PB のファイル保存が可能であること。
・3240億のファイルを保存できること。
桁がぶっ壊れてる感じがしていてとても楽しいです。

セキュリティ案件
・全国の医療機関(12 万)アカウントを作成できること。なお、ストレージのみでアカウント管理をす
る必要はなく、必要に応じて認証機能を持った別システムと連携すること。
・全国の医療機関ごとにストレージ領域を持つことができ、他の医療機関からアクセスできないこと。
・ストレージのアカウントを設定でき、アカウントごとに設定されたストレージ領域に書き込めること。
とかとか。いやー単位がえぐい。

以上。つづきはそのうち・・・AWSつよつよおじさんたちの意見が効きたい。
僕のレベルでは「へーなるへそー」としかならない。鍛錬がたりませんねぇ・・・

【読書感想】コーチングについてちょっとわかったような気になる本を読んだ感想


目次

読んだ本

新版 コーチングの基本 この1冊ですべてわかる
Amazon CAPTCHA

概要

コーチングのやり方、具体例。どのような目的でコーチングを行うか。またする価値があるのか。
タイトルの通り。と言ってしまえばそれまでではあるが、まさしくコーチングの基本ともいえる本であると考える。

記者について

・読む前はコーチングのことは言葉ぐらいしか知らなかった。
・だれかを責任をもって教育をしたことはない。専門的な実習も積んでいない。

読まなくてもよさそうな人。

コーチングについて理解している。
・指導について絶対の自信があって、とくに不安を感じていない。
というような人。後者については読んで得るものがあるかもしれないが、得ようとしないと時間の無駄かもしれない。
コーチングに不安や悩みを抱えているほうがすんなり入ってくると思う。

感想

コーチング is なに?という疑問の解消から具体的な対応、テクニックに至るまで、入門としては申し分ない情報量と説得力であった。
クライアント(コーチングを受ける人)の物分かりが良すぎるような気もするが、そこはそうなるはずだという「信頼」ということでいいかな。本筋とも違うだろうし。
読んだらできるかといったら別の話になるだろうから、実践してみて調整はいると思うが、ひとまず実践するには十分な情報だろう。
本書を読むきっかけは個人的に教育の必要があって「コーチング」に目を付け、どうやってよいものか模索する中でのこと。
特に苦慮していたのが教育の対象が私自身の「上長」であったが、本書で読んだことを実践し今のところ有効的に活用できている。
「自責」「他責」の考え方はよいと思う。が突き詰めすぎると精神を壊しそうなので適当にストレス等の解消手段もセットで覚えておかないと、自分に適用するときは危ないなだろうしもちろん人に適用する際もそのあたりのケアは大事だろう。なんにせよ「観察」が大事だという風に私は解釈をした。

2dust/V2rayNのソースコードを読んでみた(~勝手にリファクタリングする~)


目次

拝借したコード

github.com
ありがとうございます。お借りして勉強させていただきます。
また、こちらは上記のソースコードを詳細に説明したり、使用法に言及した記事ではないため、
そのような記事をお求めの方は別の記事を参照ください。ここにはありません。

ライセンス

github.com
GPLのようです。コピーレフトということで、今回一部解析して書き直しを行いますが、
そのソースコードについても同様のライセンスとしてここに宣言します。

いざ。

環境

VisualStdio2017 Community
Windows10

ひとまず対象メソッドの選定。

今回はC#ソースコードなので、VisualStudioのコードメトリクス計算機能を使うと客観的に数値を拾うことができます。便利
gyazo.com

ということでbtoOK_Clickメソッドを対象とします。
イベント系の操作は初めは簡潔に書けるんですが、よくよく見ると長大になっていく傾向がありますね。このメソッドがそうなってしまったのかは知りませんが。

メソッドを俯瞰する。

btnOK_clikc

        private void btnOK_Click(object sender, EventArgs e)
        {
            try
            {
                Process[] existing = Process.GetProcessesByName("v2rayN");
                foreach (Process p in existing)
                {
                    string path = p.MainModule.FileName;
                    if (path == GetPath("v2rayN.exe"))
                    {
                        p.Kill();
                        p.WaitForExit(100);
                    }
                }
            }
            catch (Exception ex)
            {
                // Access may be denied without admin right. The user may not be an administrator.
                showWarn("Failed to close v2rayN(关闭v2rayN失败).\n" +
                    "Close it manually, or the upgrade may fail.(请手动关闭正在运行的v2rayN,否则可能升级失败。\n\n" + ex.StackTrace);
            }

            StringBuilder sb = new StringBuilder();
            try
            {
                if (!File.Exists(fileName))
                {
                    if (File.Exists(defaultFilename))
                    {
                        fileName = defaultFilename;
                    }
                    else
                    {
                        showWarn("Upgrade Failed, File Not Exist(升级失败,文件不存在).");
                        return;
                    }
                }

                string thisAppOldFile = Application.ExecutablePath + ".tmp";
                File.Delete(thisAppOldFile);
                string startKey = "v2rayN/";


                using (ZipArchive archive = ZipFile.OpenRead(fileName))
                {
                    foreach (ZipArchiveEntry entry in archive.Entries)
                    {
                        try
                        {
                            if (entry.Length == 0)
                            {
                                continue;
                            }
                            string fullName = entry.FullName;
                            if (fullName.StartsWith(startKey))
                            {
                                fullName = fullName.Substring(startKey.Length, fullName.Length - startKey.Length);
                            }
                            if (Application.ExecutablePath.ToLower() == GetPath(fullName).ToLower())
                            {
                                File.Move(Application.ExecutablePath, thisAppOldFile);
                            }

                            string entryOuputPath = GetPath(fullName);

                            FileInfo fileInfo = new FileInfo(entryOuputPath);
                            fileInfo.Directory.Create();
                            entry.ExtractToFile(entryOuputPath, true);
                        }
                        catch (Exception ex)
                        {
                            sb.Append(ex.StackTrace);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                showWarn("Upgrade Failed(升级失败)." + ex.StackTrace);
                return;
            }
            if (sb.Length > 0)
            {
                showWarn("Upgrade Failed,Hold ctrl + c to copy to clipboard.\n" +
                    "(升级失败,按住ctrl+c可以复制到剪贴板)." + sb.ToString());
                return;
            }

            Process.Start("v2rayN.exe");
            MessageBox.Show("Upgrade successed(升级成功)", "", MessageBoxButtons.OK, MessageBoxIcon.Information);

            Close();
        }

うーん。ながい。およそ100Stepあります。クレイジーなネストは見受けられないので、それほど回収するのは困難ではないかもしれません。

プロセスを切る処理
            try
            {
                Process[] existing = Process.GetProcessesByName("v2rayN");
                foreach (Process p in existing)
                {
                    string path = p.MainModule.FileName;
                    if (path == GetPath("v2rayN.exe"))
                    {
                        p.Kill();
                        p.WaitForExit(100);
                    }
                }
            }
            catch (Exception ex)
            {
                // Access may be denied without admin right. The user may not be an administrator.
                showWarn("Failed to close v2rayN(关闭v2rayN失败).\n" +
                    "Close it manually, or the upgrade may fail.(请手动关闭正在运行的v2rayN,否则可能升级失败。\n\n" + ex.StackTrace);
            }

管理者権限がなければタスクの終了に失敗することがある。ということでしょうか。ここはおそらく処理をするのに都合の悪いプロセスを断ち切っている処理と思われます。
引数が一つも使われていないので、このメソッドはもうそのまま切り出してしまっても問題ないでしょう。
わざわざ次のブロックとの間に改行もあるところから、この処理が以降の処理と分離していることはこのソースコードを作った人も気づいていたことだと思います。
気になるのはExceptionは吐きますが、移行の処理は実行されてしまうところです。おそらくになりますが、returnで処理を中断すべきところに見えます。

考慮して、このように変形してみました。

        private bool Killv2RayN()
        {
            try
            {
                Process[] existing = Process.GetProcessesByName("v2rayN");
                foreach (Process p in existing)
                {
                    string path = p.MainModule.FileName;
                    if (path == GetPath("v2rayN.exe"))
                    {
                        p.Kill();
                        p.WaitForExit(100);
                    }
                }
            }
            catch (Exception ex)
            {
                // Access may be denied without admin right. The user may not be an administrator.
                showWarn("Failed to close v2rayN(关闭v2rayN失败).\n" +
                    "Close it manually, or the upgrade may fail.(请手动关闭正在运行的v2rayN,否则可能升级失败。\n\n" + ex.StackTrace);
                return false;
            }
            return true;
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            if (!Killv2RayN()) return;

Kill2RayNメソッドの中身はある程度シンプルな形に保たれているので、これ以上刻むのは置いておき、次に移ります。

fileNameのセットアップ処理
            StringBuilder sb = new StringBuilder();
            try
            {
                if (!File.Exists(fileName))
                {
                    if (File.Exists(defaultFilename))
                    {
                        fileName = defaultFilename;
                    }
                    else
                    {
                        showWarn("Upgrade Failed, File Not Exist(升级失败,文件不存在).");
                        return;
                    }
                }

ファイルの存在確認処理でしょうか。特に問題もないように見えますが・・・ややIf文の辺りがさらっと読み飛ばすには引っかかりを覚えます。
おっと。ここのshowWarnではreturnをしているので、やはり先ほどのException処理はreturnを忘れていたのでしょう。
こちらも引数は使用していないので、メソッドに切り出すことができるかもしれません。
また、この処理をtryの中でやる必要も見えてきません。外側でやってしまっても十分に機能するのではないでしょうか。

        private bool ExistsUpdateFile()
        {
            if (File.Exists(fileName))
            {
                return true;
            }

            if (File.Exists(defaultFilename))
            {
                fileName = defaultFilename;
                return true;
            }
            showWarn("Upgrade Failed, File Not Exist(升级失败,文件不存在).");
            return false;
        }

まずは早期return で逃げ切りました。ここでこのメソッドはExistsを行っているのではなく、fileNameのセットアップを行っているのだと気づきました。
メソッド名は妥当なものに変更します。

        private bool SetupFileName()
        {
            if (File.Exists(fileName))
            {
                return true;
            }

            if (File.Exists(defaultFilename))
            {
                fileName = defaultFilename;
                return true;
            }
            showWarn("Upgrade Failed, File Not Exist(升级失败,文件不存在).");
            return false;
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            if (!Killv2RayN()) return;
            if (!SetupFileName()) return;
            StringBuilder sb = new StringBuilder();
            try
            {    。。。
}

このような形に変更しました。幾分、見通しが良くなったと思います。ここでまた一つ気付きます。
tryを行う直前のif分の判定はこれから行う処理のセットアップを行っている。セットアップに失敗した場合は処理を中断したい。きっとそのようなことに見えます。
せっかくなのでメソッドに切り出して表現しておきます。

        private bool SetupOKClickProcess()
        {
            if (!Killv2RayN()) return false;
            if (!SetupFileName()) return false;
            return false;
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            if (!SetupOKClickProcess()) return;

            StringBuilder sb = new StringBuilder();

これは冗長な対応かもしれません。SetupOKClickProcess側の処理はやや見にくいものになってしまいました。

ゴミFileの削除処理
            StringBuilder sb = new StringBuilder();
            try
            {              
                string thisAppOldFile = Application.ExecutablePath + ".tmp";
                File.Delete(thisAppOldFile);
                string startKey = "v2rayN/";

startKey の宣言と、その上の部分に特別な関係はなさそうです。また、fileDeleteを含む処理はこれまで見てきた処理と同様これから実行するメインロジックの前準備に相当していると考えます。
少し位置を調整してみましょう。DeleteFileはメソッドに切り出し、前処理の仲間に加えます。
startKeyの生成位置はTryの中でやる必要はなく、説明変数であったり、メソッド内定数に相当する立ち位置です。ので、setup処理の直後に宣言することにしました。

        private bool DeleteTemporaryFile()
        {
            try
            {
                string thisAppOldFile = Application.ExecutablePath + ".tmp";
                File.Delete(thisAppOldFile);
            }
            catch (Exception ex)
            {
                showWarn("Upgrade Failed(升级失败)." + ex.StackTrace);
                return false;
            }
            return true;
        }

        private bool SetupOKClickProcess()
        {
            if (!Killv2RayN()) return false;
            if (!SetupFileName()) return false;
            if (!DeleteTemporaryFile()) return false;
            return false;
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            if (!SetupOKClickProcess()) return;

            string startKey = "v2rayN/";
            StringBuilder sb = new StringBuilder();
            try
            {              
                using (ZipArchive archive = ZipFile.OpenRead(fileName))
                {

さて、ここで困ったことが発生しました。参照を適当に確認していたためにDeleteFile時に生成していた「thisAppOldFile」が以降の処理で使用されているのに気付くことができませんでした。
thisAppOldFileは

string thisAppOldFile = Application.ExecutablePath + ".tmp";

となっています。この生成であれば、コンストラクタ生成時に決定することでさしたる問題はなさそうです。あまりメンバ変数が増えてしまうのもどうかと思いますが・・・メンバ変数に移管しましょう。

        private readonly string thisAppOldFile = Application.ExecutablePath + ".tmp";

こんなかんじで。

メインロジック
using (ZipArchive archive = ZipFile.OpenRead(fileName))
                {
                    foreach (ZipArchiveEntry entry in archive.Entries)
                    {
                        try
                        {
                            if (entry.Length == 0)
                            {
                                continue;
                            }
                            string fullName = entry.FullName;
                            if (fullName.StartsWith(startKey))
                            {
                                fullName = fullName.Substring(startKey.Length, fullName.Length - startKey.Length);
                            }
                            if (Application.ExecutablePath.ToLower() == GetPath(fullName).ToLower())
                            {
                                File.Move(Application.ExecutablePath, thisAppOldFile);
                            }

                            string entryOuputPath = GetPath(fullName);

                            FileInfo fileInfo = new FileInfo(entryOuputPath);
                            fileInfo.Directory.Create();
                            entry.ExtractToFile(entryOuputPath, true);
                        }
                        catch (Exception ex)
                        {
                            sb.Append(ex.StackTrace);
                        }
                    }
                }

usingもforeachもある程度どうしようもありませんが・・・一度メソッドに全文切り出します。

private bool Upgrade()
        {
            string startKey = "v2rayN/";
            StringBuilder sb = new StringBuilder();
            try
            {
                using (ZipArchive archive = ZipFile.OpenRead(fileName))
                {
                    foreach (ZipArchiveEntry entry in archive.Entries)
                    {
                        try
                        {
                            if (entry.Length == 0)
                            {
                                continue;
                            }
                            string fullName = entry.FullName;
                            if (fullName.StartsWith(startKey))
                            {
                                fullName = fullName.Substring(startKey.Length, fullName.Length - startKey.Length);
                            }
                            if (Application.ExecutablePath.ToLower() == GetPath(fullName).ToLower())
                            {
                                File.Move(Application.ExecutablePath, thisAppOldFile);
                            }

                            string entryOuputPath = GetPath(fullName);

                            FileInfo fileInfo = new FileInfo(entryOuputPath);
                            fileInfo.Directory.Create();
                            entry.ExtractToFile(entryOuputPath, true);
                        }
                        catch (Exception ex)
                        {
                            sb.Append(ex.StackTrace);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                showWarn("Upgrade Failed(升级失败)." + ex.StackTrace);
                return false;
            }
            if (sb.Length > 0)
            {
                showWarn("Upgrade Failed,Hold ctrl + c to copy to clipboard.\n" +
                    "(升级失败,按住ctrl+c可以复制到剪贴板)." + sb.ToString());
                return false;
            }
            return true;
        }

これでは単にややこしいのが移動しただけです。内部を見ていきます。実はこれと言って直せるところが・・・
というわけにはいかないのですね。ただ、この辺りであればとくに影響もなく修正もできてしまえるので、よい塩梅かもしれません。
ぱっと見問題がなさそうなので、各処理に名前を付けることができないか見ていきます。

                            if (entry.Length == 0)
                            {
                                continue;
                            }

後続の処理をみるかぎり、entryFullNameに対して処理を行うので判定しているようです。ただこれがやらないとエラーになるからやっているのか、
単に不要な処理だからやっているのか判断に迷うところです。迷ったときはコメントを置いておくほうがよさそうです。今回は省略します。

                            string fullName = entry.FullName;
                            if (fullName.StartsWith(startKey))
                            {
                                fullName = fullName.Substring(startKey.Length, fullName.Length - startKey.Length);
                            }

ここの処理は・・・fullNameで宣言していますが、その後内容が書き換わっています。fullNameなのかfullNameではない何かなのか。
判断に迷うところです。こういった情報は使用する側で考慮するのが面倒な場合が多いので、fullNameの取得をentry側に持たせて悩むのをやめてみます。
どうにもここ以外で使用箇所がなさそうなので、ZipArchiveEntryの拡張クラスで対応してみます。
ざっとこのような感じに

using System.IO.Compression;

namespace v2rayUpgrade
{
    public static class ZipArchiveEntryExtensions
    {
        public static string FullName(this ZipArchiveEntry instance, string startKey)
        {
            string fullName = instance.FullName;
            if (fullName.StartsWith(startKey))
            {
                fullName = fullName.Substring(startKey.Length, fullName.Length - startKey.Length);
            }
            return fullName;
        }
    }
}

使用側はnamespaceもおなじなので特にUsingの定義もなく

                        try
                        {
                            if (entry.Length == 0)
                            {
                                continue;
                            }
                            string fullName = entry.FullName(startKey);
                            if (Application.ExecutablePath.ToLower() == GetPath(fullName).ToLower())
                            {
                                File.Move(Application.ExecutablePath, thisAppOldFile);
                            }

                            string entryOuputPath = GetPath(fullName);

                            FileInfo fileInfo = new FileInfo(entryOuputPath);
                            fileInfo.Directory.Create();
                            entry.ExtractToFile(entryOuputPath, true);
                        }
                        catch (Exception ex)
                        {
                            sb.Append(ex.StackTrace);
                        }

少し見通しがよくなりましたか?次いきます。

                            string entryOuputPath = GetPath(fullName);

                            FileInfo fileInfo = new FileInfo(entryOuputPath);
                            fileInfo.Directory.Create();
                            entry.ExtractToFile(entryOuputPath, true);

ここも一連の処理ですが、ゴニョゴニョしてExtractToFileしたいってのが本音だろうな。と思います。こちらも先ほど作った拡張クラスに放り込んでみましょう。

        public static void ExtractToFile(this ZipArchiveEntry instance, string path)
        {
            FileInfo fileInfo = new FileInfo(path);
            fileInfo.Directory.Create();
            instance.ExtractToFile(path, true);
        }

こんなかんじに・・・どうも無理やり感がでてきました。fullNameも別処理で作ったFullNameがあるはずです。
おそらく、このArchiveEntryを操作するクラスを作ったほうがよいでしょう。今回はあきらめます。

対応後の確認

さてひとしきり対応が終わったので変更箇所を見てみます。

       /// <summary>
        /// 宣言位置はおかしいが、コード説明する際にわかりやすいのでこの位置に宣言。
        /// </summary>
        private readonly string thisAppOldFile = Application.ExecutablePath + ".tmp";
        private bool SetupFileName()
        {
            if (File.Exists(fileName))
            {
                return true;
            }

            if (File.Exists(defaultFilename))
            {
                fileName = defaultFilename;
                return true;
            }
            showWarn("Upgrade Failed, File Not Exist(升级失败,文件不存在).");
            return false;
        }
        private bool DeleteTemporaryFile()
        {
            try
            {
                File.Delete(thisAppOldFile);
            }
            catch (Exception ex)
            {
                showWarn("Upgrade Failed(升级失败)." + ex.StackTrace);
                return false;
            }
            return true;
        }

        private bool SetupOKClickProcess()
        {
            if (!Killv2RayN()) return false;
            if (!SetupFileName()) return false;
            if (!DeleteTemporaryFile()) return false;
            return false;
        }

        private bool Upgrade()
        {
            string startKey = "v2rayN/";
            StringBuilder sb = new StringBuilder();
            try
            {
                using (ZipArchive archive = ZipFile.OpenRead(fileName))
                {
                    foreach (ZipArchiveEntry entry in archive.Entries)
                    {
                        try
                        {
                            if (entry.Length == 0)
                            {
                                continue;
                            }
                            string fullName = entry.FullName(startKey);
                            if (Application.ExecutablePath.ToLower() == GetPath(fullName).ToLower())
                            {
                                File.Move(Application.ExecutablePath, thisAppOldFile);
                            }
                            entry.ExtractToFile(GetPath(fullName));
                        }
                        catch (Exception ex)
                        {
                            sb.Append(ex.StackTrace);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                showWarn("Upgrade Failed(升级失败)." + ex.StackTrace);
                return false;
            }
            if (sb.Length > 0)
            {
                showWarn("Upgrade Failed,Hold ctrl + c to copy to clipboard.\n" +
                    "(升级失败,按住ctrl+c可以复制到剪贴板)." + sb.ToString());
                return false;
            }
            return true;
        }

どうでしょうか。見通しがよくなったような。悪くなったような。自己満足で終わるのもよろしくないので再度コードメトリクスを計算します。
gyazo.com
btoOK_Clickの保守容易性指数はずいぶん回復しました。
メソッドは分割しましたがクラス全体の指数も改善しているので、悪い部分が移動した。ということでもないということがわかります。
よかったよかった。めでたしめでたし。

ドメイン駆動設計 モデリング/実装入門勉強会の動画みて感想など


目次

このところ本を読むよむと本にカマかけてばかりで(それすらもままならないですが)
アウトプットが壊滅的な状態です。
アウトプットをするにはインプットが必要でしょうが!ということでyoutube2倍速パワーで見てみたので感想をアウトプットとすることにします。
あくまでも私の解釈になってしまうので、本体は動画見たほうがよいでしょう。

見た動画。全体の総評

DDD。ドメイン駆動設計ということで「モデル」とか「ドメイン」という基本的なことから説明と具体的な例示もある素晴らしい動画を見てきました。
こちら。3時間超の動画です。私は1.5時間分しか見れていないので、残りの部分についてはノータッチとなります。
https://www.youtube.com/watch?v=19Gbx9jWLdc&feature=youtu.be

対象としている人は誰なのでしょうか。

動画の言葉を借りるなら
・DDDとかなんかよくわからん。怖い。
みたいな状態の人の背中を押す動画。ということで解釈しました。
個人的には「DDD」怖い。ということはないですが、エバンス氏何言ってるか全然わからんでござる。という状態でした。
・なんだか「DDD」なるものがあるぞ。いいぞぉ!
・設計的な話なんだよなぁ。業務の理解を進めてどうにかする手法だぜ!
みたいなイメージで「モデル」とか「DDD」とか「ドメイン」というような言葉は何となくの雰囲気だけで掴んでいて、はっきりとした言葉では理解できいなかったですが、
また一つ解像度があがったかなぁ。といったところ。まだまだ実践に投入するには知識も理解も足りてないけど、
「軽量DDD」の話でもあったように「問題の解決」のために少しでもやっていくことは悪くないと思えました。
サルでもわかるシリーズではないですがわりかし「入門編」が全然入門じゃないのでは?なんて思うことも多くありますが、この動画はまさしく「入門」にも良いのではないでしょうか。

反対に対象外としている人は誰なのでしょうか

・DDDならよく知ってる。エバンス氏も何言ってるか大体わかる。
・もう実際にやってるし、いまさら基本的なことなんて必要ないよ。
なんていうすでに門をくぐられていて、実勢にも投入できている方には不要な動画だと思います。

以降の記事は動画内で触れられていることです。興味が出た人は動画を見ていただく。がベストの行動と思います。

DDD is ・・・

動画の内の言葉で行けば「ソフトウェアの開発手法」の一つ。となります。この認識には変わりはないです。
DDD reference を見よ!というようなメッセージを受け取った気がします。が、布教のためにせれでも砕いて説明してくれている。
というのが本編の動画だと思います。27分ごろからメインが始まるので見ましょう。見ましょう。

モデル is ・・・

問題解決のための物事の特定の側面を抽象化したもの。をモデルと言います。
と言われても何となくイメージがつかなかったが。履歴書を例にされている説明である程度腑に落ちてくれたから―自分。といった印象。
ただ、履歴書としては「筆圧」とかいらないような気もするけど、採用面接という側面からしたら履歴書の「筆圧」とか興味あったりするんじゃないかな。とおもったり。
要求「履歴書」にはいらないは確かかな。けど「採用」したいわけだから本当に履歴書の要素からそぎ落としていいのかなー。どうだろう。いらんちゃいらんだろうが。(そこは業務にそってね)

まぁ、つまりなんだ。「問題を解決したい」ということでおおむねあっているのかな。

ということが分かったので良いと思う。問題を解決し続けるためにはどうするか。生産性の向上や、自動化されたテストなんかもきっと、
問題を解決したい。そこに注力したい。ということだと思う。私もそうありたい。

おんぼろソースコードからリファクタリングするのまき。

本編のなかで実際のソースコードリファクタリングしていきます。Javaで解説されていたけどC#のほうがよく理解できるので書き直してみました。
gitに差分をあげてみてコメント記載。
github.com
あちこちリンクが飛ぶのは不本意なのですがどうやらソースコードベースのほうが私はコメントを書きやすいようなのでこちらの方法を採用しました。
ところで松岡氏はなんというかもう「汚い」ソースは書けないのではないかと思う。名称等からにじみ出る感じが・・・
「こんなもんじゃねぇぜ・・・実際のとこ・・・」という気もしないこともないが、そちらも趣旨から大きく離れるだろうから特に問題はないと思う。

ということで

めちゃくちゃグッドな動画も見られた上に、なんと別回の動画もあるというので今度はそちらを見てみようと思います。
松岡さんとても貴重な動画をありがとうございました。

lambda->AWS RDS Proxyにうまく接続できなかった(そのうちリベンジする)


目次

※掲題の件、達成できなかったのでその手の情報が欲しくてたどり着いてかたすみません。※

リングフィットアドベンチャーの力で子供二人(2歳、4歳)を同時に抱きかかえても平気になってきました。
お腹周りは一向に痩せません。痩せろ。
勉強がてらにRDS Proxy なるものを使ってみようとやってみたけど、どうにもうまくできないので一回敗北宣言。
というのが今回の内容。

環境とか

・リージョン:東京
・作業環境 :マネージドコンソール

何がうれしくなるためにRDS Proxyを使うのか。

lambdaからのRDS接続用。ということで間違いないのかな。と思っています。
RDSはMySQLだとか、OracleなんかのRDBMSを使うことのできるサービスです。
lambdaはサーバレスアーキテクチャを構成するうえで、欠かすことのできないコアなサービスだと思いますが、
lambdaからのRDS(というよりリレーショナルデータベース)へのアクセスは「同時接続数」の問題で基本的にはアンチパターンとされていました。
と、いうのも例えばlambdaでRDSに対してなんらかのクエリを発行するように組み込んでいた場合、
lambdaは状況に応じて自動的にスケーリングし、10なり100なり。それ以上同時にクエリ発行=接続数ということがありえてしまいます。
一方RDS(RDBMS)はそのような大量の同時接続数に対してサポートしていることは稀です。(できないことはないでしょうが・・・)
オンプレ環境の開発であれば、コネクションプールなどの仕組みを組み込んでこの辺りをうまいことやりくりしたりするんですが、
lambdaで構築するのも骨がおれる作業かと思います。(やって見たことがないのでなんともです。)
ということでlambdaでRDS(RDBMS)接続。はアンチパターンとなっていました。
このあたりはblackbeltのyoutube動画見てみるとよいかと思います。

とりあえずやってみたらええねや。

ここを参考にしながら実際にやっていきます。
aws.amazon.com

とりあえず接続用のRDSをたてる。

RDSコンソールでRDSを建てちゃいましょう。現行(2020/3/21時点)でRDS Proxyはプレビュー版で、全部のRDBMSに対応しているわけではないです。
今回はMySQLを使用します。説明は画像ペタペタ作戦でいきます。
でーたべーす~
gyazo.com
設定~
gyazo.com
初期のデータベース名。接続確認でつかうのでお忘れなく設定!
gyazo.com
ほかはデフォルトのままなので割愛しますが、パブリックアクセスはオフでやります。
gyazo.com

AWS SeacretManagerにアクセスできるIAMポリシーを作成する。

AWS SeacretManagerにシークレットを作成する

といっても初めて使うサービスなのでだいぶわからない状態ではありますが。
ユーザー名はRDSのほうと合わせました。
gyazo.com
シークレット名はわかりやすく「proxy-sample-secret」としました。
gyazo.com
あとはデフォルトでよいよいなのでそのままポチポチ。で出来上がります。
gyazo.com
詳細確認してARNをどこかにメモしておきましょう。後ほど使います。
gyazo.com

シークレットを読み取れるポリシーを作る

IAMコンソールから新規のポリシーを作ります。
JSONベタバリでARNを読み替えてもよさそうですし、ビジュアルエディタでポチポチでもよいと思います。
私はJSONベタバリしました。
gyazo.com
ポリシーの名前はわかりやすいもので、好きなものでよいでしょう。
gyazo.com

シークレットを読み取れるIAMロールを作る

IAMコンソールから新規のロールを作成します。
gyazo.com
作成ボタンをぽちっとやって、作成していきます。
RDS用のロールなので、RDSを選びます。
gyazo.com
JSONは手順に従うので「Add Role to Database」でよいのではないでしょうか。
gyazo.com
ポリシーは先ほど作成したものを使用します。このタイミングで作成しても問題ないでしょう。
gyazo.com
ロール名もポリシーと同じで、いい感じな名前でOKです。
gyazo.com

ロールに信頼ポリシー(?)を確認する

おそらく「信頼関係」のことだと思われる。
先ほど作成したポリシーを選択して、信頼関係タブを選択します。
gyazo.com
信頼関係の編集を確認して、以下のようになっていることを確認します。
※編集が必要かとおもいましたが、初めから編集済みのようでした。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "rds.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

RDSProxyとlambdaを関連付ける

とりあえず新規に関数を作成

gyazo.com
sampleがNode.jsなのでとりあえずコピペで行けるようにつかったことないけどNode.jsにした。

RDS Proxyを作る

先につくった関数の最下部にプレビューでデータベースプロキシ。の設定がある。ここから作っていく。
gyazo.com
すでに前準備はすましているので、適当な項目を選択するのに説明はいらないと思う。
それぞれ、これまで作ってきたRoleだったり、シークレットのARNだったりを設定する。
gyazo.com

プロキシへの接続情報の環境変数を作る。

本当はIAM認証がよいらしい。サンプルでは説明の簡易さを優先してSecrets Managerの認証情報を利用しているので、
合わせます。とのこと。
なので私も合わせます。ひとまずRDSへのアクセス用の文字列をLambda環境変数に放り込んでいきます。

endpoint

RDSのエンドポイントはRDSコンソール->Proxiesから確認できる。
gyazo.com

user

データベースのユーザ名。とりあえず管理者でいいと思う。

password

パスワード。管理者のパスワード

dbname

データベーススキーマ名。設定で間違えていなけれ、sample_dbという名前で作成されているはず。
わからなくなったらRDSコンソールの設定タブで確認できるので、確認しよう。

ここまでは順調に来たのだけれど。

Node.jsのMysql周りの環境はcloud9を使うと簡単にlambdaにデプロイできてらくちんだった。
ところが、いざ接続テストをしてみるとつながらない。 ETIMEOUT, ETIMEOUT, ETIMEあうとぉぉぉぉ。
で一向にクエリの結果が返ってこない。

経験的に、タイムアウトの発生は権限周りのことがおおいので、もう一回改めてチャレンジしてみようと思う。

君にはこれが・・・

・lambda -> RDS のそもそもの知識が足りない
・secret manager への理解が足りない。
・Node.js は初めて使ったけどたぶん使い方はあってる。

というのが現状と思われる。無理に外からの接続もOFFにしてMySQL建てちゃったけど、
次は外部からもつなぎながらどこが悪いのか検証していく予定。

あとは、なんかロールつくったり、プロキシを先にたてとかなくてもlambdaからぽちぽち適当にやっていくと諸所の設定をやってくれるみたいなので、それと今回のとで何が違うのか比較するつもり。だ。
ということで次。がんばる。次。

SQLSERVERのパーティションテーブルつかってみた


目次

確認環境

SQL Server Express Edition For AWS RDS
SSMS(SQL Server Management Studio 18)
Python3 3.6.0

パーティション

詳しいことはこいつだ。公式が最強なのだ
docs.microsoft.com

とりあえずテーブルを作るよ!

CREATE TABLE partition_sample(
	ID int,
	RELATION_ID int,
	START_DATE datetime2(0),
	VALUE1 int,
	VALUE2 int,
	VALUE3 int,
	VALUE4 int
);

IDという列名はよろしくないような気もしつつ。サンプルだからまぁいいかの精神。
列名は適当だけど、START_DATE が「datetime2(0)」型ってのはポイントなので、ちょっとだけ意識してくれるとうれしい。

データを放り込むよ。

効果を実感するには10年分ぐらいデータがほしい。年数が重要になる。重要なのだ。
ちまちまINSERT分を打ち込むのは非常にめんどうなのでPythonでさらっとしあげよう。

import datetime
start = datetime.date(1900, 1, 1)
for i in range(365 * 10):
  print('insert into partition_sample values (' + str(i) + ',1,\'' +  str(start) + '\',1,1,1,1);')
  start = start + datetime.timedelta(days=1)

Paiza.IOさんでも利用してたたけばPrintされます。全実行しちゃいましょう。
※やってみたらPaizaさん4年分ぐらいでテキストはくのやめちゃったから、適当に分割してつかーさい。

件数にしたらこんなもんで。
gyazo.com
毎日4年分でも1500件ぐらい。

パーティション設定するよ!

※先にもつくれそうなんだけど、データいれてからのほうがマップ作るの楽なので、
データ入れた後にしよう。

gyazo.com
つくったテーブルを右クリックして、ストレージ->パーティションの作成を選ぶ
gyazo.com
ウィザードを適当にポチポチしていく。
gyazo.com
START_DATEを選ぶよ。
gyazo.com
関数名も適当に
gyazo.com
パーティションマップもこんな具合でよいでしょう。からのすぐに実行で完了です。
gyazo.com

本当にできてるか、テーブルのプロパティをみてみる。
gyazo.com
うん。できてるっぽい。いい感じに7分割。初めて作った割にはまともにできたと思う。

適当にSQL発行してみてパーティションを味わう。

SELECT * FROM 
partition_sample
WHERE
	ID = 1
;

とかいいながら、パーティションきかないぱてぃーん。その1。
パーティション列を指定してないのでききまてん。実行計画にもこのとおり。
gyazo.com

SELECT * FROM partition_sample
WHERE
	ID = 1 AND
	START_DATE BETWEEN CAST('1900/01/01' AS datetime2(0)) AND CAST('1900/01/31' AS datetime2(0))
;

こんな感じできちんと指定してあげると、パーティションが有効になる。
gyazo.com

SELECT * FROM partition_sample
WHERE
	ID = 1 AND
	START_DATE BETWEEN CAST('1900/01/01' AS datetime2(1)) AND CAST('1900/01/31' AS datetime2(1))
;

微妙にちがうこちらはNGぱってぃーん。パーティション列は厳密に型をみているようで。
datetime2の有効少数桁までみてるので、要注意。

SELECT * FROM partition_sample
WHERE
	ID = 1 AND
	START_DATE < CAST('1902/01/31' AS datetime2(1))
;

これもよろしくない。パーティションはきっちり区切って使おう。

なんでもつかえばいいってわけじゃないし、良い感じで分散されている日付列なんかが適当になってくるだろうし、
調整は必要だけど「強い」機能だと思います。ます。パーティション。使ったことなければ使てみてね。

読書感想文。達人に学ぶSQL徹底指南書、DB設計徹底指南書を読んだで


目次

こんにちは。競プロが土日にあり、かつDBスペシャリストの方の勉強が忙しかったりなんだったりで、
全然「調べもの」をしてませんが。読み切ったので感想文。
一つの指標として面白そうだったので「読まなくてもよさそうな人」を入れてみた。
曰く、購入を考えている人は「自分にいるかどうか」の情報がうんぬんかんぬん。売上的には敵かもしれない。
けど、口コミ等もばかにならないし、刺さるべく人に届くってのは大事だと思います。はい。

読んだ本

達人に学ぶSQL徹底指南書。
達人に学ぶDB設計徹底指南書。
二冊
タイトル的にぐぐったらすぐでそうだけど、間違えてもしょうがないので
gyazo.com
こげなやつ

読まなくてもよさそうな人。

・「達人」
まぁ、そりゃね。
・古くからSQLと付き合ってきた人も読まなくてもいいかもしれない。
なんだろう。SQLの生い立ちみたいなものエッセンスとしてふんだんに盛り込まれているので、そもそもそれを肌で感じてきたぐらいの方ならいらんかな。
ほぼ「達人」と同義だけど。
・マネージャークラスとかで「おれは全然SQL書きません。レビューもしません」的な人(それがいいかどうかは知らないが)にも不要かも。
特に「SQL徹底指南書」の方はいらないかな。「DB設計徹底指南書」の方は話のタネ的もいいかもしれない。けど、個人的には中途半端に知識放り込んで
上からねじ込まれるのが一番きついので読まないでほしいかもしれない。
IPA DBスペシャリスト受験用
私もそのつもりでかったのだけど、いらないとおもう。試験合格だけ目指すならかなり冗長。過去問ゴリゴリやったほうがいいと思う。
ただSQLに興味がわくというか、無機物ではなくなるかもしれないし、好きになるかもしれないので効果はあるかもしれない。
そういった意味では「SQL徹底指南書」の方はギリギリおすすめ。
SQLでパフォーマンス問題を抱えている人
期待して買ったのだけれど、あまり期待してはいけない。書中にも書いてるけど、そこまでカバーしきれないし、
RDBMSによっていたり、ハードウェア等の物理的な部分の考慮も大きいので。。。とのこと。
別の情報を探しましょう。

読んだら気づきがありそうな人。

・現場でSQLつかってるひと。またはこれから使う人。
読んどいてとりあえず損はないかな。理解が深まると思われる。
・学生さん。
私は専門学校時代にこの本にあいたかった。どうしても資格試験重視だったりする上で、
基本情報(応用情報)取るための道具状態だったから、仕事初めてもあんまりSQL好きになれなかった経緯もあり・・・
理解として、DB用の言語というニュアンス。そうなんだけど、そういうこっちゃないんだよね。SQLは。
なのでひっついてデータベース周りそのものがあんまり好きじゃなかったりした。
データベースの方の理解も、ファイルじゃ面倒だからおいとくか。ぐらいの理解。そうじゃないんだなぁ。
必要にかられて。というのもあるけど、業務をとうしてゴリゴリやっていくうちに苦手意識とかそういうのはなくなりはしたが、当回りしたなぁと思う。
そんなところでこの本を読んだので「あーいいこと書いてる。というかそーゆーことなんだね。把握」みたいな状態になった。
SQLはほかの言語とくらべてかなり方言(あってるかな)がきつくなりやすくて実際の現場だとチェックリストとかなんとかで意図無視して強制(矯正)したりするので、
そのあたりも含めて知識回収できる感じです。

なお、SELECT UPDATE とか、すごく基本的なところは理解していないと読むのがつらいと思います。

感想

好き。というかなんだろうな。著者のミック氏の語調が好きなのかもしれない。
SQLとか関係なくこの人の本であれば他のも読みたいかな。(小並