2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 05 | 06 |
2013 | 05 | 08 | 09 | 10 | 11 | 12 |
2014 | 08 |
2015 | 04 |
2015-04-08
■[C#]最近のASP.NET MVCでのお仕事、の感想とか
あれからもう5年以上( ˘ω˘ )
最近もまた1本ASP.NET MVCでシステムを作ってみたので*1、ちょっと比較とかしてみようかと思って。
システム構成
今回の構成はこんな感じ(・ω・)
OS | Windows Server 2012 R2 |
---|---|
DB | SQL Server 2014 |
Web | ASP.NET MVC 5 |
データ量 | 1テナントあたり8000件/日追加 |
フレームワークに関しては、MVCの他にWeb APIやSignalRなんかも使用してますが、詳細は次で。
使用ライブラリ
主要なものをABC順で。
AutoMapper | オブジェクトマッピング |
---|---|
Dapper | データアクセス |
Elmah | 例外処理 |
Glimpse | 診断 |
ASP.NET MVC | MVC |
ASP.NET SignalR | リアルタイム処理 |
ASP.NET Web API | API用 |
Ninject | Dependency Injection |
Quartz | 簡易バックグラウンド処理用 |
StackExchange.Redis | Redis用 |
どれも定番なので特に語ることはなし(・ω・)
個別の要素についてはまた後で。
ソリューション構成
今回、Webの他にWindows CE端末のクライアントや周辺ツールなんかもあるわけですが、主な構成としては以下のようなプロジェクトで構成。
Xxxxx.Api | API用のRequest/Response構造体等 |
---|---|
Xxxxx.Domain | Enum(ストラテジ)的なものや固有ロジック等、入れ物じゃないよ |
Xxxxx.Infrastructure | 拡張メソッドが主*2 |
Xxxxx.Infrastructure.Web | MVC、SignalR、API関連の拡張類 |
Xxxxx.Infrastructure.Windows | WPF関連のBehaviorやらConverterやら |
Xxxxx.Report | 帳票ツール |
Xxxxx.Client | CEクライアント |
Xxxxx.Server.Core | サーバ側の要はM |
Xxxxx.Server.Core.Tests | 上記テスト |
Xxxxx.Server.Web.Admin | 管理Webサイト |
Xxxxx.Server.Web.Tenant | 業務Webサイト |
Database.Tenant | 業務データベースプロジェクト |
Database.Master | 管理データベースプロジェクト |
データベースプロジェクトが2つあるのは、このシステムはマルチテナントを扱うから。
マルチテナントを考慮したテーブル設計なんてしたくないし、っというわけ(?)で、管理用のデータベースと、テナント毎に物理的に分かれたの業務データベースから構成してますにゃん。
アーキテクチャというか諸要素雑感
っで、構成が出たところで、諸要素についての雑感をゆる〜く(・ω・)
データアクセス
データアクセスについては最近はDapper一択。
理由は性能と言うよりも、ORMで表現しきれないSQLなんて普通に出てくるから(・ω・)
まあ、Expressions使って無駄にランタイムでコストに見合わないアホな処理もしたくはないけど、単純クエリの生成ならエンティティクラスからT4で生成で良いじゃんとか。
正直、抽象化された実行プランそのものを直接組み立てるくらいのAPIにならないと、RDB用の抽象化された高度なフレームワークとかいらんかな〜、っという感じで。
あとそうそう、Dapperと言えばマルチマッピング大活躍(`・ω・´)
俺にはこういうのでいいんだよ、こういうので。
キャッシュ
ぎょーむアプリでもRedisとかは使う時代。
単純なキャッシュというよりも、ソート済みセットの使用だったり、pub/sub的な使い方だけど。
ルーティング
MVCについては変わったことはなく。
APIについては、カスタムIHttpControllerSelectorで"api/{version}/{controller}/{action}/{id}"を使用。
ちなみにAPIについてはCE端末専用の口で、Web用途では未使用、っというか、画面で必要なものはMVCのコントローラーでPartialView使ってフラグメントを返すとかにしちゃってますが。
あと「おまえが本当に必要だったものはリソースへのアクセスではなくRPCなのではないか?」問題、つまりはそれってRESTなの?、的な話についてもちょっと。
まあ、今回に関して言えば、(イベントとかの)仮想リソース的な扱いで問題ない範囲なので、そういう設計、っという感じで。
世間一般的な話をするならどうだろね(・ω・)?
まあ、欲しいのはRESTというよりRPCというケースが多いんだろうけど、抽象化された仮想リソース的な設計で済むケースも同じく多いんとちゃうん?、くらいのイメージかしらね?
Area
本プロジェクトでは未使用。
同時進行していた別プロジェクトでは使ったけど(・ω・)
Areaを使うかどうかは、要はサブモジュール(サブシステム)の管理をどうするかという話で。
それなりの規模、っというかバラエティに富んだ機能を持たない限りは必要無いし。
最初、なにも考えずに管理機能をAreaで、…とか思っちゃったけど、今回のはマルチテナント自体の管理だし、意味合いが違うな、っということで、別Webアプリで構成。
認証
ASP.NET Identityとか使ってないよ。
これどうなるかわからんし(´д`;)
…っといいつつも、Identityモドキのような何かを自前してしまっているというしょうもないオチ(・∀・;)
まあ「認証APIとパッケージ管理ツールで納得いくものに出会うのは難しい」っという私の言葉を残しておきます。
リアルタイムWeb
SignalR使ってますが、世間にある例ってチャットとかつまんねーものしかないけど、ぎょーむアプリではリアルタイムな処理って何に使っているの、っというあたりについて。
本システムでの用途は以下のあたり。
- ダッシュボードの更新
- 簡易メッセージング
- レポートのキュー
このシステムでは、Webにはダッシュボードがあって、主に開いているのもその画面な使い方で(・ω・)
その情報は、端末からのリクエストや、期限のあるアラートなんかもあるので定期的な更新なんかも発生。
っで、その通知にSignalRを使用。
なお、サーバトリガーの通知はIHubConnectionContext<dynamic>を使うけど、IHubConnectionContext<dynamic>をそのまま使うのではなく、通知用のインターフェース及び実装を用意して、実装にIHubConnectionContext<dynamic>をDIする形でラッピングして使用。
ちなみに、アラートトリガーの発行には印プロセスでQuartzを使っていますが。
これ、本当は別プロセスに分離して、そこからSignalRを使って通知したいところですが、現状、SignalRのホストなしに通知だけを行うって出来ないのよね。*3
将来的にはサポートされる(?)ようですが、今回はこんなんで(・ω・)
簡易メッセージングは、メールモドキというか、ポケベルみたいな機能だけど、これはよくある例で。
自分宛のメッセージが届いたらヘッダ部に未読件数を表示とか、そういう用途に使用。
キューの処理は、印刷用のツールがいるんだけど、そこへ速やかに印刷要求イベントを通知するために使用。
Dependency Injection
Ninjectを使用。
特にこだわりはなく、なんとなくNinjectを使っていますが(・ω・)
コンテナ自体はNinjectを使うとして、ASP.NET MVCだとアダプタが、
- System.Web.Mvc.IDependencyResolver(MVC用)
- System.Web.Http.Dependencies.IDependencyResolver(Web API用)
- Microsoft.AspNet.SignalR.IDependencyResolver(SignalR用)
全然違うじゃん!、っという笑い話がありますが。*4
幻滅しました…、ASP.NET MVCのファンやめます、もとい、これも6では解消されるらしいので期待、っと(・ω・)
JSON
Json.NETのみを使用。
例えば速度面とかで言えば、Json.NETよりも優秀なシリアライザもありますが、そっちはカスタマイズポイントが不十分だったりもするのでJson.NETのみの使用で統一。
日付をISO 8601にしたり、クライアント側はCamelケース、サーバ側はPascalケースで統一するだの小細工したいときには、JsonConverterだったりIContractResolverみたいなカスタマイズポイントが用意されているJson.NETステキ、っということで(・∀・;)
オブジェクトマッピング
自動マッピングとかも賛否はあるけどね(・ω・)
エンティティと、APIのRequest/Responseと、WebのViewModel、っという呼び方はしていなくて、Formに対応するものをXxxFormってしているんだけど、そのマッピングにはAutoMapperを使用。
なお、あくまで使用される領域が異なる入れ物とエンティティ(入れ物はその射影みたいな)の変換用であって、構造的な変換とかを対象外。
なので、マップの定義もForMemberはIgnoreがあるくらいで、色々ルールを書かなくてはならないようのは違う、っという方針。
ユニットテスト
Xxxxx.Server.Coreのユニットテストしか用意していないわけですが(・∀・;)
なにをテストしたかったんだと思う?、っという点を考えたときに。
テスト(主にCIで回すリグレッションテスト)する価値が高い部分から手をつけるわけで、そう考えるとコントローラーの処理なんて自明だし、ビューのテストはコストに見合わない、っと。
っで、テストの対象は効果の高い処理(ユースケースに対応する、UIの絡まない)部分に絞った上で、そこのテストをどう書くのか?、っという点について。
要はモックをどうするか、っというあたりの話ですけど(・ω・)
処理がRDBの制約に依存するものであれば、そこは本物のDBを使ったテストをしたいところだけど、そこの価値を更に考えた上で、またユニットテストなのかという点も考えて。
ユニットテストの対象としては、あくまで「処理」の振る舞いに絞り、モックはIDbConnectionのレイヤで用意して、他は具象クラスを使う形で記述。
テストの為だけにIFと実装を分離するとか変な話ではなく、本来IFと実装をわけて設計すべき箇所にモックを差し込めればそれで十分、っというのが本来あるべき形とちゃうん(・ω・)?、みたいな感じで。
また、その場合は明示的なモック実装があった方が楽にテストを書けたりもするので、黒魔術的なモックの生成は出番が無いという結果に。
この辺の考えは、このプロジェクトに限らず最近の自分の考えだけど。
まとめ
ってな感じでやってみたわけですが、どんなもんでしょう(・ω・)?
本当は、もっとJSときゃっきゃうふふしたり出来ると、モダンなWebアプリっぽくなると思いますがこのロリコンめ。
ぎょーむ用なので尖ってはいないし、むしろ手堅さが見える作りなのです٩(๑•ㅅ•๑)و
- 36 http://t.co/TgyrwSPVv9
- 15 https://www.google.co.jp/
- 6 http://t.co/wbR1D7Tx5d
- 6 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&frm=1&source=web&cd=1&ved=0CBwQFjAA&url=http://d.hatena.ne.jp/machi_pon/20120202/1328187399&ei=d6QkVYGaG8yD8gWgmYC4Bg&usg=AFQjCNGgBfGlRivrpskrJPWq6olt_4DJlA
- 4 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=0CCQQFjAB&url=http://d.hatena.ne.jp/machi_pon/20100131/1264901795&ei=WakkVcHUB6PEmwXE9oBw&usg=AFQjCNHS4TQv4WKuJswrf-qZJQUyOtsMFQ&bvm=bv.90237346,d.dGY
- 3 http://feedly.com/i/latest
- 3 http://www.google.co.jp/url?url=http://d.hatena.ne.jp/machi_pon/20091105/1257410986&rct=j&frm=1&q=&esrc=s&sa=U&ei=pLckVbGBG4uV8QWZ84GYAg&ved=0CCMQFjAD&usg=AFQjCNGL7nVVBP8l6-I2KqZX4ypto8I-Hw
- 2 http://b.hatena.ne.jp/
- 2 http://d.hatena.ne.jp/dotnetmemo/20111015/1318663995
- 2 http://jikkenjo.net/385.html