Quantcast
Channel: とあるコンサルタントのつぶやき
Viewing all 47 articles
Browse latest View live

『変わらない開発現場』を変えていくために。

$
0
0

前回のエントリですが、はてブや Twitter でびっくりするほどたくさんのコメントをいただきました。当該ページの PV も 2 万超え、そして 1,000 件を超えるはてブは初めてで、勉強になるコメントも多く、なるほどと頷かされるご指摘が多かったです。この場を借りてお礼申し上げます。ありがとうございました。(はてブでエールを送っていただいた皆様もありがとうございました。励みになります。m(_ _)m)

コメントを読んでいると、やはりものすごく現場で苦しまれている方が多いと改めて感じると共に、年次や役職で抱える悩みはずいぶん違う、という印象も受けました。ただ、いずれの悩みにも共通するのは、

「意思決定者の方々になかなか理解してもらえない・動かせない」
(意思決定者=自分のマネージャ、お客様など)

という点だと思いました。実際問題として、意思決定権者にしか変えられないこと・決められないことについて、正論をタテに現場担当者に解決を詰め寄るのはただの無理ゲーで、これだと、あんまり建設的な議論にはならないのですよね;。この件に関しては様々な宗教論争、もとい議論を見てきましたが、全体的にビジネスの仕組み的な観点からの考察が不足していることや適切な問題分解・整理がなされていないこと、さらに多彩な開発現場を十把一絡げに議論しようとしていることが、建設的な議論を妨げている、と感じました。

この辺の解決方法は、結局、現場や人ごとに大きく変わるので、万能な処方箋は存在しない(個々人で自分の問題としてじっくり考えるしかない)のですが、コンサル的な視点から、考え方に関して多少のヒントは書けるかも? と思って、補足エントリとして書いてみることにしました。蛇足ではありますが、何かのヒントになれば幸いです。

今日のエントリで書きたいポイントは 3 つです。タイトルだけでだいたい内容の想像はつくと思いますので、ピンと来た方は特に読まなくてもよいと思います、はい^^。

  • 問題分解の必要性:責任境界線を明らかにする
  • 変わらない意思決定者への「伝え方」のヒント
  • 鶏と卵、先に変わるべきはどちらか?:プロフェッショナルとして

■ 問題分解の必要性:責任境界線を明らかにする

前述したように、SI 開発現場に関わる様々な問題には、自分でも変えられることと、意思決定者にしか変えられないことが存在します。(下記の例はとりあえずのケーススタディとして書きましたが、それぞれの開発現場、そしてご自身の役職やロール次第で、話は大きく変わります。なので、ちょっと時間を取って書き出してみるとよいかと思います)

  • 例 1. 「請負契約」を前提条件として、大きな粒度のアジャイルを可能にするために必要なことは?
    • お客様(意思決定者)に求められること
      • 要件定義への積極的な参画
      • 素早い契約締結
      • 柔軟な予算確保と執行 (※ アジャイルの大きな阻害要因のひとつは、日本でよくある予算確保と予算執行の硬直性です。)
    • SIer (現場担当者)に求められること
      • 適切なベースライン定義と変更管理
      • 精度の高い見積もりを素早く出せること
      • 高速な開発(設計・実装・ビルド・テスト)
    • 両者に求められること
      • 誠実な信頼関係とプロフェッショナリズム
  • 例 2. 請負契約の境界を超えない範囲で、小さな粒度のアジャイルを回していくために必要なことは?
    • SIer のプロパー(意思決定者)に求められること
      • Scrum などのアジャイル方法論の導入
      • 見積もり手法と実績管理手法の変更(プラニングポーカーとかタスクボードとか)
      • チーム運営方法の変更
      • 係数ベースのプロジェクト管理手法の導入
      • フラットで風通しのよい現場環境の醸成
      • 品質管理指標やプロセスの変更(または二重管理) (※ 全社的な品質管理基準が今どきの開発スタイルに合っておらず、そこがネックになって新しい手法などが導入しにくくなっているケースがあります。特に大きな SIer にありがちなパターンです。)
      • 現場への超優秀なエンジニアの投入 (※ Scrum のような開発プロセスでは、一時的であっても構わないので超優秀なエンジニアを投入し、その人のノウハウを周囲に伝搬させると、開発現場のレベルを一気に引き上げやすいです。)
    • 協力会社の担当エンジニア(現場担当者)に求められること
      • ALM ソリューションの導入(できれば TFS/VSTS 使ってね!(ぉ))
      • 日々の作業におけるチケット駆動開発の利用
      • 高速開発に必要な高い技術スキルの習得
      • CI/CD 環境の構築
      • 社外とのコミュニケーションによるノウハウ吸収(例:コミュニティ勉強会への参加など)
    • 両者に求められること
      • 誠実な信頼関係とプロフェッショナリズム

自分たちに関わることは自分たちの努力で解決できる問題です。しかし多くの人が悩んでいるのはそこではなく、相対する意思決定者にしか変えられないことでしょう。ここに対する具体的なソリューションがないのに何とかしろと言われると、「どないせいっちゅうねん;」と思ってしまいますし、仮に具体的なソリューションが書かれていたとしても、自分のシチュエーションに当てはめられないと、「そんな無茶いうな!」となります;。

先に書いたように、開発現場、さらにそこに関わる皆様の役職やロールも十人十色です。なので、残念ながら、皆様それぞれが抱える個々の問題に対する最適解はご自身で考えていただくしかないです;。確かにコンサルタントとかに頼めば、問題分解してくれて、最適解を教えてくれるかもしれませんが、その場合であっても、結果的にそれを実行するのは皆様ご自身です。変わらない意思決定者の方々を変えていくことすらも、自分自身の問題の一部として解決を目指すことが大切だと思います。


■ 変わらない意思決定者への「伝え方」のヒント

一般に、「他人を変えることはできない」と言われます。確かに、他人を自分の思うように変える、ということは不可能です。けれども、その人が変わりたい・変えたいと思っているときに、その人のサポートをすることはできます。

なぜそんな当たり前のことを書くのかというと、実際に意思決定者となる人と会話してみると、問題意識は持っているが上手い変え方がわからないとか、また単に知らないから変える必要性を感じていない、といったことも少なくないからです。例えばアジャイルを使った現場改善について考えてみると、その理解度は意思決定者ごとにまちまちです。

  • そもそも知らない。(知らないのでやらない or 知ってもやることに対して懐疑的になる。)
  • 中途半端に知っている。(懐疑的な人だと「やらない理由」で理論武装していることもある。)
  • かつてやってみて失敗した。(あつものに懲りてなますを吹くパターン。割と頭が固くなる。)
  • 自分は絶対的に正しいと思い込んでいる。(かなり困った頑固タイプ。)

こんなふうに分類した場合、意外と「そもそも知らないだけ」あるいは「中途半端に知っている」というパターンも多いです。実際、SIer のマネージャクラスの方々を招いて前回のエントリのような話をしつつ、VSTS のデモをすると、「知らなかった」「ぜひ使ってみたい」という F/B になることが結構あります。まず、食わず嫌いなのかどうなのか、またなぜそう思っているのかを確認することが大切です。

それがわかったら、相手の知識レベルに併せて説明を打ち込んでいくのですが、このとき、いくつか重要なポイントがあります。私が思う特に重要なポイントは以下の 3 つなのですが、IT エンジニアは(私も含めて)これらがかなり苦手だと思います……orz

  • 相手のロジックに併せて説明する(相手の土俵で語る)
  • 端的かつロジカルに、メリットをズバリ説明する
  • 第三者からの援護射撃をうまく使う

[相手のロジックに併せて説明する(相手の土俵で語る)]

特にその必要性を感じていない相手に、「1 日 10 回 deploy するのが重要です!」とか説明しても全くスルーされるのがオチですが、その最大の理由は、自分の土俵から、自分の目線と自分の価値観で物事を語ってしまうことです。これをやってしまうと嫌われるのはもちろんのこと、うっかりすると二度と話を聞いてもらえなくなります;。一般的に、優秀なエンジニアは技術に対して深いを持っていることが多く、それゆえに愛が暴走してついテクノロジ目線でモノを語ってしまうことが多いのですが、意思決定者は物事をビジネス目線で見ていることがほとんどです。なので、まず主語を意思決定者にして、相手の目線でメリットを訴求するのが基本中の基本です。(← というか、昔、これに関してさんざん怒られました、私;。今でもなかなか難しいのですが。)

例えば大きな粒度のアジャイルに関してであれば、

  • × アジャイルでやると、仕様変更を頻繁に取り込むことができるようになります。
  • ○ フルオーダーメイド方式でありながら、「どんなものが欲しいのか」が曖昧にしかわからない状態で最初から契約を切って請負発注すれば、そりゃバッファ積みまくりで高くつくでしょうし、逆に先に請負契約で金額が確定しちゃってたら、できる限りやらずにラクしてお金稼ごうとするに決まってますよね? これってお互いにとってかなり不健全ですよね?

あるいは小さな粒度のアジャイルであれば、

  • × Scrum では、スプリントという固定の長さの期間を区切り、スプリント初日にタスクの作業時間を見積もります。一日一回、残作業時間を足し合わせてグラフ化し、バーンダウンチャートを作ります。
  • ○ Scrum のスプリントプラクティスは、見積もりとその振り返りを一定期間ごとに繰り返させるというもので、これを使うと現場担当者のボトムアップの作業見積もりスキルを向上させることができます。

といった感じ。小難しい専門用語を一切使わず、一般人でもわかる言葉で、お客様目線でのメリットを訴求するのが最初のポイントです。

[端的かつロジカルに、メリットをズバリ説明する]

次に重要なのが、メリットをロジカルかつ端的にズバリ説明する、という点。説明がやたら長いお前が言うなというツッコミは甘んじて受けますが(なので私も苦手;)、上の Scrum の説明なんかが恒例で、端的に何がよいのかをスパーンと説明できないと、意思決定者には伝わりません。

IT エンジニアの悪いクセは、原理主義・経典主義に陥りやすいことです。多くの場合、意思決定者にとっては、例えばアジャイルとかオブジェクト指向の定義なんて激しくどうでもよいのです。にもかかわらず、学者肌の人ほど、自分と同じレベルの知識と理解を他者に求めようとする傾向があります。Facebook で先日のエントリの話をした際に、会社の同僚の一人がこう言いました。

「現場に近いところにいる人間からいうと、どれだけの機能が何時、いくらでできるの?という顧客の問いかけに回答できれば手法は問いません」

いやホントこれ。名前なんてホントどうでもいいですから。……といいつつ、私も経典主義の傾向はあるので;、ちょっと意識的に注意してます。ここは自戒を込めて;。

[第三者からの援護射撃をうまく使う]

そして最後に、第三者からの援護射撃をうまく使うこと。これは、当の本人が何を言ってもポジショントークか愚痴だとみなされる危険性が高いという話で、第三者の調査や事例などをうまく使ったり、あるいは第三者を連れて来て同じ話をしてもらう、というのが非常に有効です。

ただしこの際、引っ張ってくる事例の開発規模や特性などが自分のシステム開発に似ているということが非常に重要です。カスタム SI の開発現場に対して、マイクロソフトの製品開発の事例を引っ張ってきても、「参考になる部分がないとは言わないけど、うちとは前提条件が違うから合わないよね」と言われるのがオチです。

違う世界の話と思わせないためにも、例えば以下のようなポイントはぜひ考えてみてください。(また、Web 上の様々な議論を見るときにも、どの世界の話をしているのかを意識することは重要です。話がどうにも腑に落ちない・かみ合わない、という場合は、話しているシステム開発の世界が違う可能性が高いです。)

  • 開発規模:数千万円 vs 数億円 vs 数十億円 vs 数百億円
  • 業務特性:随時更新業務 vs 塩漬け業務
  • アプリ特性:低難易度×多数画面 vs 高難易度×少数画面
  • 重視ポイント:安定性 vs 先進性
  • 開発形態:内製 vs 外注
  • 契約形態:請負 vs 準委任
  • 開発チームの技術的熟練度:高 vs 低
  • 開発チームの成熟度:自律型 vs 指揮命令型

ちなみに、実際にアジャイルや DevOps などを導入しようとした場合、参考になる類似事例がないことも多いです。そういう場合には、一気にやらずに、ちょっとずつ試験的に導入してみるとか、真似できるところから少しずつやってみるとか、リスクのないシステム(研究開発プロジェクトとか)で試験的にやってみた方がよいです。慣れない開発方法論を振り回すと、開発のリスクも当然上がりますし、痛い思いをすると二度とやりたくなくなるリスクもあります。この辺はうまくバランスすることが大切です。


■ 鶏と卵、先に変わるべきはどちらか?:プロフェッショナルとして

このエントリの最初に、自分でも変えられることと、意思決定者にしか変えられないことに問題を切り分けましょう、という話を書きましたが、これに関するもうひとつ大きな問題として、先に変わるべきはどちらなのか? という点があります。

例えば、請負契約の境界を超えない範囲で、小さな粒度のアジャイルを回していくために必要なことは何か? という話題を例にとってみると、「Scrum を採用する」と意思決定者が決めることが先なのか、それとも現場側の人間が「Scrum を回していくために必要な高度なスキルセットを備える」ことが先なのか、という問題です。この二つは鶏と卵の関係を持っていることが多く、意思決定者が判断を下せば現場が変わる、けれども変わっていない現場を前提に意思決定者が判断を下すのは難しい、という側面があり、どちらが先に一歩を踏み出すべきか? という問題があります。(意思決定者がサーバントリーダシップの場合にはここは問題にならないのですが、多くの日本の現場はそうではないでしょうから、先に変わるべきはどちらなのか? という点が問題になります。)

この点に関しては、それぞれが同時に変わることを目指すべき……ではあるのですが、少なくとも現場担当者の目線で見た場合(=自分たちの問題として考えた場合)には、自分たちが先に変わっていないのに他人に変わることや理解を求めることは間違っている、と思います。

  • 他人に何かを求める前に、そもそも自分は他者の模範となり得ているのか?
  • 意思決定者に話を聞いてもらえるだけの「価値」を自分は提供できているか?

といった点を、厳しく自省することも必要なのではないかと思います。そしてそのためには、

  • プロフェッショナルとして、自分たちの価値をちゃんと図る
  • そしてその価値を伸ばしていく

ことが必要です。会社という看板が外れたときに、自分は人月何万円の価値のある仕事ができるのか? を自問し、それを高めるためには何を伸ばしていけばよいのか? を常に自問することはとても重要だと思います。

本来、意思決定者と担当者の間は、理想を言えば、互いにプロフェッショナルとしての緊張感のある関係であることが望ましいです。けれども実際にはそうなっておらず、意思決定者側にパワーバランスが偏っていることが多い。でもこのパワーバランスを生じさせているのは、単に上下関係や発注関係があるからというだけではなく、担当者側の力量不足という側面も一部にはある、と思うのです。そう話は簡単ではないことは私もわかっているのですが;、でもそういったところを目指して、意思決定者の方ばかりではなく、たまには市場の方も見て、自分/自分たちの価値を考えてそれを磨いていくことは、ものすごく大切なことだと思います。


■ 最後に:変わらない開発現場を嘆く皆様へ。

最後にポエムを少しばかり。ここまであれこれ書いてきたのですが、こう思われた方も多いと思います。

「開発現場を変えるのって、ちょーめんどくね?」

……はい、ぶっちゃけ私もそう思います。実際、この話は全くもって他人事ではなく、私自身もまさに今現在、似たような課題で激しく悩んで困っていたりするのが実情です;(なので私が解決策みたいな話をすること自体、極めて説得力に欠けることは自覚してますし、書いててグサグサと自分自身に刺さるという;。っつーかマジ変わらないし変えられなくてつらい……orz;)。

でも、こういうのもなんですが、それが現実でありリアルなんですよね;。正直、無理ゲーだと思うことは多いですし、すべて投げ出して別の素敵な職場に移った方が圧倒的にラクなケースも多いと思います。けれども多分、この話で悩んでいる方の多くは、それができないから悩んでいる。自分のことだけ考えれば転職した方が圧倒的にラク、けれどもメンバーや同僚を放り出せるかといえばそんなこともできないし、共に歩んできた家族やお世話になってきた会社、そして日本に対して背負うものや帰属意識もある。やっぱり今、自分が属しているこの現場をなんとかしたい、と思って悩んでいる人の方が、私は圧倒的多数なんじゃないかと思うのです。

となると、もうどんなに大変でも覚悟を決めて頑張るしかないのですよね;。言葉にすると陳腐でしかないのですが、

  • プロフェッショナリズム
    • お互いがやるべき役割、果たすべきミッションをきちんと線引きした上で頑張る
  • 相互理解
    • お互いの置かれている立場や気持ちを理解してコミュニケーションを取り、互いに歩み寄る

の 2 つがやっぱり大切で、これを現場担当者と意思決定者の目線に分けて考えてみると、

  • 現場担当者としての目線
    • 本来、意思決定者と自分の関係は上下関係ではなく、仕事という場におけるロール(立場)の違いであるはずです。
    • もし今現在、それが上下関係であるのなら、それを対等な(あるいは健全な)関係にするために、自分に何ができるのかを考えて行動に移していくこと、そしてそれを以て意思決定者に適切に働きかけていくことが大切なのだと私は思います。
  • 意思決定者としての目線
    • 今のやり方(発注方法とか仕事の進め方とか)が完璧だと思っている人など、多分どこにもいないと思います。問題意識を持ちながらも、なかなか解が見つけられずに困っているのが実態ではないでしょうか?
    • もしそうなら、その問題を一緒に考えながら解決していける、プロフェッショナルなスキルを持った会社や担当者を探して相談するのがよいと思います。実際、そういった人たちは上手いやり方を知っていることがよくあるもので、その力を上手く引き出すこともまた、意思決定者としての役割です。自分たちにとっての最適解は、相対する会社や担当者とともに作り上げていくしかないものだと思います。

といった具合に、それぞれの立場毎に問題を分解して『自分のコト』として捉えることが大切、だと思うのです。

正直、道は長いですし、どんなに頑張っても完全に解決できるものではないかもしれませんが、とはいえあきらめたらそこで試合終了です。現場担当者と意思決定者とが、互いにプロフェッショナルとして仕事に関与し、一方で飲み会ではつらさを語り合えるような関係になれるところを目指して、きちんと問題分解し、まずは自分にできることから一歩ずつ改善を進めてゴールに近付くことが大切……というよりそれ以外他にないんじゃないかな、と思います。このエントリが、改善を目指す皆様にとって、多少でも何かのヒントになれば幸いです。

# ……つらくなったら、みんなで語り合いたいです、ええ;。
# そしてもっと上手い方法やテクニックがあったら教えて欲しいです。いやマジで;;。

というわけでここ数回、「ゆるっとした」話をつらつらと書きましたが、こういうゆるふわ系エントリはホントに書くのが難しいです;。最後まで読まれて気分を害された方もきっといらっしゃると思いますが、その場合はすみません & ごめんなさい;。個人的には、かちっと白黒つきやすい話の方が気持ち的にもラクなので、次回は今まさに旬の、ASP.NET Core 1.0 な話をしたいと思います、はい^^。


Hello World, ASP.NET Core 1.0!

$
0
0

ご存じの方も多いと思いますが、先日、クロスプラットフォーム対応の ASP.NET Core 1.0 が正式リリースされました。Windows OS だけでなく、Linux や Mac OS 上でも動作する新しいランタイムとして設計・実装されているのですが、半面、MVC/EF の知識を必須とするため、ASP.NET Web Forms や型付きデータセットで開発されてきた方々には、非常にハードルが高い開発プラットフォームにもなっています。

このハードルの高さは、先日の de:code 2016 DEV-010 「エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法」でもお話しさせていただいた SPA 型開発でも触れた話で、高水準 UI 部品が大量に存在していた ASP.NET Web Forms から、いきなりスクラッチ開発に近い ASP.NET MVC/EF 開発ベースに移れと言われても正直キツすぎる、と感じられている方も多いのではないでしょうか。(いや、実際キツいですけどね;)

今回のエントリでは、de:code 2016 のセッションで利用したサンプルアプリの具体的な作り方などもご紹介しつつ、ASP.NET Core 1.0 ベースでのアプリケーション開発の基本について解説したいと思います。

なお、最近は MS も OSS 押しではありますが、私は純血派ですのでw、利用する環境は安心の Visual Studio 2015 Update 3 + .NET Core 1.0 for Visual Studio です(インストール手順はリンクを参照してください)。Visual Studio をお持ちでない方は Community Edition をインストールしてご利用ください。Visual Studio Code を使った開発方法については……誰かがきっと書いてくれると思います。(ぇ

※ 今回はかなりのボリュームのため、細かい解説をかなり端折っています。このため、例えば社内勉強会でみんなで調べながらやってみるとか、あるいは社内の詳しそうな誰かに聞いてみるとか、あるいは書籍(大きくは変わっていないため、ASP.NET MVC 5 などの本も非常に参考になります)などを確認しながら読み進めてみてください。……というか、この内容をさらに噛み砕いたエントリとか誰か書いてくださると助かります。(ぉ

Part 1. ASP.NET Core 1.0 ことはじめ

$
0
0

まずは基本的な ASP.NET Core 1.0 のアプリの作り方から開始していきます。

■ ソリューションファイルとプロジェクトファイルの作成

Visual Studio を開き、新しいプロジェクトを作成します。

image

今回、Application Insights は利用しないのでチェックを外しておきます。名前は “Decode2016.WebApp” にしておきます。

image

テンプレートは「空」を選びましょう。認証は今回はなし、Microsoft Azure へのホストもなしにしておきます。作成すると、以下のようなプロジェクトファイルが出来上がります。

image

従来の ASP.NET から大きく変わった点として、以下 2 つに注意してください。

  • wwwroot と書かれたフォルダは、公開する静的コンテンツ(HTML ファイルや JPEG ファイルなど)を置く場所です。従来と異なり、アプリケーションコードはここには置かず、上位のフォルダに置きます。
  • ソースコードフォルダ内のファイルは、基本的にすべてプロジェクトに所属するものとして扱われます。今回の場合だと、C:\Users\nakama\Documents\Visual Studio 2015\Projects\Decode2016.WebApp\src\Decode2016.WebApp にプロジェクトフォルダが作成されていますが、ここにファイルを配置すると、そのファイルは自動的にプロジェクトのメンバであるとみなされるようになっています。(従来の *.csproj のような管理ファイルは存在しません)

プロジェクトファイル内には以下の 2 つのコードがあります。それぞれざっくりとした解説は以下の通りです。

  • Program.cs ファイル
    • Main() 関数が書かれており、Web サーバを起動するためのコードが書かれています。
  • Startup.cs ファイル
    • ASP.NET ランタイムの初期化コードが書かれています。

では、早速 Ctrl + F5 で実行してみてください。Hello World というメッセージが表示されます。

image

このような実行結果になったのは、Startup.cs ファイル内にある “context.Response.WriteAsync(“Hello World!”);” 命令により、Hello World メッセージがブラウザに送り返されたからです。ちなみに詳細はまた別エントリで解説しますが、この Startup.cs ファイルの ConfigureServices() メソッドは、ASP.NET ランタイムの DI コンテナの初期化処理を行うためのメソッド、Configure() メソッドは、ASP.NET ランタイムのパイプラインを構成するためのメソッドです。(← 意味がわからなければとりあえず「ふーん」で OK)


public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole();

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

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

■ ライブラリの追加

では、まず基本的なライブラリをこのプロジェクトに組み込みます。参照設定は GUI からは行えませんので、project.json ファイルを開き、以下 5 つを追加します。

Microsoft.AspNetCore.Mvc ASP.NET Core MVC 1.0 本体
Microsoft.AspNetCore.Mvc.TagHelpers View ページの作成でタグヘルパーを使えるようにする
Microsoft.AspNetCore.StaticFiles 静的ファイルを返せるようにするライブラリ
Microsoft.EntityFrameworkCore データアクセスライブラリ Entity Framework Core 1.0
Microsoft.EntityFrameworkCore.SqlServer Entity Framework Core の SQL Server 接続ドライバ

 


{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Diagnostics": "1.0.0",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.Extensions.Logging.Console": "1.0.0",

    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0",
    "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0",
    "Microsoft.EntityFrameworkCore": "1.0.0",
    "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0"
  },
(以下略)...

実際の入力時は IntelliSense が効きますのでうまく活用してください。また、各ライブラリについては相互に依存関係がありますので、必ず動作確認されているバージョンの組み合わせで利用してください。今回利用しているライブラリの場合はすべて 1.0.0 にそろえれば OK ですが、モノによっては動作確認が取れているバージョンの組み合わせが違うケースも当然ありますので注意しましょう。

Part 2. Entity Framework Core 1.0 の基本的な使い方

$
0
0

では引き続き、Entity Framework Core の利用方法を解説していきます。

■ 基本的な Entity Framework Core の利用方法

従来の Entity Framework と異なり、EF Core では O/R マッパーファイル(*.dbml)を使うことができません。このため、O/R マッピング(データベーステーブルのどこを構造体クラスのどこにマッピングするのか?)はすべてコードで指定する必要があります。ツールを利用して自動生成させることも(ある程度は)可能ですが、現時点(2016/07/02)では、手で書いてしまったほうがやりやすいと思います。

まずは以下 2 つのファイルを用意します。

  • データベースファイル
    • プロジェクトファイル直下に App_Data フォルダを掘り、pubs.mdf ファイルを置きます。
    • データベースファイルは公開する必要がないため、wwwroot 下に置く必要はありません。私は昔の名残で App_Data という名前を使っていますが、この名前である必要性も特にありません。お好みで変えていただいて結構です。
  • モデルファイル
    • プロジェクトファイル直下に Models フォルダを切り、Pubs.cs ファイルを作成します。

image_thumb[13]

Pubs.cs ファイルに、データを取り出すための構造体クラスを記述し、そこに O/R マッピング情報を記述していきます。今回は、全テーブルをやるとキリがないので、以下 6 つのテーブルについてだけ取り出してマッピングしてみます。

  • authors : 著者データ
  • titles : 書籍データ
  • titleauthor : 著者と書籍の多対多中間テーブル
  • publishers : 出版社データ
  • stores : 店舗データ
  • sales : 売上データ

image

最終的なコードは以下の通りです(※ OnConfiguring() メソッド内のパス情報は適宜書き換えてください)。えらい長いコードで恐縮ですが;、順番に説明していきます。


using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;

namespace Decode2016.WebApp.Models
{
    public partial class PubsEntities : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            options.UseSqlServer(
                @"data source=(LocalDB)\mssqllocaldb;attachdbfilename=|DataDirectory|\pubs.mdf;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
                .Replace("|DataDirectory|", @"C:\Users\nakama\Documents\Visual Studio 2015\Projects\Decode2016.WebApp\src\Decode2016.WebApp\App_Data"));
        }

        public DbSet<Author> Authors { get; set; }
        public DbSet<Title> Titles { get; set; }
        public DbSet<Publisher> Publishers { get; set; }
        public DbSet<Store> Stores { get; set; }
        public DbSet<Sale> Sales { get; set; }
        public DbSet<TitleAuthor> TitleAuthors { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Sale>().HasKey(s => new { s.StoreId, s.OrderNumber, s.TitleId });
            modelBuilder.Entity<TitleAuthor>().HasKey(ta => new { ta.AuthorId, ta.TitleId });
            modelBuilder.Entity<Sale>().HasOne(s => s.Title).WithMany(t => t.Sales).IsRequired();
            modelBuilder.Entity<Sale>().HasOne(s => s.Store).WithMany(s => s.Sales).IsRequired();
            modelBuilder.Entity<Publisher>().HasMany(p => p.Titles).WithOne(t => t.Publisher).IsRequired();
            modelBuilder.Entity<Author>().HasMany(a => a.TitleAuthors).WithOne(ta => ta.Author).IsRequired();
            modelBuilder.Entity<Title>().HasMany(t => t.TitleAuthors).WithOne(ta => ta.Title).IsRequired();
        }
    }

    [Table("authors")]
    public partial class Author
    {
        [Column("au_id"), Required, MaxLength(11), Key]
        public string AuthorId { get; set; }

        [Column("au_fname"), Required, MaxLength(20)]
        public string AuthorFirstName { get; set; }

        [Column("au_lname"), Required, MaxLength(40)]
        public string AuthorLastName { get; set; }

        [Column("phone"), Required, MaxLength(12)]
        public string Phone { get; set; }

        [Column("address"), MaxLength(40)]
        public string Address { get; set; }

        [Column("city"), MaxLength(20)]
        public string City { get; set; }

        [Column("state"), MaxLength(2)]
        public string State { get; set; }

        [Column("zip"), MaxLength(5)]
        public string Zip { get; set; }

        [Column("contract"), Required]
        public bool Contract { get; set; }

        [Column("rowversion"), Timestamp, ConcurrencyCheck]
        public byte[] RowVersion { get; set; }

        public ICollection<TitleAuthor> TitleAuthors { get; set; }

    }

    [Table("publishers")]
    public partial class Publisher
    {
        [Column("pub_id"), Required, MaxLength(4)]
        public string PublisherId { get; set; }

        [Column("pub_name"), MaxLength(40)]
        public string PublisherName { get; set; }

        [Column("city"), MaxLength(20)]
        public string City { get; set; }

        [Column("state"), MaxLength(2)]
        public string State { get; set; }

        [Column("country"), MaxLength(30)]
        public string Country { get; set; }

        public ICollection<Title> Titles { get; set; }
    }

    [Table("titles")]
    public partial class Title
    {
        [Column("title_id"), Required, MaxLength(6), Key]
        public string TitleId { get; set; }

        [Column("title"), Required, MaxLength(80)]
        public string TitleName { get; set; }

        [Column("type"), Required, MaxLength(12)]
        public string Type { get; set; }

        [Column("price")]
        public decimal? Price { get; set; }

        [Column("advance")]
        public decimal? Advance { get; set; }

        [Column("royalty")]
        public int? Royalty { get; set; }

        [Column("ytd_sales")]
        public int? YeatToDateSales { get; set; }

        [Column("notes"), MaxLength(200)]
        public string Notes { get; set; }

        [Column("pubdate"), Required]
        public DateTime PublishedDate { get; set; }

        [Column("pub_id"), MaxLength(4)]
        public string PublisherId { get; set; }

        public Publisher Publisher { get; set; }

        public ICollection<Sale> Sales { get; set; }

        public ICollection<TitleAuthor> TitleAuthors { get; set; }
    }

    [Table("sales")]
    public partial class Sale
    {
        // ※ 複合キーは Data Annotation で指定できないため、Fluent API を使う

        [Column("stor_id"), Required, MaxLength(4)]
        public string StoreId { get; set; }

        [Column("ord_num"), Required, MaxLength(20)]
        public string OrderNumber { get; set; }

        [Column("ord_date"), Required]
        public DateTime OrderDate { get; set; }

        [Column("qty"), Required]
        public int Quantity { get; set; }

        [Column("payterms"), Required, MaxLength(12)]
        public string PayTerms { get; set; }

        [Column("title_id"), Required, MaxLength(6)]
        public string TitleId { get; set; }

        public Store Store { get; set; }
        public Title Title { get; set; }
    }

    [Table("stores")]
    public partial class Store
    {
        [Column("stor_id"), Required, MaxLength(4), Key]
        public string StoreId { get; set; }

        [Column("stor_name"), Required, MaxLength(40)]
        public string StoreName { get; set; }

        [Column("stor_addr"), Required, MaxLength(40)]
        public string Address { get; set; }

        [Column("city"), Required, MaxLength(20)]
        public string City { get; set; }

        [Column("state"), Required, MaxLength(22)]
        public string State { get; set; }

        [Column("zip"), Required, MaxLength(5)]
        public string Zip { get; set; }

        public ICollection<Sale> Sales { get; set; }
    }

    [Table("titleauthor")]
    public partial class TitleAuthor
    {
        [Column("au_id"), Required]
        public string AuthorId { get; set; }

        [Column("title_id"), Required]
        public string TitleId { get; set; }

        [Column("au_ord")]
        public byte AuthorOrder { get; set; }

        [Column("royaltyper")]
        public int RoyaltyPercentage { get; set; }

        public Author Author { get; set; }

        public Title Title { get; set; }

    }
}

■ コードの構造について

上記のソースの各クラスの役割は以下の通りです。

  • PubsEntites
    • データベース接続を管理するクラス。DbContext クラスから派生させて作成する。
    • この接続下で取り扱うテーブル一覧もここに記述される。
    • 接続文字列の指定方法は複数あるが、基本的には OnConfiguring メソッド内で指定する。
      • EF Core では SQL Server 以外にも接続できるため、SQL Server に接続したい場合には、project.json ファイルで Microsoft.EntityFrameworkCore.SqlServer ライブラリを組み込んだ上で、options.UseSqlServer(…) として接続先を指定する。
  • Author, Publisher, Title, Sale, Store, TitleAuthor クラス
    • データベースの各テーブルに対応させて作成した構造体クラス。
    • データベース上の 1 レコードが、これらのオブジェクトの 1 インスタンスに読みだされる。
      • このため、データベース上のテーブル名は複数形、C# のクラス名は単数形になるのが一般的。
    • 属性(クラス定義やプロパティ定義の上につけられたカギカッコ)により、データベースとのマッピング方法を指定する。
      • ほとんどのマッピングは属性で指定できるが、複合キーやリレーションシップに関する情報は(現時点では)属性では指定できない。このような場合には、PubsEntities クラスの OnModelCreating() メソッド内でコードで O/R マッピング情報を指定する。
      • 属性を使って O/R マッピングを指定する方法をデータアノテーション方式、OnModelCreating() メソッド内でコードを使って O/R マッピングを指定する方法を Fluent API 方式と呼ぶ。

おおまかなコードの構造は以下の通りです。(リレーションシップの指定などに関しては少し重要なので、後述します。)


    public partial class PubsEntities : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            // ここに接続文字列を書く
        }

        // ここにテーブル一覧を書く
        public DbSet<Author> Authors { get; set; }
        public DbSet<Title> Titles { get; set; }
        public DbSet<Publisher> Publishers { get; set; }
        public DbSet<Store> Stores { get; set; }
        public DbSet<Sale> Sales { get; set; }
        public DbSet<TitleAuthor> TitleAuthors { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // ここにデータアノテーションで指定できない O/R マッピング情報を書く
        }
    }

    [Table("authors")]
    public partial class Author
    {
        [Column("au_id"), Required, MaxLength(11), Key]
        public string AuthorId { get; set; }
        [Column("au_fname"), Required, MaxLength(20)]
        public string AuthorFirstName { get; set; }
        ...
    }

    [Table("publishers")]
    public partial class Publisher
    {
        [Column("pub_id"), Required, MaxLength(4)]
        public string PublisherId { get; set; }
        ...
    }
    ...

■ リレーションシップの O/R マッピング方法について

リレーションシップについては、以下のように実装します。

  • 1 : 多について
    • 例えば、出版社(Publisher)と書籍(Title)は 1 : 多の関係になりますが、これは以下のように実装します。
    • Publisher クラス側
      • 1 つの Publisher に複数の Title が紐づけられるので、ICollection<Title> Titles プロパティを作成します。
    • Title クラス側
      • 1 つの Title には 1 つの Publisher が紐づくので、Publisher Publisher プロパティを作成します。
      • PublisherId プロパティは持っても持たなくても構いませんが、通常はあると便利なので持たせてしまいます。
    • 上記準備を済ませたうえで、リレーションシップに関する O/R マッピング情報を OnModelCreating() メソッドに記述します。

[Table("publishers")]
public partial class Publisher
{
    [Column("pub_id"), Required, MaxLength(4)]
    public string PublisherId { get; set; }
    [Column("pub_name"), MaxLength(40)]
    ...
    public ICollection<Title> Titles { get; set; }
}

[Table("titles")]
public partial class Title
{
    [Column("title_id"), Required, MaxLength(6), Key]
    public string TitleId { get; set; }
    [Column("title"), Required, MaxLength(80)]
    public string TitleName { get; set; }
    ...
    [Column("pub_id"), MaxLength(4)]
    public string PublisherId { get; set; }
    public Publisher Publisher { get; set; }
}

modelBuilder.Entity<Publisher>().HasMany(p => p.Titles).WithOne(t => t.Publisher).IsRequired();
  • 多 : 多について
    • 例えば、著者(Author)と書籍(Title)は、多 : 多テーブルである TitleAuthor テーブルを介して 多 : 多 の関係を持ちます。
    • このような多 : 多の関係については、代表的には以下の 2 つの方式での O/R マッピングが考えられます。
      • ① 直接、多 : 多の関係をオブジェクトで表現する
        • Author オブジェクトのプロパティとして ICollection<Title> Titles を、Title オブジェクトのプロパティとして ICollection<Author> Authors を持つが…
        • 中間テーブルである TitleAuthor に対応するオブジェクトは作らない
      • ② 中間テーブルまで含めてオブジェクトで表現する
        • 中間テーブルである TitleAuthor に対応するオブジェクトを明示的に作り、Author オブジェクトのプロパティとして ICollection<TitleAuthor> TitleAuthors を、Title オブジェクトのプロパティとして ICollection<TitleAuthor> TitleAuthors を持たせる
        • すなわち、2 組の 1 : 多 の関係であるとして表現してしまう
    • どちらにもメリット・デメリットがありますが、一般的には②方式の方が幅広く利用できます。
      • 今回のサンプルのように、著者(Author)と書籍(Title)の間の TitleAuthor に印税料(Royalty)などの属性がついている場合には、明確にオブジェクトとして作成する必要があります。
    • ①の方式の場合には、単に 1 : 多の関係を 2 つ作成するだけになります。

■ LINQ クエリの記述

O/R マッピングファイルができたら、あとは LINQ クエリを実行します。今回は簡単のため、Startup.cs の app.Run() 内を書き換えて実行してみましょう。


app.Run(async (context) =>
{
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = from a in pubs.Authors where a.State == "CA" select a;
        await context.Response.WriteAsync(query.Count().ToString());
    }
});

正しく動作すれば、15 件という結果が帰ってくるはずです。

image

なお、EF で必要となる LINQ クエリの記述方法についてはここでは説明しませんが、LINQ クエリは EF を扱う上での必須技術であるため、まだ知らないという方は必ず学習してください。拙著「LINQ テクノロジ入門」は EF の前進となる LINQ to SQL という技術を使って書かれていますが、LINQ クエリの書き方そのものは基本的に変わりません。こちらの本をざっと流し読みしていただければ、LINQ の基本的な考え方などは学習できると思います。

■ 従来の EF からの大きな変更点について

(ここはちょっと難しいのでわかる人だけ読んでください) Entity Framework Core では様々な変更が入っていますが、実用側面から見た場合、以下の 2 つは非常に大きな変更点ですので、ここで解説しておきます。

Lazy Loading の廃止

従来の EF では、リレーションシップの先にあるデータを、クエリ実行後に後から手繰れるというトンデモ仕様が含まれていました。現場側の人間からすると、誰だこの学術的機能を入れた人は;、と全力でツッコミたかったわけですが、EF Core ではこの仕様が廃止されました。このため、以下のクエリは実行時に例外が発生します。

image

image

もちろん、場合によっては「クエリ実行時に、リレーションシップの先までデータを取得しておいてほしい」ということもあるはずです。この場合には、クエリ発行前に、明示的に Include, ThenInclude 命令で取り込む対象を指定してください。

image

非同期処理

従来の EF では、ToList() や FirstOrDefault() などによるクエリ実行は同期的にしか実行できませんでしたが、こうしたクエリ実行命令に、非同期処理版が追加されました。このため、以下のようなクエリは await/async 構文を利用して、以下のように記述できるようになりました。


var query = from a in pubs.Authors where a.State == "CA" select a;
var result = query.ToList();
↓
var result = await query.ToListAsync();

EF のこのような機能拡張に合わせて、ASP.NET MVC のアクションメソッドや Web API でも、async 構文を使った定義ができるようになりました。


[HttpGet]
public ActionResult ShowTitlesByPublishers()
{
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = pubs.Publishers....;
        ViewData["TitlesByPublisher"] = await query.ToList();
    }
    return View();
}

↓

[HttpGet]
public async Task<ActionResult> ShowTitlesByPublishers()
{
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = pubs.Publishers....;
        ViewData["TitlesByPublisher"] = await query.ToListAsync();
    }
    return View();
}

この新機能については様々なところでよく紹介されていますが、クライアント側(UI 処理)における async/await とは意味合いがずいぶん違うので注意してください。ざっくり説明すると、以下の通りです。

  • クライアント側における async/await 処理
    • UI スレッド(メインスレッド)上で時間がかかる処理をしてしまうと、「UI が固まる」という現象が起こる。
    • この問題を起こさないように、長時間(だいたい 30 msec よりも長い時間)を要する処理を別スレッドに切り出して行うために、async/await を使う。
  • サーバ側における async/await 処理
    • サーバ側は、通常、下図のようなマルチスレッド処理で複数のユーザの処理を捌いている。
    • この際、データアクセスのように時間のかかる処理をしてしまうと、当該スレッドは待機状態となり、CPU が遊んでしまう。
    • async/await 処理を行って、CPU を明示的に他のスレッドに回すことで、より CPU の利用効率を高めることができる。

image

サーバサイドではもともとマルチスレッドで処理が行われているため、「サーバが固まる」という現象が起こるわけではないですし、仮に async/await を明示的に行わなかったとしても、OS のマルチスレッド制御機能により、自動的に CPU リソースが他のスレッドに回されますので、すぐさま問題が起こるというわけではありません。ただ、ASP.NET ランタイムが Windows OS 以外でも動作するという話になってくると、OS によってはこのマルチスレッド制御の機能が貧弱なケースも考えられ、そのような場合には「明示的なリソース解放」を行わないと性能が出ない、というケースが出てくるかもしれません。正直、今どきの OS であればそうそう問題が起こるケースはないだろうとは思いますが、とはいえお作法としては、async/await 処理をきちんと書いた方が、環境依存のトラブルが出にくくなるという意味では安心です。

いずれにしても、サーバ側の async/await は、クライアント側の async/await とは利用目的が違う、という点は知っておくとよいでしょう。

■ 接続文字列の管理方法について

先の例では、接続文字列をハードコーディングしましたが、実際のアプリではいくつか課題があります。解決策をいくつかここで示しておきます。

  • アプリが配置されているディレクトリの自動解決
    • 従来の ASP.NET で利用していた |DataDirectory| 文字列は残念ながら ASP.NET Core では利用できません。Startup.cs ファイル内であれば、ソースコードのフォルダ名を比較的簡単に解決できます。これを用いて、Startup.cs から PubsEntities.cs にデータを引き渡すとよいでしょう。
    • 具体的には以下の通りです。
      • Startup.cs にコンストラクタを作る。コンストラクタの引数に IHostingEnvironment を付けておくと、自動的にホスティング環境の情報を渡してくれます。(詳細はここでは解説しませんが、コンストラクタインジェクションと呼ばれる ASP.NET ランタイムの DI コンテナ機能によるものです。)
      • さらに、Pubs.cs ファイル側でこれを拾うようにコードを修正します。
    • ※ (つぶやき)本来を言えば PubsEntities 側でパスを自動解決するように実装したいのですが、1.0.0 RTM 版の時点では PlatformServices クラスに ApplicationEnvironment プロパティしかなく、HostingEnvironment プロパティが存在しないため、そのような実装ができません。将来的には PlatformServices クラスから解決できるようになるのではないかと思います。

public class Startup
{
    public static string App_Data { get; set; }

    public Startup(IHostingEnvironment env)
    {
        App_Data = Path.Combine(env.ContentRootPath, "App_Data");
    }
...

public partial class PubsEntities : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlServer(
            @"data source=(LocalDB)\mssqllocaldb;attachdbfilename=|DataDirectory|\pubs.mdf;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
            .Replace("|DataDirectory|", Startup.App_Data));
    }
...
  • 開発環境/運用環境の切り替え
    • #if DEBUG 文を差し込んでおき、開発環境と運用環境の設定を切り替えます。

#if DEBUG
            options.UseSqlServer(
                @"data source=(LocalDB)\mssqllocaldb;attachdbfilename=|DataDirectory|\pubs.mdf;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
                .Replace("|DataDirectory|", Startup.App_Data));
#else
            options.UseSqlServer(
                @"Server=tcp:xxxxxxxx.database.windows.net,1433;Database=pubs;User ID=xxxxxxxx@xxxxxxxx;Password=xxxxxxxx;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;");
#endif

なお、ASP.NET Core では構成設定値を web.config ファイルに書きません。この点は従来の ASP.NET Web Forms から大きく変わっている点ですのでご注意ください。背景は以下の通りです。

  • 変更になった最大の理由は、従来の ASP.NET Web Forms では web.config の設定が肥大化し、メンテナンスがつらくなってしまったため
  • 従来の web.config ファイルには、大別して以下の 2 つの設定情報が書かれたが、ASP.NET Core では、これらの情報を分離して扱う。
    • ① ASP.NET ランタイムパイプラインのカスタマイズ方法や設定
      • Startup.cs ファイルの ConfigureServices(), Configure() の 2 つのメソッド内にコードとして記述する。
      • ConfigureServices() で DI コンテナの設定を、Configure() でパイプラインの設定をする。
    • ② 接続文字列などの純粋な設定値
      • 単純なものや、セキュリティ上の問題がないものであれば、上記の例のようにソースコード中にハードコードしてしまうのがラク。
      • どうしてもファイルに切り出したい場合などは、ASP.NET Core の新しい構成設定システムを利用する。(ASP.NET Core の新しい構成設定システムについてはこのエントリでは解説しないので、こちらを参考にしてください。)

■ その他の制約事項・注意事項について

その他、現在の Entity Framework Core 1.0 に関する注意点は以下の通りです。

  • Azure の SQL Database に対する利用について
    • Windows Azure の PaaS データベースである SQL Database に対してクエリを実行する場合、スロットリング(流量制限)によりクエリ実行が失敗するケースがあります。このため、SQL Database に対してクエリを実行する場合には、自動リトライ処理を入れるのがベストプラクティスになっています。
    • EF6 ではこの自動リトライ処理の組み込みが簡単に行えましたが、EF Core 1.0 ではまだこれが実装されていません(2016/07/02 時点)。近いうちに実装されるでしょうが、現時点では制約事項と考えておく必要があります。(ちょっと自力で作り込むのは大変;)
  • 自動トランザクションとの組み合わせについて
    • ASP.NET Core では、System.Transactions 配下の TransactionScope が利用できません。Windows プラットフォームを前提とできる場合には、ランタイムとして Full CLR を利用することで TransactionScope の利用もできなくはないですが、その場合でもあまり組み合わせて利用することはオススメはしません。
    • これはそもそも Enity Framework と自動トランザクションは、設計として相性が悪いためです。
      • Entity Framework では、基本的に、データベースの入出力とは、エンティティというデータの塊の出し入れである、と発想を持っており、「エンティティ」という単位を超えるトランザクション制御はほとんどないよね? という思想を持っています。もちろん実際の業務を見るとエンティティという単位を超えるトランザクション制御が必要になる場合も存在するのですが、そういうものはごく一部だし、高速性を要求する処理だったりすることが多いので、ストプロで実装しちゃってね、という割り切り思想を持っています。(よいか悪いかはともかく)
      • 一方、自動トランザクションは、そうした割り切り型の思想はなく、むしろ「開発者の都合次第でいかようにも組んでよい」という、Entity Framework よりも低水準の技術です。このため、自動トランザクションと Entity Framework を組み合わせようとすると、「UPDLOCK ロックヒントが簡単につけられない」などの問題に突き当たることになります。
    • このため、EF Core を使う場合、(少なくとも現時点では)以下のようにするのがオススメです。(ロックヒントなどの問題は、中長期的には解決されてくる問題かもしれませんが、現時点では以下のように考えておくとよいと思います。)
      • 自動トランザクションとは組み合わせないこと。
      • どうしてもトランザクション制御が必要になるところは、ストプロで実装する。
      • あまりにもストプロ実装が増えそうなら、EF Core ではなく、生の System.Data.SqlClient などを利用することを検討してみる。

以上が EF Core 1.0 の基本的な使い方になります。その他、より詳しい情報については、de:code 2016 DEV-003 セッション 「新しく生まれ変わったデータアクセステクノロジ~Entity Framework Core 1.0 の全貌~」などを見ていただくとよいと思います。

Part 3. ASP.NET MVC Core 1.0 の基本的な使い方

$
0
0

では、データアクセスができるようになったところで、ASP.NET MVC Core 1.0 の基本的な使い方を見ていくことにします。

■ ASP.NET MVC の超簡単解説

ASP.NET Web Forms しか開発したことがない方のために、超ざっくりと ASP.NET MVC を解説しておきます。(ASP.NET MVC を触ったことがある方はここは飛ばしていただいて OK です。)

  • ASP.NET MVC とは、論理 3 階層型の Web アプリケーション(UI/BC/DAC に分解される Web アプリケーション)を開発する際に、UI 部分を「きれいに分解しながら」作ることができる技術のひとつ。
  • クライアントのブラウザから、特定のコントローラクラスのアクションメソッドを呼び出すと、それに対応する処理が動き、対応するビューによりレスポンス HTML データが作成され、ブラウザに返却される、という仕組みになっている。

image

ASP.NET MVC に関して、まず特に覚えておいていただきたいポイントは以下の 3 つです。

  • ブラウザからのリクエストは、URL で指定されたコントローラクラスのアクションメソッドで処理される。
    • 例えば、ブラウザから http://localhost:xxx/Sample01/GetAuthors/ という URL を呼び出すと、Sample01Controller.cs というクラスの、GetAuthors() というメソッドが呼び出される。(第一引数でコントローラクラス名を指定し、第二引数でアクションメソッド名を指定する。
    • このメソッドの中では、ビジネスロジッククラスやデータアクセスクラスを呼び出してデータベースからデータを取り出したり、処理結果をビューに引き渡したりする。
  • クライアントに送り返す HTML データは、対応するビューファイル(.cshtml ファイル)により作成される。
    • コントローラクラスからデータを受け取り、それを使って、クライアントに送り返す HTML ページを作成する。
  • ASP.NET MVC では、モデルに相当するものが何であるのかは決まっていない。
    • ここは重要なポイントですが、”MVC” = Model + View + Controller という名前がついているものの、ASP.NET MVC において明確に扱いが決まっているのは Controller と View のみであり、Model が何であるのかの定義はありません
    • 一般的には、以下のうちのいずれか(または複数)が Model に相当します。(詳細は本 blog を読み進めていくうちにわかってくると思いますので、ここではわからなくてもよいです。)
      • ① データベースとやり取りされるデータの構造(=EF のデータモデル)
      • ② ビューに対して引き渡すデータの構造(=ビューモデル)
      • ③ クライアントから送られてくるデータの構造(=リクエストパラメータバインディングのモデル)

では前置きはこのぐらいにして、具体的な実装を見ながら説明していきます。

■ ASP.NET MVC Core ランタイムの組み込み

ASP.NET Core 1.0 のデフォルト状態では、何もアプリケーションランタイムが組み込まれていません。このため、Startup.cs の Configure() メソッドを修正し、ASP.NET MVC Core 1.0 ランタイムを組み込みます。


public class Startup
{
    ...

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();
        if (env.IsDevelopment()) app.UseDeveloperExceptionPage();

        app.UseStaticFiles();
        app.UseMvcWithDefaultRoute();
    }
}

Part 1. で説明したように、project.json ファイルにてライブラリに対する参照設定を加えたのち、ConfigureServices() メソッドに services.AddMvc() メソッドを、また Configure() メソッドに app.UseMvcWithDefaultRoute() メソッドを追加します。これにより、このアプリで MVC が利用できるようになります。

なお、ConfigureServices(), Configure() メソッドは、サンプルコードによって引数が違っていたりします。これは ASP.NET ランタイムの DI コンテナ機能によるものですが、わからなければとりあえず引数が違っていてもあまり気にしなくてよい、と思っておいてください。

ではまず手始めに、データベースから著者データを取ってきて一覧表示するアプリを作ってみます。(いつもながら芸がないですがw)

■ コントローラクラスとビューファイルの配置

まずプロジェクトに Controllers という名前のフォルダを掘り、そこに HomeController.cs という名前で C# のクラスファイルをひとつ追加します。なお、以降ではフォルダ名やクラス名、ファイル名などの大文字・小文字、単数形・複数形に注意してください。ASP.NET MVC Core では、名前が正しく規約に基づいてつけられていることを元に動作します。標準的な名付けルールに則ることで、構成設定などを書く手間を減らすことができます。(この考え方を「設定より規約」と呼び、ASP.NET MVC はこの考え方に基づいて作られています。)

image

作成したら、以下のような HomeController クラスを実装します。ポイントは以下の通りですが、いずれも ASP.NET MVC のお約束の書き方だと思って覚えてください。

  • コントローラクラスは、必ず Controller クラスを継承させて作る。
  • アクションメソッドの戻り値は必ず ActionResult クラスとする。
  • 実際にメソッドを終了するときは、View() を返す。(※ このメソッドは、Controller クラスが持っているメソッドで、ActionResult クラスを継承した ViewResult クラスのインスタンスを返します。)
  • このページを呼び出す HTTP プロトコルを制限したい場合には、属性 [HttpGet] や [HttpPost] などを指定します。(無指定だとどんなプロトコルでも呼び出しが可能になります。)

using Microsoft.AspNetCore.Mvc;

namespace Decode2016.WebApp.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View();
        }
    }
}

続いて、Views というフォルダ、その下に Home というフォルダを作成し、その下に Index.cshtml という名前で MVC ビューページという項目を配置します。

image

この中に、クライアントに送り返す HTML データを記述します。いったんファイルの中身を全部消し、以下のように記述します。


<!DOCTYPE html>
<html>
<head>
    <title>著者データ一覧</title>
</head>
<body>
    これからコードを書く...
</body>
</html>

ここまで作成したらアプリを実行し、http://localhost:xxxx/Home/Index/ を呼び出します。すると、① コントローラクラスのアクションメソッドが呼び出され、② そこから return View(); によりビューファイルが呼び出され、③ Views/Home/Index.cshtml ファイルから HTML データが作られ、ブラウザに送り返されます。

image

※ なお、この例では http://localhost:xxxx/ を呼び出しも同じ結果になります。これは、ASP.NET MVC では、パスを省略すると /Home/Index を呼び出したと解釈されるためです。(Startup.cs ファイルに記述した app.UseMvcWithDefaultRoute(); の中に、既定値の設定が含まれています)

では、基本的な構造ができたので、ここにプログラムを作りこんでいきます。

■ データアクセスページの作成

HomeController クラスの Index() メソッド内に、データを取り出すコードを記述します。ビジネスロジッククラスを別に作って呼び出しても構いませんが、今回のサンプルではここに直接データアクセスコードを記述してしまうことにします。

取り出したデータは、ViewData という汎用的な入れ物を使ってビューに引き渡すことができます。


using Microsoft.AspNetCore.Mvc;
using Decode2016.WebApp.Models;
using System.Linq;

namespace Decode2016.WebApp.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            using (PubsEntities pubs = new PubsEntities())
            {
                var query = from a in pubs.Authors select a;
                ViewData["Authors"] = query.ToList();
            }
            return View();
        }
    }
}

続いて、Index.cshtml ファイルを書き換えます。*.cshtml ファイルでは Razor と呼ばれる構文を利用し、サーバ側でレンダリングを行います。


@using Decode2016.WebApp.Models

<!DOCTYPE html>
<html>
<head>
    <title>著者データ一覧</title>
</head>
<body>
    @{
        var authors = (List<Author>)ViewData["Authors"];
    }
    <table>
        <thead>
            <tr>
                <th>著者ID</th>
                <th>著者名</th>
                <th>電話番号</th>
                <th>契約有無</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var a in authors)
            {
                <tr>
                    <td>@a.AuthorId</td>
                    <td>@a.AuthorFirstName @a.AuthorLastName</td>
                    <td>@a.Phone</td>
                    <td>@(a.Contract ? "契約あり" : "契約なし")</td>
                </tr>
            }
        </tbody>
    </table>
</body>
</html>

要点を以下に解説します。

  • *.cshtml ファイルは、サーバサイドで HTML データを作り出すために使われます。
    • classic ASP(ASP.NET の前の技術)に非常によく似た技術です。大昔を知っている方ならむしろ馴染み深いものかもしれません。
  • Razor 構文では、@ マークを使って C# のコードを記述します。
    • 基本構文は @{ … } ですが、カッコなしで @foreach { … } といきなり命令を書くこともできます。

また、Razor 構文では HTML 出力を簡単に作れるようになっています。例えば…

以下のように書くべきところを… 以下のように書ける
@{
    Write(DateTime.Now);
}
@DateTime.Now
@{
    Write(“<td>” + a.AuthorId + “</td>”);
}
<td>@a.AuthorId</td>

といった感じです。慣れるとサクサクと書けます。実行してみるとこんな感じです。

image

このサンプルを見て、<table> タグを自力で書かなければならないのかよ! と思われた方、はい、その通りです

  • ASP.NET Web Forms の場合、GridView という便利な部品が存在していましたが、ASP.NET MVC Core では(今のところ)そのような高機能な便利部品は存在していません。ちなみに ASP.NET MVC 5 には WebGrid という部品は存在しており、これについてはいずれ ASP.NET MVC Core にも移植されると思いますが、ASP.NET Web Forms の GridView に比べるとかなり機能的には劣ります。なので、過度な期待は禁物です。
  • ASP.NET Web Forms で利用していたような高機能なグリッド部品が必要な場合には、3rd party 製の部品に頼る形になります。この詳細については、この後の SPA 型アプリ開発のセクションで解説します。

■ ASP.NET MVC のフォルダ/ファイルのレイアウトルール

ここまで見てきた例からわかるように、ASP.NET MVC では、フォルダ/ファイル/メソッドのレイアウトと命名にルールがあります。上では 1 つのページしかありませんでしたが、もしこれが本格的な Web アプリになったらどうなるかを考えてみます。例えば、以下のような業務構造・画面遷移を持つアプリを作ろうと思ったとします。

image

この場合には、以下のようにファイルをレイアウトします。(※ コントローラクラスの中には、各ページに対応する複数のアクションメソッドが並ぶことになります。) このファイルのレイアウト関係を、しっかり頭に入れておいてください。

image

基本的な ASP.NET MVC のアプリの作り方がわかったら、今度はページの構造化をしていくことにします。

Part 4. ページレイアウトの構造化

$
0
0

ここまでで ASP.NET MVC Core を使った簡単なページはできましたが、ちょっとダサいです;。なのでこれを jQuery や Bootstrap でちょっと綺麗にしてみます。

■ Bootstrap の利用

Bootstrap は非常に有名な CSS フレームワークのひとつで、これを利用することにより、レスポンシブ対応の Web サイトを比較的容易に構築していくことができます。Bootstrap については日本語でも多数の書籍が出ていますので、詳細についてはここでは書きません。先に作成した Index.cshtml ファイルの Web ページを、Bootstrap を使ってカッコよくしてみると、以下のようになります。

image

Index.cshtml ファイルのソースコードは以下の通りです。(かなり長いですが、大半は呪文です。要点はソースコードの後ろに書いてあります。)


@using Decode2016.WebApp.Models

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>de:code 2016</title>
    <meta name="viewport" content="width=device-width, intial-scale=1.0" />

    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

    <style type="text/css">
        @@media only screen and (min-width : 768px) {
            html {
                position: relative;
                height: 100%;
            }
            body {
                height: 100%;
                padding-top: 50px;
                padding-bottom: 50px;
            }
            .contentWrapper {
                overflow: auto;
                height: 100%;
            }
            .contentBody {
                padding-top: 10px;
                padding-bottom: 10px;
                min-height: 100%;
            }
            .footer {
                position: fixed;
                bottom: 0;
                margin-bottom: 0;
                width: 100%;
                height: 50px;
                background-color: #f5f5f5;
            }
            .navbar-custom-responsive {
                position: fixed;
                width:100%;
                top:0px;
                border-width: 0 0 1px 0;
            }
        }
        @@media only screen and (min-width : 0px) {
            html {
            }
            body {
            }
            .contentWrapper {
            }
            .contentBody {
                padding-top: 10px;
                padding-bottom: 10px;
            }
            .footer {
                bottom: 0;
                margin-bottom: 0;
                width: 100%;
                height: 50px;
                background-color: #f5f5f5;
            }
            .navbar-custom-responsive {
                margin-bottom: 0;
            }
        }
    </style>

    <style type="text/css">
        div.row ul {
            padding-left: 30px;
        }
    </style>


</head>
<body>
    <nav class="navbar navbar-static-top navbar-default navbar-custom-responsive" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand" href="/"><span class="glyphicon glyphicon-leaf"></span>&nbsp;&nbsp;de:code 2016</a>
                <button type="button" class="navbar-toggle" data-target="#navbar" data-toggle="collapse">
                    <span class="sr-only">ナビゲーションの表示</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>
            <div class="navbar-collapse collapse" id="navbar">
                <ul class="nav navbar-nav navbar-right">
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                            各種サンプル&nbsp;<span class="caret"></span>
                        </a>
                        <ul class="dropdown-menu">
                            <li><a href="/Sample01/FilterByStateWithSort">従来型の Web アプリ</a></li>
                            <li><a href="/Sample02/FilterByStateWithSort">SPA 型の Web アプリ</a></li>
                            <li><a href="/Sample03/FilterByState">jQuery + Bootstrap + knockout.js</a></li>
                            <li><a href="/Sample04/ListAuthors">非 SPA ベースでの実装</a></li>
                            <li><a href="/Sample05/ListAuthors">SPA ベースでの実装</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="contentWrapper">
        <div class="container contentBody">

            @{
                var authors = (List<Author>)ViewData["Authors"];
            }
            <table class="table table-condensed table-striped table-hover">
                <thead>
                    <tr>
                        <th>著者ID</th>
                        <th>著者名</th>
                        <th>電話番号</th>
                        <th>契約有無</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var a in authors)
                    {
                        <tr>
                            <td>@a.AuthorId</td>
                            <td>@a.AuthorFirstName @a.AuthorLastName</td>
                            <td>@a.Phone</td>
                            <td>@(a.Contract ? "契約あり" : "契約なし")</td>
                        </tr>
                    }
                </tbody>
            </table>

        </div>
    </div>

    <footer class="footer">
        <div class="text-right" style="padding-top: 13px; padding-right: 20px; ">&copy; 2016 Microsoft Corporation. All rights reserved.</div>
    </footer>
</body>
</html>

ややこしいソースコードになっていますが、要点は以下の通りです。細かいコードはわからなくてもとりあえず構いません

  • Bootstrap は簡単のため、CDN 上で公開されているものをそのまま参照して利用しています。
  • ぐだぐた書かれているスタイルシート <style type=”text/css”> 部分は、ヘッダー・フッター部の制御のためのものです。
  • <body> 内は、ヘッダー部分+中身のコンテンツ部分+フッター部分の 3 ブロックで構成されています。
    • ヘッダー部分 <nav> は、Bootstrap の機能を使って作っています。これにより、ドロップダウンタイプのショートカットメニューを実現しています。(リンク先はとりあえずダミーですが)
    • <div class=”container contentBody”> 内が本体部分です。Part 3. の最後で作成したテーブルをここに移植してあります。(Bootstrap のデザインを適用するため、<table>タグに class=”table table-condensed table-striped table-hover” の指定をつけています。これによりきれいなデザインになります。)
    • フッター部分 <footer> は自力で作成しています。フッターの位置制御は、スタイルシートで行っています。

■ ページの構造化

さて、今回はまだ 1 つのページしか作っていませんが、実際の Web サイトでは複数の Web ページを作成するのがふつうで、しかもこれは下図のように共通のヘッダーやフッター、あるいはライブラリを使うのがふつうです。

image

上に示したように、個々のページは下図のようなレイアウトを持ちますが、共通部分を各ページの *.cshtml ファイルにコピペで貼り付けていたら、明らかにサイトのメンテナンス性は落ちます。

image

このため、実際の Web サイトでは、下図のように構造化を行い、① 共通 UI 部分などについてはレイアウトページに掃き出す、② 個々のページは差分だけ実装する、③ ページごとに組み込むか否かは異なるものの、よく使うものについては部品として用意しておく、ということを行います。

image

具体的には以下の作業を行います。

  • /Views/Shared フォルダを作成し、ここに _Layout.cshtml ファイルを作成する。
  • /Views/Home/Index.cshtml ファイルを、差分情報のみに変更する。

[/Views/Shread/_Layout.cshtml ファイル]

ポイントとなるのは以下の 5 箇所です。コードからわかるように、required:false になっているものは、指定してもしなくても OK になっています。

  • @ViewData[“Title”] : ページタイトルを個別ページ側から指定できるようにするためのもの
  • @RenderSection(“Libraries”, required: false) : 標準的に組み込まれている、Bootstrap, jQuery 以外のライブラリを個々のページで組み込む際に利用
  • @RenderSection(“Styles”, required: false) : 個々のページで追加の CSS 指定をしたい場合に利用
  • @RenderBody() : 本体部分を埋め込み
  • @RenderSection(“Scripts”, required: false) : 個々のページの JavaScript を指定

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewData["Title"]</title>
    <meta name="viewport" content="width=device-width, intial-scale=1.0" />

    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

    @RenderSection("Libraries", required: false)

    <style type="text/css">
        @@media only screen and (min-width : 768px) {
            html {
                position: relative;
                height: 100%;
            }

            body {
                height: 100%;
                padding-top: 50px;
                padding-bottom: 50px;
            }

            .contentWrapper {
                overflow: auto;
                height: 100%;
            }

            .contentBody {
                padding-top: 10px;
                padding-bottom: 10px;
                min-height: 100%;
            }

            .footer {
                position: fixed;
                bottom: 0;
                margin-bottom: 0;
                width: 100%;
                height: 50px;
                background-color: #f5f5f5;
            }

            .navbar-custom-responsive {
                position: fixed;
                width: 100%;
                top: 0px;
                border-width: 0 0 1px 0;
            }
        }

        @@media only screen and (min-width : 0px) {
            html {
            }

            body {
            }

            .contentWrapper {
            }

            .contentBody {
                padding-top: 10px;
                padding-bottom: 10px;
            }

            .footer {
                bottom: 0;
                margin-bottom: 0;
                width: 100%;
                height: 50px;
                background-color: #f5f5f5;
            }

            .navbar-custom-responsive {
                margin-bottom: 0;
            }
        }
    </style>

    @RenderSection("Styles", required: false)

</head>
<body>
    <nav class="navbar navbar-static-top navbar-default navbar-custom-responsive" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand" href="/"><span class="glyphicon glyphicon-leaf"></span>&nbsp;&nbsp;de:code 2016</a>
                <button type="button" class="navbar-toggle" data-target="#navbar" data-toggle="collapse">
                    <span class="sr-only">ナビゲーションの表示</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>
            <div class="navbar-collapse collapse" id="navbar">
                <ul class="nav navbar-nav navbar-right">
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                            各種サンプル&nbsp;<span class="caret"></span>
                        </a>
                        <ul class="dropdown-menu">
                            <li><a href="/Sample01/FilterByStateWithSort">従来型の Web アプリ</a></li>
                            <li><a href="/Sample02/FilterByStateWithSort">SPA 型の Web アプリ</a></li>
                            <li><a href="/Sample03/FilterByState">jQuery + Bootstrap + knockout.js</a></li>
                            <li><a href="/Sample04/ListAuthors">非 SPA ベースでの実装</a></li>
                            <li><a href="/Sample05/ListAuthors">SPA ベースでの実装</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="contentWrapper">
        <div class="container contentBody">

            @RenderBody()

        </div>
    </div>

    <footer class="footer">
        <div class="text-right" style="padding-top: 13px; padding-right: 20px; ">&copy; 2016 Microsoft Corporation. All rights reserved.</div>
    </footer>

    @RenderSection("Scripts", required: false)

</body>
</html>

[/Views/Home/Index.cshtml ファイル]

こちらは差分情報だけ残せばよくなるので、以下のコードのみで済むようになります。


@using Decode2016.WebApp.Models
@{
    Layout = "_Layout";
    ViewData["Title"] = "著者データ一覧";
}

@{
    var authors = (List<Author>)ViewData["Authors"];
}
<table class="table table-condensed table-striped table-hover">
    <thead>
        <tr>
            <th>著者ID</th>
            <th>著者名</th>
            <th>電話番号</th>
            <th>契約有無</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var a in authors)
        {
            <tr>
                <td>@a.AuthorId</td>
                <td>@a.AuthorFirstName @a.AuthorLastName</td>
                <td>@a.Phone</td>
                <td>@(a.Contract ? "契約あり" : "契約なし")</td>
            </tr>
        }
    </tbody>
</table>

上記のように修正して実行しても、全く動作結果が変わらないことを確認していただければと思います。

■ 共通レイアウトの強制

上記のサンプルで、Layout=”_Layout”; というコードがありますが、これはレイアウトページを指定するためのコードです。このコードをいちいち個別に指定していると面倒、ということもあると思います。このような場合には、/Views/_ViewStart.cshtml ファイルを追加し、そこに以下のように記述してください。これで個々のページでのレイアウトページ指定は不要になります。


@{
    Layout = "_Layout";
}

image

以上で ASP.NET MVC Core の Web アプリの骨格ができあがりました。引き続き、ASP.NET MVC Core を使った SPA 型 Web アプリ開発の基礎を説明していきます。

Part 5. ASP.NET Web API を使った SPA 型 Web アプリ開発

$
0
0

では引き続き、ASP.NET Web API を使って SPA 型で同じアプリを開発してみたいと思います。

■ SPA 型 Web アプリとは?

SPA (Single Page Application)型 Web アプリとは、単一ページで機能を提供する Web アプリです。具体的な設計・実装モデルとしては、データを Web API で取得し、画面を構築する処理をブラウザ側にもってくる形になります。

image

非 SPA 型 Web アプリと SPA 型 Web アプリの作り方の違いについては、de:code 2016 DEV-010 セッションにて詳しく解説しているのでそちらを見ていただくことにして、ここでは具体的な作り方について解説したいと思います。ここでは、① すべての著者データを一覧表示する、② 州によるフィルタリングを行う、という 2 つの画面を作成してみます。

image image

■ ファイルの配置

まず、コントローラクラス Sample01Controller.cs とビューファイル ShowAllAuthors.cshtml, ShowAuthorsByState.cshtml を配置します。作成するのは 2 つのページですが、コントローラクラスは業務(ビューのフォルダ)単位でよいので、ここでは 1 つだけ作ります。(コントローラとビューは、先にビューから考えると配置しやすいです。)

image

まずは空のページを作っておきましょう。Sample01Controller.cs ファイルにアクションメソッド 2 つを用意しておきます。


using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Decode2016.WebApp.Controllers
{
    public class Sample01Controller : Controller
    {
        [HttpGet]
        public ActionResult ShowAllAuthors()
        {
            return View();
        }

        [HttpGet]
        public ActionResult ShowAuthorsByState()
        {
            return View();
        }
    }
}

*.cshtml ファイルの方はこれから作っていきますので、とりあえず中身はタイトルぐらいで OK です。


@{ ViewData["Title"] = "全著者データの一覧"; }

<h4>全著者データの一覧</h4>

@{ ViewData["Title"] = "州による著者データの検索"; }

<h4>州による著者データの検索</h4>

作成したら、http://localhost:xxx/Sample01/ShowAllAuthors/ や http://localhost:xxx/Sample01/ShowAuthorsByState/ などを呼び出していただき、ページが表示されることを確認します。

image

■ ASP.NET Web API の作成

次に、ブラウザからデータを取り出すために必要な ASP.NET Web API の作成を行います。ASP.NET MVC Core ランタイムには ASP.NET Web API ランタイムも包含されていますので、このまま Web API を開発していくことが可能です。まずは全件のデータを取り出すための GetAllAuthors() メソッドを、Sample01Controller.cs ファイルに追記します。


using Decode2016.WebApp.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Decode2016.WebApp.Controllers
{
    public class Sample01Controller : Controller
    {
        [HttpGet]
        public ActionResult ShowAllAuthors()
        {
            return View();
        }

        [HttpGet]
        public ActionResult ShowAuthorsByState()
        {
            return View();
        }

        [HttpGet]
        public List<Author> GetAllAuthors()
        {
            using (PubsEntities pubs = new PubsEntities())
            {
                var query = from a in pubs.Authors select a;
                return query.ToList();
            }
        }
    }
}

MVC と Web API のアクションメソッドはよく似ていますが、戻り値が異なる点に注意してください。実装できたら、ブラウザから http://localhost:xxxx/Sample01/GetAllAuthors を呼び出すと、この Web API の動作を確認することができます。

image

ここで押さえてほしいポイントは以下の通りです。

  • MVC と Web API は、同一のコントローラクラス上に混在させることができる。(もちろんコントローラクラスを分けても構いません)
  • サーバからは、JSON と呼ばれる形式でデータが送り返される。
  • JSON データの各フィールドの先頭一文字が、小文字に自動的に置換されている

3 点目に関しては若干注意が必要です。一般的に、サーバ側の C# ではクラス名やフィールド名は先頭を大文字にしますが、ブラウザ側の JavaScript では先頭を小文字にすることが多いです。このため、ASP.NET Web API の背後で利用されている Newtonsoft の Json.NET では、自動的にこの大文字/小文字変換を行うようになっています(※ 正確には ASP.NET Core 1.0 版から変更されています)。この挙動は、カジュアルな開発では便利ですが、きっちり開発する業務アプリ開発では気持ち悪いと感じる人もいると思います(← 私的には気持ち悪い;)。この挙動を抑えるためには、Startup.cs ファイルに以下のコードを追加してください。この blog では、下記コードがあるものとして解説を進めます。


public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
            .AddJsonOptions(options =>
            {
                // 大文字・小文字の自動補正機能を無効化
                options.SerializerSettings.ContractResolver = null; // CamelCasePropertyNamesContractResolver が刺さっているためこれを外す
            });

    //// 以下は XML フォーマッタを入れたり、単一文字列を返す Web API を作りたい場合に入れるとよい設定。
    //services.Configure<MvcOptions>(options =>
    //{
    //    options.RespectBrowserAcceptHeader = true;
    //    options.OutputFormatters.RemoveType<Microsoft.AspNetCore.Mvc.Formatters.StringOutputFormatter>();
    //});
}

これを加えて同じ Web API を呼び出すと、以下のようになります。各フィールドの先頭一文字が C# と同じく大文字になります(=そのまま送出されている)。

image

さて、このまま使ってもよいのですが、実際の一覧表はすべてのフィールドのデータが必要なわけではないので、一部だけ絞り込むことにします。Models フォルダの下に AuthorOverview.cs クラスを追加し、以下のような構造体クラスを実装します。

image


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Decode2016.WebApp.Models
{
    public class AuthorOverview
    {
        public string AuthorId { get; set; }
        public string AuthorName { get; set; }
        public string Phone { get; set; }
        public string State { get; set; }
        public bool Contract { get; set; }
    }
}

そして、先に実装した Sample01Controller.cs クラスの GetAllAuthors() メソッドを以下のように修正します。


[HttpGet]
public List<AuthorOverview> GetAllAuthors()
{
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = from a in pubs.Authors
                    select new AuthorOverview()
                    {
                        AuthorId = a.AuthorId,
                        AuthorName = a.AuthorFirstName + " " + a.AuthorLastName,
                        Phone = a.Phone,
                        State = a.State,
                        Contract = a.Contract
                    };
        return query.ToList();
    }
}

このように実装すると、列が絞り込まれた JSON データが返されるようになります。(ブラウザから直接叩いて動作確認してみるとよいでしょう)

■ ブラウザ側の実装

引き続き、ブラウザ側の処理を実装します。ブラウザ側では、① Web API からのデータの取得、② 取得したデータの一覧表示、の 2 つが必要です。前者には jQuery を使うのがよいですが、後者については様々な方法があります。考え方や選択方法については de:code のセッションで解説しているのでそちらを確認していただくことにして、ここでは最も簡単(=予備知識が不要)な方法として、knockout.js を使ったデータバインドを使ってみたいと思います。

/Views/Sample01/ShowAllAuthors.cshtml ファイルを修正し、まずは jQuery を使ってすべてのデータを Web API から取り寄せる処理を記述します。下図にあるように、JavaScript のコードはページ固有の JavaScript コードブロックに記述しますので、@section Scripts { … } に記述します。

image_thumb16


@{ ViewData["Title"] = "全著者データの一覧"; }

<h4>全著者データの一覧</h4>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            $.getJSON("/Sample01/GetAllAuthors", function (result) {
                console.debug(result);
            });
        });
    </script>
}

Ctrl + F5 キーでアプリを実行してから http://localhost:xxx/Sample01/ShowAllAuthors ページを呼び出しますが、その際、F12 ツールを利用すると、リモート通信の中身や、console.debug() 命令でロギングした情報を確認することができます。これにより、Web サーバと正しく通信できているのかが確認できます。

image

続いて、取り寄せたデータを knockout.js ライブラリを利用して表示してみます。まず、knockout.js ライブラリの組み込みは @section Libraries { … } で行いますが、このライブラリは複数のページで利用する可能性のあるライブラリです。このため、/Views/Sample01/ShowAllAuthors.cshtml ファイルにハードコードしてしまうとメンテナンス性が落ちます。このような場合には、/Views/Shared フォルダ下にファイルを作成しておき、これを組み込むようにしておくとよいでしょう。

image

[/Views/Shared/_ImportsLibraryKnockout.cshtml](中身はたったの一行しかありませんが、敢えて切り出しておき、複数のページで再利用する)


<script src="https://ajax.aspnetcdn.com/ajax/knockout/knockout-3.3.0.js"></script>

[/Views/Sample01/ShowAllAuthors.cshtml]


@{ ViewData["Title"] = "全著者データの一覧"; }

@section Libraries {
    @Html.Partial("_ImportsLibraryKnockout")
}

<h4>全著者データの一覧</h4>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            $.getJSON("/Sample01/GetAllAuthors", function (result) {
                console.debug(result);
            });
        });
    </script>
}

ここまでできたら、knockout.js を使ってデータバインドを行います。基本的な考え方は下図の通りで、ko.observableArray() を利用して ViewModel を作成し、これを介してコードと UI との間でデータバインドを行います。(knockout.js を本格的に使いたい、というのでなければ、細かいコードは理解しなくて構いません。ざっくり「こんな感じ」と理解してもらえれば十分です。)

image

image

ソースコードは以下のようになります。


@{ ViewData["Title"] = "全著者データの一覧"; }

@section Libraries {
    @Html.Partial("_ImportsLibraryKnockout")
}

<h4>全著者データの一覧</h4>

<div class="table-responsive">
    <table class="table table-condensed table-striped table-hover">
        <thead>
            <tr>
                <th>著者ID</th>
                <th>著者名</th>
                <th>電話番号</th>
                <th>州</th>
                <th>契約有無</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: Authors">
            <tr>
                <td data-bind="text: AuthorId"></td>
                <td data-bind="text: AuthorName"></td>
                <td data-bind="text: Phone"></td>
                <td data-bind="text: State"></td>
                <td>
                    <input type="checkbox" disabled data-bind="checked: Contract" />&nbsp;
                    <text data-bind="text: (Contract ? '契約あり' : '契約なし')"></text>
                </td>
            </tr>
        </tbody>
    </table>
</div>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            var viewModel = {
                Authors: ko.observableArray()
            };
            ko.applyBindings(viewModel);

            $.getJSON("/Sample01/GetAllAuthors", function (result) {
                viewModel.Authors(result);
            });
        });
    </script>
}

実行結果は下図のようになります。

image

では、同じ要領で、州によるフィルタリングアプリを開発してみたいと思います。

■ 州によるフィルタリングアプリの実装

州によるフィルタリングアプリを作成するためには、① 州の一覧データを取り出す Web API と、② 指定された州に属する著者の一覧を検索取得する Web API、の 2 つが必要です。Sample01Controller.cs クラスに以下の 2 つのメソッドを追加しましょう。


[HttpGet]
public string[] GetStates()
{
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = pubs.Authors.Select(a => a.State).Distinct();
        return query.ToArray();
    }
}

[HttpGet]
public List<AuthorOverview> GetAuthorsByState(string state)
{
    if (Regex.IsMatch(state, "^[A-Z]{2}$") == false) throw new ArgumentOutOfRangeException("state");

    List<AuthorOverview> result;
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = pubs.Authors.Where(a => a.State == state)
                    .Select(a => new AuthorOverview()
                    {
                        AuthorId = a.AuthorId,
                        AuthorName = a.AuthorFirstName + " " + a.AuthorLastName,
                        Phone = a.Phone,
                        State = a.State,
                        Contract = a.Contract
                    });
        result = query.ToList();
    }
    return result;
}

なお、GetAuthorsByState() メソッドについては、[HttpGet] で定義する方法と、[HttpPost] で定義する方法の両方が考えられます。正直なところ、どっちでも動作するのでどっちでもいいっちゃいいのですが;、このケースでは [HttpGet] にしておいたほうが便利です。理由は以下の通り。

  • [HttpGet] にしておくと、ブラウザから簡単に動作確認ができます。
    • http://localhost:xxx/Sample01/GetAuthorsByState/?state=CA などとして呼び出しを行うと、HTTP-GET でこの Web API を呼び出して、動作確認をとることができて便利です。
  • このケースでは、HTTP プロトコルの規約からすると、[HttpGet] のほうが適切です。
    • 通常、同じ州に対しては同じ著者一覧が帰ってくるはずです。このような場合には [HttpGet] のほうがよいです。
    • 一方で、「処理要求伝票データを送って、処理結果伝票データを受け取る」ような設計の場合には、処理結果が毎回変わる可能性があるため、[HttpPost](伝票を投入する)の方がベターです。

ただし注意点として、HTTP-GET プロトコルで Web API を呼び出す場合、jQuery ライブラリはブラウザ側で呼び出し結果を自動的にキャッシュします。これは HTTP プロトコルの規約の考え方からすると正しいのですが、その一方で、業務アプリだと HTTP-GET の場合でもキャッシュしてほしくない、という場合もあると思います。このような場合には、jQuery の $.ajaxSetup() 処理を使って、キャッシュを無効化してください。集約例外処理も含め、_Layout.cshtml ファイルに以下のようなコードを追加するとよいでしょう。


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewData["Title"]</title>
    <meta name="viewport" content="width=device-width, intial-scale=1.0" />

    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

    <script type="text/javascript">
    $(function () {
        $.ajaxSetup({
            cache: false,
            error: function (xhr, status, err) { alert("通信エラーが発生しました。"); } // 集約通信例外ハンドラ
        });

        window.onerror = function (message, url, lineNumber) {
            console.log(message);
            var msg = "処理中にエラーが発生しました。" + message;
            alert(msg);
            return true;
        };
    });
    </script>

    @RenderSection("Libraries", required: false)

(以下略...)

続いて、UI を実装します。/Views/Sample01/ShowAuthorsByState.cshtml ファイルを開き、以下を実装します。考え方は上と同じです。


@{ ViewData["Title"] = "州による著者データの検索"; }

@section Libraries {
    @Html.Partial("_ImportsLibraryKnockout")
}

<h4>州による著者データの検索</h4>

<div>
    <select id="ddlStates" data-bind="options: States"></select>
    <button id="btnShowAuthors">データ表示</button>
</div>

<hr />

<div class="table-responsive">
    <table id="tblAuthors" class="table table-condensed table-striped table-hover">
        <thead>
            <tr>
                <th>著者ID</th>
                <th>著者名</th>
                <th>電話番号</th>
                <th>州</th>
                <th>契約有無</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: Authors">
            <tr>
                <td data-bind="text: AuthorId"></td>
                <td data-bind="text: AuthorName"></td>
                <td data-bind="text: Phone"></td>
                <td data-bind="text: State"></td>
                <td>
                    <input type="checkbox" disabled data-bind="checked: Contract" />&nbsp;
                    <text data-bind="text: (Contract ? '契約あり' : '契約なし')"></text>
                </td>
            </tr>
        </tbody>
    </table>
</div>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            $("#tblAuthors").hide(); // css('display', 'none') と同じ

            // 後から値を入れたい場合には、ko.observable() と ko.observableArray() を割り当てておく
            var viewModel = {
                States: ko.observableArray(),
                Authors: ko.observableArray()
            };
            ko.applyBindings(viewModel);

            // サーバから州一覧を取り寄せてバインド
            $.getJSON("/Sample01/GetStates", function (result) {
                viewModel.States(result);
            });

            $("#btnShowAuthors").click(function () {
                // クエリ文字列を引数に渡すには、第二パラメータにオブジェクトを渡す
                $.getJSON("/Sample01/GetAuthorsByState", { state: $("#ddlStates").val() }, function (result) {
                    viewModel.Authors(result); // データを observableArray に流し込み
                    $("#tblAuthors").show(); // css('display', 'block') と同じ
                });
            });
        });
    </script>
}

できあがったら http://localhost:xxxx/Sample01/ShowAuthorsByState/ を呼び出して動作を確認してください。SPA 型で作られたデータバインド Web アプリケーションが動作します。

image

■ 様々な SPA 型アプリケーションの作り方

ここでは、ASP.NET Web API + ASP.NET MVC + jQuery + Bootstrap + knockout.js というライブラリの組み合わせにより SPA 型 Web アプリケーションを開発しました。しかし、ここまでのコードからわかるように、この方法は <table> タグを手で組み上げていく方法であるため、実装効率は必ずしもよくありません。業務アプリケーションのように、表が大量に出てくるようなケースでは、この方法では生産性がどうしても上がらないでしょう。このような場合には、ライセンス料は発生するものの、3rd party 製の高水準 UI ライブラリなどを使ったほうが生産性としてはよくなります。

クライアント側のライブラリをどのように選択するのかは、SPA 型 Web アプリケーション開発における大きな命題です。業務アプリケーションの場合には、下記のように高水準 UI 部品を必要とするか否かによって、基本的な方針を決めていくのがラクだと思いますが、最先端の開発技術を使って高度な UI を作っていくのであれば、まったく別の考え方を採ったほうがよいこともあります。

image

本エントリは ASP.NET Core 1.0 の概要を解説するものであるため、この部分についてはこれ以上踏み込みませんが、特にクライアント側のライブラリ選択や開発指針についてどのように考えていけばよいのかについては、de:code 2016 のセッション、およびそこからさらに発展させていただいている HTML5 experts さんのサイトが参考になると思います。より深い理解を進めたい方は、これらに目を通していくことをオススメします。

Part 6. ASP.NET MVC Coreによるデータ更新アプリ

$
0
0

では引き続き、更新系アプリを作成してみます。Web API でデータ更新アプリを作るのはちょっと骨が折れるので、まずは ASP.NET MVC Core でデータ更新アプリを作ってみることにします。若干遠回りではありますが、こちらをいったん理解しておくと、Web API でのデータ更新アプリの作り方も理解しやすくなるでしょう。

image image

■ ファイルの追加

ここまで作ってきたプロジェクトに /Controllers/Sample02Controller.cs、/Views/Sample02/ListAuthors.cshtml、/Views/Sample02/EditAuthor.cshtml ファイルを追加しておきます。

image

■ 著者一覧ページの実装

まずは Part 3 で解説した方法をもとに、著者一覧ページを作成しましょう。


using Decode2016.WebApp.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Decode2016.WebApp.Controllers
{
    public class Sample02Controller : Controller
    {
        [HttpGet]
        public ActionResult ListAuthors()
        {
            using (PubsEntities pubs = new PubsEntities())
            {
                var query = pubs.Authors
                            .Select(a => new AuthorOverview()
                            {
                                AuthorId = a.AuthorId,
                                AuthorName = a.AuthorFirstName + " " + a.AuthorLastName,
                                Phone = a.Phone,
                                State = a.State,
                                Contract = a.Contract
                            });
                ViewData["Authors"] = query.ToList();
            }
            return View();
        }
    }
}

ListAuthors.cshtml ファイル側では、著者データ編集ページに遷移できるように、著者 ID 部分をハイパーリンク化しておきます。例えば、”172-32-1176″ のデータをクリックした際には、/Sample02/EditAuthor/172-32-1176 に遷移するようにしておきます。このようにしておくと、URL の 3 つ目の値を、EditAuthor() アクションメソッドの id パラメータにより受け取ることができるようになります。(この辺については、ASP.NET MVC の書籍に詳しく解説されていますので、調べてみてください。)


@using Decode2016.WebApp.Models
@{
    ViewBag.Title = "編集対象の著者選択";
}

<h4>編集対象となる著者を選択してください。</h4>

@{
    if (ViewData["Authors"] != null)
    {
        var data = ViewData["Authors"] as List<AuthorOverview>;
        <div class="table-responsive">
            <table class="table table-condensed table-striped table-hover">
                <thead>
                    <tr>
                        <th>著者ID</th>
                        <th>著者名</th>
                        <th>電話番号</th>
                        <th>州</th>
                        <th>契約有無</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (AuthorOverview a in data)
                    {
                        <tr>
                            <td><a href="/Sample02/EditAuthor/@a.AuthorId">@a.AuthorId</a></td>
                            <td>@a.AuthorName</td>
                            <td>@a.Phone</td>
                            <td>@a.State</td>
                            <td><input type="checkbox" disabled @(a.Contract ? "checked" : "") /></td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>
    }
}

<hr />

<p>
    <a href="/">業務メニューに戻る</a>
</p>

■ データ編集ページの実装

続いてデータ編集ページを実装します。データ編集ページは、① 一覧ページからハイパーリンクで飛んできて画面が表示され(HTTP-GET)、② フォームデータを入力し、再度呼び出す(HTTP-POST)ことになります。EditAuthor ページ側の実装については、HTTP-GET, HTTP-POST のプロトコルで、初回/ポストバックのどちらであるのかを見分けるとよいでしょう。

image

さて、このような更新系アプリケーションの実装の厄介なところは、単体入力チェックを、ブラウザ上/サーバ側の両方で実装しなければならない点です。このポイントについては、以前、2009 年に書いたこちらのエントリの考え方と同じですが、ASP.NET MVC を使う場合、二重実装の回避には、より洗練された手法であるデータアノテーション方式を使います。

image

まず、入力フォームと同じデータ構造を持つ構造体クラス(ViewModel クラスと呼ばれます)を作成し、ここにデータアノテーションを使って、単体入力エラーチェックの内容を指定します。(ViewModel のコードはどこに定義してもよいですが、この Sample02 でしか利用しないので、Sample02Controller.cs クラスの内部クラスとして定義してしまうとよいでしょう。)


public class EditViewModel
{
    public string AuthorId { get; set; }

    [Required(ErrorMessage = "著者名(名)は必須入力項目です。")]
    [RegularExpression(@"^[\u0020-\u007e]{1,20}$", ErrorMessage = "著者名(名)は半角 20 文字以内で指定してください。")]
    public string AuthorFirstName { get; set; }

    [Required(ErrorMessage = "著者名(姓)は必須入力項目です。")]
    [RegularExpression(@"^[\u0020-\u007e]{1,40}$", ErrorMessage = "著者名(姓)は半角 40 文字以内で指定してください。")]
    public string AuthorLastName { get; set; }

    [Required(ErrorMessage = "電話番号は必須入力項目です。")]
    [RegularExpression(@"^\d{3} \d{3}-\d{4}$", ErrorMessage = "電話番号は 012 345-6789 のような形式で指定してください。")]
    public string Phone { get; set; }

    [Required(ErrorMessage = "州は必須入力項目です。")]
    [RegularExpression(@"^[A-Z]{2}$", ErrorMessage = "州は半角大文字 2 文字で指定してください。")]
    public string State { get; set; }
}

続いて、アクションメソッドを定義します。要点は以下の 2 つです。

  • アクションメソッドの引数
    • string id パラメータをつけておくと、URL の第 3 引数を受け取ることができます。
  • ビューへのデータ引き渡し
    • データベースからデータを取ってきて、EditViewModel クラスのインスタンスに代入し、return View() の引数とすることで、View 側にデータを引き渡すことができます。(※ 通常、ビューへは ViewData[…] を使ってデータを引き渡しますが、ViewModel クラスから JavaScript のエラーチェックロジックを動的に生成させたい場合には、後述するように *.cshtml 側の先頭に @model を定義し、データを return View() の引数として渡します。なお、コードからわかるように、ViewData[…] との併用は可能です。)

[HttpGet]
public ActionResult EditAuthor(string id)
{
    // 当該著者 ID のデータを読み取る
    Author editAuthor = null;
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = from a in pubs.Authors
                    where a.AuthorId == id
                    select a;
        editAuthor = query.FirstOrDefault();
    }

    // View に引き渡すデータを準備する
    EditViewModel vm = new EditViewModel()
    {
        AuthorId = editAuthor.AuthorId,
        AuthorFirstName = editAuthor.AuthorFirstName,
        AuthorLastName = editAuthor.AuthorLastName,
        Phone = editAuthor.Phone,
        State = editAuthor.State
    };

    // View にデータを引き渡すにあたり、入力データと周辺データを分けておく。
    // (ViewModel に周辺データを入れることで、ViewModel を完全にフォームモデルに一致させるように設計)
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = pubs.Authors.Select(a => a.State).Distinct();
        ViewData["AllStates"] = query.ToList();
    }

    return View(vm); // 編集画面を作成して返す
}

次に、*.cshtml ファイルを作成します。ASP.NET MVC では、jQuery Validation を内部で利用するため、JavaScript ライブラリを追加で組み込みます。後で再利用できるように、/Views/Shared/_ImportsLibraryValidation.cshtml と /Views/Shared/_ImportsStyleValidation.cshtml に共有ファイルとして切り出しておきましょう。

image

[/Views/Shared/_ImportsLibraryValidation.cshtml]

<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/localization/messages_ja.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js"></script>

[/Views/Shared/_ImportsStyleValidation.cshtml]


<style type="text/css">
    @@media only screen and (min-width : 0px) and (max-width : 767px) {
    }

    @@media only screen and (min-width : 768px) and (max-width : 991px) {
        dl {
            width: 738px; /* 750-12 */
            margin: 6px;
        }

        dl dt {
            float: left;
        }

        dl dd {
            margin-left: 200px;
        }
    }

    @@media only screen and (min-width : 992px) and (max-width : 1199px) {
        dl {
            width: 958px; /* 970-12 */
            margin: 6px;
        }

        dl dt {
            float: left;
        }

        dl dd {
            margin-left: 200px;
        }
    }

    @@media only screen and (min-width : 1200px) {
        dl {
            width: 1158px; /* 1170-12 */
            margin: 6px;
        }

        dl dt {
            float: left;
        }

        dl dd {
            margin-left: 200px;
        }
    }

    /* エラーメッセージ用 */
    /* jQuery unobtrusive validation 用 */
    .field-validation-error {
        color: #ff0000;
    }

    .field-validation-valid {
        display: none;
    }

    .input-validation-error {
        border: 2px solid #ff0000;
        background-color: #ffeeee;
    }

    .validation-summary-errors {
        font-weight: bold;
        color: #ff0000;
    }

    .validation-summary-valid {
        display: none;
    }

    /* jQuery Validation 用 */
    .error {
        color:red
    }
    input.error, select.error, textarea.error {
        border: 2px solid red;
        background-color: #ffeeee;
    }

</style>

続いて /Views/Sample02/EditAuthor.cshtml ファイルを実装します。(要点は後述)


@model Decode2016.WebApp.Controllers.Sample02Controller.EditViewModel
@{
    ViewBag.Title = "著者データの編集";
}
@section Libraries {
    @Html.Partial("_ImportsLibraryValidation")
}

@section Styles {
    @Html.Partial("_ImportsStyleValidation")
}

<h4>著者データを修正してください。</h4>

@using (Html.BeginForm("EditAuthor", "Sample02", new { id = Model.AuthorId }))
{
    <dl>
        <dt>著者ID</dt>
        <dd>@Model.AuthorId</dd>
    </dl>
    <dl>
        <dt>著者名(名)</dt>
        <dd>@Html.TextBoxFor(m => m.AuthorFirstName, new { data_val_specialnamecheck = "指定された名前(名・姓の組み合わせ)は使えません。" }) @Html.ValidationMessageFor(m => m.AuthorFirstName, "*")</dd>
    </dl>
    <dl>
        <dt>著者姓(姓)</dt>
        <dd>@Html.TextBoxFor(m => m.AuthorLastName, new { data_val_specialnamecheck = "指定された名前(名・姓の組み合わせ)は使えません。" }) @Html.ValidationMessageFor(m => m.AuthorLastName, "*")</dd>
    </dl>
    <dl>
        <dt>電話番号</dt>
        <dd>@Html.TextBoxFor(m => m.Phone) @Html.ValidationMessageFor(m => m.Phone, "*")</dd>
    </dl>
    <dl>
        <dt>州</dt>
        <dd>
            @{
                List<string> states = (List<string>)ViewData["AllStates"];
            }
            @Html.DropDownList("State", states.Select(s => new SelectListItem() { Text = s, Value = s, Selected = (s == Model.State) }))
        </dd>
    </dl>

    <p>
        <input type="submit" value="登録" />
        <input type="button" id="btnCancel" value="キャンセル" />
    </p>
    @Html.ValidationSummary("入力にエラーがあります。修正してください。")
}

<hr />

<p>
    <a href="/">業務メニューに戻る</a>
</p>


@section Scripts {

    <script type="text/javascript">
    $(function () {
        $("#btnCancel").click(function () {
            window.location = "@Url.Action("ListAuthors")";
            return false;
        });
    });
    </script>
}

上記コードの要点は以下の通りです。

  • モデルクラスの指定
    • ファイルの先頭に記述している “@model Decode2016.WebApp.Controllers.Sample02Controller.EditViewModel” が重要です。JavaScript エラーチェックロジックの動的生成機能を利用したい場合には、このモデルクラスの指定が必要になります。
  • 入力フォームの作成
    • データ入力フォームを作成するために、@using (Html.BeginForm(“EditAuthor”, “Sample02”, new { id = Model.AuthorId })) を使います。これにより、HTTP-POST でデータを送信するフォームを作成することができます。
  • 単体入力エラーチェック機能つきテキストボックスの作成
    • モデルクラスを指定した上で、@Html.TextBoxFor(m => m.XXX) という指定を行うことにより、入力エラーチェック機能つきテキストボックスを作成することができます。
  • 単体入力エラー一括表示部分の作成
    • @Html.ValidationSummary(…) 命令により、単体入力エラーを一括して表示する領域を作成することができます。

image

以上でクライアント側の実装は終わりです。続いて、登録ボタンを押下して HTTP-POST データを送信した際の、サーバ側の受け取りロジックを実装します。


[HttpPost]
public ActionResult EditAuthor(string id, EditViewModel model)
{
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = pubs.Authors.Select(a => a.State).Distinct();
        ViewData["AllStates"] = query.ToList();
    }

    // 送信されてきたデータを再チェック
    if (ModelState.IsValid == false)
    {
        // 前画面を返す
        // ID フィールドがロストしているので補完する
        model.AuthorId = id;
        return View(model);
    }

    model.AuthorId = id;

    // データベースに登録を試みる
    using (PubsEntities pubs = new PubsEntities())
    {
        Author target = pubs.Authors.Where(a => a.AuthorId == model.AuthorId).FirstOrDefault();
        target.AuthorFirstName = model.AuthorFirstName;
        target.AuthorLastName = model.AuthorLastName;
        target.Phone = model.Phone;
        target.State = model.State;

        pubs.SaveChanges();
    }
    // 一覧画面に帰る
    return RedirectToAction("ListAuthors");
}

コードの要点は以下の通りです。

  • ビューモデルによる送信データの受け取り
    • ブラウザから送られてくるデータは、public ActionResult EditAuthor(string id, string AuthorFirstName, string AuthorLastName, string Phone, …) などのようにして、パラメータを使って受け取ることができます。(これをパラメータバインディングと呼びます)
    • しかし、上記のコードに示すように、構造体クラスを使って一括して受け取ることもできます。(これをモデルバインディングと呼びます)
  • モデルバインディングによる単体入力エラーチェック
    • モデルバインディングを使ってデータを受け取った場合、データアノテーションで指定した単体入力チェックにエラーがあるか否かを、ModelState.IsValid メソッドで簡単に確認することができます。また、サーバ側で単体入力エラーが見つかった場合、ビューを使ってエラーメッセージつき画面を簡単に返すこともできます。

以上により、ViewModel クラスにデータアノテーションにより付与した単体入力エラーチェックロジックを、ブラウザ上での JavaScript チェックとサーバ側での再チェックの両方に利用したデータ更新アプリケーションを作成することができます。

image

なお、今回は話を簡単にするため、以下の 2 点については説明を割愛しています。興味がある方は、各自で調査・実装してみてください。

  • アンチリクエストフォージェリ対策
    • 現在の実装の場合、サーバ側ではねつ造されたフォーム送信データも受け取って処理してしまいます。(いわゆる「なりすまし書き込み」ができてしまう)
    • この問題を避けるため、ASP.NET MVC ではアンチリクエストフォージェリ機能が備わっています。([ValidateAntiForgeryToken()]) 非常に簡単にこの機能を使うことができるようになっていますので、必ず追加で実装するようにしてください。
  • 楽観同時実行制御機能
    • 現在の実装では、データベース上のデータが他のユーザにより書き換えられたとしても、何も考えずに上書き更新してしまいます。
    • これを避けるために、通常は楽観同時実行制御機能による制御ロジックを組み込みますが、今回は簡単のため、これを実装していません。EF Core でも楽観同時実行制御機能の利用は可能ですので、こちらもぜひ組み込んでみてください。

引き続き最後のエントリでは、同じアプリケーションを Web API 方式(SPA 型)で開発してみたいと思います。


Part 7. ASP.NET Web API による SPA 型データ更新アプリ

$
0
0

いよいよ最後のエントリです。引き続き、Part 6. で作成したアプリを SPA 型で実装してみることにします。SPA 型でのデータ更新アプリ実装で厄介なのは、下図に示すように、クライアント側では JavaScript、サーバ側では C# を使って単体入力チェックロジックを実装する必要があり、うっかりすると二重実装になってしまう点です。

image

これに関しては、Part 6. で解説した、ASP.NET MVC のモデルバリデーション機能を活用するとうまく回避することができます。では、具体的な作成方法について解説します。

■ ファイルの配置

まずはコントローラクラス、ビュークラスのファイルをそれぞれ配置しておきます。(Part 6. で配置した、入力チェック用の JavaScript, CSS 定義も利用します。)

  • /Controllers/Sample03Controller.cs
  • /Views/Sample03/ListAuthors.cshtml
  • /Views/Sample03/EditAuthor.cshtml

image

■ 著者一覧ページの実装

まずは Part 5 で解説した方法をもとに、著者一覧ページを作成しましょう。 これについては特に説明は不要でしょう。

[/Controllers/Sample03Controller.cs]


using Decode2016.WebApp.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Decode2016.WebApp.Controllers
{
    public class Sample03Controller : Controller
    {
        [HttpGet]
        public ActionResult ListAuthors()
        {
            return View();
        }

        [HttpGet]
        public List<AuthorOverview> GetAuthors()
        {
            using (PubsEntities pubs = new PubsEntities())
            {
                var query = pubs.Authors.Select(a => new AuthorOverview()
                {
                    AuthorId = a.AuthorId,
                    AuthorName = a.AuthorFirstName + " " + a.AuthorLastName,
                    Phone = a.Phone,
                    State = a.State,
                    Contract = a.Contract
                });
                return query.ToList();
            }
        }
    }
}

[/Views/Sample03/ListAuthors.cshtml]

@{
    ViewBag.Title = "編集対象の著者選択";
}
@section Libraries {
    @Html.Partial("_ImportsLibraryKnockout")
}

<h4>編集対象となる著者を選択してください。</h4>

<div class="table-responsive">
    <table id="tblAuthors" class="table table-condensed table-striped table-hover">
        <thead>
            <tr>
                <th>著者ID</th>
                <th>著者名</th>
                <th>電話番号</th>
                <th>州</th>
                <th>契約有無</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: authors">
            <tr>
                <td><a data-bind="text: AuthorId, attr: { href: 'EditAuthor/' + AuthorId }"></a></td>
                <td data-bind="text: AuthorName"></td>
                <td data-bind="text: Phone"></td>
                <td data-bind="text: State"></td>
                <td>
                    <input type="checkbox" disabled data-bind="checked: Contract" />&nbsp;
                    <text data-bind="text: (Contract ? '契約あり' : '契約なし')"></text>
                </td>
            </tr>
        </tbody>
    </table>
</div>

<hr />

<p>
    <a href="/">業務メニューに戻る</a>
</p>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            // ブラウザキャッシュ無効化 (これを入れておかないと、Edit ページから戻ってきたときにリストが更新されない)
            $.ajaxSetup({
                cache: false
            });

            $("#tblAuthors").hide(); // css('display', 'none') と同じ

            // 後から値を入れたい場合には、ko.observable() と ko.observableArray() を割り当てておく
            var viewModel = {
                states: ko.observableArray(),
                authors: ko.observableArray()
            };
            ko.applyBindings(viewModel);

            $.getJSON("/Sample03/GetAuthors", null, function (result) {
                viewModel.authors(result); // データを observableArray に流し込み
                $("#tblAuthors").show(); // css('display', 'block') と同じ

                console.dir(result); // 全体表示
            });

        });
    </script>
}

引き続き、SPA 型の編集ページを作成します。

■ 編集ページの作成

一般に、SPA 型で Web アプリを作成する場合、サーバ側からは静的な Web ページを返します。しかし、データ入力チェック用の JavaScript を含む Web ページを作成するのは面倒なため、Part 6. で解説した、@model + @Html.TextBoxFor(m => m.XXX) を利用して、動的に C# ViewModel クラスから JavaScript チェックロジックを生成してクライアントに送り返します。

image

ただし、以下の点に注意してください。

  • 実際に画面に表示するデータは、HTTP-GET で画面を取り寄せたのち、ASP.NET Web API 経由でサーバから取り出します
  • また、登録ボタンを押した際のデータ送信は、通常の form submit ではなく、JavaScript による制御を行います

では、段階的に実装していきましょう。まず、Sample03Controller.cs クラスの内部クラスとして、UpdateAuthorRequest クラスを追加します。

public class UpdateAuthorRequest
{
    [Required]
    [RegularExpression(@"^[0-9]{3}-[0-9]{2}-[0-9]{4}$")]
    public string AuthorId { get; set; }

    [Required(ErrorMessage = "著者名(名)は必須入力項目です。")]
    [RegularExpression(@"^[\u0020-\u007e]{1,20}$", ErrorMessage = "著者名(名)は半角 20 文字以内で指定してください。")]
    public string AuthorFirstName { get; set; }

    [Required(ErrorMessage = "著者名(姓)は必須入力項目です。")]
    [RegularExpression(@"^[\u0020-\u007e]{1,40}$", ErrorMessage = "著者名(姓)は半角 40 文字以内で指定してください。")]
    public string AuthorLastName { get; set; }

    [Required(ErrorMessage = "電話番号は必須入力項目です。")]
    [RegularExpression(@"^\d{3} \d{3}-\d{4}$", ErrorMessage = "電話番号は 012 345-6789 のような形式で指定してください。")]
    public string Phone { get; set; }

    [Required(ErrorMessage = "州は必須入力項目です。")]
    [RegularExpression(@"^[A-Z]{2}$", ErrorMessage = "州は半角大文字 2 文字で指定してください。")]
    public string State { get; set; }
}

Part 6 で使った EditViewModel クラスとほぼ同じですが、以下の点が異なります。

  • この構造体は、入力フォームではなく、ASP.NET Web API で受け取るデータ構造に合わせて作ります
  • 著者 ID は画面上での入力項目ではありませんが、ASP.NET Web API で受け取るデータ項目であるため、サーバ側での単体入力チェックが必要です。このため、データアノテーションによるチェックロジックを付与してあります。

続いて、ビューを送出するコード、及び画面に表示する著者データを送り返す Web API をコントローラクラスに作成します。

[HttpGet]
public ActionResult EditAuthor(string id)
{
    if (Regex.IsMatch(id, @"^[0-9]{3}-[0-9]{2}-[0-9]{4}$") == false) throw new ArgumentOutOfRangeException("id");
    ViewData["AuthorId"] = id;
    return View(new UpdateAuthorRequest());
}


[HttpGet]
public Author GetAuthorByAuthorId(string authorId)
{
    if (Regex.IsMatch(authorId, @"^[0-9]{3}-[0-9]{2}-[0-9]{4}$") == false) throw new ArgumentOutOfRangeException("authorId");

    // 当該著者 ID のデータを読み取る
    Author editAuthor = null;
    using (PubsEntities pubs = new PubsEntities())
    {
        var query = from a in pubs.Authors
                    where a.AuthorId == authorId
                    select a;
        editAuthor = query.FirstOrDefault();
    }
    return editAuthor;
}

続いて、ビューを実装します。とりあえず、画面上にデータを採ってきて表示するところまで。

@model Decode2016.WebApp.Controllers.Sample03Controller.UpdateAuthorRequest
@{
    ViewBag.Title = "著者データの編集";
}
@section Libraries {
    @Html.Partial("_ImportsLibraryValidation")
}

@section Styles {
    @Html.Partial("_ImportsStyleValidation")
}

<h4>著者データを修正してください。</h4>

<form id="frmInput">
    <dl>
        <dt>著者ID</dt>
        <dd>@ViewData["AuthorId"]</dd>
    </dl>
    <dl>
        <dt>著者名(名)</dt>
        <dd>@Html.TextBoxFor(m => m.AuthorFirstName, new { data_val_specialnamecheck = "指定された名前(名・姓の組み合わせ)は使えません。" }) @Html.ValidationMessageFor(m => m.AuthorFirstName, "*")</dd>
    </dl>
    <dl>
        <dt>著者姓(姓)</dt>
        <dd>@Html.TextBoxFor(m => m.AuthorLastName, new { data_val_specialnamecheck = "指定された名前(名・姓の組み合わせ)は使えません。" }) @Html.ValidationMessageFor(m => m.AuthorLastName, "*")</dd>
    </dl>
    <dl>
        <dt>電話番号</dt>
        <dd>@Html.TextBoxFor(m => m.Phone) @Html.ValidationMessageFor(m => m.Phone, "*")</dd>
    </dl>
    <dl>
        <dt>州</dt>
        <dd><select id="State" name="State"></select></dd>
    </dl>

    <p>
        <input type="button" id="btnUpdate" value="登録" />
        <input type="button" id="btnCancel" value="キャンセル" />
    </p>
    @Html.ValidationSummary("入力にエラーがあります。修正してください。")

    <p id="lblErrorMessage" class="error">
    </p>
</form>

<hr />

<p>
    @Html.ActionLink("業務メニューに戻る", "Index", "Home", new { area = "Common" }, null)
</p>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            var authorId = "@ViewData["AuthorId"]";
            var authorToEdit = null;

            $.getJSON("/Sample03/GetAllStates", null, function (result) {
                $.each(result, function () {
                    $("#State")
                        .append($("<option></option>")
                        .attr("value", this)
                        .text(this));
                });
                $.getJSON("/Sample03/GetAuthorByAuthorId", { authorId: authorId }, function (result) {
                    $("#AuthorFirstName").val(result.AuthorFirstName);
                    $("#AuthorLastName").val(result.AuthorLastName);
                    $("#Phone").val(result.Phone);
                    authorToEdit = result;
                    $("#State option[value='" + result.State + "']").attr("selected", true);
                });
            });

            $("#btnUpdate").click(function () {
                // 後述
            });

            $("#btnCancel").click(function () {
                window.location = "@Url.Action("ListAuthors")";
                return false;
            });
        });
    </script>
}

いくつかポイントがありますので解説します。

  • <form> タグについて
    • このページから実際にデータ更新を行う場合は、フォームによるデータ送信ではなく、jQuery を使って Web API へデータを送信することにより処理を行います。この部分だけ見ると <form> タグは不要です。
    • が、実際には単体入力エラーチェックに(内部的に)jQuery Validation を利用します。jQuery Validation では入力フォームが <form> タグで囲まれていることが必要なため、ダミーで <form> タグを差し込んでいます。
  • 登録ボタンについて
    • <form> の送信機能を利用するわけではないので、<input type=submit> ではなく、<input type=button> で定義してください。
  • JavaScript 内での authorId の拾い方について
    • 上記のコードでは、var authorId = “@ViewData[“AuthorId”]”; というコードを記述しており、サーバ側で MVC ビューページを作成する際に著者 ID を埋め込むようにしています。これは、厳密にいえば SPA 型の作り方としては不完全です(URL から取り出して使うのが正しい)。
    • ……が、クライアント側 JavaScript チェックロジックの生成に MVC ビューページを使っているので、ここだけ頑張ってもあまり意味がありません;。なので、サーバ側で MVC ビューページを作成する際に著者 ID を埋め込む形で十分だと思います。

では最後に、サーバ側へのデータ送信処理を記述します。ASP.NET Web API では、MVC と同じモデルバインディングとそれによる単体入力エラーチェック機能を利用することができます。このため、以下のようにすれば簡単にサーバ側でのデータ再チェックを行うことができます。(※ サーバ側でのエラー発覚時は不正クライアントからのアクセスとみなせるため、例外を発生させて処理を止めてしまえば十分です。)

[HttpPost]
public void UpdateAuthor(UpdateAuthorRequest request)
{
    if (ModelState.IsValid == false) throw new ArgumentException();

    // データベースに登録する
    using (PubsEntities pubs = new PubsEntities())
    {
        Author target = pubs.Authors.Where(a => a.AuthorId == request.AuthorId).FirstOrDefault();
        target.AuthorFirstName = request.AuthorFirstName;
        target.AuthorLastName = request.AuthorLastName;
        target.Phone = request.Phone;
        target.State = request.State;
        pubs.SaveChanges();
    }
}

一方、クライアント側については、ボタン押下時に自力で jQuery Validation を呼び出して入力フォームをチェックし、そのうえでサーバに対してデータ送信するようなコードを記述します。(※ エラーサマリメッセージのクリアが厄介ですが、これは ASP.NET MVC の入力検証機能が Web API/SPA 型アプリでの利用を想定していないためです。なのでここはハックする形での実装になります。)

$("#btnUpdate").click(function () {
    if ($("#frmInput").valid() == true) {
        // エラーサマリメッセージをクリア
        $("#frmInput").find("[data-valmsg-summary=true]").removeClass("validation-summary-errors").addClass("validation-summary-valid").find("ul").empty();

        $.post(
            "/Sample03/UpdateAuthor",
            {
                AuthorId: authorId,
                AuthorFirstName: $("#AuthorFirstName").val(),
                AuthorLastName: $("#AuthorLastName").val(),
                Phone: $("#Phone").val(),
                State: $("#State").val()
            },
            function (result) {
                window.location = "@Url.Action("ListAuthors")";
            }
        );
    }
});

以上でアプリを動作させると、SPA 型でも単体入力チェックの二重実装を回避することができます。

image

■ 参考&補足事項

なお、今回は話を簡単にするため、ASP.NET MVC と同様、以下の 2 点については説明を割愛しています。興味がある方は、各自で調査・実装してみてください。

  • アンチリクエストフォージェリ対策
    • ASP.NET Web API の場合であっても、やはりアンチリクエストフォージェリ対策をしておくことが望ましいです。
    • 上記のコードからわかるように、ASP.NET Web API の内部動作は、ほとんど ASP.NET MVC と同じです。よって、アンチリクエストフォーじぇり機能も Web API で転用することが可能です。
  • 楽観同時実行制御機能
    • ASP.NET MVC 同様、Web API の場合でも楽観同時実行制御について考えることが望ましいですが、ちょっと厄介なのは、特に SPA 型でアプリを作る場合、更新前の状態のデータをどこにどうやって残すのか、という点です。
    • ASP.NET MVC ではサーバ側に Session オブジェクトで残す、という実装方法が最も簡単ですが、Web API で同じことをすると、サーバ側 API をステートレスにすることができません。このため、基本的にはクライアント側で、更新前の状態を残しておき、これをサーバに(更新したデータとともに)渡して処理してもらう必要があります。
    • 具体的なやり方としては、シリアル化したデータを Base64 エンコードしてクライアント側に渡しておき、それをサーバ側に再送してもらってデシリアル化して使う、という方法になりますが、シリアル化/デシリアル化などの処理を自作しなければならないこともあり、ちょっと面倒です。とはいえ実際の開発では必要になるので、力試しも兼ねて頑張って実装してみてください。

また上記の実装では、ASP.NET MVC の入力検証機能を ASP.NET Web API に転用しているのですが、もともとこの方法は非サポートな方法です。このため、現在のバージョンでは動作しますが、今後の実装でも動き続けるかどうかはわかりません;。ただ、今回それでも敢えてこの実装方法を示したのは、実際のシステムで更新系アプリを作る場合、サーバ/クライアントでのロジック二重実装をどうやって避けるのかは必ず考えなければならない問題であるからです。これに対する考え方は何通りかに分かれます。

  • 上記のような実装を、自己責任で行う。
  • 3rd party 製ライブラリ(SPA ライブラリ)でこの問題を解決するものを探して使う。
  • あきらめて、サーバ側とクライアント側を二重実装する。
  • 更新系アプリについては、SPA 型での実装をあきらめ、Part 6 で示した MVC 型での実装にする。(=参照系アプリのみ SPA 型で実装する)

実際には一番最後の手法も一考の余地があります。というのも、SPA 型アーキテクチャにしてリッチにしたい UI の多くは、参照系であることが多いためです。この辺は、実際に開発しようとするアプリによって最適な選択肢が変わると思いますが、いずれにしても無理に SPA 型実装にこだわりすぎないようすることも、業務アプリ開発の場合には重要です。全体のバランスを見ながら、最適な実装指針を決めていただければと思います。

■ さらに学習を進めたい方へ

……というわけで、ASP.NET Core 1.0 を使った Web アプリの開発手法についてここまで解説してきました。今回はあくまで入門編、ということで、わかりやすい実装を心がけていますが、実際のシステム開発では DI コンテナを活用するなど、いろいろ知っておくと便利なテクニックもたくさんあります。さらに ASP.NET Core の学習を進めたい場合には、以下のようなリソースをぜひ参照してみてください。

  • ASP.NET Core Documentation
    • https://docs.asp.net/en/latest/
    • ASP.NET Core の公式ドキュメント。最も詳しくてわかりやすいので、詰まった場合は必ずここをチェック。
  • GitHub ASP.NET
    • https://github.com/aspnet
    • ASP.NET Core のソースコード。この中にテストコードが含まれており、それを見るとライブラリの使い方が確認できたりする。
    • ロードマップ情報や設計・実装に関する議論もすべてここでオープンになって開発されているため、突っ込んだ学習がしたい場合にはここを参照するとよい。

Part 1. Windows 10 の導入と WaaS モデルへの対応

$
0
0

[Windows 10 の現状]

Windows 10 は 2015/07/29 にリリースされていますが、この blog を書いている 2016 年 8 月時点では、特にエンタープライズ系企業での導入はまだ十分に進んでいるとは言えません。大きな背景としては、日本の大企業ではハードウェアの老朽更新に併せて OS 入れ替えを行うことが多いこと、ハードウェアの買い替えサイクルが諸外国より長いことがあるのですが、とはいえ、以下のようないくつかの要因から、昨今、次期端末の購入・導入計画に併せて Windows 10 の導入計画を進めているお客様が非常に増えてきています。

  • Windows 10 のリリースから約 1 年間が経過している(のでそろそろ安定しているだろうw)
  • 昨今のセキュリティ脅威に対する安全性を高めるために OS を最新化したい
  • 多彩な Windows 10 デバイスとモバイルワークスタイルで、社員の生産性を高めたい

image image

今年から来年にかけて多くの企業で Windows 10 の導入が進むことを願っているのですが、注意すべき点として、Windows 10 の導入は、単に OS を入れ替えればよい、というものではありません。必ず WaaS (Windows as a Service)モデルへの適応方法を検討しておく必要があります。

[WaaS とは何か]

WaaS(Windows as a Service)とは、OS のアップデートに関して、従来のような「数年に一度の大型アップデート」というモデルをやめ、「より頻繁に、小さな改善を積み重ねていく」というモデルに変え、オンラインでいち早く皆様に提供していこう、というものです。実際、Windows 10 は 2015/07/29 の提供後、すでに 2 回の機能アップグレードを行っており(November Update 及び Anniversary Update)、アップグレードの提供頻度が従来と大きく変わっていることがわかります。

この WaaS というモデルを Windows OS に導入した理由・背景はいくつかあります。代表的なものは以下の 3 つです。

  • IT のコンシューマ化
    • 昨今、我々は iPhone, iPad, Android (もちろん Windows Mobile も!)をはじめとするスマートデバイスを多用しています。こうしたスマートデバイスの世界では、OS を最新化して使うことは常識と化しています。
    • 例えば iOS の場合、OS シェアが開発者向けページに掲載されていますが、ほとんどのデバイスが最新 OS を使っていることがわかります。これは、会社に戻ると依然として 2 世代前の Windows 7 を使っている……という Windows PC の世界とは全く違う世界ですが、今のスマホネイティブ世代にとっては、PC の世界はいかにもレガシーです。
  • セキュリティ脅威の進化
    • 詳細は次のパートで解説しますが、近年は民間企業を対象にしたサイバー攻撃が激化しており、その攻撃手法の進化はすさまじいものがあります。こうした攻撃手法の進化に追随するためには最新の対策が必要であり、そのためには必然的に OS の最新化が必要になります。
    • この話を聞くと、「古い OS でもセキュリティパッチを全部当てればいいんでしょ?」と単純に考える方も少なからずいますが、セキュリティパッチというのはその名の通り、脆弱性の穴を塞ぐためのものです。抜本的に、仕組みレベルからの安全性の強化を行うためには、どうしても OS やハードウェアの最新化が必要になります。
  • テクノロジーの進化
    • ご存じのように、昨今のハードウェア技術とソフトウェア技術はどんどん進化しており、利用者の生産性を高める最新技術が次々と出てきています。
    • こうしたものを貪欲に取り込んでいかないと、新しいサービスをお客様に提供していくことは困難です。

こうした時代背景を元に、Windows 10 では、新しい機能(機能追加や機能強化)を、いち早く、オンラインでのアップデートとしてこまめに提供していくこととなった、というのが WaaS(Windows as a Service)です。

image

[Windows 10 導入時に考えるべきこと]

このことからわかるように、Windows 10 への移行というのは、言い換えれば、継続的に最新化されていく IT インフラへの移行である、ということができます。このため、従来とは異なる考え方やアプローチが必要になる部分も多々出てきます。そのために何を理解し、何を考えていけばよいのか? を整理すると、以下のようになります。

  • Part 2. 代表的な Windows 10 導入の目的
    • Windows 10 の導入を始めるにあたって、どのようなメリットを得るのかを予め検討しておく必要があります。
    • これを決めておかないと、そもそも Windows 10 導入の予算が取れなかったり、Windows 10 導入時にどこを重点的に検討すべきなのかが決められません。
  • Part 3. WaaS の正しい理解
    • 「継続的に最新化される」といっても、どのような形でどんなアップグレードやアップデートがどのような頻度で降ってくるのか、を理解しておく必要があります。
    • この際、機能アップグレードの受け取り方の選択肢(IP/CB/CBB/LTSB)とその使い分けが極めて重要になるため、これを理解します。
  • Part 4. Windows 10 導入時の検討の流れ
    • Windows 10 の導入では、アプリ/インフラの両面での検討が必要になり、またすぐに対応できること/できないことが出てきます。
    • これらをどのように組み立てて導入検討を進めるべきなのかを理解します。
  • Part 5. Windows 10 導入時に考え方・やり方を変えるべきポイント – アプリ編
    • アプリ面では、「半年に一度の互換性検証」をどのように効率的にやるかを考えなければなりません。
    • このための基本的な考え方である、① テストケース絞り込み、② 段階展開によるリスクヘッジ、を理解します。
  • Part 6. Windows 10 導入時に考え方・やり方を変えるべきポイント – インフラ編
    • インフラ面では、過去の「負の遺産」ともいうべき旧態依然としたやり方を見直すことが重要です。(パラメータシートとか;)
    • Windows 10 導入時に、従来と特に考え方が変わるポイントを理解します。
  • Part 7. Windows OS の変更ログの入手方法
    • アプリ/インフラどちらに関しても、OS 更新時に「何が変わったのか?」を把握することが必要になります。
    • この情報の入手方法を理解します。

なお、皆様が IT Pro なのか Dev なのかによって、興味のあるパートが異なると思いますが、どちらであっても、可能な限りすべてのパートにざっと目を通してください。というのも、Windows 10 WaaS では、インフラとアプリが互いに影響を及ぼしうる部分があるため、互いのことを知らないと正しいアプローチができないからです。細かく理解しなくても構いませんので、とにかくざっと要点だけつかむようにしていただくことをオススメします。

(以下は余談です)

実は上記のポイントは、結構マイクロソフトの社内でも問題になっている部分ではあったりします。というのも、例えば Windows OS の展開支援はインフラ系コンサルのみで行うのがかつての常識だったのですが、Windows 10 の WaaS に関しては継続的なアプリ互換性検証が問題になるため、私のようなアプリ系のコンサルが関わらなければならないケースというのが増えているのです。要点さえ理解できれば実は全く難しくないのですが、IT Pro であってもある程度はアプリのことを知らなければならないし、 Dev であってもある程度は WaaS のことを知らなければ、これからの時代は通用しなくなってくる、ということだと思います。ぜひ一連のエントリを読んでいただいて、最低限知っておくべき知識を身に着けていただければと思います。

Part 2. 代表的な Windows 10 導入の目的

$
0
0

[Windows 10 導入目的の明確化の必要性]

Windows 10 を導入する目的は、おそらくお客様ごとで様々に異なるでしょう。ハードウェアサポート切れなどの消極的な理由で Windows 10 を導入する、という場合もあるでしょうし、一方で積極的に Windows 10 の最新技術を使いたい、という場合もあるかもしれません。ただ、いずれの場合であっても、Windows 10 の導入を始めるにあたって、どのようなメリットを得るのかを予め検討しておかないと、そもそも Windows 10 導入の予算が取れなかったり、Windows 10 導入時にどこを重点的に検討すべきなのかが決められません。

Windows 10 導入の代表的な目的としては、以下の 3 つが挙げられます。いずれも IT インフラ管理者の目線ではなく、「お客様目線(エンドユーザ目線)」でのメリットですが、こうした目線で訴求ポイントをまとめておくと、その後の検討や提案も進めやすくなると思います。

image

以下、順番に見ていきたいと思います。

[1. セキュリティ強化]

サイバー攻撃は直近 10 年で大きく変化しています。従来は単なるいたずらを動機としていたものが、現在では利益やテロが目的となっており、その攻撃規模も従来とは比べ物にならないほど大きくなっています。

image

恐ろしいことに、現在ではゼロデイ脆弱性(パッチが提供されていない脆弱性)を売買する企業が存在していたり、さらにそれを元にマルウェアを開発するためのツールキットも存在しており、超有能なクラッカーでなくてもサイバー攻撃ができる環境が整いつつあります。サイバー攻撃による ROI は 1000% 以上などとも言われており、さらには企業の約 9 割は未知の脅威が侵入済みであるといった話まで存在します。

この辺については、de:code 2016 のセキュリティ関連セッションが詳しいため詳細はそちらに譲りたいと思いますが、重要なポイントとして、最新の攻撃への一番効果的な対策は、最新の OS への乗り換えである、という点を理解しておく必要があります。

  • 典型的な誤解として、セキュリティパッチをすべて当てさえすれば大丈夫、というものがあります。もちろん、セキュリティパッチを適切に当てていくことは基本中の基本ですが、これはあくまで「現在の『穴』を塞ぐ」というものでしかなく、「根本的な安全性を高める」ことはセキュリティパッチではできない、という点を理解する必要があります。
  • 例えば Credential Guard という仮想化ベースセキュリティの仕組みは、Pass-the-Hash と呼ばれる攻撃への安全性を大きく高めますが、これは内部的に Hyper-V を使って「やり方そのもの」を変えています。パッチだけでどうにかなるものではありません。
  • 例え話ですが、車の安全性を高めるためにエアバックを取り付けよう……というときに、50 年前の車に取り付けることはまず無茶でしょう;。でも、最新の車であれば、エアバックなんて当然のように標準装備です。

一例として、米国防総省は、400 万台の PC を Windows 10 にアップグレードする、という基本方針を決めました。仔細はリンク先を見ていただけるとよいと思いますが、その際の決定のキーポイントは以下の 2 点だったそうです。

  • 最新・最高レベルのセキュリティを持つ OS の活用
  • 単一のプラットフォーム利用により、運用環境の合理化とコスト削減を同時に実現

Windows 10 やマイクロソフトの製品が安全なのかどうか? という点については人により意見が分かれるところでしょうが、ただ、米国防総省は全世界で最もサイバー攻撃を受けているといわれており(ちなみに二番手はマイクロソフトだそうです;)、そうした組織が上記のような判断をしている、というところはひとつ参考になる部分だと思います。

[2. モバイルワークスタイル]

日本の労働人口の生産性は、先進諸国の中では群を抜いて低い、という話を聞かれたことがある方も多いかと思います。実際、仕事がとにかく大変、と実感されているサラリーマンの方は非常に多いのではないかと思いますが、今のやり方を変えなければ、その延長線上にある未来はとても明るいとは言えないでしょう。今後の日本は少子高齢化により、社会保障費の増大・貯蓄率低下・投資率低下などが進むことが見えており、従来のような労働集約型産業は成立しなくなっていきます。となると、必然的に、老若男女が共に働き、高い生産性で(=短時間で)稼ぎ、さらにに多様な働き方を認めることが必要になってくるはずです……が、日本は特にホワイトカラー(知的労働者)の生産性が依然として低いのが実態です。(これに関しては、樋口さんのこちらのエントリが非常にわかりやすいです。一読の価値があるかと。)

ホワイトカラーの生産性を高める手法は様々にありますが、その一つとして注目されているのが、モバイルワークスタイルと呼ばれる考え方です。簡単に言えば、タブレット PC とモバイルルータとスマホ、そしていつでもどこでも使えるアプリを使って、いつでもどこでも仕事ができるようにすることで、1 日 24 時間をより柔軟かつ効率的に使いましょう、というワークスタイルです。

image

このワークスタイルは、例えばマイクロソフトの社員はまさに日々実践しており、私自身も正直これがなければ生きていけない、と実感するほど極めて重要なものになっています。例えば私の場合、少し前に息子が生まれたのですが、

  • 朝は少し家事を手伝いつつ頭の中で情報を整理し、自宅である程度仕事をこなして作業が行き詰まったところでラッシュアワーを避けて出社。
  • 電車に乗りながらアイディアを練り込み、会社についたら資料作成を継続。
  • そして早めに自宅に帰って子供をお風呂に入れ、家族で食事を取ってから再び仕事に戻る。

なんていうワークスタイルを取っているのですが、こういうワークスタイルが取れるからこそ日常を回していける、という実態があります。

こうしたモバイルワークスタイルの話をすると必ず出てくるのが、人や制度に関わる課題、あるいは情報漏えいなどに関わるセキュリティ上のリスクなどです。これらの懸念があるのは当然ですし、それをヘッジするためにルールを設けるのは当然のことですが、必要以上にがんじがらめにして社員の生産性を損なうようでは本末転倒です。(概してルールを決める人がリスクを取りたくないが故に、がんじがらめにしすぎる傾向がありますが、この点については意思決定者の方にはホントに考えてほしいところです、いやマジで;。)

image

特にセキュリティに関しては後ろのパートでも触れていきたいと思いますが、近代的なセキュリティの考え方は、「安全性と利便性の両方を取る」「ルールを増やすのではなく技術をうまく活用する」のが基本です。最も高度なセキュリティを要求される銀行でも、従業員の業務効率化やワークスタイル変革に向けた動きを始めているほどで、例えば三井住友銀行様では Windows 10 とパブリッククラウドを活用した改革へと舵を切っています(情報はこちら)。Windows 10 の導入は非常によいタイミングでもあるので、ワークスタイル変革についてもぜひ検討してみてください。

[3. 多彩なデバイス活用]

前述したモバイルワークスタイルはユビキタスコンピューティングとの両輪で実現されるもので、豊富なクラウドサービス、いつでも利用可能な高速モバイル回線、そして多彩なデバイスによって下支えされています。マイクロソフトもこうした「多彩なデバイス」の重要性を認識しており、現在ではウェアラブルから大型デバイスまでを、Windows 10 というシングル OS でサポートする、という環境を実現しています。

image

こうした多彩なデバイスを、状況に応じて使い分けたり併用したりすることが、特にモバイルワークスタイルなどでは必要になってきますが、この際、マルチデバイスに対する重複開発の問題をいかに最小限に抑え込むかが重要になります。この点に関しては、UWP(Universal Windows Platform)を使えば、複数の Windows 10 デバイスに対して一つのバイナリで対応するアプリケーションの開発ができる、というポイントが大きなメリットになります。(もちろん、複数のスクリーンサイズに対応するためのレスポンシブ対応などの工夫は必要になりますが、開発技術が同じであり、ロジックなどを使いまわせるメリットは非常に大きいです。)

※(注記) 少し話が逸れますが、マルチデバイスアプリなら UWP ではなく HTML5 での開発ではないか? と考える方も稀にいますが、HTML5 ではネイティブアプリ開発技術ほどの開発生産性や操作性を実現できません。例えば UWP, Xamain, HTML5 の 3 つの技術を比較すると、これらは開発生産性やユーザビリティと、リーチ性の間でトレードオフの関係を持ちます。ここではこれ以上深掘りはしませんが、なんでも HTML5 で作ればよい、という発想は、業務アプリ開発の世界では必ずしも正しくないことに注意してください。

こうした多彩なデバイスの活用は、ビジネスのやり方をも変える力を持ち得ます。下図はデバイスの活用による業務改革の例ですが、① 社員の生産性の改善、② オペレーションコストの低減、③ お客様サービスの改善、といったシナリオでデバイス活用を考えてみると、様々なアイディアが出てくるのではないかと思います。Windows 10 の導入に併せて、モバイルワークスタイルだけでなく、こうした業務改善・改革も検討してみていただくとよいでしょう。

image

ここでは、代表的な Windows 10 導入の目的を 3 つ例として挙げましたが、いずれにせよ、こうした訴求メットを明らかにしておかないと、せっかくの Windows 10 の導入も「単なる OS 入れ替え」だけに留まってしまいます。新しい OS を入れるのであれば、新しいメリットをぜひ入れられるよう検討してみてください。

Part 3. WaaS の正しい理解

$
0
0

さて、ここからいよいよ本題である WaaS の解説に移っていきます。

[WaaS の正しい理解 – Windows 10 における OS 更新モデル]

ここまで解説したきたように、「Windows 10 の導入」とは、「継続的にアップグレードされ続ける環境への移行」でもあります。このため、Windows 10 の導入に際しては、『導入後にどのようになるのか?』を理解・検討しておくことが極めて重要です。まずは、Windows 10 移行後の、WaaS による継続的更新(機能追加)の仕組みについて理解しましょう。

image

[4 つの OS 更新モデル(IP, CB, CBB, LTSB)]

ご存じのように、Windows には Home, Enterprise, Pro などの「エディション」という分類が存在しますが、これとは別にもう一つ重要な分類として、「機能追加の受け取りタイミング」という選択肢が存在します。例えばこれまで、Windows 10 は November Update、Anniversary Update という機能追加が実施されているのですが、この機能追加をいつのタイミングで受け取るのかを調整することができるようになっています。具体的には、以下の 4 つの選択肢があります。この 4 つの選択肢については(名称も含めて)暗記してください。

名称 この資料での略称 概要 主な利用者
Insider Preview IP 機能追加の受け取り時期を早める 一部の早期利用ユーザ
Current Branch CB 4~8 ヶ月間隔で OS の機能追加を行う 一般のコンシューマユーザ
Current Branch for Business CBB 機能追加の受け取り時期を 4 ヶ月間 遅らせる 一般的なビジネスユーザ
Long-term Servicing Branch LTSB 機能追加を一切受け取らない 特定用途の固定端末

これらの考え方ですが、CB を基準にして考えると非常にわかりやすいです。

  • CB : コンシューマユーザには、約 4~8 ヵ月の間隔で、OS の機能追加が行われる
  • IP, CBB : 機能追加の受け取り時期を、ある程度、早める or 遅らせることができる
  • LTSB : 特定用途の端末向けに、機能追加を一切受け取らない方式も提供される

企業内では、用途に応じてこの 4 つの選択肢を使い分ける必要があります。すなわち、以下のようになります。

  • 一般的なビジネスユーザ(大多数の従業員)は CBB を利用することで、コンシューマ市場で「4 ヶ月間揉まれて安定性を高めた」 Windows OS を利用することができる。
  • パワーユーザーは、CB や IP を利用することで、早い段階で OS の最新機能を使ったり試したりすることができる。
  • 一方で、特定用途の固定端末では、LTSB を使うことで、「ずっと変わらない Windows」を使うことができる。

 

image

なお、LTSB に関しては一般的な OA 端末での利用を想定していないため、利用に際しては十分な注意が必要です。このため、LTSB に関しては IP/CB/CBB とは分けてこのエントリの後ろの方で詳しく解説します。

上に示した IP/CB/CBB の違いをより詳しく理解するために、ここまでの Windows OS のリリースの流れを見てみると、次のようになります。

image

要点は以下の通りです。

  • まずは CB のリリースの流れに着目すると理解しやすい
    • 従来のサービスパックに相当する「機能アップグレード」が 4~8 ヶ月おきに登場
      • 当初想定では 4 ヶ月おきだったが、実際には約半年に 1 回程度のペースで機能アップグレードが行われている
      • この「機能アップグレード」により、機能の追加・改善が行われる(例:Cortana など)
  • これに対して、IP を利用すると、開発中の β 版を先行して利用できる
  • 逆に CBB を利用すると、 4 ヶ月間コンシューマ市場で揉まれた後の、より安定化した OS を利用することができる

すなわち、CBB = CB + 4 ヶ月分の累積パッチ、であり、CB/CBB の機能的差異はありません。ここは非常に重要なポイントなので、必ず押さえておいてください。

※ また、上記の各機能アップグレードのコード名とバージョン番号/ビルド番号についても覚えておくとよいでしょう。マイクロソフトの資料では、これらの名称がよく出てくるためです。

  • 無印(初回リリース):TH1, 1507, 10240
  • November Update : TH2, 1511, 10586
  • Anniversay Update : RS1, 1607, 14393

[IP/CB/CBB の切り替え方法]

上で述べたように、IP/CB/CBB の違いは、機能アップグレードの受け取りタイミングの違いにあります。IP/CB/CBB は、OS の設定オプションから切り替えることができます。

image

また、Insider Preview に関しては、受け取り頻度を Fast/Microsoft/Slow などの中から選択することができるようになっています。Insider Preview は開発中の中間ビルドを受け取るオプションであり、場合によっては極めて不安定なビルドを受け取ってしまうことがあるためです。中間ビルドを受け取るときでも、ある程度安定したもののみを受け取りたい場合には、Slow などを選択していただけるとよい、ということになります。(ちなみに最も展開が早いビルドはカナリアビルドと言われていますが、これはさすがにマイクロソフト社内の R&D 内部のみでしか入手することができません。私も受け取れません;。)

image image

ここは重要なポイントですが、Windows 10 では、ビジネスユーザーが受け取る OS(すなわち CBB)がなるべく安定したものになるよう、OS の利用者を段階的に増やしていっている、という形になっています。ある意味、Insider Preview の参加者は一般コンシューマユーザに対する人柱であり、そして一般コンシューマユーザはビジネスユーザの人柱である、という構図が読み取れるわけですが;、このように、段階的な展開の仕組みを使うことで、最終的に安定したものを大多数のユーザーに届けられるようにする配信方式の考え方を、リング配信(Ringed Deployment)と呼びます。このリング配信の考え方は、アプリ互換性検証においても極めて重要なポイントになるため、しっかり理解しておいてください。

[IP/CB/CBB/LTSB と Home/Enterprise/Pro/Ent-LTSB の関係]

さて、ここまで機能アップグレードの受け取りタイミングについて解説してきましたが、OS のエディション(Home/Enterprise/Pro)により、利用できる受け取りタイミングオプションが異なります。具体的には下図の通りです。

image

ポイントは以下の通りです。

  • Home エディションでは CBB は使えない(=機能アップグレードの受け取りタイミングを遅らせることはできない)。
    • 理由は……上に書いた話から察してくださいw。
  • LTSB は特殊なエディションのため、異なるインストールメディアが必要になる。
    • Home/Enteprise/Pro のオプション変更では、LTSB にすることはできません。LTSB を使いたい場合には、Enterprise-LTSB と呼ばれる特殊なインストールメディアが必要です。
    • 例えば下図は MSDN サブスクライバダウンロードの画面ですが、この中の “Windows 10 Enterprise 2015 LTSB” と書かれているものが Enterprise-LTSB エディションに該当します。
      image

[企業内における機能アップグレードのタイミングの考え方]

次に、企業内ではどのタイミングでどのように OS をアップグレードしていくべきなのか、という点について考えてみます。下図は現在までの Windows 10 の機能アップグレードのリリースタイミングを示したものです。

image

まずはこの図の要点をいくつか説明します。

  • 機能追加・機能変更は、Insider Preview の期間中(図中の “Evaluate” の期間中)しか行われない。
    • 一度 CB がリリースされると、以降は脆弱性・信頼性などの修正(パッチ)しか出ない。
    • このため、CB/CBB 間には機能的差異はない
  • CB リリース後、4 ヶ月後にそれが CBB 扱いとなる。
    • 名目上の取り扱いが変わるだけなので、インストールメディアなどが入れ替えになるわけではない。
    • ただし、インストールの手間を容易化するため、CBB 扱いになったタイミングで、累積パッチを取り込んだメディアが再作成されて提供されている。
    • (TH1 だけは初回リリースが CB 兼 CBB となっているが、これは例外的取り扱い)
  • 各機能アップグレード(TH1, TH2, RS1, RS2,…)に対するパッチ提供は、n+2 バージョンの CBB がリリースされるまで行われる
    • よって、どんなに遅くても n+2 バージョンの CBB がリリースされるまでに(脆弱性パッチが提供されなくなるので)アップグレードを行う必要がある。

さて、実際の企業内システムの場合に問題になるのは、CBB リリース後のいつのタイミングで新しい CBB にアップグレードするのか、です。例えば、業務アプリの互換性検証が終わっていない場合や一部のサードパーティ製アプリが新しい CBB のサポート表明をしていない場合、たとえ新しい CBB がリリースされてもアップグレードができないということが起こりえます。このため、グループポリシーを使うことで、通常は CB リリースから 4 ヶ月後とする機能アップグレードの適用を、さらに遅らせることができるようになっています

ただし(ここめちゃめちゃ重要です)注意していただきたいこととして、上記の機能アップグレードの適用遅延機能は、CBB の 1 バージョンスキップを認めるものではない(=必ずすべての CBB を順次適用していく必要がある)、ということを理解しておく必要があります。

  • 先に述べたように、各機能アップグレード(TH1, TH2, RS1, RS2,…)に対するパッチ提供は、n+2 バージョンの CBB がリリースされるまで行われます。
    • 例えば TH1 へのパッチ提供は、RS1 の CBB リリースと共に打ち切られることになります。
  • このことに関連して、パッチ提供期間を少し(60 日間)だけ延長する、という話があるのですが、仮にパッチ提供期間が延長されたとしても、CBB を 1 バージョンスキップする、という考え方は避けるべきです
    • 例えば下図のように n+2 バージョンの CBB がリリースされたら速やかに 1 バージョンスキップして機能アップグレードを適用する、という考え方を採るのは非現実的です。
    • image
    • もともとこの “60日間延長” は、パッチ提供期間を少しだけ伸ばしてもらうことで、CBB を 1 バージョンスキップして業務アプリの互換性テストを年 2 回程度から年 1 回程度に減らしたい、というお客様の要望から来ています。
    • しかしその一方で、上図のようにアップグレードを行おうとした場合、n+2 の CB リリース後、6 ヶ月以内に業務アプリや ISV アプリの対応を完全に済ませ、社内すべての OS を完全にアップグレードする必要があります。しかしこれはどう考えても極めてリスクが高く、現実的ではありません。 (特に ISV アプリのような、社外アプリの対応が間に合わないリスクがあります)
    • このため、n+1 の CBB リリースから n+2 の CBB リリース + 60 日間が、機能アップグレードの適用猶予期間である、と考えて、ひとつずつ CBB のアップグレードを繰り返していく必要があります。
    • image

というわけで、企業内における機能アップグレードの適用タイミングの考え方の基本について説明しましたが、実際のアップグレードタイミングについては業務アプリの互換性検証と強くかかわる部分がありますので、それについては Part 6 にて解説します。

[Windows 10 に対する「更新」の分類について]

さて、ここまで Windows 10 の機能アップグレード(November Update や Anniversary Update)について解説してきましたが、Windows 10 に対して行われる「更新」について、ここで整理しておきたいと思います。Windows 10 では、3 種類の「更新」が行われており、それぞれ名称が異なります。これらについては名称をしっかり覚えておいてください

  • ① 機能アップグレード(Feature Upgrades)
    • 従来のサービスパックに相当するもの。
    • スタートメニュー、設定画面、Cortana、Edge ブラウザなどの機能追加・変更が含まれる。
    • CB としてリリースれさる前に先行検証したい場合には、Insider Program に参加する。
  • ➁ サービス更新プログラム(Servicing Updates)(または 品質更新プログラム(Quality Updates/QU))
    • 従来の更新プログラムに相当するもの。
    • 脆弱性修正(セキュリティパッチ)が中心で、信頼性やバグに関する修正も含まれる。
    • 以下の 2 点に注意する。
      • OS Edge ブラウザに対する機能追加・変更は含まれない。
      • 従来と異なり、必ず累積パッチとして提供される(個別の適用はできない)
    • 配布前に先行検証したい場合には、SUVP(Security Update Validation Program)に参加する。
  • ③ ストアアプリの更新の配信
    • メールやカレンダアプリの更新は、①➁とは別に、Windows ストア経由で配布される

整理すると、以下のようになります。これを覚えておくとよいでしょう

配信内容 英語表記 簡単に言うと… 備考
① 機能アップグレード Feature Upgrades (FU) サービスパック OS や Edge ブラウザの更新
② サービス更新プログラム Servicing Updates (SU) 累積セキュリティパッチ 通常、毎月第二火曜日に配信
③ ストアアプリの更新の配信 メールやカレンダーアプリの自動更新 ①②とは無関係に配信

ちなみに細かい話で恐縮ですが、”Upgrade” と “Update” という用語が使い分けられていることに注意してください。FU は「アップグレード」、SU は「アップデート」です。(といいつつ、一般的に使われている November Update や Anniversary Update はいずれも FU なのに Update という名称になっているのはどうよ? という感じなんですけどね;、とグチってみる。)

なお、②に関しては RS1 以降は品質更新プログラム(Quality Updates, QU)の名称に変更される予定です。しばらくは SU/QU 両方の表記が出てくると思いますのでご注意ください。(この blog では SU で統一しています)

[LTSB (Long Term Servicing Branch)について]

さて、ここまで IP/CB/CBB を念頭に置いて解説を進めてきましたが、ここで LTSB について整理します。LTSB は、10 年間にわたりパッチのみが提供され続ける、非常に特殊なエディションで、その間、機能追加・変更は行われないというものです。今はまだしも、10 年後には超レガシー端末になるというもので、一般な OA 端末では使うべきではない特殊なエディションです。

もともと LTSB は、例えば製造ラインに据え付けで利用する制御用の PC や、キオスク端末のように特定の業務アプリだけしか動作させない PC での利用を想定しているもので、いわば組み込み用の Windows OS である、と理解するとわかりやすいです。インストールに利用する媒体も通常の Home/Enterprise/Pro とは別のメディアになっており、インストールした際の画面も大きく異なります(ストアもなければ Edge ブラウザもない)。ストアや Edge ブラウザが削られているのは、上記のような「ずっと変わらない端末」での用途を想定していることを念頭におけば当然のことと言えます。

image

LTSB に関しては、頻繁な(半年に一度の)アプリ互換性検証を回避するための魔法の杖であると誤解されているケースが非常に多いのですが、これは全くの誤りです。確かに LTSB を使えば機能追加・変更が行われなくなりますが、それは同時に技術革新が一切行われない、ということでもあります。10 年間同じ OS を使い続けるということは、言ってみれば 2016 年においてもまだ 2007 年にリリースされた Vista を使い続けている、というようなものです。

基本的に、一般社員向けの端末では、CBB を利用することをまず念頭に置いて考えてください。イメージとしては、Office を入れて使うような一般的な OA 端末は CBB を選択するのが基本、と考えるとわかりやすいでしょう。逆に、OS に新しい機能を取り込む必要のない特定用途の固定端末では、LTSB を使うことも検討していただければと思います。

# LTSB に関するこの誤解を誘発しているのはいくつかの原因がありますが、大きくは、① アプリ互換性検証に関して正しい理解や考え方がされていない、② LTSB にも MSI 版の Office がインストールできてしまう、③ LTSB → LTSB のアップグレードがサポートされている、といったところがあります。しかし、繰り返しになりますが、LTSB はそもそも特定用途の固定端末で使うことを想定して作られているエディションである、という点については必ず念頭において考えてください。企業内のユーザのほとんどが LTSB を使って仕事をする、といったシナリオは想定されていません

[バージョンの混在を前提とするインフラへの移行]

さて、ここまで IP/CB/CBB/LTSB について解説してきたわけですが、最後に見落としやすい、けれども非常に重要なポイントを指摘しておきます。それは、 今後の IT インフラは、複数の OS バージョンが混在することになる、という点です。

従来の IT インフラでは、基本的に、すべての社員が完全に同一の OS を利用することを前提としていたケースも多かったと思います。しかしここまで解説してきたように、Windows 10 WaaS 時代では、端末あるいはユーザごとに、複数の更新モデルを使い分ける必要があります。それは結果的に、複数の機能バージョンの Windows OS が混在する環境を生み出します。

image

ですので、例えば RS1 を使っている A さんが隣の B さんの端末を見たらすでに RS2 を使っていたとか、あるいは部屋の隅に置かれている特定業務端末は LTSB で動いている、といったことが発生します。欧米では BYOD も多く、端末ごとに OS バージョンが違うことも珍しくないらしいのですが、日本の場合にはこうした環境にまだ馴染みがないというケースが多いと思いますので注意していただければと思います。

Part 4. Windows 10 導入時の検討の流れ

$
0
0

[導入計画立案の必要性]

ここまで述べてきたように、Windows 10 の導入では WaaS を見越した検討を行う必要がありますが、特に大企業の場合、WaaS における頻繁なアプリ互換性検証の実施が課題になりやすい、という特徴があります。インフラ面では従来の Windows OS のバージョンアップとそれほど大きく変わるわけではないものの、とはいえ保守性の向上(保守コストの削減)や、ネットワーク経由での継続的な FU/SU 配信については課題になるケースも多いでしょう。

こうしたことを考えると、Windows 10 導入では、アプリ/インフラ両側面から対応検討を行うことが望ましいのですが、大企業の場合、インフラを管理する IT 部門と、業務システムを管理している事業部門とが分断されているケースがよくあります。このため、IT 部門と各事業部門とがしっかり連携できる状況を作っておくことも重要です。

image

また Part 5 以降では実際の検討ポイントを解説していきますが、特にレガシーな業務アプリは一朝一夕に改善できないケースが多いため、直近での「とりあえずの」対応と、中長期的な「抜本的な」改善とを分けて考える必要があります。Part 5, 6 を確認していただいたら、下図のような絵を整理していただき、やるべきこと(対応施策)を「インフラ/アプリ」「短期/中長期」に分類していただくことをオススメします。

image

[特に検討を必要とするポイントについて]

特に、企業内の IT インフラを管理している IT 部門の方からすると、「Windows 10 への移行は、従来の OS バージョンアップと何が違うのか? どこの考え方を特に変える必要があるのか?」という点が気になると思いますので、簡単に下図に整理してみました。

image

ざっと要点を解説すると、以下の通りです。

  • インフラ系
    • クライアント設計やハードウェア選定などの作業は従来と全く変わりがありませんが、今後は「よりセキュアな環境」を「低コストで維持し続ける」ことが今まで以上に重要になってきます。このため、① セキュリティに関する検討、② 従来手作業で実施していてかなりコストがかかっているポイントの改善、はぜひ実施すべきです。
    • ②に関しては皆様個々に懸念点が異なると思いますが、多くのお客様で実施されている、パラメータシート管理と手作業でのマスタイメージ作成の 2 点については、この機会にぜひ見直しをオススメしたいです。
    • また、「継続的に進化し続ける IT 基盤」を実現するために、③ FU/SU の配布方法、を検討することも重要になります。この FU/SU の配信方法はアプリ互換性検証と密接な関係を持っており、最終的な結論としては、FU/SU を段階展開(リング配信)する必要があります。
  • アプリ系
    • なんといっても、① WaaS における頻繁なアプリ互換性検証の実施方法、が最大の課題ですが、これをスムーズに行っていくためには、最終的には、② CI/CD やテスト自動化、DevOps などに代表されるようなシステム開発手法の近代化、を進める必要があります。とはいえ、既存レガシーアプリケーションに対して②を推進しろと言われても、そうそう簡単に行えないことがほとんどでしょう。
    • このため実際の対策に関しては、① 塩漬けタイプの業務アプリに対しては可能な範囲でアプリ互換性検証を効率化しつつ、② 新規開発するシステムや再構築するシステムに関しては、開発時に CI/CD やテスト自動化、DevOps などを取り入れながら近代的な開発をする、というアプローチが必要になります。

これらの一連の項目の中で、実は全体を大きく左右しているのが、既存レガシー業務システムのアプリ互換性検証です。互換性検証については旧態依然としたアプローチをしているとコストも莫大になりやすいため、まずはこの部分を抑えてから他の項目について検討していくようにするとよいと思います。このため、本資料では Part 5 でまずアプリ系に関する考え方を、Part 6 でインフラ系に関する改善ポイントを解説していくことにしたいと思います。

Part 5-a. Windows 10 導入時に考え方・やり方を変えるべきポイント –アプリ編①

$
0
0

[大企業におけるレガシー業務アプリ資産について]

ここまで解説してきている通り、Windows 10 への移行における大きな障壁のひとつに、アプリ互換性検証の問題があります。特に大企業では、多数のレガシー業務アプリ資産を抱えていることが多く、従来は数年に一回の OS アップグレード、あるいはサービスパック適用時などに大規模な再テストを手作業で実施していたわけですが、WaaS のように頻度が高まると、同じやり方が通用しなくなります。

image

特に課題になるのは、非近代的なアプローチで開発された、作りっぱなしの既存の業務アプリ資産です。近代的な業務アプリ開発では、テストの自動化や CI/CD、DevOps といった手法を取り込み、リリース後の継続的な保守が容易になるように、常に技術的負債を一定以下に抑え込むような開発・保守スタイルを取ります(詳細は後述)。しかしこうした考え方が一般的ではなかった時に作られたアプリ(いや日本だと今でも、ですけど;)のほとんどは、作った後の保守や互換性検証のことがきちんと考えられておらず、作りっぱなしで後は場当たり的な保守が行われているというのが実態です。こうしたアプリはうっかりすると資産というより負債になっていますが;、既存アプリ負債とか呼ぶと切なすぎるので;、ここではレガシー業務アプリ資産と呼ぶことにしたいと思います。

現在のレガシー業務アプリ資産の大半は、おそらく従来のテクノロジ、すなわち .NET ランタイムや VB6 ランタイムなどで作られていることが多いと思いますので、ランタイム的にも以下のものだけを想定して解説を進めることにしたいと思います。

  • VB6
  • フル .NET Framework (CLR2, 4 系)※ .NET 2.0~4.6 のアプリ
  • IE11
  • ※ 上記のいずれもカーネル互換性のない業務アプリを想定しています

逆に、以下のようなランタイム上で作られているアプリは、今回は対象外として解説します。これらのランタイムはもともと頻繁にバージョンアップを繰り返しており、モダン(近代的)なアプリ開発アプローチを取らないと、そもそも開発・保守ができないためです。

  • エッジブラウザ向け Web アプリ(HTML5, JavaScript, CSS )※ Microsoft Edge, Google Chrome, Mozzila FireFox など向けの Web アプリ
  • UWP (Universal Windows Platform)
  • ASP.NET Core
  • Xamarin

[既存レガシーアプリに対する互換性検証の基本的な考え方]

さて、ここから既存レガシーアプリに対する互換性検証の考え方を解説していきますが、細かい話をする前に、まず基本的なポイントとして、OS バージョン間の互換性は昔に比べて高まっている、という実態を抑えておく必要があります。

image

特にコンピュータ歴の長い方は、ご自宅でのパソコン利用に際して、98 から XP への移行、あるいは XP でのサービスパック適用、さらには XP から 7 への移行などにおいて、アプリの非互換問題が多発した、という記憶をお持ちの方が多いと思います。が、その一方で、7→8、8→8.1、8.1→10 への移行、さらには November Update や Anniversary Update においてはアプリ非互換問題が大幅に低減している、ということを体感されている方が多いのではないかと思います。

実際、カーネル依存性のないデスクトップアプリケーションの場合、Windows 7 以降では高い互換性が実現されています。もちろん、16 ビットアプリ、OS バージョン番号に基づいて制御をかけているアプリ(特にインストーラ)、デバイスドライバに依存するアプリなどのように、クラッシュしたり全く動かなかったりインストールできないアプリなども確かにありますが、カーネル依存性のないデスクトップアプリの場合、少なくとも「まったく動作しなくなる」といった致命的な問題はほとんど出ないと思います。Windows 7 で動作していた .NET 3.5 のアプリのほとんどは、Windows 10 上の .NET 3.5 でもまず十中八九動作するはずで、先行検証しているあるお客様からは、「まるで非互換問題が出てこないんだけれども、名前だけ変えてお金稼ごうとしてない?」とか言われたことがあるぐらいです;。

このようになった最大の理由は、Windows 7 以降、マイクロソフト社内の製品開発において互換性に関するアプローチが大きく変わったことです。簡単に言えば、従来の製品開発では、「とりあえず機能を追加しまくってから、出てきた非互換問題をモグラ叩きのように潰していく」というアプローチであったのに対して、現在の製品開発では、「設計段階から互換性の維持を念頭において開発を進める」というアプローチに変わっています。このため、OS バージョン間の互換性はそれ以前の Windows に比べて遥かに高くなっています。もちろん原理的に非互換問題がゼロになることはないが、かといって必要以上に恐れすぎる必要もない、ということです。

アプリケーションランタイムの観点から見てみると、下図にあるように、レガシー業務アプリの開発でよく利用されるランタイム(VB6, CLR2.0, CLR4.0, IE11)に関しては、細かいバグ修正などを除けば、Windows 7 以降、ほとんど手が入っていません。OS としての機能は継続的に強化されていますが、これらの機能(例えば Cortana など)は UWP などから使われることが多いため、既存のレガシー業務アプリはそのままお使いください、という構図があるわけです。

image

[「現実の」互換性検証で起こる問題点]

……などというオフィシャルな話をすると、「おいおいちょっとマテ、実際に Windows 10 を使うと UI の見た目が変わるじゃないか、これが非互換でなくてなんだというのだ!」と必ず怒られるのが日本の世界です;。例えば同じ .NET アプリを Windows 7, 10 それぞれで動作させてみると、以下のように違いが出てきます。

image

(これに関しては、私自身、米国本社と話しているときに温度感の違いとして強く感じる部分の一つでもあるのですが)、特にミッションクリティカルな業務システムの場合には、「アプリが起動すればよい」というものではありません。確かに上の図を見た場合、「だいたい動作は同じ」ですが、ウィンドウ枠のデザインの違い、UI 部品の見た目の違い、ドット単位の微妙な描画の違い、連動する標準ブラウザなどの細かい違いが出てきます。そしてこれらは、ミッションクリティカルアプリでは、時として業務継続を妨げる問題となり得るものであり、無視できるものとは言えません。

そもそもなぜ .NET ランタイムに変更が入っていないのに UI の見た目が OS によって変わるのか? これは、アプリケーションランタイムが OS の機能を利用しているために他なりません。

image

例えば .NET アプリの場合、計算処理などは .NET ランタイムの中で行われるため、OS をバージョンアップした際に計算結果が変わる、といったリスクはほぼない、と考えてよいでしょう。しかし、UI 描画処理などは OS の API(Win32 API など)を呼び出して行われるため、OS がバージョンアップすれば挙動が変わることがあります。日本のお客様が「OS によるアプリ非互換」を問題にされる場合には、ここを問題にしているケースがほとんどで、これがあるが故に、安易に OS をバージョンアップできない、というケースが多いと思います。

ただ、その一方で現実論として我々が意識すべき点は、非互換問題の発生リスクの大きさです。OS やランタイムに対して加えられる「変更」というのは、その度合いや程度によっていくつかのランクに分けることができます。

  • A. 機能追加や機能変更
    • 例えば Win32 UI 部品の描画ロジックの変更や、既定のブラウザの変更。
  • B. 脆弱性や信頼性に関する修正
    • 例えばセキュリティパッチや、ブルースクリーン発生に対する修正パッチ。

このように分けて考えると、当たり前ですが非互換リスクが大きいのは前者の機能追加や機能変更です。脆弱性や信頼性に対する修正でも、非互換問題が発生しないとは言い切れませんが、そのリスクは前者に比べれば圧倒的に低いです。よって、我々がまずしっかり押さえるべきは A. 機能追加や機能変更、その上でさらに B. 脆弱性や信頼性に関する修正、という具合に、濃淡をつけて考える必要があります。

[アプリケーションのミッションクリティカル度による分類]

アプリの互換性検証を考える際、理屈上は「あらゆる修正に対して互換性を検証する」のが正しいです。このため、大企業の IT 部門の方と話をしていると、半年に一度の大規模再テストは無理だ! と言われてしまうのですが、実態として(よいか悪いかは別として)、実際の開発現場ではそこまできちんとした互換性検証が行われていない場合が少なくありません。例えば、互換性検証の実施レベルを以下の 3 つに分類してみた場合、①または②のレベルでしか互換性検証をしていない、ということが実は非常に多いです。

  • ① 事前の互換性検証をしていない
  • ② サービスパックのような機能追加・変更がある場合のみ、互換性検証をする
  • ③ パッチを含め、あらゆる修正に対して互換性検証を行う

どこまでの互換性検証をすべきか? はアプリケーションのミッションクリティカル度によって異なります。このため、例えば金融系システムや各種基幹系システムでは、③のレベルで互換性検証をしている場合もあるでしょう。しかし、すべてのレガシー業務システムが③のレベルで互換性検証をしていることはまず十中八九ないはずです。これは、③のレベルでの互換性検証が必要な場合には、毎月リリースされるセキュリティパッチに対して毎月テストを繰り返さなければならないためで、このようなテストは事実上手作業では無理(自動化が必須)です。手作業での互換性テストができるのは、現実的には②のレベルまでです。

image

そしてもう一つ、これに併せてご確認いただきたいのが、(Windows 7 以降における)過去のサービスパックやセキュリティパッチの適用において、実際に非互換問題がどの程度発生したか、です。最初に述べたように、Windows 7 以降では OS バージョン間の互換性が高くなっているため、ほとんどのケースでは実態として非互換問題は発生していないはずです。もしそうであるのなら、Windows 10 への移行時に、現在の実施レベルをむやみに高める必要はありません。例えば、現在の実施レベルが②であるのなら、Windows 10 移行後も、②のレベルに留めるべきです。

なぜこのような話が重要になるのかというと、最大の理由は、IT 部門の方が、実際の業務部門側で行われているシステム開発の実態を完全に把握していないことが多いからです。実際にあった話ですが、ある大企業の IT 部門での Windows 10 導入検討において、当初、半年に一度の OS バージョンアップではアプリ互換性検証が間に合わない! ということが大問題になりました。しかし実際の業務部門に確認してみると、Windows 7 の SP1 リリースの際に事前のアプリ互換性検証が行われていなかったこと、またその際に非互換問題が発生していなかった、という事実が判明し、必要以上に心配しすぎだということがわかった、ということがありました。

このアプリ互換性検証は(私もそうだったのですが)つい頭でっかちに考えやすい部分なので、IT 部門の方は、実際にいくかつの業務部門の方にヒヤリングしていただき、現在の互換性検証の実態がどうなっているのかを必ず確認してください

……とはいえ、問題なのは、WaaS の FU(機能アップグレード)において実際にどの程度の機能追加・変更がなされているのか、そしてそれがどの程度、既存レガシー業務アプリに影響を及ぼしうるのか、という点です。もしそれがたくさんあるのであれば、結局は半年に一回、大規模な再テストを繰り返すことになります。これについては、「実際のところどうなのよ?」という議論が必要になるはずです。これについて引き続き見ていくことにします。

Part 5-b. Windows 10 導入時に考え方・やり方を変えるべきポイント –アプリ編②

$
0
0

WaaS におけるアプリ互換性問題を考える上では、Windows 10 → 10 への機能アップグレードにおいて、どの程度、既存レガシー業務アプリに影響を及ぼしうる変更が加えてられているのか、という点が重要になります。もちろん、将来のことはわからない部分もありますが、とはいえ November Update や Anniversary Update の内容を精査してみることは、FU(機能アップグレード)の中身の特性を理解する上で重要になりますので、もう少し細かく見てみることにします。

[Windows 10 FU 間の互換性の実態]

詳細は Part 7 にて解説しますが、Windows 10 の機能アップグレード(FU)で行われた内容については、非公式サイトですが changewindows.org というサイトが(英語ですが)非常によくまとまっており、このサイトを見ると、一覧形式で 8.1 → TH1, TH1 → TH2, TH2 → RS1 で行われた主な機能追加・変更を確認することができます。こちらを確認していただくと、Windows 10 の FU は大まかには以下のような傾向があることがわかります。

  • Windows 8.1 → Windows 10 TH1
    • UI まわりが大きく変更されている。(このため、.NET や VB6 アプリもその影響を受ける)
    • その他、OS 全体に比較的大きな機能追加・変更が加えられている
  • Windows 10 TH1 → TH2 および TH2 → RS1
    • TH1 → TH2 : Edge ブラウザの強化が中心で、他の機能強化はほとんどない
    • TH2 → RS1 : Edge ブラウザに加え、スタートメニューや設定画面、IME などの強化が中心であり、既存レガシーアプリには影響が出にくい変更内容がほとんど

image

changewindows.org サイトを見てみると、各 FU には 100~400 近くの機能追加・変更項目があることがわかりますが、私なりの視点で「レガシーアプリに対して非互換問題を起こしそうな」変更項目をざっとピックアップしてみたのが下のリストです(一部、日本語関係で私が追加した項目も入っています)。もちろん私なりの視点なので、当然、すべての業務アプリでこれだけチェックすれば十分、とはなりませんが、大半の機能追加・変更はスタートメニューなど既存レガシー業務アプリに影響を及ぼさない場所に入っているため、半年に一度の FU のつど、アプリ全体を入念に再テストしなければならない、というわけでもなさそう、という感覚は掴めるかと思います。

  • 8.1 → TH1
    • IME 関連
      • デスクトップアプリのテストボックスに触れるとスクリーンキーボードが表示されるようになった
      • テキスト入力キャンバスが改善された
    • UI デザイン関連
      • ウィンドウ枠のデザインが変更された
      • Win32 コントロールのデザインが変更された
      • タスクバーの既定の色がダークに変更された
      • ウィンドウ枠の色が白に変更された
      • 日本語の既定のフォントが Yu Gothic UI に変更された
    • インストーラ関連
      • Windows OS のバージョン番号が変更された
    • 拡張子関連
      • 既定のアプリの変更方法が変更された
    • ブラウザ
      • 既定のブラウザが IE から Edge に変更された
      • 全画面型 IE が廃止された
  • TH1 → TH2
    • IME 関連
      • テキスト入力パネルが改善された
      • 新しい絵文字が追加された
    • インストーラ関連
      • Windows OS のバージョン番号が変更された
    • 拡張子関連
      • 既定のアプリが “Windows 10 で推奨” と表示されるようになった
    • その他
      • タイムゾーンを自動的に補正する機能が備わった
      • 最後に利用したプリンタが「既定のプリンタ」として扱われるようになった
  • TH2 → RS1
    • IME 関連
      • 日本語 IME が大幅に機能強化された(クラウド候補、入力履歴、候補予測、片手入力キーボード、性能など)
      • 絵文字のイラストが変更された
    • UI デザイン
      • 認証ダイアログのデザインが変更された
      • 游ゴシックが小さい文字でも読みやすいように変更された
    • その他
      • NTFS が 260 文字以上のファイルパスをサポートするようになった

以上を加味すると、実際の Windows 10 への移行に関しては、7/8/8.1 → 10 の移行と、10 → 10 の移行とを分けて考えた方がよい、ということになるでしょう。具体的なやり方は後述しますが、おおまかな考え方は以下の通りです。

  • 7/8/8.1 → 10 : 比較的きっちりと検証作業を行う
    • 日本の業務アプリケーションの多くは UI が非常に複雑なため、若干の見た目の変更であってもエンドユーザからのクレームに繋がり兼ねないため。
    • ただし、以降の 10 → 10 の互換性検証を見据えながら検証作業を進めるのがポイント。
  • 10 → 10 : 極力、効率化・省力化を行う
    • 既存のレガシーアプリに対して影響を及ぼしうる OS への機能追加・変更はごくわずか。
    • このため、この互換性検証は、実施するとしても極力、効率的に実施する必要がある。

image

[アプリ互換性検証の効率化の手法]

では、実際にアプリ互換性検証を効率化するためにはどのような手法があるのか? ですが、代表的なものとしては以下の 3 つがあります。

  • ① テストケースの絞り込み
    • 実施する非互換検証の物理的な実施量を減らす
  • ② 見逃しリスクへの対応
    • テストケースを減らしたことによる見逃しリスクを緩和するための施策を打つ
  • ③ 更なる実施効率化
    • ①②を行った上で、さらに効率化を進めたい場合に追加の施策を打つ

主な具体的施策と併せて示すと、下図のようになります。

image

うちの開発現場ではすでに実施済みだよ、という施策も多いでしょうが、以下に順に、考え方ややり方を示していきます。

[アプリ互換性検証の効率化の手法① テストケースの絞り込み]

まず真っ先にやるべきことは、非互換問題を起こしそうなところに絞ってテストを実施する、というものです。絞り込み方については主に以下の 3 通りがあり、これらを組み合わせて効率的なテストケースを組み立てます。

  • A. アーキテクチャ情報を元に絞り込む(処理の類似性や技術要素などから絞る)
  • B. 業務的観点から絞り込む(重要度の高い業務に絞る)
  • C. 実践的な知見から絞り込む(過去の互換性検証の結果や探索的テストから絞る)

この 3 つの絞り込み方の中でも特に重要なのは A. アーキテクチャ情報を元に絞り込む(処理の類似性や技術要素などから絞る)の考え方です。というのも、実際の非互換問題は下記の図に示したような『技術的接点』で発生しやすいためで、むやみにテストケースを設計するのではなく、こうしたパスを少なくとも 1 度は通すようにテストケースを設計することで、効率的なテストケースを組みやすくなります。
image

上図ではデスクトップアプリの場合について考え方を示しましたが、Web アプリについては注意が必要です。特に既存レガシー業務 Web アプリの場合、Web アプリと言いながら、実際には ActiveX をはじめとするプラグイン技術を多用していることが多く、これらはデスクトップアプリと同様な非互換検証を必要とします。このため、IE11 自体が変わっていなくても、プラグイン部分については、デスクトップアプリ的な考え方でテストケースを組み立てる必要があります。(なお、IE11 は基本的に機能変更が入らない、という指針にはなっていますが、とはいえエンタープライズモードをはじめとする Edge ブラウザとの切り替え機能や、セキュリティ強化に伴う機能変更などは今後も入る可能性があります。このため、こうしたポイントについては念のため確認した方がよいでしょう。)

image

また、テストケースの効率的な絞り込みのためには、濃淡をつけるという考え方が重要です。技術要素に分解して考えた場合、場所ごとに非互換リスクの度合いはずいぶん違います。ですから、例えば下のような表を作成し、領域ごとにどの程度濃淡をつけてテストするのか? ということを意識しながらテストケースを組み立てていくと、費用対効果の高い互換性テストが実施できます。

image

さて、この「非互換情報との突合せによるテストケースの絞り込み」では、以下の 2 つが特に重要になります。

  • どのレベルで非互換情報を確認するのか?
  • どこから非互換情報を入手するのか?

どのレベルで非互換情報を確認するのか?

Part 5-a で述べたように、互換性検証の深さはアプリのミッションクリティカル度に応じて調整する必要があります。例えば、

  • 今まで月例パッチ(毎月のセキュリティパッチ)に対して互換性検証をしてこなかったというアプリの場合は…
    • 今後も、アプリ互換性検証は機能アップグレード(FU)が行われる際に、機能追加・変更に対してのみ行えばよい。
  • 毎月のセキュリティパッチに対しても互換性検証をきちんとしてきたというアプリの場合には…
    • 今後も、毎月のサービシングアップデート(SU)に対して、互換性検証を続けていけばよい。(ただしこの場合は事実上、自動化が必須)

となります。

image

どこから非互換情報を入手するのか?

非互換情報は、公式/非公式様々なサイトから提供されていますが、実のところ、このテストケース絞り込みの目的ですぐさま役に立つものは少ないのが実情です。詳細は Part 7. 「Windows OS の変更ログの入手方法」で解説しますが、例えば『機能アップグレード(FU)が行われる際に、機能追加・変更に対してのみ行う』のであれば、FU リリース時に、機能追加・変更の一覧情報を入手する必要があります。非公式サイトかつ英語という問題点はありますが、最も使いやすいサイトは最初に説明した ChangeWindows.org というサイトだと思います。ちょっと時間を取って実際に見てみていただければと思いますが、

  • ページ上部の「Milestones」を開くと FU の一覧が出てくる。(November Update, Anniversary Update など)
  • そこから FU を選択すると、中間ビルドの一覧(Windows Insider で公開された中間ビルドの一覧)が出てくる。各中間ビルドには、前回の中間ビルドからの差分情報が整理されている。
  • 最後のビルド(緑で示されている)が、最終的に公開されたビルド。これを選択すると、前回の FU からの変更点の積算情報が表示される

例えば、TH1 から TH2(November Update) での変更点はこのページに、TH2 から RS1 (Anniversary Update) での変更点はこのページに情報がありますが、(英語であるという難点を除けば;)使いやすい形で、機能追加・変更に関する変更ログが整理されていることが確認できます。こうしたリストを使って、特に重点的にテストする場所を決めていくとよいでしょう。

なお、SU(累積パッチ)での修正情報については、Windows 10 リリース情報ページが詳しいです。こちらは、今までに提供された SU の一覧と、それぞれに含まれている代表的な修正内容について情報が提供されています。(こちらは KB 情報なので日本語でも情報が提供されています。FU の情報が日本語で提供されてなくて SU の情報が日本語で提供されているというのは正直逆だろう、と思うのですが;

非互換情報を利用する場合の注意点 ~機能追加・変更・修正の境界線問題~

さて、こうした非互換情報を利用してアプリの互換性検証をする場合の注意点ですが、

「どのような非互換情報リストを利用しても、絞り込んだテストだけで非互換問題を事前に100% 洗い出すことは原理的にできない」

ということを理解しておく必要があります。このポイントは敢えて赤字で書くほどむちゃくちゃ重要なことなので、しっかり理解してください

こうした互換性検証のよくある誤解のひとつに、「ベンダーから提供される非互換情報(例えば KB などの情報)を使って検証することで、非互換問題を事前に(完全に)回避することができる」、というものがあります。しかし、この考え方は原理的に誤りです。なぜなら、マイクロソフトに限らず、ベンダーから提供される非互換情報そのものは、すべての変更点を網羅したリストにはそもそもなっていないから(=すべての変更点を網羅したリストを提供していない)であり、同時に、「何を機能追加・変更・修正とみなすのか?」に関しては、ある程度、人間の恣意的な判断が入ることが原理的に避けられないためです。このことを概念的に示したのが下図です。

image

まず、極端な話をしてみると、同一ソースコードをリビルドしても、完全に 1 バイト単位で同じバイナリにはなりませんから、リビルドするだけで、理屈上は挙動が変わる恐れがあります。けれども、我々は多くの場合、同一ソースコードをリビルドした場合には挙動は同じになると考えます。なぜかというと、コンパイラに不具合がない限り、現実的には論理的な挙動は同じになるだろうと考えるからです。あるいは、CPU として Core i7 のマシンを使う場合と、Core i5 のマシンを使う場合とで、理屈上は挙動が変わる恐れがありますが、現実的には業務アプリの挙動は変わらないと考えるのが普通です。このように、「理屈上、原理的に非互換問題が起こりうる」という話と、「現実的に、非互換問題が起こる可能性が高い」という話には、隔たりがあるのが普通です。

このことを Windows OS に当てはめて考えてみると、製品に対するあらゆる修正は、たとえ API 外部仕様が一致していても、挙動が 100% 同じになるとは言えないため、機能変更であるとも言えます。その一方で、あらゆる修正を機能変更であるとみなすと、非互換情報リストが膨大になり、突合せ確認を行うことが不可能になってしまいます。Windows OS の場合だと、細目レベルでは数千~数万件単位の修正が入っているそうなのですが、そんな API レベルの微修正の一覧リストをもらったところで、非互換検証の突合せを行うことは不可能です。このため、実際の業務アプリの非互換検証においては、「実用的なレベル感・粒度感」に整理された非互換情報リストを使うことが、最も重要になります。(項目数ベースでいえば、最大でも 100~500 個ぐらいの粒度に整理されていないと、リストとしては使い物にならないと思います)

実際、現場にいると、お客様から「Windows OS や .NET Framework のバージョンアップに際して、すべての変更点リストを提供してください」と言われることが多いのですが、本当にすべての変更点リストを提供してしまったら、項目数が多すぎて、実際には使い物にはなりません。実際にお客様が期待しているものは、適度な粒度感にまとめられつつ、なおかつそのリストを使うと自分の業務アプリの非互換問題が 100% 事前に洗い出せるという魔法のような非互換情報リストなわけですが、そのようなものを作ることは原理的に考えて不可能です。

結局、過去の経験に基づき、最も実用的なレベル感・粒度感で、最も非互換問題を起こしそうな項目を情報として取りまとめて提供するのがお客様側にとっても最もメリットがあるわけで、マイクロソフトをはじめ、多くのベンダーはそのような取り組みをしています。一方で、こうした情報の取りまとめでは、「何を機能追加・変更・修正とみなすのか?」に関して、ある程度、人間の恣意的な判断が入ることが原理的に避けられません。上図に示したように、機能追加はともかくも、「機能追加」と「修正」の境界線、あるいは「修正」と「微修正」の境界線は、どうしても曖昧になる、ということです。

ですから、非互換情報リストを使って絞り込み再テストを行う方法では、見逃しリスクを大きく低減できるものの、見逃しリスクをゼロにすることは原理的にできません。このため、より高い安全性を実現するためには、実際に見逃しにより不具合が発生した場合のリスクヘッジを考える必要があります。そのための施策が、次に述べる、リング配信と迅速な障害対応です。


Part 5-c. Windows 10 導入時に考え方・やり方を変えるべきポイント –アプリ編③

$
0
0

[アプリ互換性検証の効率化の手法② 見逃しリスクへの対応]

前述したように、非互換情報リスト(変更ログ)を使ってテストケースを絞り込むことで、アプリ互換性検証に関わる工数を大きく削減できますが、その一方で、非互換問題の見逃しによる障害リスクをゼロにすることは原理的にできません。このため、非互換障害が発生した場合の被害を最小化する施策も併せて取る必要があります。具体的な方法は主に以下の 2 つです。

  • A. 一部のアーリアダプタユーザに対する CB(または IP) の先行配信(リング配信)
    • 社員の一部に、先行して CB (または IP)を配布して実業務で使ってもらい、非互換障害が発生した場合には、すみやかに修正を行う。
  • B. ベンダーとの協業強化による迅速な障害修正対応フローの確立
    • 特にアプリを外注している場合には、速やかな修正ができる体制を整えてくおく。

image

それぞれについて補足説明すると、以下の通りです。

A. 一部のアーリアダプタユーザに対する CB(または IP) の先行配信

「アーリアダプタ」とは「早期利用ユーザ」のことで、いきなり全社員に一律で FU/SU を配信するのではなく、アーリアダプタから段階的に FU/SU を配信して利用者を増やしていくことにより、非互換問題発生時の問題を極小化しよう、というものになります。この考え方は Windows OS そのものの開発でも使われているもので、リング配信と呼ばれています。(ここでは話を簡単にするため、FU のリング配信についてのみ解説します。これがわかれば、SU についても似たような考え方ができるはずです。)

FU の実際のリング配信のやり方については、対象となるアプリの種類によって変わります。

  • コンシューマ向けサービスや ISV パッケージ製品・サービスの場合
    • この場合、ユーザ側は FU(November Update や Anniversary Update など)が CB としてリリースされると FU 適用を始めますので、それより先行して利用検証をしなければなりません。
    • このため、このケースでは Insider Preview を使って先行検証・リスクヘッジをすることになります。
  • 社内業務アプリの場合
    • この場合、大多数のユーザ(社員)が FU を使い始めるのは、FU が CBB 扱いになってからになります。
    • このため、一部のアーリーアダプタの社員に対して CB (としてリリースされた FU)を先行配信してしまい、社内業務アプリで不具合が出ないかどうかを確認してもらうことになります。

このように、アプリの種類によって、先行検証の時期や利用するビルドが異なるため、アプリの特性に合わせた、実地検証の期間の取り方などを事前に決定しておくことが必要です。下図はこれを概念的に示したものになりますが、こうした図をまとめておき、誰にどのように配信するのかを決めておくとよいでしょう。

image

ちなみにマイクロソフトの社内においても似たような取り組みが行われており、先行して Windows OS や製品の最新中間版などを積極的に利用する社員を募集する形を採用しています。この仕組みは社内ではエリートプログラムと呼ばれており、新し物好きな社員が積極的に新しい OS を使う形になっています。(私自身も Surface Pro 4 を 2 台使っており、1 台は CB、もう 1 台は Insider Preview Fast Ring で利用しています。)(余談ですが、エリートプログラムは昔はドッグフードと呼ばれていたのですが、ドッグフードはちょっと名前的にイマイチですよねぇ;。犬の餌って;;)

B. ベンダーとの協業強化による迅速な障害修正対応フローの確立

また、併せて見直していただきたいのが、アーリーアダプタやエンドユーザから非互換障害が報告された際に、速やかに修正・再リリースが実施できる体制を整えておくことです。というのも、日本の大企業ではシステム開発を SI ベンダーや協力会社に外注していることが多く、不具合修正のやり取りに大きなオーバヘッドがかかるケースが少なくないためです。

  • 内製あるいはそれに準じる体制を取っていない場合
  • リリースに必要なテスト(デグレードテスト)が手作業で行われている場合
  • 修正作業の依頼や確認に、大量の書類による手続きが必要になっている場合

具体的な改善方法はケースバイケースであまりにも違いすぎると思いますのでここでは触れませんが、このような場合には、やり方の見直しや効率化を図り、速やかな障害修正対応フローを確立するようにしていただければと思います。

image

[アプリ互換性検証の効率化の手法③ テストのさらなる実施効率化]

ここまで、基本的なアプリ互換性検証の効率化として、① テストケースの絞り込み、② 見逃しリスクへの対応、という基本指針を示してきましたが、さらに以下のような手法を組み合わせていくことにより、互換性検証の作業をより効率化していくことができます。

  • A. 仮想環境の活用
    • 以下のような用途で仮想環境を活用すると、テストの作業効率が大幅に改善する
      • 次期 CBB クライアント環境を仮想環境として用意する(互換性検証用のテストマシンの準備工数が大きく削減される)
      • テスト用サーバ側環境を仮想環境として構築する(スナップショットを活用することで、テスト用サーバを容易に初期状態に巻き戻せるようになる)
  • B. 非有識者による再テストの実施
    • 互換性検証のたびに、業務や技術に詳しい SE やエンジニアが狩り出されると、現場業務に支障が出る
      • このため、非有識者でも再テストできるようにする
    • 具体的には、以下のような手法を組み合わせる
      • テスト環境の状態によって、テスト実施結果が変わらないようにテストケースを作成する(リピータブルにテストを設計する)
      • 有識者によるテストを、ビデオ録画などで記録しておき、これを活用する
  • C. UI テストの自動化
    • 仮想環境の利用や、リピータブルなテストケース設計が正しくできていると、UI テストの自動化も可能になる
      • UI テストの完全自動化はコストメリットが薄いが…
      • 互換性検証に必要な最小限のテストケースのみを自動化すると、メリットが非常に大きい
      • 累積パッチに対しても素早い互換性検証が可能になる他、迅速なデグレードテストなども可能になる
    • CUIT (Coded UI Test)ツールや 3rd party 製テストツールなどを用いて、UI 打鍵の自動化や適切な結果検証を行う
  • D. 全社的視点での互換性検証の最適化
    • 特に大企業の場合、全社的な目線で考えると、互換性検証を大きく最適化できる可能性がある
    • 主なポイントとしては…
      • テスト対象とする業務システムそのものの絞り込み(アーキテクチャが似ているシステム群は、そのうち一つだけを互換性検証の対象とする)
      • テストインフラの準備(個々の業務システムごとに用意するより、全社的に用意した方が安価)
      • 互換性検証テストチームの運営(専任チームを作った方が互換性検証のノウハウが貯まる)

こうしたテスト実施改善を進めていくためには、テストインフラの構築とテストツールの導入が欠かせません。というのも、残念ながら日本の開発現場の多くでは、未だ以下のような Excel テストが横行していると思います。

  • Excel 表を使ってテストケース一覧を書き出し、そこに〇×をつけていく。
  • 実施結果の証跡(エビデンス)を取るために、画面スナップショットを Excel の別シートにぺたぺたと貼り付けていく。

しかしこんなことをやっていてはテストの実施効率が全く上がりませんし、過去のテスト結果データを生かして次のテストに結び付けていくこともできません。近代的なテストインフラの構築とテストツールの導入により、テスト実施作業の効率化を推進していくべきでしょう。例えばマイクロソフト製品であれば、Hyper-V を用いた仮想化テスト環境(※ ラボ環境と呼ばれる)を構築したり、テスター向けツールである Microsoft Test Manager (MTM)を使うことでテスト実施作業の効率化を推進できますし、これらのツールや環境を、全社レベルで導入すると費用対効果もさらに高くなります。

image

image

[実際のテスト実施効率改善の難しさ]

ただその一方で、ここで述べた「仮想環境の活用」「非有識者による再テストの実施」「UI テストの自動化」などは、実際の現場で実施されていることは非常に少なく、また「全社視点での非互換検証の最適化」などは、大企業であれば大企業であるほどやりにくいケースが多いのも実際だと思います。

下図は、「開発時にやっておくべきこと」と「スムーズなアプリ互換性検証の実施に必要なこと」の対応関係を示したものですが、この図からわかるように、場当たり的な開発やテストが行われてきた場合、後付けでアプリ互換性検証の効率化を進めることには限界があります。このため、既存レガシーアプリに対しては、① テストケース割愛や、② 見逃しリスクへの対応、を行うのが精いっぱいであり、③ 更なるテストの実施効率化の施策が打てないことがよくあります。このような場合には、敢えて現行システムに対しては施策を打たず、次期大規模改修の際に『テストしやすいように』アプリ開発を行うようにしていくのが現実的でしょう。

image

このように、現実的な既存レガシー業務システムでは、「短期的な対応策」と「中長期的な対応策」とにわけて考えていくことが重要です。今一度、アプリ互換性検証の効率化の大方針を再掲しますが、直近でどこまでやるのか、長期的にはどこを目指すのか、その両方を考えるようにしてください。

Part 5-d. Windows 10 導入時に考え方・やり方を変えるべきポイント –アプリ編④

$
0
0

さて、実際の既存業務アプリを考えると、「理想的なアプリ開発」が行われていないことが多く、「理想的なアプリ互換性検証」を行えない場合が少なくありません。短期的な対策としてはやむなしという部分も大きいですが、中長期的を見据えると、今のままのやり方を繰り返していては苦しくなるだけです。ですからここで今一度、「理想的なアプリ互換性検証」や「理想的なアプリ開発」を再考し、次のシステム開発で何に注意すればよいのか、を考えておくべきです。この観点では、マイクロソフトの社内のやり方に、参考になるヒントが多数存在しますので、少しご紹介してみることにします。

[事例:マイクロソフト社内のアプリ互換性検証作業の効率化]

当たり前のことですが、マイクロソフトの社内にも、皆様の会社と同様、多数の業務システムが存在します。マイクロソフト社内では、MSIT と呼ばれる社内 IT 部門が社内業務アプリを管理しており、経費精算アプリ、物品購買システム、マッサージ予約システム、出退勤管理システム、パフォーマンスレビューシステムなどなど、全世界トータルでは約 2,100 個の社内業務アプリが存在しています。こうした社内業務アプリをどのように Windows 10 WaaS に対応させているのかが、かなり詳細な事例情報として Web 上に公開されています。

image

https://www.microsoft.com/itshowcase/Article/Content/668/Deploying-Windows-10-at-Microsoft-as-an-inplace-upgrade
https://www.microsoft.com/itshowcase/Article/Content/519/Microsoft-IT-improves-LOB-application-testing-ensuring-readiness-for-Windows
https://www.microsoft.com/itshowcase/Article/Content/520/Microsoft-IT-prepares-LOB-apps-for-Windows

マイクロソフト社内の場合、非互換障害が発見された際には必要に応じてアプリ側の修正ではなく Windows OS 側に修正をかける、という点は一般企業と異なりますが、とはいえ非互換検証作業の具体的な効率化手法に関しては、参考になる部分も非常に多いです。なのでぜひ上記の URL をご確認いただきたい……のですが、英文全部を確認するのはさすがに大変だと思いますので、要点を私なりにざっくりと整理すると、以下のようになります。(間違ってたらごめんなさい;、古い情報と新しい情報が混在しており一部情報に食い違いがあったりしているため、完全に 100% 正しくはないかもしれませんが、細かい数字はともかくも要点は間違っていないはずです。)

  • 特に重要なポイント
    • 互換性検証を効率的に進めるため、グローバルで集約テストチームを運営している。
    • 2,100 個の業務アプリから 300 個を選出して集中的にテストしており、さらに段階的な OS 配布を併用することで、致命的な非互換問題の発生を防いでいる。
    • これらにより、互換性維持の費用を $700k/year (約 7,000 万円/年程度)にキープし続けている。
  • 具体的なテスト効率化の方法
    • ① テストケースの絞り込み
      • テスト対象システムの絞り込み
        • テストケースの絞り込みの前に、そもそもテスト対象システム(アプリ)自体を絞り込んでいる。
          • アプリケーションポートフォリオ管理ツールで、社内 LOB システム 約 2,100 個を一元管理。
          • この中から 約 300 個を Key Test Group として選出、これを重点的にテストし、その結果を元にアップグレードを判断。
        • 選出の観点は以下の 2 つ。
          • 業務の重要度 : 約 150 個
          • 技術的類似性や過去の非互換問題に基づく選出 : 約 150 個
        • 選出されなかったシステムは、特に積極的なテストはしない。
          • ただし OS 展開前に、ローカル部門の判断でテストできる猶予期間を 3 週間設けてはいる。
    • ② 見逃しリスクへの対応
      • 段階的な OS 配布
        • 社内アーリアダプタユーザ(ボランティア)が  Insider Program に参加して業務で利用
        • 見つかった障害に対しては R&D と連携し、必要に応じて OS 側を修正して互換性を向上させる
      • コード修正
        • 多くのシステム(約 80%)は内製のため、速やかなコード修正が可能。
        • 非内製アプリは修正に時間がかかるため、なるべくテスト優先度を高めている。
    • ③ テストの更なる実施効率化
      • 集約テストチームの運営
        • Key Test Group に関しては、グローバルで集約テストチームが積極的にテストを実施
      • テストの自動化
        • 自動化テストを駆使し、5 日間で全システムをテストできる状態を保ち続けている
        • 自動化率は 50~85% 程度
      • 仮想環境の活用
        • アプリ互換性検証はすべて仮想マシンで実施
        • ローカル部門のテスト容易化のために、テスト用仮想マシンの貸し出しを実施(※ 現在はクラウドを活用)

この事例でおそらく最も重要なポイントは、アプリ互換性検証の効率化を大きく高めたい場合には、会社全体としての取り組みが必要になる、という点です。これについて次に解説します。

[大企業への WaaS 導入時の注意点]

MSIT の事例では、個々の業務システムの開発チームがずっと個別に保守対応(互換性検証)を続けると限界があるため、会社全体としての最適化を図ることで全体の維持コストを抑える、ということをしています。実際、アプリ互換性検証の効率化を全社視点で考えた場合、会社全体として取り組むと効果が高いものと、個別システムで効率化を進めるべきものにわけることができます

image

しかし日本の大企業の場合、こうした全社最適化のアプローチが困難なことも実際には多いです。その理由は、以下のようなものです。

  • 各業務部門が異なる SIer・ベンダーを使っており、システムが個別最適化されている
    • 技術的アーキテクチャ、作業プロセスがすべて異なるため、全体最適化ができない
  • 予算管理・執行が分断しており、全体最適化が図られていない
    • IT インフラは標準化されているが、開発ツールやテストインフラは標準化されていない
  • IT 部門がインフラ管理に特化しており、アプリ領域の実態に関する知見を持たない
    • IT 部門側がアプリの作りや非互換検証の実態を把握できていない

image

社長目線で考えればこうしたサイロ化はどう考えても悪いわけで、これらの問題が解決されないと、ある一定以上の改善は困難なことが多いです。しかしその一方で、これらは現場の努力だけでは解決できないケースがほとんどであるため、経営層も巻き込んだ検討が必要になります。まずは自分たちだけでできる改善をしつつも、中長期的には全社視点での最適化を進められるよう、経営層に働きかけていくこともぜひ考えてください。

[近代的な業務アプリケーション開発を目指すために]

また、ここまでの話を振り返ってみると、既存レガシー業務アプリの Windows 10 WaaS への対応が困難な理由の多くが、保守フェーズを意識しない作りっぱなしのアプリ開発スタイルに起因していることもわかるはずです。私は新人の頃、先輩社員から「アプリは動けばよいというものではない、保守しやすいようにアプリを作るのがプロの開発者だ」と言われて育ち、今でもやはりこの考え方は大切にしていますし、実際、保守フェーズにかかるコストは開発フェーズにかかるコストよりも大きいと言われるぐらいですから、保守フェーズを意識してアプリを作るのは当然のことでもあります。納期に追われてそれどころじゃないとか、動かすのに手いっぱいで保守のことなんて考えてられないとか、そういう実情があることもわかるものの;、一方で、今のままのやり方を続けることができないのもまた事実です。

Windows 10 WaaS は時代の要求から生まれてきたものであり、業務アプリも、これからは基本的に「進化していく基盤上で動作を保証し続ける」ことが必要になります。このためには、開発手法や開発のガバナンスの考え方の見直しが必要になってきます。

image

では具体的にどのようなポイントを見直せばよいのか? に関して、特に重要な 4 つの点をピックアップしてみます。

  • テクニカルアーキテクチャ領域
  • 画面設計領域
  • テスト領域
  • 技術的負債への対処

テクニカルアーキテクチャ領域

先に述べたように、全社最適化のアプローチが取りづらいのは、各部門がアプリ単位に最適化を行ってしまうことが大きな理由の一つです。ですから、以下のようなポイントを検討することが重要になります。

  • 利用する開発技術と、作り方に関する開発標準の導入
    • 大企業では、各部門ごとに異なる SI ベンダーを利用し、SI ベンダーごとのやり方をそのまま採用しているケースも少なくない
    • 縛りを入れすぎると SI ベンダー側の生産性を損なうが、ある程度の「緩い」開発標準を導入して、『作り』に関する標準化を進めることは、運用や保守の容易化につながる
  • 具体的なポイントとしては…
    • クラウド PaaS モデルの考え方の利用(運用や配置などの流れを標準化できる)
    • 開発技術の「標準」パターンの策定(開発パターンごとに利用する技術を決めておく)

image

画面設計領域

日本で OS 間の非互換問題がここまで大きな問題になりやすい一つの理由は、UI に対する要求が理不尽なほど硬直的である、という点です。ですから、以下のようなポイントを検討することが重要になります。

  • 硬直的な UI デザインの回避(柔軟な UI デザインの採用)
    • 非互換問題のひとつに、日本ならではの職人的精密さを持つ UI デザインがある
    • しかし Windows OS の UI は、時代の UI トレンドに併せて進化してきている
    • 硬直的な UI デザインを行うと、OS バージョンアップ時にレイアウトが崩れやすくなる
  • 具体的なポイントとしては…
    • レスポンシブ対応による、様々なスクリーンサイズへの柔軟な対応
    • XP, 7, 10 などを振り返った際に、すべての OS で通用しうる UI デザインの採用(を考えておけば、硬直的な UI デザインを回避できる)

image

テスト領域

先に解説したように、テストの効率化は「後付け」で考えることが困難です。ですから、以下のようなポイントを検討することが重要になります。

  • 「テスタブル」にアプリケーションを開発する
    • 何も考えずに作ったアプリをテストするよりも、「テストしやすいように」作られたアプリの方が、圧倒的に効率的にテストを行うことができる
  • テスト駆動型(テストファースト)な実装スタイルを確立する
    • 「出来上がってからテスト」するのではなく、「作りながらテストも開発する」
    • 単体機能テストや結合機能テストをコードとして実装し、手作業で行うテストを最小化しておくことにより、繰り返しの互換性検証の実施が容易になる

こうした「先々の非互換検証」や「先々の機能追加」を見越した開発スタイルは、現在の多くの開発現場の開発スタイルと大きく異なるため、現場の実務レベルでの改善が必要になることにも注意してください(結構大変です;)。

image

技術的負債への対処

保守効率を落とす大きな要因の一つは、作りっぱなし・やりっぱなし文化によって発生する「技術的負債の蓄積」です。技術的負債とは、継続的・持続的な開発・テストを行う理想的な状態からの逸脱の度合いのことで、要するに、その場しのぎ・その場限りの作業を繰り返しているとものすごく保守がしにくくなっていく、ということです。

  • 開発中に積極的にテスト自動化を行っていても、ちょっとした緩みでコードが汚くなり、テスト自動化が困難になる
    • 随時更新型アプリやパッケージ製品(SaaS 型製品)などでは、テストの自動化は極めて重要
      • 特に随時更新型アプリでは、きちんとテストが自動化された状態を保つことは死活問題でもある
    • しかし、一時的であっても「実装作業」を優先させてしまうと、テスト自動化は急激に困難になることが多い
      • 開発中は「動くこと」を優先させがち = テストは後回しになりやすい
      • 結果として、設計やコードが汚くなり、テスト自動化がどんどん困難になっていくことがよくある
    • 継続的・持続的な開発・テストのためには、この技術的負債を常に一定以下に抑え込むことが重要になる
  • 主な「技術的負債」の例としては…
    • 行き当たりばったりなアーキテクチャや設計
    • 汚いコード、読みにくいコード
    • 文章化されていない仕様
    • テストコードが書かれていないソースコード
    • ソースコード中の積み残し作業(TODO 項目)
    • 無視されているコンパイラ警告
    • etc…

こうした『とりあえず動作はするが、汚いやりかけ状態の作業』をそのまま放置すると、保守・維持管理や再構築時に大問題となります。実際、業務仕様が不明なため、ストレートコンバージョンであるにもかかわらず再テストがうまくできずに膨大なコストと時間がかかる、なんていうのはよく聞く話です。こうした状況を生み出さないようにするためには、システム開発および保守フェーズにおいて、定期的かつ強制的に「技術的負債を解消する期間(リファクタリング期間)」を取ることが重要になります。

image

なお、ここでいうリファクタリングというのは、コードに限った話ではない、という点に注意してください。設計書、アーキテクチャ、テストなど、システム開発の成果物すべてについてクリーンな状態を保つことが大切です。お客様も我々も、「出来上がったアプリ」だけに目線が行きがちですが、「作ること」だけを是とするのではなく、「高品質に保つこと」も是とする考え方に変えていくことをしないと、結果的に全体コストは下がらない、という点に注意してください。

[まとめ-Windows 10 WaaS での既存レガシー業務アプリ互換性検証の考え方]

ここまで 4 つのパートに分けて、アプリの互換性検証の考え方について解説してきましたが、全体をふりかえって整理すると、以下のようになります。

  • Windows 10 → 10 の既存レガシーアプリ非互換を、過度に恐れる必要はない
    • Windows 10 WaaS に移行した後に、非互換検証を過度に繰り返す必要はない
    • 基本的なアプローチは以下の 3 つだが、既存レガシーアプリでは①②の対応しか取れないことが多い
      • ① テストケース割愛
      • ② リスク低減のための OS 段階展開と迅速な非互換障害修正
      • ③ 更なる互換性検証の実施効率化
    • 現在実施している非互換検証のレベルに基づいて、Win10 移行後の非互換検証方式を決めるとよい
  • 中長期的な対応としては、開発のやり方そのものに対する改善が必要
    • テクニカルアーキテクチャの領域
      • 利用する開発技術と、作り方に関する開発標準の導入により、全体最適化を図る
    • 画面設計の領域
      • 硬直的な UI デザインの回避(柔軟な UI デザインの採用)
    • 非互換検証の領域
      • 「テスタブル」にアプリケーションを開発する
      • テスト駆動型(テストファースト)な実装スタイルを確立する
    • 技術的負債への対処
      • 定期的かつ強制的に「技術的負債を解消する期間(リファクタリング期間)」を取る
      • これにより、アプリケーションを保守しやすい状態に保ち続ける

全体を振り返れば明らかなように、すべての施策を短期的に打つことは不可能ですが、短期的なところだけを見ていればよいわけでもありません。最終的に目指していくべきところ、すなわちシステム開発の近代化(Modernization)を意識しつつ、短期的対応と中長期的対応の両方を考えていくようにしてください。

# なお、私が所属しているコンサルティングサービスでは、(主に大企業様向けに特化していますが)Windows 10 導入支援や、開発近代化支援などのカスタムコンサルティングサービスを提供しています。Windows 10 WaaS 環境への移行や、システム開発を近代化して WaaS 対応はもとよりさらなる高開発生産性を目指したい、といったニーズに関しては、コンサルティングサービスにご相談ください。

Part 6. Windows 10 導入時に考え方・やり方を変えるべきポイント –インフラ編

$
0
0

ここまで Windows 10 導入におけるアプリ互換性検証の話題を扱ってきましたが、一方で、IT インフラに関しても管理方法の見直しが重要になります。現在の日本の大企業では IT インフラ管理に関する「人手での作業」が非常に多く行われていますが、昨今のクラウド化の流れや IT 自動化の流れを踏まえると、こうした「人手での作業」は限界を迎えつつあります。このため、Windows 10 導入に併せて『負の遺産』を清算し、IT インフラの近代化を進めることをぜひ検討してみてください。

見直すべきポイントはいろいろあると思いますが、ここでは特に以下の 3 つのトピックについて取り上げてみたいと思います。

image

[1. セキュリティ]

日本におけるセキュリティ対策の考え方は、どちらかというと「コスト度外視で究極の安全性を追及する」傾向があり、結果的に現場の社員ががんじがらめのやりすぎルールに縛られて苦しんでいるというケースが多いです。ひどいケースになると、セキュリティ関連のルールの抜け道を一生懸命探して仕事を効率化する、なんていうこともありますが、これでは本末転倒です。社員の生産性を高めつつも近代的なセキュリティの脅威に対応していくためには、以下のようなことを考えていく必要があります。

  • 安全性と利便性の両方を取る
    • 安全性を高めようとして制限を増やしすぎると、社員の生産性が低下してしまう
    • 安全性と利便性のバランス(=効用)を考えて、適切な落としどころを考える必要がある
  • 「ルール」を増やさず「技術」を活用する
    • 覚えられない & 守れないルールを作るのではなく、「技術」で安全性を高める

技術的に見た場合には、主に以下の 3 つのカテゴリに分けて考えるとよいと思います。Azure RMS など一部 Windows 10 の新機能ではないものもありますが、既存環境でまだ使われていない場合には、この機会に導入を検討してみてください。

image

なお、これらのセキュリティ機能はすべてを使うということもないでしょうし、また特定端末に対してのみ使う(例えば AD を操作するマシンはセキュアにする必要があるのでそこだけ特に堅牢にする、など)こともあると思います。ただ気を付けておくべき点として、デバイス, OS(アーキテクチャ), ブートシステムなどの選択に影響が出るセキュリティ機能もあります。例えば Windows Hello は生体認証に対応したカメラなどが必要になりますし、Credential Guard や Device Guard であれば、ハードウェアの対応だけでなく UEFI ブートや x64 OS なども必要になります。このように、Windows 10 導入直後には採用しないものの、いずれ採用したいというセキュリティ機能がある場合には、その制約を確認しておくようにしてください。

[2. マスタイメージ管理]

日本の大企業では、Excel による設定パラメータ値の管理と、Word による各種の作業手順書の作成が、今でもかなり行われています。もちろんこれらのやり方は、特に IT スキルのないお客様にぱっと見せて理解してもらう、といった観点で一定の有効性はありますが、その一方で、こうしたシートや手順書の管理が IT 保守コストの増大を招いている側面もあります。できれば以下のような見直しを行って、保守コストの低減を目指した方がよいです。(ちなみにパラメータシートによる管理は日本特有の文化らしいです。まあわかる気がする……;)

  • ① パラメータシートによる設定値の一覧管理
    • 増え続けるパラメータを管理し続けること自体に無理がある
    • 「実態」を実物管理し、そこに機能差分を調べて「追加していく」アプローチを採用する
  • ② 手作業でのクライアント端末マスタイメージの作成・保守作業
    • 端末種類が増えるとイメージ管理作業が膨大になるため、タスクシーケンスを作成する
    • さらに SCCM を使えば、キッティング作業も大幅に簡素化される

image

[3. FU/SU 配信制御]

Windows 10 の導入とは、最新化され続ける IT インフラへの移行であり、そのためには FU/SU をオンラインで配布し続けられる環境を整えることが重要です。しかし、ネットワークを使って OS を更新する、という仕組みはまだまだ日本の大企業では馴染みが薄い(現在は端末入れ替えに併せて OS を更新するのが一般的)ため、FU/SU の配信制御方式について検討しておく必要があります。中でも特に重要なのは以下の 2 点です。

  • ① リング配信制御
    • アプリ非互換の見逃しリスク低減のため、FU/SU の段階展開が必要になる
    • 拠点のような単位ではなく、部門内・拠点内での端末単位の段階展開が必要であることに注意する
  • ② 分散拠点への FU/SU 配信
    • FU は 3~4GB(半年ごと)程度、SU は(累積パッチであるため)最大 1GB 程度(月次)
    • このため、細い WAN 回線を利用している分散拠点については、配信方法の検討が必要

image

①については特に注意が必要なので気をつけてほしいのですが、FU/SU を先行配信するのはアーリアダプタユーザ、すなわち『特定のユーザ』に対してです。例えば②の図において、各拠点単位に順次 FU/SU を配信していくのではなく、各拠点の中の特定端末から順次配信していく、という形が求められます。また逆に、デバイスドライバの不具合などでアップグレードを抑止しなければならないようなケースでは、特定のハードウェアを使っているユーザに対しては配信を一時的に差し止める、という動きをさせなければなりません。

FU/SU の代表的な配信方法は主に以下の 3 通り(+メディア配布)です。

配信方法 リング制御 分散配布 P2P オプション 備考
SCCM ◎ 柔軟に可能 ◎ 配布ポイント ◎ BranchCache •SCCM のバージョンアップが必要

•SCCM は端末管理やマスタ管理にも活用可能

WSUS △ 可能だが面倒 ◎ 子サーバ ◎ BranchCache
◎ DO (RS1 以降)
•差分配布(高速インストールファイル)をサポート

•O365 ProPlus の配信ができない

WU × 細かい制御は困難 × なし ◎ DO (WUDO)
メディア (手作業での配布) (手作業での配布) (手作業での配布)

※ 略称は以下の通りです。製品などの概要は Web サイトに情報が多々ありますので、検索してみてください。

  • SCCM : System Center Configuration Manager
  • WSUS : Windows Server Update Services
  • DO : Delivery Optimization
  • WU : Windows Update
  • WUDO : Windows Update Delivery Optimization

お客様の環境に応じたケースバイケースの検討が必要なため、一概にどれを選べばよいとは言えないのですが、まずは以下のような指針で考え始めてみるとよいと思います。

  • ① リング配信制御
    • 『特定のユーザ』や『特定の端末』への段階展開が必要だが、この制御は SCCM が最も柔軟にできる
    • SCCM はマスタイメージの作成や端末管理などでも活用できるため、SCCM が導入されている場合には、これが第一選択となる
    • SCCM が導入されておらず、かつ導入予定がなければ、WSUS, WU のどちらかを選ぶ(が、制限があったりリング配信制御が大変だったりするので要注意)
  • ② 分散拠点への FU/SU 配信
    • WAN 帯域が細い場合、分散拠点側に配布ポイントや子サーバを配置する方法が考えられるが、分散拠点側でこうしたサーバマシンを運用できないケースもある
    • このような場合には、P2P オプションを使う
    • WAN 帯域が極端に細い場合には、最後はメディア配布でカバーする

実際のところ、どの配信方式が最適なのかは机上検討だけでは決めきれない部分があります(特に帯域については机上検討だけでは困難)。このため、ある程度は「やってみて考える」という部分が出てくるかと思います。また、回線コストも下がっている傾向はありますので、中長期的には WAN 回線の増強なども視野に入れて検討を進めていただければと思います。

[継続的な情報入手について]

さて、Windows 10 の WaaS では、機能追加は例えばスタートメニューや設定画面、Cortana などの OS そのものに入ってきます。ということは、IT インフラ管理者は、Windows 10 WaaS で提供される新しい機能をどのように活用するのかを積極的に考えていく必要がある、ということになります。この検討に関しては、レガシー業務アプリの互換性検証に利用する変更点一覧情報のような細かいリストを元に考えるよりも、まず「セキュリティ」や「管理機能」などの大きな粒度で確認していった方がやりやすいと思います。この目的のためには、Windows for Business サイトを活用すると便利です。

image

この 2 つのマイクロソフト公式サイトは、「大きな粒度での」機能追加・変更を把握するのに便利です。まずはこちらで概要を把握し、その後、新機能を詳細検討し、グループポリシーやマスタイメージへの組み込みなどを進めていくとよいでしょう。

Part 7. Windows OS の変更ログの入手方法

$
0
0

ここまでアプリ・インフラ両側面から Windows 10 WaaS 環境への移行についての注意点などを解説してきましたが、最終的にはインフラ/アプリどちらの観点であっても、FU/SU での変更点を把握して検討することが重要になります。

  • インフラ観点 : FU で提供される新機能をどのように取り込むか?
  • アプリ観点 : FU/SU で行われた変更でどのようなデグレードが発生しそうか?

この Windows OS の変更ログは様々なチャネルを介して提供されており、自分の目的に合ったものを探して利用する必要があります。

[FU/SU 配信の流れと IP/CB/CBB の関係の再整理]

まず復習として、FU/SU の配信タイミングと、IP/CB/CBB の関係を整理すると、以下のようになります。

image

  • 用語の整理
    • IP : Insider Preview (アーリアダプタ向けに公開される中間ビルド)
    • CB : Current Branch (コンシューマ向けのリリース)
    • CBB : Current Branch for Business (ビジネスユーザ向けのリリース)
    • FU : Feature Upgrade (いわゆる従来のサービスパックに相当)
    • SU : Servicing Update  (いわゆる従来の累積セキュリティパッチに相当)(※ 今後は QU : Quality Update に名称変更の予定)
  • 基本的な流れは、以下のように整理することができます。
    • ① IP リリースごとに、機能追加・変更と、修正(脆弱性・バグ)が行われる
    • ② ①がある程度貯まると、CB として FU がリリースされる
    • ③ FU リリース後は、サポート終了まで修正(脆弱性・バグ)が SU として配信される

この一連の流れにおいて、我々は以下を整理しておく必要があります。

  • 公式/非公式サイトから、どのタイミングでどのような情報が提供されているのか?
  • 自社での検討のために、どのタイミングでどのような情報が欲しいのか?

重要なので Part 5-b で解説したことを繰り返しますが、変更情報には、「機能追加・変更」に関する情報と、「修正」に関する情報があり、どのレベルで非互換情報を確認するのかを意識することが非常に大切です。

image

簡単に言えば、以下のようになります。

  • ミッションクリティカルが高い場合には…
    • 機能追加・変更だけでなく、修正情報(パッチ情報)についても確認することになるが…
    • この場合、パッチリリースごとの確認が必要 → 自動化されたテストの繰り返しが必要です。
  • ミッションクリティカル度が中程度の場合には..
    • この場合には、機能追加・変更を中心に確認することになります。
    • よって、FU のリリースのつど(=CB がリリースされるつど)、機能追加・変更を確認することになります。

ではおさらいはこの程度にして、実際にどのような場所から機能追加・変更・修正に関する情報を入手できるのかを整理します。

[代表的な変更ログ情報の提供サイトについて]

公式/非公式含め、代表的な変更ログ情報サイトには以下のようなものがあります。

サイト名 スコープ 提供頻度 情報粒度 公式/非公式 言語 注意事項
Windows 10 ロードマップ OS 全体 FU 概要レベル 公式 日本語あり 全体像を掴むのに便利
Windows Insider blog OS 全体 IP 機能追加・変更・修正 公式 英語のみ 情報が細かいため整理が必要
Microsoft Edge Changelog Edge のみ IP/FU 機能追加・変更・修正 公式 英語のみ Edge に特化
What’s new in Windows for developers 主に UWP FU 機能追加・変更 公式 英語のみ ほぼ UWP に特化
Windows blog for Japan OS 全体 主に FU 機能追加・変更 公式 日本語のみ 重要なトピックに絞って解説
ChangeWindows.org OS 全体 IP/FU 機能追加・変更 非公式 英語のみ 非公式だが極めて便利
ASKVG OS 全体 IP/FU 機能追加・変更 非公式 英語のみ  
Windows 10 リリース情報 OS 全体 SU 修正 公式 日本語あり いわゆる KB 情報
Windows and Windows Server compatibility cookbook OS 全体 機能追加・変更 公式 英語のみ Windows 7~8.1 までの情報

多分に私見が入りまくりますが;、それぞれのサイトを紹介すると、以下のようになります。

Windows 10 ロードマップ

  • https://www.microsoft.com/ja-jp/WindowsForBusiness/windows-roadmap
  • よいところ
    • 概要レベルで、機能追加・変更の全体像を掴むことができる
    • ユーザの用途(大企業向け/中小企業向けなど)によって情報が整理されている
    • 今後のロードマップも(ある程度)知ることができる
  • 残念なところ
    • 細かい内容はほとんどわからないため、他の資料を確認する必要がある
  • image

Windows Insider blog

  • https://blogs.windows.com/windowsexperience/tag/windows-10-insider-preview/
  • よいところ
    • マイクロソフトから公式情報として公開されている、機能追加・変更・修正情報
    • 機能追加・変更をほぼ網羅しており、修正情報は代表的なものが取り上げられている
  • 残念なところ
    • プレビュービルド毎に情報公開しているが、機能アップグレード毎に整理されていない
    • このため、アプリ互換性検証で利用しようとすると、FU 間でのすべての IP の情報を自力でまとめ上げなければならない
  • image

Microsoft Edge Changelog

  • https://developer.microsoft.com/en-us/microsoft-edge/platform/changelog/
  • よいところ
    • Edge 開発チームから公式情報として公開されている機能追加・変更・修正情報
    • 機能追加・変更を網羅しており、修正情報は代表的なものが取り上げられている
    • Web 上で、任意のプレビュービルド間や CB 間での差分情報をまとめて表示することが可能(← この機能がよくできています)
  • 残念なところ
    • (当たり前ですが)ブラウザ関連の情報しかないこと
  • image

What’s new in Windows for developers

  • https://msdn.microsoft.com/en-us/windows/uwp/whats-new/windows-10-version-1607
  • よいところ
    • マイクロソフトから公開されている開発者向けの機能追加・変更の情報
    • 主に UWP 開発における FU 間でのアプリ開発者向けの機能追加・変更点が整理されている
  • 残念なところ
    • UWP 関連(アプリ開発ランタイム)の情報しか含まれていないこと
    • OS 側の変更点についてはほとんど含まれていないこと
  • image

Windows blog for Japan

  • https://blogs.windows.com/japan/
  • よいところ
    • 日本固有の情報について提供している公式の Web サイト
    • 日本語で重要な情報が提供される
  • 残念なところ
    • 重要な変更情報の提供にとどまっており、OS の変更情報が網羅されているわけではないこと
  • image

ChangeWindows.org

  • http://changewindows.org/
  • よいところ
    • 公式サイトなどの情報を元に作られた、非公式の機能追加・変更情報サイト
    • Insider Preview 単位の情報提供に加え、機能アップグレードごとに集約情報を提供
    • PC, Mobile, Xbox, Server, IoT すべての情報をわかりやすく一覧化して情報提供
  • 残念なところ
    • 公式サイトではないこと(というかこれが非公式サイトというのが未だ信じられないのですが;;)

ASKVG

  • http://www.askvg.com/category/windows-10/
  • よいところ
    • 公式サイトなどの情報を元に作られた、非公式の機能追加・変更情報サイト
    • Insider Preview 単位の情報提供に加え、機能アップグレードごとに集約情報を提供
  • 残念なところ
    • ChangeWindows.org に比べて説明は細かいが、一覧性には劣る

Windows 10 リリース情報

  • https://technet.microsoft.com/en-us/windows/release-info.aspx
  • よいところ
    • サービスアップデート(累積パッチ)の内容を解説した内容
    • CB リリース後の脆弱性・信頼性・バグ修正に関する情報を提供するもの
    • 日本語での情報提供もある
  • 残念なところ
    • サービスアップデートの情報しかない=機能アップグレードの情報がない
    • このため、実態としては使いどころがほとんどない
  • image

また、Windows 10 WaaS とは少し異なるのですが、こちらの情報についても紹介しておきます。

Windows and Windows Server compatibility cookbook

  • https://msdn.microsoft.com/en-us/library/windows/desktop/hh848074.aspx
    (日本語版はこちら、ただし Windows 10 部分のみ) 
  • よいところ
    • Windows 7→8, 8→8.1 における主な機能追加・変更に関する情報が取りまとめられている
    • 非互換項目についてはワークアラウンド(回避策)が掲載されているものもある
    • 下図にあるように、実際の Windows 10 への移行においては、① 初回の Windows 10 への移行と、② 移行した後の WaaS による継続的な更新とがある。②に関してはここまでに取り上げてきた情報が役立つが、①の部分に関してはこの Cookbook の情報が役立つ。
    • image
    • ただし、7/8/8.1 からの移行の場合、UI コントロールの見た目の変更に伴い、比較的大規模なアプリ互換性検証を行う場合が多いと考えられるため、この情報を使う必要があるかどうかに関してはケースバイケース
  • 残念なところ
    • 機能追加・変更がきちんとまとまっているのは 8.1 までで、10 以降に関しては情報がほとんどない
    • 英語の情報しかない

[Premier WaaS 非互換情報提供サービスについて]

ここまで代表的な情報提供サイトをご紹介してきたのですが、中程度のミッションクリティカル度を持つアプリ互換性検証を想定した場合、実際にほしいのは以下のような情報です。

  • FU ごとに、機能追加・変更に関する情報が一覧として整理されている情報がベンダーから欲しい。
  • 突合せ確認が簡単にできるように、表形式などでわかりやすく一覧化されていると使いやすい。できれば日本語で。

この要件を考えた場合、上記の書いたサイトはどれも一長一短で、どうしてもある程度自分でのとりまとめが必要になります。

  • Windows 10 ロードマップ情報 : アプリ互換性検証の目的で使うには情報が粗すぎる。
  • Windows Insider blog : 情報としてはよいが、Insider Preview 単位に情報が細かく分割されてしまっているため、整理する手間がかかる。
  • Edge Changelog : 情報をまとめて一括表示できるのは便利だが、ブラウザの情報しかない。
  • What’s new in Windows for developers : FU 間の差分情報が一覧で整理されているのは便利だが、UWP 系ランタイムの情報しかない。
  • Windows blog for Japan : 情報の網羅性がない。
  • ChangeWindows.org : 内容・まとめ方ともにほぼベストだが、英語かつ非公式。
  • ASKVG : 一覧形式にまとまっていないため、見るのが大変。
  • Windows 10 リリース情報 : そもそも SU の修正情報しか掲載されていない。
  • Windows and Windows Server compatibility cookbook : Windows 10 以降については情報がほとんど含まれていない。

私が一通り使ってみた感触では、ChangeWindows.org の情報が一番使い勝手がよく、次点がその情報ソースである Windows Insider blog かな、という印象があるのですが、いずれも上記に述べた課題があります。正直なところ、実用上は ChangeWindows.org + Windows blog for Japan の 2 つの情報でいけると思うのですが、大企業の方(特に IT 部門の方)だとなかなかこれでは納得できない & 各業務部門の開発現場を納得させられない、ということがあるかもしれません。

こうした事情から、私の方で Premier サポートの方々と相談し、(有償サポートではあるのですが)日本の Premier サポートサービスの提供サービスメニューの一つとして、「WaaS 非互換情報提供サービス」「Windows 7 → 10 非互換情報提供サービス」という名称で、機能追加・変更の一覧情報を FU リリースのつど提供できるように調整しました。

image

以下のような項目を一覧化して取りまとめています。ざーっと機能追加・変更を眺めて、各自の業務アプリに影響しそうな項目を抽出するのに役立てることができると思います。弊社プレミアサポート契約をお持ちであれば、有償ですがそこから入手が可能ですので、どうしても非公式情報や英語の情報では……といったことがあれば、弊社プレミアサポートまでお問合せください。

  • カテゴリ
  • 変更内容
  • 変更が加えられたバージョン/ビルド番号
  • 変更種別(機能追加・変更・削除)
  • 参考 URL(情報ソースとなっているより詳しい URL)
  • 要注意事項(レガシー業務アプリに非互換問題を引き起こしやすそうな項目)
  • 備考

またここでは特に取り上げていませんが、私が所属しているコンサルティングサービスでは、(大企業様向けに特化していますが)Windows 10 導入支援や、開発近代化支援などのカスタムコンサルティングサービスを提供しています。Windows 10 WaaS 環境への移行や、システム開発を近代化して WaaS 対応はもとよりさらなる高開発生産性を目指したい、といったニーズに関しては、コンサルティングサービスにご相談ください。

[Windows OS の変更ログの利用上の注意点について]

最後に、Part 5-b. で触れたことの繰り返しになりますが、どのような非互換情報リストを利用しても、絞り込んだテストだけで非互換問題を事前に100% 洗い出すことは原理的にできない、という点についてはくれぐれも注意してください。マイクロソフトに限らず、ベンダーから提供される非互換情報そのものは、すべての変更点を網羅したリストにはそもそもなっておらず(=すべての変更点を網羅したリストを提供していない)、また同時に、「何を機能追加・変更・修正とみなすのか?」に関しては、ある程度、人間の恣意的な判断が入ることが原理的に避けられません

image

ですから、以下の 2 点については必ず意識するようにしてください。

  • どのような変更ログを利用するとしても、絞り込み再テストを行う方法では原理的に見逃しリスクをゼロにすることはできない。
  • 故にリスクヘッジのためには FU/SU のリング配信と迅速な障害対応を組み合わせることが必須である。

[まとめ~エンタープライズ企業における Windows 10 導入と WaaS 適用の要点]

というわけでかなりの大ボリュームでお送りした一連の blog エントリですが、締め括りとして要点をまとめておきます。

  • エンタープライズ企業における Windows 10 の導入では、その後の WaaS 適用を見越した対応を行うことが重要である
    • Windows 10 の導入 = 継続的にアップグレードされ続ける環境への移行である。
    • このため、WaaS での継続的な機能アップグレードの仕組みを理解するとともに、過去のやり方を見直し、効率的な保守・運用ができる仕組みを整えることが重要になる。
  • 代表的な Windows 10 導入の目的
    • 1. セキュリティ強化、2. モバイルワークスタイル、3. 多彩なデバイス活用、などを検討してみる
  • WaaS の正しい理解
    • IP/CB/CBB/LTSB の使い分けが重要、特に LTSB は一般の OA 端末で利用するものではないことに注意する
    • Windows 10 の更新には、FU、SU、ストアアプリの更新がある
    • FU は 1 バージョンスキップするものではなく、順次アップグレードしていくべきものであることに注意する
  • Windows 10 導入時の検討の流れ
    • Windows 10 の導入時にやるべきことは、アプリ/インフラ、すぐに対応できること/できないことに分けて考える
  • Windows 10 導入時に考え方・やり方を変えるべきポイント – アプリ編
    • ミッションクリティカル度に応じて、互換性検証のレベルを変える
    • アプリ互換性検証の効率化の基本指針は、① テストケースの絞り込みと、② FU/SU のリング配信によるリスクヘッジ。
    • さらに効率化を進めたい場合、全体最適化が必要なこともある。その際は、中長期的な計画として、システム開発の近代化に取り組んでいく。
  • Windows 10 導入時に考え方・やり方を変えるべきポイント – インフラ編
    • 特に、セキュリティ、マスタイメージ管理、FU/SU 配信方式について検討するとよい。
  • Windows OS の変更ログの入手方法
    • いつどのような目的でどんな非互換情報(変更ログ)が必要になるのかを考え、それに合わせた情報入手方法を決定する。
    • 公式/非公式の様々な Web サイトがあるため、自分の目的にあった情報入手方法を検討しておく。

Windows 10 の導入は、新しい近代的な IT 環境への移行である、と考え、本 blog エントリを参考に、ぜひ IT 環境やシステム開発の近代化を進めていただければと思います。長文にもかかわらず、最後までお付き合いいただいてありがとうございました。

拝啓『変わらない開発現場』を嘆く皆様へ ~変わっていくエンタープライズ系業務システム開発とマイクロソフトエンタープライズサービスの取り組み~

$
0
0

image

 昨年、今年と 2 回に渡って de:code にてエンプラ系 SIer さんの PL, PM, SE を対象としたセッションを担当しました。エンプラ系 SIer の『闇』はかなり深いものがあり、現場担当の方々はそれを改善すべく日々奮闘されていると思うのですが、その一方で、全体論としての捉え方が正しくないが故に、アプローチが誤っていたり掛け声だけで終わってしまっているケースも少なくありません。例えばエンプラ系開発現場でも最近はトップダウンで DevOps に取り組め、なんていう指示が出たりすることもあるのですが、実際にそれがうまくいっているお客様をほとんど見かけないのも事実です。

 こうした背景があり、de:code 2017 ではセッションを担当すると同時に、参加者全員に配布するマーケティングのリーフレットを使って、筆者赤間の所属するマイクロソフト エンタープライズサービス(有償サービス部門)での最近のコンサルティングの取り組みをいくつかご紹介するという試みをしてみました。マーケティング部門の費用を使っている関係で、弊社サービス部門の営業色が入った内容にはなってしまっているのですが、その一方で、この情報は(特にエンプラ系 SIer のマネジメント層の方々にとって)組織やチームをどういった方向に導いていけばよいのかの指針になる、とも考えています。実際、いくつかのお客様でこの取り組みを私からご紹介したのですが、自組織の今後の方向性を整理する上で非常に参考になった、という F/B が多かったです。

 個々の皆様の事情はそれぞれ違えど、多少でも参考になる部分があればと思い、blog 化してこちらのサイトにも掲載することにしました(基本的な骨子はリーフレットと同じですが、ページ数の関係で書ききれなかったことを加筆修正しています)。よろしければぜひご覧いただければ幸いです。


■ エンプラ系 業務システム開発を俯瞰する

 クラウドに代表される昨今の技術革新は、業務システム開発の在り方に大きな影響を与えてきました。インフラ、アプリ、運用などの領域に分けてみると、以下のような変化が起きています。

image

 これらの中でも特に重要なのが、「アプリ特性に応じた開発のやり方をする」という考え方です。様々な分類方法が提唱されているのですが、ここではシンプルでわかりやすい、SoE(System of Engagement / お客様との絆を強めるためのシステム)SoR (System of Record / 事実を記録していくためのシステム)という分類を取り上げてみます。SoE 型システムとは「コンシューマ向け、フロントエンド、オープン、B2C」といった特性を持つシステムで、代表例としてはコンシューマ向け Web サイトやゲームアプリ。一方、SoR 型システムとは「エンタープライズ系、バックエンド、基幹系、B2B/B2E」といった作成を持つシステムで、代表例としては金融系勘定システムのようなものが挙げられます。

image

 並べてみると明らかなように、これらはシステム開発としての基本的な特性が大きく異なります。例えば SoE 型システムはページ数や業務数は少なめですが、かわりに極めて先進的な技術が投入されます。一方、SoR 型システムは、一つ一つのロジックはさほど難しくないものの膨大な数の業務が存在し、品質の安定性重視のために枯れた技術が好まれる傾向にあります。

 また、開発特性が異なれば、求められる開発文化も当然異なります。例えば SoE 型システムでは「素早くリリースして素早く軌道修正していく」という Try & Error の文化が重視されますし、SoR 型システムでは「大規模システムを早く・安く・上手く作るためにいかにプロジェクトやメンバーを制御するのか」といった点が重視されます。

 もちろん実際の個々のシステムはここまで両極端ではないでしょうが、この分類に従って考えてみると、いろいろ考えやすくなるのも事実です。例えば日本のシステム開発市場に関して考えてみると、

  • いわゆるエンプラ系 SIer での業務システム開発は SoR 型システムが圧倒的多数。(この傾向は今後もおそらく変わらない)
  • アジャイルやDevOps プラクティスは SoE 型システム開発界隈から生まれてきた手法であり、エンプラ系 SIer の SoR 型システム開発には単純適用できないことが多い。
  • 最近ではお客様が新規性の高いビジネスを模索していることも多く、エンプラ系 SIer といえど SoE 型システムの開発に取り組まねばならないことが多くなってきている。

といった実情があるのですが、こうした状況が整理されないまま、DevOps やアジャイルは日本に定着するのか? 的な議論が数多くなされています。こうしたことが、日本の開発現場の改善に向けた建設的な議論が進みにくいことの一因ではないかと私は考えています。

 また加えて言うのであれば、私は日本と欧米とでは随分と状況が異なると考えています。

  • 例えば、「欧米では Scrum が基本で DevOps が当たり前」と言われるが、そもそも欧米で 2000 年代にオフショア化が非常に進んだことを考えると、欧米の国内に残った「高付加価値 SI」は、必然的に内製でも成立するような(内製でないと成立しないような) SoE 型のような先進性を求めたものになる。よって、そこでアジャイルや DevOps などのプラクティスが進化・発展するのはある意味当たり前。
  • その一方、日本は世界中でも言語障壁が特に高い国であり、欧米のように大規模 SoR 型システム開発をうまくオフショアできていないのが実態。結果として、国内には Web サービス系企業やゲーム業界のように積極的に DevOps プラクティスを活用する企業と、確実性をなにより重視し、膨大なドキュメントに基づく大規模な SoR 型システム開発を実施するエンプラ系 SIer 企業とがごった煮で共存している形となっている。

 よく、「システム開発に関して日本はガラパゴスだ」と言われるのですが、むしろこれは逆で、実際には欧米(特にアメリカ)のシステム開発の方がある意味よっぽどガラパゴスなのではないかと思うのです。というのも、アメリカにおけるオフショア先であるインドや中国などにおいて大規模開発が行われる場合は当然受託開発であり、そこではやはりウォーターフォール的な開発文化が強いらしく(私の見聞きしている範囲なのでバイアスはあるかもしれませんが)、その部分に関しては、日本の SIer とあまり状況は変わらないように見えるのです。……と考えると、おそらく日本のシステム開発の市場というのは、簡単に言えば「世界の縮図」ではないかと思うのです。

 しかしここで勘違いして欲しくないのは、仮に欧米のシステム開発がガラパゴス化していたとしても、そこからエンプラ系 SIer の SoR 型システム開発が学ぶべきこと・学べることはたくさんある、という点です。「ガラパゴス化」というのはあまりいい意味で使われない言葉ですが、最先端を突っ走ることによって多数の様々なプラクティス(実践的手法)が生み出されていることも事実であり、こうした内容は開発文化の違いを意識ししつつ(=きちんと取捨選択しながら)積極的に取り入れていく必要があります。

image

 こうした背景を元にすると、私を含めた、エンプラ系 SI 界隈に携わる人々が取り組まなければならない課題は主に以下の 2 つであると考えています。

  • ① SoR 型システム開発における、更なる開発生産性の改善
  • ② 先進的ビジネスのための SoE 型システム開発への取り組み

 以降では、これらに関する私の考えと、マイクロソフト エンタープライズサービスによるコンサルティングの取り組みや事例をご紹介していきます。


■ ① SoR 型システム開発における開発生産性の改善 ~開発近代化コンサルティングサービス~

 開発技術の進化があれば、その恩恵により開発生産性は改善されるはずです。……にもかかわらず、実際のエンプラ系システム開発現場ではその進化の恩恵を受けられていないことが多いのではないでしょうか? これには様々な理由があると思いますが、中でも大きな理由のひとつとして、日本の SI 商慣習(多重請負構造)における『上流』の仕事のやり方が、10 年以上も変化していない(ような現場が多い)ことが挙げられると私は考えています。

image

 当たり前のことですが、モノづくりのやり方が変われば、設計手法やプロマネ手法もそれに合わせた見直しが必要になります。この点は、私が昨年・今年と de:code で一貫して取り上げてきたテーマで、実践論として具体的なレベルまで踏み込んで解説しています(仔細は以下の ppt やビデオをご覧ください)。

  • de:code 2016 CLT-016 拝啓『変わらない開発現場』を嘆く皆様へ ~エンプラ系 SI 開発現場の「今」を変えていくために~
    https://channel9.msdn.com/Events/de-code/2016/CLT-016
    https://docs.com/decode2016/9106
  • de:code 2017 DO-08 『変わらない開発現場』を変えていくために ~エンプラ系レガシー SIer のための DevOps 再入門~
    (ppt, ビデオは後日公開予定)

 しかし上記のセッションで説明されているような「ベストプラクティス」がすでに存在するにもかかわらず、 Excel 設計書に代表されるように、日本の開発現場は昔ながらのやり方を淡々と続けていることが少なくありません。実際の開発現場を数多く訪問している私の肌感覚ですが、その最大の理由は、IT 部門や SI 関連会社、あるいは SIer のプロパーが、最新の開発技術やモノづくりのやり方を単に「知らない」、そしてその結果、それに併せた最適な SI のやり方を考えられなかったり、改善の打ち手を考えられなかったりすることが多いためだと考えています。

 これは、SIer のプロパーが怠けている、ということではありません。いやむしろ、SIer のプロパーさんほど身を粉にして働いている方々はいないでしょう。にもかかわらずなぜこのような状況が発生するのか? その原因の一つに、私は IT 部門や SIer における過度な外注やアウトソーシングがあると考えています。

image

 上図に示したのはシステム開発の業務遂行に必要な「5 つの専門性」です(仔細はこちらのエントリを参照)。こうした役割を、SIer のプロパーさんと、協力会社の方々によって分担していくわけですが、このアウトソースの仕方に問題がある場合が少なくありません。通常、SIer のコアコンピタンス は「プロマネ」「業務」「技術」の 3 つですが、 行き過ぎたアウトソースや協力会社への依存により、 社内にアーキテクトがいなくなっているケースが非常に多いです。その結果、現場あるあるとして以下のようなことが起こります。……というよりホントにこんな現場ばっかです;(涙)。

  • プロマネや業務 SE に対して 技術的視点から下支えする人材がおらず、技術的に正しい開発方針を取れていない
  • ベンダーや協力会社に対して不適切な指示を出してしまう。
  • 技術がわからないので協力会社の成果物を全くレビューできない。
  • 意味のない・効果の薄いドキュメントや報告書を大量に作らせてしまったりしている。

 このアーキテクト不在問題は SoR 型システム開発の改善を考えていく上で極めて根深い問題です。これについては、de:code 2017 DO-08 セッションにて詳細に取り扱っているのでご確認いただければと思うのですが、こうした状況を改善していくための基本的な手立ては以下の 2 つです。

  • 現在のプロマネや業務 SE の使っている、10 年前から進化していない SI 方法論を、近代的なものにリニューアルしていく。
  • 社内に技術がわかるアーキテクトを育てて保有していく。

 これらに関するマイクロソフト エンタープライズサービスの取り組みを私は「開発近代化サービス」と銘打っているのですが、具体的には以下のようなコンサルティングをお客様向けに実施しています。

  • A. 中堅層のプロマネ・業務 SE の再教育プログラム
  • B. アーキテクト育成プログラム

 以下に順に解説します。

[A. 中堅層のプロマネ・業務 SE の再教育プログラム]

 前述したように、モノづくりのやり方が変われば、設計手法やプロマネ手法もそれに合わせた見直しが必要になります。だからこそ、手持ちの仕事で忙殺されている業務 SE やプロマネの方々を、アーキテクトが技術面から下支えし、近代的な SI を実践していくことが必要なのですが、現状を踏まえると、以下の 2 つの問題があります。

  • そもそもアーキテクトが SIer の中にいない(or 不足している)
  • そもそも SoR 型システム開発における、「近代的な SI 手法」が(世の中的に)十分に整備されていない

 これらのうち、後者は非常に大きな問題です。先に述べたように、欧米は内製ベースの SoE 型システム開発を前提として、アジャイルや DevOps といった手法・プラクティス群を開発・整備し続けてきました。しかしその一方で、受発注ベースの SoR 型システム開発に関する「近代的な SI 手法」の改善に関しては手つかずで放置されており、全体としてはほとんど進化していません。私見ですが、おそらくこの問題は日本固有の問題ではなくグローバル共通の課題であり、これに対するソリューションは誰かが先陣を切って新しく作っていかなければならない状況である、と思います。そしてその先陣を切れるのは、SoE/SoR 型システム開発が共存する国である日本をおいて他にないだろう、とも考えています。

 なぜかというと、先に述べたように、SoR 型システム開発手法を進化させていく際には、SoE 型システム開発向けとして開発された各種の DevOps 系プラクティス群が参考になります。右から左に導入することはできませんが、本質論を踏まえて取捨選択しながら取り込んだり融合させていくことは十分にできるわけで(具体的な手法は de:code イベントセッションを参照)、こうした「SoR 型システム開発向けの近代的な SI 手法」を、自社の状況に併せて作り上げていくことが、SIer や IT 子会社の多くに求められている、と考えています。そしてこうした「他国/他社のいいところを参考にして、自分たちのやり方を上手に進化させていく」ことは、もともと日本のお家芸、だったのではないでしょうか?

 幸い、私はマイクロソフトというグローバル企業に所属しており本社の R&D 部門の手法の話やDevOps 系プラクティスについて聞く機会が多いこと、また日本のいわゆるエンプラ系システム開発現場に向けたコンサルティングサービスを 15 年以上手掛けていること、そしてこの領域をなんとかしたいという思いがあり、日本独自のソリューションとして開発近代化サービスを開発し、展開しています。具体的には、主に現場経験 10 年程度のプロマネ・業務 SE を対象として、V 字型モデル開発の基本を、現在の開発技術やトレンドに沿った実践的手法と共に学び直す、という再教育プログラムを開発・デリバリしています(実は de:code の昨年・今年の私のセッションは、この再教育プログラムからごく一部を切り出したものです)。

image

 内容としては、開発プロセス、チーム編成、外部設計、内部設計、ソフトウェアテストなどについて、日本の現状や欧米でのベストプラクティス、また日本の様々な現場に見られる課題の解決のヒントなどをひたすら紹介していく、というものになっています。実装方法を解説するものではないので、コードはほとんど出てこず、それ故に特にマイクロソフト技術に縛られる内容にもなっていません。実際の開発現場において「なぜそれをしなければならないのか」というところに踏み込むことで、プロマネ・業務 SE として外してはならない要点、SI において『幹』となる考え方やタスクをきちんと理解・習得していただくことを目的としています。

 また、こちらは単発トレーニングとして実施することも多いのですが、より踏み込んだお客様では、この内容を元に実際のお客様プロジェクトにおける設計・テストドキュメントの改善レビューを行い、より一層深い理解・改善をしていただいていることもあります。

[B. アーキテクト育成プログラム]

 もう 1 つは、そもそもの問題の根幹であるアーキテクト不足を解消するために、社内アーキテクトを育成していくプログラムです。アーキテクト育成のための設計・実装技術のスキルトランスファーといった昔ながらのサービスも手掛けていますが、アーキテクトとしての重要スキルの一つであるコンサルテーションスキルについても OJT での育成サービスを行っています。

 もう少し具体的に書くと、SIer や IT 子会社におけるアーキテクトは非常に貴重な人材であるため、共通部門(CoE, Center of Excellence)に集約され、下図のような様々なミッションを担っていることがよくあります。こうした人材はアーキテクトとしての活動を通して、究極的には現場向けの社内コンサルタントになっていくことが望ましいのですが、一朝一夕にコンサルタントとしての立ち振る舞いができるようになるわけでもありません。そこで弊社コンサルタントが『お手本』となって、ガイドラインの作成支援や実際の現場プロジェクト支援などに参画し、メンバーを育てていくといったこともしています。

image


■ ② 先進的ビジネスのための SoE 型開発への取り組み ~PoC ラボによるアイディアの早期具現化~

 さてここまで述べてきたように、SoR 型システム開発の改善は、従来のやり方の延長線で考えていくことが可能です。しかし SoE 型システム開発への取り組みに関しては、全く異なる発想とアプローチが必要になります。これについて、ビジネス的な背景も含めて少し掘り下げます。

 ご存じのように、昨今の IT(デジタル)は人々の生活(アナログ)をあらゆる面で支え、よりよいものにしています。このような変化は「デジタル変革」(デジタルトランスフォーメーション)と呼ばれていますが、このデジタル変革は極めて急激に進むという特徴を持っています。具体的に言うと、ひと昔前であれば Facebook や Google、そして最近であれば Uber や Airbnb などといった、いわゆるスタートアップと呼ばれる企業は、恐るべき速度でサービスを拡大してきました。そして既存企業がその速度についていけず、あっという間に既存ビジネスが破壊されるということが現実のものとなっています。

 この既存ビジネスの破壊において重要なポイントの一つは、そこで使われている技術は必ずしも新しいものではない、という点です。よく語られることですが、例えば 2007 年に発売された iPhone は、当時の既存技術を組み合わせて作られており、iPhone のために何か全く新しい技術が発明されたというわけではありません。この例からわかるように、現代は、すでに多数の「発明」(AI や VR などの革新的技術)が存在しており、それが「ユースケース」(使い方・ニーズ)の発見を待っている、そしてそれを最初に見つけてビジネス化できた人がイノベーションを起こす、という形になっているわけです。

image

 こうした「ニーズ」の発見には Try & Error による試行錯誤が欠かせませんが、昨今はクラウドの登場、デバイスのコモディティ化などによって IT 技術の利用コストが大幅に低減しており、挑戦のためのハードルが昔に比べて圧倒的に下がっています。その結果、数多のスタートアップ企業がリスクをどんどん取って、イノベーションへの挑戦(簡単に言えば「一発当てる」ための挑戦)を繰り返している、という状況が生まれています。米国におけるスタートアップブームの背景にはこうした IT 環境の変化があるわけです(このあたりは馬田さんの良著「逆説のスタートアップ思考」にて詳細に解説されていますので、興味がある方はご一読いただくとよいと思います)。

 上記のような状況を元に考えると、先進的テクノロジを使った新しいビジネスの取り組みに必要なポイントは以下の 3 つであり、マイクロソフト エンタープライズサービスではこれらに対応するサービスをそれぞれ展開しています。

image

 以下に順に解説します。

[A. 最新テクノロジの理解(インベンションの理解)]

 革新的なサービスを考える際、シーズ(種)となる最新技術を知ることは極めて重要です。特に事業部門のお客様に近ければ近いほど、ビジネスに深い造詣がある一方、最新技術を知らないことが多くなるため、こうした最前線の方々の業務知識と、最新技術とを突き合わせてみることがより重要になってきます。

 とはいえ数多の技術をすべて把握することは非現実的でもあります。こうしたことから、弊社では定着期に入りつつある技術を効率的に把握するための最新技術セミナーを用意しています。このセミナーによって、ビジネスアイディアとマッチングできる最新技術を探っていくわけです。

image

[B. ビジネスニーズの深掘り(ユースケースの模索)]

 2000 年以降に成人になった若者の方々をミレニアル世代と呼びますが、消費世代に差し掛かってきたミレニアル世代の人々の考え方は、旧世代の人たちとは大きく異なることが知られています。よく言われることとして、デジタルネイティブである、所有欲がない(シェア・利用)、出世よりワークライフバランス、自分が納得したものを消費する、ソーシャルで緩く繋がる、といったことが挙げられますが、こうした価値観の変化にうまく追随できていない既存企業が増えつつあります。

 実はこの問題はミレニアル世代に限った話ではなく、リーマンショック以降の生活様式の変化や人々の価値観の変化といったものに追随できないことで、新しい企業にお客様を奪われていくことがしばしば発生します。その大きな理由の一つとして、「従来型のビジネスを熟知していること」、すなわち過去の成功体験が発想・思考の妨げになることが挙げられます。こうした課題を解決していくためには、なによりお客様を深く理解すること、そしてそこから隠れたビジネスニーズを深掘り・発見していくことが必要になりますが、そのための手法として最近着目されているのが、デザイン思考(Design Thinking)と呼ばれる手法です。

 本論から逸れるため、ここではデザイン思考の詳細に立ち入ることは避けますが、もともとデザイン思考は優秀なデザイナさんによる物事の「思考方法」「考え方」を真似るところからスタートしており、下図に示すような発展の経緯を辿ってビジネスの世界へと入ってきています。このことからもわかるように、デザイン思考そのものは必ずしも IT に限定されるものではないのですが、近年では、こうした「優秀なデザイナさんによる物事の思考方法」を、各業界に特化した形で進化・発展させ、より使いやすい手法(プロセス)としたものが各社から提唱・提供されているようになってきました。

image

 マイクロソフトでもこうした流れを受け、デジタルエンビジョニングワークショップ(DEWS)という手法を開発し、サービスを提供しています。これはお客様への深い洞察を元に、新たなイノベーションを IT 技術との組み合わせにより生み出していくビジネスデザイン手法を体系化したもので、デジタルトランスフォーメーションへの足掛かりとして、現在マイクロソフト エンタープライズサービスよりサービスを提供しています。

image

[C. プロトタイプによる PoC 検証(Try & Error による模索)]

 さて、最新技術とユーザニーズの掛け合わせから生まれたアイディアは、PoC (Proof of Concept、プロトタイピングによる概念検証)により素早く具現化・検証していくことが必要です。前述したように、今日では IT 実現コストの低減により「まずは試してみる」ことが昔に比べて圧倒的に容易化しています。アイディアを素早く形にして試しいみることで、お客様からの F/B を早期に得ることができ、より優れたアイディアやビジネスに昇華させていくことができるわけです。

 こうしたアイディアの素早い具現化のためには、以下の 2 つが必要不可欠です。

  • 社内 PoC ラボセンターの設置
    アイディアの具現化は素早く社内で行うことが必要であり、いちいち請負型でベンダーに発注することは非現実的です。このため、PoC アプリ開発チームは社内に持つ必要があります。(社内アーキテクトは SoR 型システム開発におけるアーキテクトロールだけでなく、こうした取り組みでも活躍してもらう(=アーキテクトの人たちが先端技術を使って直接プロトタイプを実装していってもらう)ことになります。)
  • 高速開発基盤の整備
    Azure などのクラウドをフル活用した開発基盤を整備し、PoC アプリ開発チームが作成したプロトタイプをすぐさま配置してテストできる環境を整えておく必要があります。

 なおこれらの取り組みは、SoE 型システム開発における PoC 検証はもとより、従来の SoR 型システム開発においても有効性が高いです。なぜなら PoC によるプロトタイピングで十分に UI などの要件を煮詰めたうえでベンダーに発注することにより、見積もりブレや手戻りなどのプロジェクトリスクを極小化することができるためです。

image

 弊社マイクロソフト エンタープライズサービスでは、上に示したような取り組みをしやすくするため、以下のようなサービスを展開しています。

  • PoC ラボセンターの提供
    弊社コンサルタントチームが(場合によってはお客様先に常駐して)実際に PoC を回し、高速にお客様アイディアを具現化していく。
  • Azure PaaS 開発基盤の整備
    Azure を利用する場合のリファレンスアーキテクチャガイドの整備などを実施する。

■ マネジメント層に求められる役割

 さて、ここまでマイクロソフト エンタープライズサービスが手掛けてきているエンプラ系 SIer 向けの取り組みをご紹介してきましたが、こうした取り組みを実際に進める際には、マネジメント層にも重要な役割を担っていただく必要が出てきます。それは、SoR 型システム開発と SoE 型システム開発とを分離し、そこに適切な経営レベルの判断を入れる、という点なのですが、このポイントは非常に重要なため、最後に解説して締めくくっていきたいと思います。

 前述した SoR 型システム開発への取り組みは漸進的アプローチ、すなわち従来の取り組みを改善していくアプローチである、ということができます。一方で、SoE 型システム開発への取り組みは革新的アプローチであり、リスクを取って進めていくべき性格が強いものです。このため、適用対象となるプロジェクトも違えば、個々の社員の向き不向きも異なってきます。もちろんノウハウを交換しあうことは重要ですが、全社一律のルールで管理しようとすると、様々なところに無理が生じます。故に、会社の中で、SoR 型システム開発への取り組みと、SoE 型システム開発への取り組みとをきちんと分けてそれぞれ考えることがまず重要になるわけです。

 また、特に SoE 型システム開発でイノベーションへ取り組む場合には、マネジメント層の『投資判断』が必要になることもしばしば発生します。イノベーションに必要なユースケースの発見には、相当な Try & Error が求められるのですが、実際のスタートアップ企業の打率はかなり低く(例えば 100 社に投資しても 1 社成功するかどうか、しかしその成功する 1 社は 1 万倍のリターンがある、といった具合)、ハイリスク・ハイリターンの博打的な側面があります。こうしたところに企業としての合理的な判断(例えば ROI や短期的なリターンなど)を求めると、イノベーションへの取り組みは必然的に失敗します(これをイノベーションのジレンマと呼びます)。

 こうした問題から最近着目されているのが「バーベル戦略」という考え方です。これは大半のリソースは従来通り安全牌にかけておき、一定のリソースをハイリスク・ハイリターンに賭けてイノベーションを目指す、というものです。先に述べたように、スタートアップ的な領域への投資では一発の大当たりが他のすべての失敗を帳消しにするものの、実際には「全部失敗することもありうる」わけで、余剰体力が残っているうちに、余剰体力で新しいビジネスの芽を見つけるために頑張るわけです。

image

 マネジメントは何かと現場に ROI を求める傾向がありますが、ROI が算出できるのであれば、マネジメントの判断など不要です原理的に白黒が付けられない投資(リソース投入など)に対して、自らの裁量、そして KKD (勘と経験と度胸)で判断をすることこそがマネジメントの仕事である、私はそう考えています。(← KKD はそういうところで使うべきものだと私は思います)

 本 blog で解説したような取り組みでは、必ず投資モードで行う作業が発生すると思います……が、そうした取り組みをいつ、どの程度、どのような形でやるのか。それを判断しサポートするのはマネジメント層の役割であり、経営判断がどうしても必要な領域になります。マネジメント層の方々には、ぜひ現場メンバーの積極的な姿勢や取り組みをサポートし、会社やチームの活性化につなげていただければと思います。


■ まとめ

 本 blog では、私が所属するエンタープライズサービス統括本部のコンサルティングの取り組みのご紹介を通して、『変わらない開発現場』に苦しむ IT 子会社さんや SIer さんの改善の取り組みのヒントになる情報を執筆してみました。要点をまとめると、以下の通りとなります。

  • システム開発の改善を考える場合には、SoE 型システム開発と SoR 型システム開発とに分けて考える。
    • SoE 型システム = 「コンシューマ向け、フロントエンド、オープン、B2C」といった特性を持つシステム、コンシューマ向け Web サイトやゲームアプリなど
    • SoR 型システム = 「エンタープライズ系、バックエンド、基幹系、B2B/B2E」といった特性を持つシステム、金融系勘定システムなど
    • これら 2 つは開発特性が全く異なるため、分けて考える必要がある
  • 欧米の手法を右から左に導入するのではなく、システム開発特性を意識した上で導入する。
    • アジャイルや DevOps などの手法は、SoE 型システム開発の文化の中で進化・発展してきたもの。
    • 日本国内には SoE 型システム開発と SoR 型システム開発の両方が共存している。このため、SoR 型システム開発へ欧米の手法を導入する際には、各種プラクティスを取捨選択しながら段階的に導入し、現場改善につなげていく必要がある。
  • SoR 型システム開発の改善は、以下の 2 軸で考えていくとよい。
    • 現在のプロマネや業務 SE の使っている、10 年前から変わらない SI 方法論を、近代的な SI 方法論にリニューアルしていく。
    • 社内に技術がわかるアーキテクトを育てて保有していく。
  • SoE 型システム開発の改善は、以下の 3 軸で考えていくとよい。
    • 最新テクノロジの理解(インベンションの理解)
    • ビジネスニーズの深掘り(ユースケースの模索)
    • プロトタイプによる PoC 検証(Try & Error による模索)

 システム開発現場の悩みは、SIer ごと、開発現場ごとにそれぞれ異なると思います。本エントリの解説を参考にしていただいて、現場改善のための具体的な戦略立案、そして実行へとつなげていただければ幸いです。

Viewing all 47 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>