読者です 読者をやめる 読者になる 読者になる

眠いしお腹すいたし(´・ω・`)

C#関連を主に書きます。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

WeakなReactivePropertyを作ってみた

C# ReactiveExtentions ReactiveProperty Xamarin.Forms

本題

ReactivePropertyを使う際にViewModelでModel層からToReactivePropertyなどを使う場合にモデル層にSubjectすることで強参照な結びつきが生まれてViewModelがいつまでも破棄されなくなってリークしてしまいます。

その解決アプローチとしてWeakReferenceを利用できないか考えてみました。

今回のリポジトリです

github.com

WeakReactiveProperty

とりあえずWeakなReactivePropertyを作成。

まずはWeakSubscriber。

gist.github.com

要するにWeakに購読します、みたいな感じです。

これを利用してWeakReactivePropertyを作成。

WeakReactivePropertyExtentions/WeakReactiveProperty.cs at master · yuka1984/WeakReactivePropertyExtentions · GitHub

基本的に素のReactivePropertyのObservableにSubScribeしている部分をWeakSubscribeに置き換える感じです。

詳細までは確認してません。

確認

まずは効果を検証するためにみんな大好きコンソールアプリでチェック

WeakReactivePropertyExtentions/Program.cs at master · yuka1984/WeakReactivePropertyExtentions · GitHub

結論としてはWeakReactivePropertyをViewModel側で使うとダメな感じ。

まだVMインスタンスが参照されているのにGC.Collect()で回収されてしまって動かなくなってしまいます。

良いパターンとしてはModel(コードで言うとWeakServiceクラス)のパターン。

モデル層でWeakReactivePropertyを使うとうまくいっています。

これならうまくいきそうなので実際にXamarin.Formsで検証してみました。

こんな感じのVMを作ります。

gist.github.com

App.csはこんな感じ。

gist.github.com

NavagationPageに沢山スタックさせてからバックボタンで最初のページまで戻ってカウントアップボタンを押します。

Weakでない普通のサービスクラスを使用するとデバッグウィンドウに作ったページ分のデバッグ出力が出てしまいます。

f:id:tamafuyou:20170315095634p:plain

所謂リークしている状態。

次にServiceをWeakServiceに書き換えて同じ動作を行います。

f:id:tamafuyou:20170315095900p:plain

いい感じに見える(; ・`д・´)

ただNavigationスタック上にはPageは一つしかないはずなのにデバッグメッセージは2行必ずでてきちゃう。

おそらくどこかでXFが参照を持っていると思われるのだけれども深追いはしないです。

最後に

一応こんな感じでWeakReferenceを使用したReactivePropertyでModel層を作る事でリークを解決できるかもしれないです。

ReactivePropertyの有識者の方、Xamarin.Formsチョットデキル以上な方で、この記事を読んでくれた人がいましたら

問題点とかおかしい点とか指摘してもらえると助かります。

よろしくお願いします。

ではでは(‘ω’)ノ

まどすた #2で登壇してきました

C# Xamarin Xamarin.Forms ReactiveProperty

ごあいさつ

まだまだ部屋に未開封の段ボールがあります(´・ω・`)

冷蔵庫と電子レンジがありません(´・ω・`)

まだリビングテーブルも無いのでご飯食べるのがちょっと大変です(´・ω・`)

通勤電車はまだまだ慣れません(´・ω・`)

登壇してきました

どこで?

metro.connpass.com

VS2017記念でC#ユーザ会との合同勉強会というイベントにて登壇させていただきました。

登壇まで

人前で話すなんて大学の卒論の発表ぶりくらいでパワポ資料つくって話すなんてことも仕事でも全然経験なくて本当に初登壇って感じでした。

引っ越しなどでゴタゴタしてパワポ資料ができたりしたのが直前になってしまったのですが岩永さんに資料の添削などを手伝っていただいて何とか登壇することができました。

ありがとうございました。

内容

speakerdeck.com

タイトルと中身に少し違いがあるのですが要約すると

マルチタイプ

  • そんな環境の中でコード共有化率の向上・移植性の向上を目指すにはモデル層の設計が重要

  • モデル層はUI要素を考えないアプリケーションの核である

というような内容です。

以前からモヤモヤと頭の片隅で考えていた事で

今回登壇をすることによってそれを言語化できたことが凄く自分の為にもよかったです。

当日

緊張でガチガチになってしまう事はなかったんですが

話している最中に自分で何をしゃべっているのかよくわからなくなってしまっていました。

あと時間とかも考える余裕が全くなかったです。

今後

今後も話したい内容があって話す機会があれば登壇していければと思っています。

それではまた( `ー´)ノ

Xamarin.FormsでUnity.Configurationしてみた。

C# Xamarin Xamarin.Forms

はじめに

引越しつらい現実逃避で記事書きました/(^o^)\

Unity

ゲーム作る方のじゃなくてDIとかする方のUnityのお話です。

www.nuget.org

現在、開発が止まって放置されているらしくて今後メンテの期待薄なので今更Unityのネタを書こうか迷ったんですがとりあえず書いてみることにしました。

UnityContainerの拡張メソッドにLoadConfigurationってあるのご存知ですか?

app.Configとかweb.configにContainerへのレジストを記述してLoadConfigurationすると、その設定どおりにContainerに登録を行ってくれます。

これ.NET45なプロジェクトだと使えるんだけどXamarinとかPCLでは使えないんです。

どうしてかというとSystem.Cofigurationに依存している拡張なのでSystem.Configurationが基本的に使えない環境を外してるんですね。

それを今回はPCLプロジェクトでもある程度使ってみるサンプルを作りました。

Xamarin.Formsへの対応

Windows8.1とかは対象外です。今回はAndroidで動作確認しています。たぶんiOSでも動くはずです。Macを既に梱包しちゃったので試せません。

UWPは頑張れば対応できそうなんですが今回は見送りました。

解説

プロジェクトの作成

まずはプロジェクトの作成を行います。

Xamarin.FormsプロジェクトをPCLにて作成してください。

PCLを.NET Standardに対応

tamafuyou.hatenablog.com

こちらの記事を参考にPCLプロジェクトを.NET Standardに対応させてください。

.NET Standardのバージョンは1.5としてください。

Unity.Configurationの追加

UnityのConfiguration機能をPCLで使用するためにUnity,Configurationというパッケージを使用します。

www.nuget.org

ソリューションのnugetマネージャーを開いてUnity.Configurationというパッケージを追加します。

Pre版ですので「プレリリースを含める」にチェックを入れて検索してください。

f:id:tamafuyou:20170207184230p:plain

PCL/Android/iOSに追加してください。

テスト動作用のインターフェースと実装クラスを追加します。

めんどくさいのでこんな感じで。

namespace App4
{
    public interface IHogeHoge
    {
        string GetHoge();
        string GetHogeHoge();
    }

    public class HogeHogeA : IHogeHoge
    {
        public string GetHoge() => "HogeA";
        public string  GetHogeHoge()=> "HogeHogeA";
    }
}

PCLにconfigファイルを追加します。

unity.configという名前でconfigファイルを追加します。

ビルドアクションは埋込リソースにしてください。

f:id:tamafuyou:20170207185333p:plain

ファイルの中はこんな感じで。

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  <!-- Define Assemblies-->
  <assembly name="App4" />
  <!-- End Assemblies-->
  <!-- Define Namespaces-->
  <namespace name="App4" />
  <!-- End Namespaces-->
  <container>
    <register type="App4.IHogeHoge, App4" mapTo="App4.HogeHogeA, App4" />
  </container>
</unity>

他にも色々とできますがUnity.Configurationの使い方は別途ぐぐってください。

ConfigurationSectionReaderクラスを追加する

c# - How to Read a Configuration Section from XML in a Database? - Stack Overflow

こちらを参考にしました(コピペ)

こんな感じのクラスを追加しておきます。

    public class ConfigurationSectionReader<T> where T : ConfigurationSection, new()
    {
        public T GetSection(string sectionXml)
        {
            T section = new T();
            using (StringReader stringReader = new StringReader(sectionXml))
            using (XmlReader reader = XmlReader.Create(stringReader, new XmlReaderSettings() { CloseInput = true }))
            {
                reader.Read();
                section.GetType().GetMethod("DeserializeElement", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(section, new object[] { reader, true });
            }
            return section;
        }
    }

LoadConfigurationする

appクラスにてcontainerを作ってLoadConfigurationします。

        public App()
        {
            InitializeComponent();

            IUnityContainer container = null;

            var assembly = typeof(App).GetTypeInfo().Assembly;
            using (var stream = assembly.GetManifestResourceStream("App4.unity.config"))
            {
                using (TextReader reader = new StreamReader(stream))
                {
                    var configtext = reader.ReadToEnd();
                    var configreader = new ConfigurationSectionReader<UnityConfigurationSection>();
                    var sect = configreader.GetSection(configtext);
                    container = new UnityContainer();
                    container.LoadConfiguration(sect);
                }
            }

            MainPage = new App4.MainPage(container.Resolve<IHogeHoge>().GetHogeHoge());

            
        }

結果

f:id:tamafuyou:20170207191636p:plain

おわりに

Unity.Configurationライブラリに関しては余裕ができたらもう少し真面目に取り組んでみようかと思います。

ではでは( `ー´)ノ

Microsoft MVPを受賞いたしました

はじめに

まじ引越し進まないんだけどぉ( ;∀;)

全然物が捨てられないんだけどぉ( ;∀;)

あるジャンルの書物が捨てられないんだけどぉ( ;∀;)

捨てられないどころか読み返しちゃってるんだけどぉ(^▽^)

ごあいさつ

2連続で技術に関係ないエントリで大変恐縮なのですが

このたびMicrosoft MVPを受賞いたしましたのでご報告いたします。

受賞カテゴリはVisual Studio and Development Technologiesになります。

なぜこんな時期に? と思われる方もいるかと思います。

これまでMicrosoft MVPは年4回(1月、4月、7月、10月)でしたが、今月より毎月審査と表彰が行われるように変更となりました。

kogelog.com

2月に42人が受賞したということで、そのうちの一人ということになります。

感想

正直、私が受賞した事が私自身予想外で信じられず、またMVP受賞時期の変更もわからなかったため、日本の事務局のほうに確認のメールを送ったほどです。

確かに去年末にノリでと言いますか落ちること前提で自薦してみました。

これまで受賞されてきている方たちと比べ私は明らかに実績も少なく技術力・知識も少なく、応募するってどういう感じなのかな?っていうのを知りたくて実際に応募を行った感じです。

これから何年か新しい環境で色々と学んでアウトプットしていって、その結果にもし受賞することができたら嬉しいなぁ。

くらいの気持ちでいました。

なので受賞が確定した当初には辞退させていただこうかと迷っていました。

ただ自薦していて辞退というのも変な話なのと

よく相談事をしている方から、受賞はあくまでもMSからの「去年いろいろありがとね」という意味だし「不満ならこれから精進すればよいよ」というアドバイスを貰いまして自身で気持ちが固まり受賞する事といたしました。

去年何してたの?

今回の受賞ですが、ほぼコード書いて技術記事かいただけでの受賞になります。

JXUGでメンターは一度していますが

興味を持ったことにサンプルコード書いて記事書いてライブラリ作ったらnugetに登録してサンプルコード書いて記事書いてみたいな事しかしていません。

ということで、去年読まれた記事のランキングを作成してみました。

ランキング生成はしばやんさんのやつです。

2016 年の人気記事ランキング生成

  1. WatsonのSpeech To TextをXamarin.Formsで試してみたよ(‘◇’)ゞ

    いきなりAzureでなくBluemixのWatsonなネタなのですが、WatsonのSpeechToTextを使用してXamarin.Formsで音声入力をテキストに変換してみるコンテンツになります。

    去年ObservableVoiceCaptureというライブラリをnugetに公開しました。

    www.nuget.org

    このライブラリはXamarinにて音声入力を扱うためのライブラリで、このライブラリを使用したサンプルを作りたいなって思いから音声認識を実装してみることにしました。

    この記事はC# Xamarin界隈の方だけでなくJS界隈などの方からも読んでいただいたみたいで凄くうれしかったです。

  2. MVVMっぽくXamarin.Formsアプリ作ってみました。その2

    ReactivePropertyを使用したデザインでの実装パターンサンプルです。

    一回では書ききれなかったので4回に分けての投稿でしたが、なぜ2番目が断トツでアクセスが多いのかは不明です。

    現在はこのデザインをベースに色々と発展させた考えを持っていて、これに関してはもし機会があればどこかでお話しできたらなぁって思っています。

  3. Xamarin.Forms開発で必要な知識

    おととしXamarin.Formsを実際に開発で使ってみた結果、開発でどんな知識が必要になったかの記事です。

    Windows環境を中心に開発してきた方がXamarin.Formsをやってみる場合に少し役にたつかもしれません。

  4. C#でRakuten MA使って形態素解析してみた。

    Edge.jsという.NET環境からnode.jsを使用してJavaScriptを動作させて結果を得るサンプルです。

    深夜にツイッターを眺めていたらRakuten MAという形態素解析ライブラリに関するツイートを見つけて思いついて調べてみて実装したら朝になってました( ゚Д゚)

  5. Xamarin.Formsを使って開発してみて

    おととしにXamarin.Formsを実戦で使用してみたポエムです。

    使ったライブラリとかだけでなく、どんな開発体制でどんな風に進めたとか書いているので今読み返すと思い出がよみがえります( 一一)

  6. Netjsを使ってC#からTypeScriptへの変換をしてみた。

    NetjsというC#で作成したdllを読み込んでTypeScriptを生成するライブラリを試した記事です。

    これを使用して後にNSpeex.TypeScriptというNSpeexをブラウザ上で使えるパッケージ(一部機能だけ)を作成出来たことは私にとっての去年一番の実績だったと思っています(たぶん需要はない)http://tamafuyou.hatenablog.com/entry/2016/05/15/230615

今後

これまで受賞されてきた方と比べて色々な面で見劣りしてしまっている事は重々承知ですが「MVPの質が落ちた」等言われないように、ゆっくりですが自分なりに頑張っていこうと思います。

また今年は勉強会等に参加しやすい環境になりましたので積極的に参加していければ良いなぁと思っております。

あとはもし機会があってその時に話したいことがあれば登壇にも挑戦していきたいと考えています。

さいごに

まだまだ全然MVPのすごい方たちには遠く及びませんが、少しでも好きな技術が広まり長続きして発展していけるように貢献できればと思っております。

今後ともよろしくお願いいたします。

約10年勤めたソフトウェア会社を退職いたしました。

先ほど9年11ヵ月という長い間勤めました会社を退職することとなりました。

これまで本当にありがとうございました。

在職中には本当に沢山のご迷惑をおかけしてしまい申し訳ございませんでした。

この先もこれまでの経験をベースに頑張ってエンジニア人生を進めていきたいと思います。

10年間どんな仕事をして何を考え何を得たか

私がブログを書き始めて1年もたっていません。

Xamarin族な人というイメージはあるかなと思うのですが私がどんなキャリアを歩いているのかちょっと書いてみたいと思います。技術ブログなので技術の側面をメインに書きます。

どんな分野の仕事だったか

私は10年間、二つのジャンルの市場に向けたシステムの製造を行ってきました。

これらのシステムは大きく基幹システムとネットシステム、リアルタイムシステムの3つに分かれ、私は6割リアルタイムシステム3割ネットシステム1割基幹システムくらいの仕事を行ってきました。

どんなキャリアだったか

1年目~3年目

主に試験と現地納品でした。

印刷すると厚さ40センチ以上にもなる試験仕様書を2~3人でこなし試験が完了するとお客様に納品し使用方法の説明などを行う。

システムの切り替え時は正月、ゴールデンウィーク、お盆などですので1年目の元旦に新幹線でお客様先に移動することになったのは結構驚きました。

これらの仕事でシステムの弱点の探し方、サーバ構成、ネットワーク、ロードバランス、などの知識を得ることができました。

特にシステムの弱点の探し方を覚えたことが大きくて

色々なシステムのテストをした事によって、「こういうシステムはこういう風にできているから、こういうところにバグが生まれやすい」というような事を考える力を身に着けることができました。

メインの仕事ではないですがASP.NETWebForms、PL/SQL、ストアド、C++MFC、ColdFusionなどのプログラムを多少触っていました。

サーバセットアップなどの仕事も少しずつはじめて、主にWindowsサーバですがクラスタを組んだりミラーリングを組んだりロードバランスを組んだり、そういうインフラ面での基本的な知識を身に着けることができました。

4~5年目

4年目のプロジェクトで

WPFを使用したアプリケーションの開発を任されました。

WPFを使用する事も私自身が考えて、一つのアプリケーションを自分で考えて自分で作るということを初めて行いました。

これまでに上司からGoFデザインパターンを勉強するといいよって言われて勉強していたのですが、WPFで開発するにあたってそれらのデザインパターンを適用する事とMVVMというデザインパターンを勉強して開発に取り入れました。

この頃からプログラムをデザインするという事がすごく楽しく感じるようになりました。

きれいに構成できた時の楽しさに惹かれていました。

またネットワーク、サーバ室、サーバラック設計、設置、配線などプログラム以外にも色々な事に挑戦して勉強することができました。

ダウンタイム少なく冗長化でするにはどうしたらよいのか、機器故障をどうやって検出するか、などインフラに関して色々と勉強できました。

またWebSocketを使用したプロジェクトにかかわりDraft76の頃からC++C#でWebSocketサーバを書いたりブラウザのパフォーマンス検証をしたりしていました。

RFCドキュメントをちゃんと読んだのはその時が初めてだったと思います。

5年目には

ASP.NET MVCを使用してECシステムを1から作成しました。

一部にはKnockout.jsを取り入れてSPAな構成を試したりある意味挑戦的なプロジェクトでした。

このプロジェクトは非常に苦しくて炎上してしまい半年の残業時間が酷いことになってしまいました。

ASP.NET MVCを使ったから、Knockout.jsを使ったから炎上したわけではなかったのですが、とにかく色々な面が苦しかったです。

その後Mono For Android(現Xamarin.Android)を使用したアプリの開発などを行いました。

一人でサーバもクライアントも開発しなければならなかったので出来る限り楽をしたくてサーバサイドをWCFで作っていたんですが、それならクライアントもC#で作った方が良いと上司を説得して1年10万円もする開発ライセンスを買ってもらいました。

6~7年目

これまで収めたシステムの改修であったりサブPLとしてプロジェクトの技術面のサポートをするような感じでの仕事をしてました。

この時期はプログラミングやアーキテクチャの側面よりもプロジェクトをどのように運用するべきか、ドキュメントの意味、協力会社さんとの連携の仕方、お客様との仕事の進め方など、そういうマネージメントの側面の方を考える事の方が多かったです。

8年目

以前に作成したWPFアプリのモバイル版の作成案件ができたためXamarinおよびモバイルサービスを提供するためのシステム作成に挑戦しました。

Xamarin.FormsにるフロントエンドだけでなくWebAPIやプッシュ通知などモバイル用のシステム全体を設計して作成しました。オンプレミスな環境なのでMBAASなどを利用する事はできない点で、逆に色々な事を細部まで知ることが出来ました。

このころは、何のためのデザインパターンなのか、何のためのUnitテストなのか、色々と理由を意識しながらアーキテクチャを考えていました。

9年目

転職を本格的に考えたのは9年目のはじめあたりです。

今年は・・・特に何もしていない年になってしまいました。

体調の事情があり3か月程の休職したこと、また所属部署の組織変更が大きくあり思うように仕事ができなくなってじまいました。

ただJXUGに参加したりブログを色々とかいたり仕事以外が充実した年でもあったと思います。

上司のお話

色々な事を教えていただいた二人の上司の方達には大変感謝しています。

2年目くらいに試験アプリを作るためにC++MFCを触っていて「お前はメモリ管理というか、そもそもプログラムを何もわかってない」って怒られたり、問題解決に対する調査方法とかアプローチの方法とか色々と教わったり、デザインパターンの基本を教えてもらったり、数多くのことを教えてもらえましすごく素敵なお二人で今でも大変尊敬しているエンジニアの方々です。

おかねのはなし

残業込みで450万円くらいが平均だったかと思います。残業代なしだと400万円をきるくらいです。2016年は休職していた事もあり350万円くらいでした。

退職理由

色々な理由が積み重なった結果、として退職・・・というか転職することにしたのですが

前向きな理由、後ろ向きな理由もあわせて、大きかった点をあげてみたいと思います。

クラウドネイティブな開発がしたくなっていた

キャリアのところに一切クラウドな用語が出てきません。

今の部署の仕事はオンプレミスの仕事のみであり、クラウドの利用、クラウドネイティブな開発とは程遠いです。

ですがこの先のシステムのバックエンドはどう考えてもクラウドが主流だと考えています。(現時点でそうなのかも・・・)

ですのでクラウドネイティブでの開発を行える環境、色々と学習できる環境に移りたかったのです。

もちろん個人の範囲で色々と触ったり勉強したりすることはできるのですけど仕事で使うことの重要さもあると思っています。

Xamarin記事が多いので意外と思われる方もいるかと思いますが、私自身はフロントエンド・バッグエンド等の場所的なこだわりはなく必要なシステムを良い形で作れる人になりたいという目指すものがあり、その為に必要な技術知識を取得できる環境で仕事がしたいと思っています。

そんな中で「今の私に必要なものはクラウドネイティブな開発技術だなぁ」と思っていたタイミングで良いお話をいただく事ができて転職を決意しました。

関東で暮らしたくなった

勉強会とか関東が多いんですよね。

土日とかなら出られるんですけど平日はちょっと無理です。また往復でお金が結構かかります。(新幹線を使うと往復8000円以上)

できればこの先は勉強会などに参加しやすい地域で暮らしたくなりました。

職場の雰囲気に合わなくなってしまった

私の所属している部署の方たちは皆さん真面目です。8時という早い始業時間に遅刻する人はほとんどいないですし休む人も少ないです。

何事にも完璧を求めバグゼロを目指して常に活動しています。バグを出すことに対して厳しく何とか不具合ゼロを目指そうと頑張られています。

私が入社した当時と同じ技術同じコードを今でも大切に守っています。開発環境も入社当時から殆どかわっていません。世の中がどんなに変わろうと貫き通す芯の強さがあります。

本当に私から見たら凄い方たちなのです。私にはとても真似ができません。

それに比べて私はあまりにも怠惰で不真面目です。

無駄だと思った事はとにかくしたくない。常に楽をしたい。

楽できる仕組みやツールが世の中にあるのであれば積極的に取り入れて試したい。

朝の早起きは苦手。

問題を運用で解決する事は私には難しくて運用ルールを覚えられない。

運用ルールよりもシステム、仕組みで解決したい。

バグはどうやったって出るもの、バグを責任問題だって取り締まって嫌な空気になるよりバグがでにくい開発プロセスや試験方法を考えて萎縮せずに開発できる環境を作ることを頑張りたい。

常に変化し続けたい。

そんな性根の腐っている不真面目な私が真面目な人達の中で仕事をする事がつらくなってしまい仕事が楽しめなくなってしまいました。

やはり楽しくないと感じる事を続ける事は私のような人間にはとてもつらくて長期間モチベーションが低い状態で仕事を続けても質の高いアウトプットも出来ませんしパフォーマンスも下がってしまいます。こんなグダグダ状態の私がいても他の社員さん達に迷惑をかけてしまうだけだと思い、この先のことを考えても転職するしかないと決断する大きな理由となりました。

これから

3月から蛎殻町某企業にお世話になる予定です。

物凄い方たちが沢山いてよい機会だしチャンスだと思って色々な勉強ができればと思っています。

入社後にしばらくしましたらエントリなども書いてみたいと思います。凄い人たちの中に入ったらどう感じるかなど書けると思います。

2月は引っ越しに休息にちょっとの旅行。そんな感じで過ごしたいと思います。

引っ越し先が運よくネコOKな物件だったので小さい頃から夢だった黒猫と暮らせる生活がおくれそうです。

この先もXamarinは当然使っていきますしAzureを中心としてクラウドも使っていきます。

JXUGやJAZUG、C#界隈、Webフロントエンド界隈などの勉強会等参加することもあるかと思いますがよろしくお願いいたします。

去年は勉強会等で名刺などを受け取るばかりでご挨拶もちゃんとできていませんでしたが3月以降は名刺もちゃんとお渡しできるかと思います。

私はこれまで、Microsoft MVPの方々を含めて多数の凄い方々のブログや著書などをたくさん見て勉強させていただいてきました。

これからも沢山勉強させていただくと思います。

そんな方々には遠く及びはしませんが、こんな私の知識や考えた事では役に立たないかもしれませんが、好きな技術の発展に少しでも貢献できればと思いますのでブログを中心として公開を続けていきたいと思っております。

今後ともよろしくお願いいたします。

ASP.NET Coreで作成したWebSocketサーバをAzure Service Busを使ってスケールアウトに対応してみた

ASP.NET Core Azure ReactiveExtentions C#

はじめに

tamafuyou.hatenablog.com

前回の記事で失敗しましたので、今回はAzure Service Busを使ってスケールアウトを実装してみました。

実装

コード

今回のコードはここに保存されています。

github.com

NET CoreでのService busの利用

良い感じのパッケージがなかったので今回はメッセージの送受信は

www.nuget.org

AMQP.Net.Liteを使いました。

Service Busの操作はRest APIを使用して実装しました。

Service Bus

AzureでService Busの設定を行います。

まずはリソースグループにServiceBusを作成します。

f:id:tamafuyou:20170120010938p:plain

価格レベルは、スケールアウトにTopicを使うためStandard以上にしてください。

次にTopicの作成を行います。

f:id:tamafuyou:20170120011414p:plain

これで、今回のAzure側での操作は完了です。

では、プログラムの解説を行っていきましす。

Service Busへのメッセージの送信

aspnetcore_webspcket_sample/TopicsSender.cs at ServiceBusTopics · yuka1984/aspnetcore_webspcket_sample · GitHub

送信は特に難しい事は無くて

AmqpLiteを使えば簡単に送信できます。

        public void OnNext(ChatMessage value)
        {
            if (senderLink == null || senderLink.IsClosed)
            {
                senderLink = new SenderLink(GetSession(GetConnection()), SenderSubscriptionId, Topic);
            }
            
            var message = new Amqp.Message(JsonConvert.SerializeObject(value));
            message.Properties = new Properties
            {
                MessageId = Guid.NewGuid().ToString()
            };
            message.ApplicationProperties = new ApplicationProperties();
            message.ApplicationProperties["Message.Type.FullName"] = typeof(string).FullName;
            senderLink.Send(message);
        }

こんなイメージです。

ちなみにSenderLinkクラスのコンストラクタ引数ですが

    public abstract class  TopicsClientBase
    {
        public string NameSpaceUrl { get; set; } = "";
        public string BaseUrl => $"https://{NameSpaceUrl}/";
        public string PolicyName { get; set; } = "";
        public string Key { get; set; } = "";
        public string ConnectionString => $"amqps://{WebUtility.UrlEncode(PolicyName)}:{WebUtility.UrlEncode(Key)}@{NameSpaceUrl}/";
        public string Topic { get; set; } = "";
        protected Address GetAddress() => new Address(ConnectionString);
        protected Connection GetConnection() => new Connection(new Address(ConnectionString));
        protected Session GetSession(Connection connection) => new Session(connection);        

    }

ベースクラスがこんな感じになっていて

NameSpaceUrlにはxxxxxxxxxx.servicebus.windows.netみたいな感じのUrlを設定します。xxxxxxxの部分は作成したServiceBusの名前です。

PolicyNameとKeyは

f:id:tamafuyou:20170120020307p:plain

この辺の値を設定します。

Topicは先ほど作成したTopicの名前です。

Service Busからの受信

Service Bus Topicからの受信を行うクラスです。

aspnetcore_webspcket_sample/TopicsReciever.cs at ServiceBusTopics · yuka1984/aspnetcore_webspcket_sample · GitHub

受信開始時にTopicに送信されたメッセージを購読するためにSubscriptionと追加します。

Subscriptionの名前はGUIDを作成して使用しています。

今回はRest APIで実装していましてこんな感じです。

        private static async Task<HttpResponseMessage> CreateSubscriptionAsync(string baseAddress, string topicName,
            string subscriptionName, string token)
        {
            var subscriptionAddress = baseAddress + topicName + "/Subscriptions/" + subscriptionName;
            var client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(token);
            var putData = @"<entry xmlns=""http://www.w3.org/2005/Atom"">
                                  <title type=""text"">" + subscriptionName + @"</title>
                                  <content type=""application/xml"">
                                    <SubscriptionDescription xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://schemas.microsoft.com/netservices/2010/10/servicebus/connect"" />
                                  </content>
                                </entry>";
            return
                await client.PutAsync(subscriptionAddress, new ByteArrayContent(Encoding.UTF8.GetBytes(putData)))
                    .ConfigureAwait(false);
        }

ほとんどマイクロソフトのサンプル通りな感じです。

そして購読を追加したらReceiverLinkクラスを使用して受信を行います。

受信の方法はいくつかあってReceiverLink.ReceiveAsyncメソッドによって受信処理を行うこともできるしReceiverLink.Startメソッドでコールバックを登録する形式でも受信できます。

今回はStartメソッドを使用しました。

そしてTopicReceiveクラスにIDisposableを設定してDispose時に、先ほど作成したSubscriptionを削除するようにしています。

削除関数はこんな感じ

        private static async Task<HttpResponseMessage> DeleteSubscriptionAsync(string baseAddress, string topicName,
            string subscriptionName, string token)
        {
            var subscriptionAddress = baseAddress + topicName + "/Subscriptions/" + subscriptionName;
            var client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(token);

            return await client.DeleteAsync(subscriptionAddress).ConfigureAwait(false);
        }

削除しないとSubscriptionが残ったままになっちゃいます。

今回も送信/受信はObserver/Observableパターンで実装しています。

なのでChatServerクラスをこんな感じにします。

aspnetcore_webspcket_sample/ChatServer.cs at ServiceBusTopics · yuka1984/aspnetcore_webspcket_sample · GitHub

今回の一番の変更点はTopicReceiverクラスがIDisposableインターフェースを持っているため、TopicReceiverをメンバに持つChatServerクラスもIDisposableを実装するようにしました。

ASP.NET MVC Coreでアプリケーションシャットダウン時に処理する方法ですが、StartUpクラスのConfigureメソッドの引数にIApplicationLifetimeインターフェースの引数を追加し、

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetim)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
                app.UseDeveloperExceptionPage();

            applicationLifetim.ApplicationStopping.Register(Stopping);
        }
        private void Stopping()
        {
            _chatServer.Dispose();
        }

こんな感じで処理を登録してあげることで終了開始時、終了時などに処理を実行することができます。

おわりに

SigalRを使わずにWebSocketサーバを作成することはそんなに難しくなく行うことができます。

もちろん、これまでのサンプル実装はエラー処理なども入れていないので、実践的に使用するためにはもっときちんとした実装が必要です、

ただSignalRでは大きすぎる、もっと細かくゴリゴリしたい、みたいな時にはこんな感じで実装していけばよいよっていう一端をご紹介できたかと思います。

それではまた(^^)/

ASP.NET Coreで作成したWebSocketサーバをAzure Event Hubsを使ってスケールアウトに対応してみたけど・・・

ASP.NET Core Azure WebSocket ReactiveExtentions

注意

2017年1月17日に大幅訂正を行いました。

今回実装してみた結果、WebSocketのスケールアウトにEvent Hubsを用いることは不適切です。

後半に何故EventHubsでスケールアウトを実装することが不適切であったかを書きました。

後日、より適切な実装を行ってみたいと思っています。

はじめに

前回の記事

tamafuyou.hatenablog.com

で、ASP.NET Coreを使ってWebSocketチャットサーバを作成してみました。

ただ、前回の実装ではスケールアウトすることができませんでした。

1プロセスで1チャットサーバとなるためマルチプロセスなサーバで動かした時にはダメな感じになってしまいますしロードバランスで負荷分散した時などもダメダメです。

今回はAzure Event Hubsを使用してスケールアウトに対応したWebSocketサーバを作成してみました。

ソースコードはこちらになります。

github.com

解説

使用した主なライブラリ

NuGet Gallery | Microsoft.Azure.EventHubs 0.0.4-preview

.NET Standard対応しているEventHubs用のライブラリです。

現時点でpreview版です。

NuGet Gallery | Microsoft.Azure.EventHubs.Processor 0.0.4-preview

.NET Standard対応しているEventHubsからメッセージを受信する為のライブラリです。

現時点でpreview版です。

前回からの変更点

Comparing master...Backplane · yuka1984/aspnetcore_webspcket_sample · GitHub

前回サンプルからの差分になります。

Azure側での操作

Event Hubsの作成

まずはMarketPlaceからEvent Hubsを選択して作成を押下します。

f:id:tamafuyou:20170112223947p:plain

Nameを入力した後にPrincing TierをBasicにします。

Basicのほうが安いです。

作成を押下します。

f:id:tamafuyou:20170112224116p:plain

Event Hubsが作成できたらEventHubEntityの追加を行います。

f:id:tamafuyou:20170112225354p:plain

EvnetHubの名前を入力して作成します。

f:id:tamafuyou:20170112225613p:plain

Azure Blob Strageの作成

リソースグループにストレージアカウントを追加します。

f:id:tamafuyou:20170112231315p:plain

作成したストレージアカウントでBlobコンテナーを追加します。

f:id:tamafuyou:20170112231915p:plain

以上でAzure上での作成は終了です。

送信クラス

まずはAzure Event Hubsへ送信を行うクラスを作成します。

gist.github.com

Azure Event Hubsライブラリを使用して送信します。

EHConnectionStringはEvent Hubs内のShared access policiesにて確認できるConnection string primary keyを設定します。

f:id:tamafuyou:20170112234558p:plain

EHEntityPathには先ほど作成したEventHubEntityの名前を指定します。

前回、WebSocketの接続をObserver/Observableパターンで作成しましたので引き続き送信クラスもObsreverな形で実装しました。

受信クラス

Event Hubsへの接続は特殊な用途でない場合にはEventProcessorHostクラスを用いて受信します。

このクラスで受信を行うとパーティションへの接続の排他制御、オフセットの管理をAzure Blob Storageを用いて行ってくれます。

C# での Event Hubs の使用 | Microsoft Docs

こちらのページが参考になるでしょう。

しかし、スケールアウトでEvent hubsを用いるためには、EventProcessorHostを用いるとうまくいきません。

そこでDirectRecieveを使用します。

DirectReceiveはイベントの受信を低レベルで行うことが可能です。

今回はDirectEventReceiveManagerというクラスを作成して受信処理を行います。

gist.github.com

Event Hubsに対して送信したメッセージはどのパーティションに送信されるかは基本的に不明です。

なんでスケールアウトを達成するためにすべてのパーティションを監視します。

ますはEventhubClient.GetRuntimeInformationAsyncにてEventHubsの情報を取得します。

PartitionIdsプロパティにてパーティションIdを取得することができます。

パーティション毎に監視を行う際にGetPartitionRuntimeInformationAsyncにてパーティションの情報を取得します。

LastEnqueuedOffsetプロパティにてこのパーティションにエンキューされた最後のオフセット情報を取得できます。

取得開始時にこのオフセットを用いることで起動以前のメッセージを受信しないようにします。

それ以降はCreateReceiverでレシーバーを作成し受信メソッド行い、受信が成功した場合にはオフセットを更新します。

DirectEventReceiveManagerはIObserverを持っているのでSubscribeしているObserverに対してメッセージを流すように実装します。

チャットへの組み込み

ChatServerクラスを変更してEvent Hubsを用いてチャットメッセージのスケールアウトに対応します。

前回からの変更点として

SendChatMessageToEventHubsObserverクラス DirectEventReceiveManagerクラスのインスタンスをもって

CreateEventProcessorの戻り値にChatMessageProcessorのインスタンスを常に返すように実装。

また前回は 送信/受信をObserver/Obesrvableで実装したWebSocket管理クラスを相互にSubScribeし合うことでチャットを実現していましたが、管理クラスのSubscribeにEventHubsObserverを繋げることで、チャットメッセージを受信した場合にEvent Hubsに送信されるように変更し、DirectEventReceiveManagerのSubscribeに管理クラスを繋げることでEvent Hubsからメッセージを受信した場合にWebSocketで送信が行われるように変更しました。

Observer/Observableパターンで実装すると繋ぎ方の変更で処理を変更できたりします。

このチェインが実装していると楽しいです(^◇^)

最後にstartupクラスにてChatServer.EventRecieveEventAsyncを実行してDirectEventReceiveManagerクラスのRecieveAsyncを実行してすれば完了です。

この実装の問題点

今回の実装には大きな問題点があります。

実は一つのPartitionへの接続は最大5クライアントまでしか行うことができません。

要するに今回の実装ではWebSocketサーバ5プロセス分までしかスケールアウトすることができないのです。

標準的な受信を実現してるEventProcessorHostでは、Partitionへの接続の排他制御をAzure Blob StorageとEpoch受信という仕組みを用いて行っています。

Epoch受信はpartitionへの接続を行う際にlong値であるEpochを指定して接続を行います。

Epoch接続を行った場合、Epoch値が大きい接続が優先して接続されます。

例えばEpoch10のクライアントがパーティション1に接続していて他のクライアントがEpoch20で接続を行ってきた場合、Epoch10は切断されEpoch20が接続されます。

またEpoch20が接続されている状態でEpoch10のクライアントが接続を行った場合、Epoch10クライアントは接続を行うことができません。

DirectRecieveでもエポックに対応した接続を行うことが可能ですが、別途クライアント間でパーティション毎のエポック管理を行う必要があります。

このようなことから、Event Hubsはスケールアウトのバックグラウンドとしては適切でありませんでした。

最後に

次回は頑張ります(´・ω・`)

今回は以上になります。

ではでは