[ホームへ戻る] [前へ] [上へ] [次へ]

システムを開発する上での重要なポイント

ある本のカバーに、「十年後も通用する基礎を身に着けよう」というフレーズが載せられていました。これは、次々と新しいことが出てきて、絶えず勉強をし続けなければならないことに嫌気が差している、プログラマーにとっては興味をひきつけるものです。

「もう五年後にはJavaなんてないだろう」

「コーディングなんて誰でもできるようになって価値を引き出すことはできない。次の時代に必要な能力は云々」

などということが巷で言われると、今自分が学んでいることがすごく無駄なように見えてきます。

確かに時代は変化します。とはいえ、システムの本質は変わっていないのではないか、と思えます。アプリケーションを作成するにあたって、重要なポイントがあります。これはどのようなシステムを作るうえでも、理解し習得しなければならない大切なことです。

この業界で仕事をしていくうちに共通項が見えてきます。それはどんな分野であっても、またソースコードを書くミクロなレベルから、サーバーを組み合わせてシステムを構成するマクロなレベルに至るまで、あるいは設計から実装に至るまで共通して言える重要なポイントがあります。

プログラマー、SEの経験を通して、これらの本質を身に着けていかなければならないと思います。これらのことが守られているシステムは優秀なシステムであり、そのように設計・作成できるSEは優秀なSEだと思います。とはいえ、完璧、というのはなかなか難しいのですが。



よいシステム、よいソースコードとはどのようなものか

  よいシステム

よいシステムとして必要な要素を以下に列挙してみたいと思います。一般的に、RASIS(信頼性・可用性・保守性・保全性・機密性)を指すのですが、ここでは、さらに、性能、操作性、効率を加えます。

可用性(Availability)[、信頼性(Reliability)、保守性(Serviceability)]

可用性とは、システムがどれだけ故障せずに使用し続けることができるかということです。サービスを24時間365日間連続運用し続けることができるとしたら、それは非常に可用性の高いシステムと言うことができます。

故障した場合にどうするかというと、一般的には予備を用意するというのが普通の考えでしょう。例えば、家の電球は消耗品です。なので電気が切れたときのために手元に予備を用意します。もし用意していれば、明かりがない時間は故障に気づいてから交換するまでの時間となります。もし用意していなければ、修理したり、交換品を買いに行って取り付けるまでとなって、故障時間は長くなります。

システムの場合も同じです。現用系(本番系)と待機系という二つの系統を用意し、現用系に問題があれば待機系に切り替えます。障害を検知してから待機系に切り替える作業は、手動で行う場合と自動で行う場合があります。手動で行う場合は、随時監視している必要があります。自動で行う場合、クラスタリングという技術が使われます。クラスタリングとは、複数のマシンをセットにして、外側から見ると1つに見せる技術です。可用性を高める目的と負荷分散をする目的で使用されます。通常、複数のマシンの前にロードバランサをおいて振り分けたり、あるいはIPアドレスを付け替えたりすることによって、切り替えを行います。

ホットスタンバイの場合は、待機系も起動して待機しているため切り替えは速いのですが、そのためには現用機の処理中のデータを引き継ぐ必要があります。コールドスタンバイの場合は、障害を検知してから待機系を起動することになります。ホットスタンバイに比べて実装は簡単ですが、現用機で処理中のデータは失われます。

処理だけを行ってデータを保持しないノードはクラスタを組まずに、単純にN分散すればいいので楽です。障害時に仕掛中のデータは失われますが、他のトラフィックは他のサーバが受けるのでサービスはそのまま継続されます。この形にできるところはそうした方が故障時間が0になるので可用性が高まります。

可用性の計算は以下の式で行います。

 可用性
 =平均故障間隔/(平均故障間隔+平均故障時間)
 =信頼性/(信頼性+保守性)

平均故障時間は、障害を検知してから、待機系に切り替える(フェールオーバー)までの時間。通常、ここはクラスタリングソフトを使って、障害を検知し、IPアドレスを振り替えて、待機系のサーバーをアクティブにするまでの時間です。この時間は計ることができますが、平均故障間隔は実績がない場合、算出することはできません。平均故障間隔は、使用時間/故障回数で算出できますが、新規に作成したソフトの場合はわかりません。ソフトウエアはバグがゼロだと考えて、ハードウエアの故障について資料から算出したり、定期的なメンテナンスの時間から計算することはできるでしょう。プログラムのバグでデータ不正や停止が起こるなら、もはや計算は不可能です。ただし、サーバーが停止したとしても再起動すればサービスを継続できる程度の場合、障害調査のためのログの収集や継続のためのデータの調整等の時間+起動時間が、故障時間となります。

多重化するのは、スイッチやルーターといったネットワーク製品、サーバー、NIC、ストレージといった故障する可能性のあるすべての箇所で行います。

ストレージについては、RAID0(ミラーリング)やRAID5という形で多重化します。また重要なデータの場合は、RAIDだけに頼るのではなく、随時バックアップを行い、CDやDATなどのメディアに移して、障害回復や履歴参照のために役立てるのです。この場合、障害復旧時間は、バックアップからリカバリするのにどれくらい時間がかかるか、またいつの時点のバックアップがあるかによって、変わってきます。

保守性(よいソースコード)

RASISの保守性は、一般にサービスの停止時間を指しています。障害やメンテナンスによって停止し再度サービスを開始するまでの時間のことです。

ここで言う保守性とは、障害の原因の追及のしやすさ、機能の追加や変更があったときの修正のしやすさを指します。

よいソースコードは、この二つの目的を満たすものです。手入れをするのは人間だから、人間にわかりやすいソースを書く必要があります。つまり、可読性が高く、またシステム内部のことを用意に調査できる(検索が容易)ことが大切です。構造が理解しやすく、きれいに分類され、記述が統一され、適切に変数名が命名されていることなども大切です。

修正箇所が限定され、機能がカプセル化されて、他との依存関係が少ない、もしくは明確になっていることも重要です。依存関係が大きいと、障害箇所の特定も難しく、テストも難しくなります。ソースコードに余計な重複がないことも、構造が理解しやすくするとともに、修正箇所が限定されるため重要です。

ただ、オブジェクト指向を使って、過度に共通化・抽象化が図られていると、実際の値、処理を探し出すのに、多くのリンクをたどっていく必要があり、すぐには理解できないという問題もあります。動的結合だと、メソッドがどのクラスのものが使われているのかがすぐにわからないため、ボトムアップで解析する場合、つまり問題箇所から追跡する場合、わかりにくいものです。過度な構造化、オブジェクト指向化は、ソースを書く場合、利用する場合はいいのですが、ソースを読む場合は大変です。

ただ、注意しておかなければならないのですが、一般的にオブジェクト指向ではない方がソースの部分部分では理解しやすいし、修正もしやすいのです。依存度が少ないので修正の際に影響を気にする必要はありません。しかし、共通化が行われていないため、同じ修正をあちこちで行う必要があり、その意味では修正は難しいのです。安易な修正はソースのスパゲッティ化を招き、すればするほどソースは理解しにくいものとなります。

また、適切なログ出力や例外対処も障害対応にとって重要です。

それからやはりテストしやすい形式、テストの自動化なども保守する上で重要なポイントといえるでしょう。

ここでは設計とコーディングにおいて必要な三つのポイントと付加的な事項について解説します。

  1. 分類
  2. 共通化
  3. 抽象化
  4. どのように実装するか
  5. レイヤー、インターフェースについて
  6. 必要な制約
  7. ソースコード作成指南

性能

処理性能は、リアルタイムの処理をする場合に特に問題になります。サーバーのレスポンスについて、リクエスト数が少ない場合は速くても、リクエスト数が多くなると急に処理が遅くなる場合があります。その際の対処として、いろいろな対処法があります。

・マシンのスペックを上げる

最も単純な方法は、マシンのスペックを上げる方法です。高性能なCPUや、CPUの数を増やしたり、メモリを増設したり、高速なストレージを使うなどです。しかし、アプリケーション側に原因がある場合、ほとんど効果がない場合があります。

・アルゴリズムを変更する

処理を見直し、無駄な処理や、不必要に繰り返している処理がないかを確認します。

・キャッシュ

データを読み込む場合、ディスクから読みこむよりも、メモリから読み込むほうが速いです。なので速度を重視するならデータをメモリに置きます。しかし、メモリ上のデータは、アプリケーションやマシンの終了と共に消えます。

データは1元管理する必要がありますが、速度を損なわないようにするためにキャッシュという手法を使います。永続的データストアからのデータをメモリに置いておくのです。そして最初のアクセスの際にキャッシュに置いて、あとからのアクセスはキャッシュから読み込むようにします。データの変更は、キャッシュに対して行って、キャッシュがデータストアにアクセスして同期を取るやり方と、直接データストアを書き換えて再度キャッシュに読み込む、あるいはキャッシュから当該データを削除するというやり方があります。キャッシュを使う場合、同期ズレには十分注意する必要があります。

また、キャッシュと同じ考え方として、処理に時間がかかるものがある場合、あらかじめ処理したものを保存しておいて、問合せがあった場合にすぐにその結果を返すという方法があります。これは正規化の考え方に反していて、データの重複が発生しますので、整合性が取れるように注意して行う必要があります。

・ロック

排他制御は、並行処理を行うために不可欠なものですが、ロックを取得するのに時間を要する上、待たされる方は待たされている間だけ処理が遅くなります。なるべくロックを使わずに整合性が保たれるようにするやり方を取ったり、ロックしなければならない処理をなるべく短くするように工夫します。

・プール

他のサーバーへの接続処理は時間がかかるものです。それらのオーバーヘッドをなくすために、プールという方法が用いられます。あらかじめコネクション(接続)を張っておき、必要とするプロセスに貸し出すようにするのです。データベースやディレクトリサーバ、他のTCP/IPコネクションをプールしたり、スレッドをプールしておいたりします。これはキャッシュの一種と言えるかもしれません。

・負荷分散(並行処理)

マシン一台の処理性能には限界があります。そのために、サーバーを増設して、別のマシンに処理をさせます。その場合、機能で分割する垂直分散と同じ処理を複数のマシンで行う水平分散とがあります。垂直分散では、サーバーで行う処理を分割して、複数のサーバーで一連の処理を行うようにします。ただし、この場合、各サーバー間でのデータの受け渡しにかかるオーバーヘッドを考慮する必要があります。

水平分散の場合、複数のサーバーを並列に並べ、その前にロードバランサを置いて、順番に各サーバーに処理を割り振ったり、各サーバーの負荷の具合に応じて処理を割り振ったりします。

ただし、1台のマシンを使い切っていない場合、CPUに空きがある場合、同じサーバー内で、同じプロセスやスレッドを複数起動して、並行して処理するようにします。

・データベースの場合

インデックスを張ったり、SQL文を見直したり、物理設計を変更したりします。

効率性(資源の有効活用)

ここで言う資源は、CPU、メモリ、ストレージ(ハードディスク)、ネットワークを指します。

CPU、メモリ、ネットワークが混雑すれば、全体性能は低下します。ハードディスクにファイルがいっぱいになればそれ以上の処理ができなくなる可能性があります。かといって安易に増設する前に、考える必要があります。資源の無駄遣いを防ぐのです。

CPUは、プログラムの中に無限ループがあれば簡単に占有されてしまいます。Windowsを使っていて、重くなったと思って、タスクマネージャーでプロセスを見ると、大体何らかのプロセスが異常を起こして、特別処理を行っているわけでもないのに、CPUを占有しているのです。おそらくプログラムの中で無限ループか何かあるのでしょう。そのプロセスを落とすと、元に戻り速くなります。また、ネットワーク資源を有効活用するために、サーバー間の通信を減らすようにプログラムを修正するなどの工夫も考えられます。

また、ソースの無駄な部分を削ることで、つまりよいアルゴリズムに変えることでメモリやCPUを節約し、性能を上げることも可能です。

ただここで、考えなければならないのは、保守性と、効率性・性能はトレードオフの関係があります。フレームワークを作って形式を統一することは、保守を容易にするメリットがあります。

しかし、形式を統一しようとすると、非常に簡単な処理も、そのテンプレートを使わなくてはならず、数行で済む処理が、何十行、何百行もの余計な処理を付け加えなくてはならなくなることもあります。形は統一されているのですが、中を覗くと、多くの無駄を見つけることになるでしょう。

一方その場その場に最適化したものは統一性がないものです。最適化するとは、余計な処理を省くことであり、共通化したコンポーネントを使う場合無駄な処理が含まれるので、使わずに必要な処理だけを切り出して直接使います。またメソッドの呼び出しにはオーバーヘッドがかかるのでメソッドに分割することもしません。こうすると、全体に統一性がないために、解読は困難になり、保守性は低下します。どこかで妥協点を見出す必要があります。

データの整合性

データの整合性とは、データに矛盾や不正がないことを言います。矛盾とは、例えば、オリジナルファイルがあって、AさんとBさんが別々に修正した場合、どちらが正しいかわからなくなるような場合のことです。この場合二つのデータの間には矛盾が存在することになります。

データの不整合を防ぐ基本は、データの一元管理です。先の例の場合、オリジナルをコピーしたために、もはや一元管理は失われています。

もし一度に一人しか更新を行わないのであれば、不整合は起こりません。そこで使われるのがロック、排他制御というものです。先の例では、オリジナルファイルを直接編集するようにして、一人が編集している間は他の人間は編集できず、読み取り専用でしかファイルを開けないようにするのです。こうしてAさんが編集し終わってから、BさんがAさんの編集後のファイルを編集するようになるため、二重原本の問題はなくなります。

ただここで問題なのは、Aさんの作業中、Bさんはずっと待機させられるということです。もしAさんが編集したい箇所と、Bさんが編集したい箇所が違う場合は、同時に編集しても問題ないのではないでしょうか。しかし中途半端にミックスしたのではかえってぐちゃぐちゃになってしまいます。通常のファイルでは難しいですが、データベースでは、その辺の工夫がなされ、極力ロックの範囲が小さくなるようにしています。通常のデータベースでは、行単位にロックをかけるため、同じ行でなければ別々の人が並行して編集することができます。

・トランザクション

また、データの整合性の要件として、一連の作業を一セットで完了しなければならない、というものがあります。例えば注文表と注文明細表は同時に編集しなければならないとします。注文表には、お客さんや担当者の名前や日付が書かれ、明細表には各商品と個数が書かれています。これは1まとまりで一つのデータであるため、中途半端に途中までしか書かれていないのは望ましくありません。したがって、最後まで書くか、編集作業を破棄するか、どちらかでなければなりません。

この一連の処理のことを、トランザクションといい、データベースにおいては理論化されています。一つの値を更新するだけの短いトランザクションもあれば、いくつもの表を更新する長いトランザクションもあります。

一般的に、データベースでは、ACID属性といって、4つの特性が必要とされています。

  1. 原子性(atomicity)
  2. 一貫性(consistency)
  3. 独立性/隔離性(isolation)
  4. 保全性/耐久性(durability)

原子性とは、先ほど述べた、トランザクションはすべて完了するか、すべて破棄するか、つまりオールオアナッシングでなければならないということです。最後まで完了しない場合は、ロールバックといって、それまでの変更をすべて破棄します。これは例えばデータベースとの接続が切れた場合などの際にデータベースが自動的に行ってくれる場合もありますが、通常は不正を検知した際にアプリケーション側でロールバック指示を出す必要があります。

一貫性とは、更新前、更新後いずれの時点であっても、保存されているデータが正しい状態にあるということです。例えば、振込みを行った際、片方からは十万円出金されているが、もう片方には一万円しか入金されてない、といったことがないことです。これについてはロジックを正しく書き、正しくトランザクションを行い、原子性が保たれていれば問題ありません。

独立性とは、複数トランザクションを同時に実行しても、順に実行しても処理結果が同じであることを言います。つまり、各トランザクションが独立して処理されるということです。ただしここではいくつかのレベルがあります。他のトランザクションで更新中のデータを読むことのできるダーティリードのレベル、自分のトランザクションの最中に完了した他のトランザクションの更新後のデータを読むことができるレベル、他のトランザクションが挿入したデータが読めるレベル、そして、自分がトランザクションを開始したときのデータだけが読めるレベル(読み取り一貫性がある)とあり、最後のもののみが完全な隔離性を実現しています。これは、ロックやバージョニング機構によって実現されます。

基本的には、トランザクションが完了するまで確定(コミット)せず、トランザクションの途中にある場合は、他のトランザクションからは更新中の内容は読むことができないようにします。

保全性とは、トランザクションが完了した場合、その後の障害などによって結果が失われないことです。もちろん、保存したデータがクラッシュしてしまえば失われてしまいますが。

通常、データベースでは、変更を直接原本に書き出すことをせず、いったんジャーナル(ログ)に出力して、後で原本に反映させるのです。なぜかというと、その方が効率的だからです。一つ一つのデータファイルを変更していくよりも、一つのログファイルに何を変更して行ったかをファイルに追記して行く方が速いのです。もし一つ一つのデータファイルを変更するとなると時間がかかる上、ロールバックするのも大変です。オールオアナッシングと言っても、所詮コンピューターは同時に書き換えをすることはできず、順次処理していくので、同時にできなかったときのために、その順次処理をログに書いておいて、ログの最後にコミットをした旨記しておいた方がいいのです。

この辺は2フェーズコミットでも問題になります。別々のマシンにあるデータベースを一つのトランザクションの中で同時に更新しなければならない場合、それらのデータベースは別個に動いているので、先のログを使ってというわけには行きません。両方のデータベースで、オールオアナッシングが保たれなければならないのです。そこで2フェーズコミットという手法で、2段階に分けてコミットを行うのです。とはいえ、これも完全ではありません。最後のコミットの通知で、片方にはコミットの通知をしたが、もう片方に(ネットワーク障害などで)通知できなかった場合、どうするのかという問題が残されます。

トランザクションは、データベースだけの考え方ではありません。メモリの情報を更新したり、ファイルを書き換えたりするときにも必要な考え方です。データベースに登録すると同時に、他のサーバに指示を送り、ファイルを書き換え、という一連の作業が、全成功か全失敗のどちらかでならないとき、途中での失敗をどのようにロールバックするのか、ということを考慮しなければなりません。

・同じリソースへアクセスする際の注意

複数のプロセスやスレッドを使っている場合、共有のメモリやファイル、データベース等のリソースへのアクセスは十分気をつけなければなりません。

一番厄介で、見落としやすい点です。1プロセス1スレッドでテストしている間は全く問題がなかったのに、並行処理を始めた途端、データの不整合が起きるというのはよくあることです。

データベースでは排他制御は理論化され、その対策が十分練られていますが、ファイルやメモリについてはおろそかになりがちです。プロセス間の同期を取る仕組みはSemaphore(セマフォ)という機構を使って実現します。Javaでは、synchronizedというキーワードを使って、排他制御を実現しています。この辺の実装の仕方にはいろいろなやり方がありますが、注意して使わないと、デッドロックを引き起こしたり、極端なパフォーマンス低下を引き起こします。

この同じリソースへのアクセスの問題はちょうど同じタイミングで複数プロセスがアクセスしてきたタイミングで起きるので、確率的にはまず起こらないというケースもあります。そのせいか見落としやすい部分でもあります。一方、万に一つ起こるか起こらないかのもののために、重たいロック処理を入れるのは気が引けます。問題が大きくなければロックは使用せず、例外が起きたときに対処するのでもいいかもしれません。

ロックについて意識しているならまだいいのですが、そういうことを全く意識していないプログラマが多いのには気になります。だれでも簡単にWebアプリケーションを作れるようになったのはいいのですが、Webアプリは基本的にマルチスレッドなので、少しはこの問題を大きく取り上げてもいいのではないかと思いますが、ほとんどの解説書では何も書いていません。C-UNIXで複数のプロセスを扱うときは必ずセマフォ等の解説がありますが、Javaではsynchronizedの解説が少しあるだけです。本当は、工夫してセマフォライクなクラスが必要だと思うのですが。

あるプロジェクトでは、複数のプロセスが同時に同じリソースに対して更新しようとするから問題が起こるのだといって、入り口を一つにして、つまり、リソースを更新する専用のプロセスを一つ設けて、不整合を起こさないようにしました。結果は、同時に複数来た場合は後から来たプロセスが待たされ、非常に性能が劣化しました。これはロックしている範囲があまりにも広範囲だからです。データベースで考慮されているのと同じことを十分に考慮して整合性が取れるようにした上で、ロックの範囲を縮めるようにする必要があるでしょう。

アプリケーションをトランザクションという点で考えると、いくつかのパターンが考えられます。

1. DBのコネクション取得〜Commit/Rollbackまで。

2. アプリケーションレベル(オブジェクトの操作や他の関連処理含む)

3. 一連の画面操作(登録・更新・削除作業で、最初フォームから確定までに何画面も通過する場合)

通常、1について考慮されますが、広義には3も含まれ、一連の処理という点で、同じリソースへのアクセスについて十分考慮する必要があります。

・履歴・差分

データの一元管理をしていた場合、通常は最新の情報で上書きされて、前の情報を見ることはできません。しかし、たびたび前の情報を見たいという要求がなされます。以前はどういう状態で、どこがどう変わったのか。また以前の状態に戻したい、とか参照したいとかいう要望も出てくることがあります。

Wordなどの文書管理ソフトでは、履歴モードにすれば、何をどう変更していったのかの履歴をたどることができます。通常のソフトではアンドゥで一回限り前の状態に戻せます。高機能なエディタやソフトは履歴をかなり前までさかのぼることができます。Windowsでもシステムの状態を以前のいずれかの時点に復元する機能があります。またVisual Source Safeなどのプロジェクトのソース共有のソフトでも、以前の履歴が残され、バージョン管理ができます。

こういうバージョニングの考え方は、データベースにおいても、隔離性、読み取り一貫性を実現するために、Oracleなどで使用されています。Oracleでは変更の履歴はロールバックセグメントという領域に保存(一時的)されるので、自分がトランザクションを開始したときのデータを、他のユーザが変更してしまったとしても、見ることができるのです。

作成するアプリケーションの要求の中で、履歴を保存できるようにするためには、工夫が必要です。最新版のデータと混同しないことや、各履歴においてもデータの一元管理をするなどの必要があります。データベースでは読み取り一貫性の他は自動的には行ってくれません。Oracleでは、LogMinerを使ってログから変更をたどることもできますが、それよりもアプリケーション側で履歴を持ったほうがいいでしょう。

ソースファイルの管理では、Visual Source SafeやCVSなどが一般的に使われていますが、CVSでは、バージョンを枝分かれさせて管理することもできます。つまりVer.1.0系とVer.2.0系のブランチを作って、それぞれマイナーバージョンアップを進めていくということです。また、ソース管理だけではなく、ドキュメントの管理も、共有化と履歴保持のため便利です。

また、ファイル共有も一般的に使われているもので、共有サーバにファイルを置いておいて、直接編集するのです。この場合、履歴は残らないので、だれかが間違った更新をした場合には元に戻せないので、定期的にバックアップするなどの対処が必要です。

また、Webを使ってドキュメント共有をする、Wikiなども、Webを使ってどこからでもアクセスできるために、非常に便利です。ただし、履歴やバックアップを取っておいたり、セキュリティに気を使わないと、折角のデータも台無しになります。

一元管理は整合性を保つための基礎です。更新は一度に1ユーザのみです。同じデータが複数の箇所に分散している場合は、同期を取る必要があります。同期を取る、というのは、どの時点をとってもすべてデータが同じであるということです。同じデータというのは、全く同じ形式ではなくても関連性が同じということです。例えば、あるテーブルやオブジェクトに購入した商品名と価格のリストがあり、別のテーブルやオブジェクトに合計金額がある場合、前者のデータを変更したなら、後者のデータも同時に変更する必要があるということです。

もっともデータベースでは、正規化といって、更新は一箇所だけにするために、合計金額のテーブルというものは持たないようにします。

・値の整合性のチェック(ヴァリデーション)

今まで二つの値の間での整合性について述べてきましたが、整合性は値が正しいという意味でも使われます。一般的にこれはヴァリデーションと呼ばれます。

・実体整合性 テーブルの主キーが一意であること

・ドメイン整合性 データ型や値の範囲が正しいこと

・参照整合性 他のテーブルで定義された値の範囲にあること

これらは二つの値の間で同期が取られているというより、メタデータ(値の範囲を定義しているもの)と同期が取られているかどうかという問題になります。

これらは、データベースでは制約というものをテーブルに付与することによって行います。それらの制約は、テーブルにデータが格納される際、正しい値かどうかをチェックし、不正な場合はそのSQL文の実行を破棄し、エラーを返します。

不正データをはじくことは重要なことです。特に、データベースには不正データが入らないようにします。

どこでチェックを行うかという問題があります。一般的なWebアプリケーションでは、以下のようなステップをたどります。

入力フォーム → 確認画面( →入力2...)→(登録・更新処理)完了画面

入力フォームでのチェック:Javascript

確認画面表示前のチェック:サーバーサイドjava

登録・更新直前:サーバーサイドjava、(DBファンクション)

登録・更新時:DB制約

すべてにおいてチェックするのか、それとも開発工数を減らすために最後の最後にチェックするのかというのは難しい問題です。最後は絶対に必要です。最後にチェックするのだから途中のチェックは要らないとも言えます。とはいえ、最後の時点で入力ミスを注意するより、最初に注意した方が親切でしょう。JavaScriptを使って、ブラウザでチェックしてしまえば、余計なトラフィックは走らなくて済みます。重複して開発しないように、途中のチェックは、フォーマットや文字数のチェックにとどめておくのがいいでしょう。

ブラウザで問題になるのは、ローカルで表示されているのはずっと前の情報であることがあることで、その情報は最新状態においては整合性が取れないものとなってしまうことがあります。例えば、在庫から10個の商品を引き当てたいと考えます。画面には15の在庫があると表示されています。ここで10個引き当てても問題ないはずです。しかし、別の担当者がすでに10引き当てていた場合、二重に引き当てることになり、またこの場合、在庫が-5となってしまい、矛盾が発生します。

こういう点についてトランザクションという観点からもあわせて考慮する必要があります。

データの整合性については、こちらでも述べています。

セキュリティ

よいシステムはセキュリティについても考慮されています。セキュリティとは、情報やアプリケーションを守ることです。つまり、権限のある人にしか情報を参照や更新させず、またアプリケーションを動作し続けるようにすることです。

もしセキュリティのレベルが低いと、不正なアクセスをされ、アプリケーションが停止したり、情報が盗まれたり、不要に書き換えられたりします。それは悪意があって故意に行う場合もあれば、間違って行ってしまう場合もあります。人間には間違いがつきものなので、自分で行う場合も、例えばrootユーザのパスワードを知っていたとしても、必要のない限りrootユーザでは作業しないようにするなどの工夫をします。

情報を盗まれたり、書き換えられたりした場合の問題は、単なる情報だけの問題ではありません。お金を引き出されたり、勝手に注文されたりと、金銭的な被害も発生します。

そのような、不正なアクセスを防ぐために、権限のないユーザに対しては、アクセスを拒否するようにします。

これはいろいろなレベルで行われます。直接コンピューターにログインする際にパスワードを要求したり、アプリケーションにログインするためにパスワードを要求したり、あるいはネットワーク経由でアクセスする際に、許可されたIPアドレスとポートのみを許可するといった具合にです。そしてログインに成功した場合も、ユーザによって、与える権限を変えます。

操作するユーザに対して、参照権限か更新権限を与えます。参照権限だけの場合、内容を閲覧することはできますが、変更することはできません。更新権限は、新規登録、更新、削除を行う権限で、権限を細分化する場合、登録権限、更新権限、削除権限という形で分けます。データベースではさらに様々な権限があります。またLINUXなどのOSでは実行権限などもあります。

また、さらに操作する対象に対しても細かく権限を設定することがあります。このユーザはこのファイルに対しては更新権限があるが、このファイルに対しては参照権限しかないなど。

ユーザの認証には、ユーザIDとパスワードが使われるのが一般的ですが、クライアント証明書などの物理的なキーファイルを必要とする場合もあります。

情報の盗難は、ユーザ認証だけでは防げません。ネットワーク経由でアクセスしてくる場合、その通信経路の途中で、盗聴される可能性があります。そうするとそのユーザしか見れない情報を盗み見たり、さらにユーザーIDとパスワードを盗んで、ログインされたり、あるいはネットワークの中継点に入って内容を改ざんしたりといろいろな可能性があります。そのために、データを暗号化して、通信経路を安全なものとするのです。たとえ情報を盗聴されたとしても、簡単に複合化できないため、盗難を防ぐことができるのです。

アプリケーションの作成においては、まずは、認証の部分と通信経路の部分の問題について検討する必要があります。

ついで、細かく権限を設定し、どのユーザ(グループ)にはどの権限を与えるか決めます。そして、それに基づいてどのページに誰がアクセスできるか決めるのです。これは通常、Webサーバーの機能を使って実現できます。また、同じページを見ていたとしても、管理者には全権限を与え、すべての情報を閲覧できるようにし、あるユーザには別のユーザの情報は見れないようにするなどの対処が必要です。

このとき、注意しなければならないのが、パラメーターが検索キーになっている場合です。もしリクエストの際のパラメーターを手動でいじり(WebでGETメソッドを使うと、アドレスバーにパラメーターが表示される)、他のユーザのIDを入力したとすると、他のユーザの情報が閲覧できてしまうということがあります。こういう点に注意を払う必要があります。

また、外部にサーバを公開する場合、ファイアーウォールを構築し必要のないポートは閉じ、また開いているポートを使って、セキュリティホールを突いてきたり、大量のデータを送りつけてくるDosアタックなどについても対処する必要があります。DOSアタック単体では機密情報が漏れることはありませんが、サーバーがダウンするなどいろいろな被害を受けます。サーバアプリケーションを使用している場合は、常にセキュリティホールの情報を監視し、発見された場合はすぐに対処する必要があります。

まとめると、セキュリティ対策の目的とは、要は、許可された人のみが情報にアクセスできるようにすることです。そのためにまず認証(Authentication)を行ってその人が誰なのかを特定します。成りすましを防止するために、IDだけではなくパスワードも求めます。よくショッピングサイトなどでの電話の問い合わせに対して、本人確認として住所や生年月日を聞かれることがありますが、本人なら知っている情報を尋ねることで、ある程度のレベルまで認証を行っているのです。

そして認証が終わったら、権限の付与(Authorization)です。そのユーザに、どの情報に対してどういう操作ができるのかという権限を与えるのです。しばしばロールといっていくつかの権限をセットにしたものが使われることがあります。ロールを作っておくと、一人一人のユーザに対していくつもの権限を与える代わりに、ロールを与えるというふうにできるので便利です。

基本的には以上でセキュリティは守られるはずですが、攻撃者は裏口をついてきます。バッファーオーバーフローを狙ったり、盗聴しようとしたり。そこでセキュリティホールをなくしたり、暗号化したりといった手法が必要になります。

最近では、ネットワーク経由ではなく、人によって情報が漏洩される事件がよく聞かれます。ここでは人の管理・教育も重要になってきます。

セキュリティについては、こちらでも述べています。

操作性

アプリケーションを使用するユーザにとって、操作性は非常に重要です。情報の表示位置やボタンやリンクの位置などはユーザの使い勝手を考えて配置する必要があります。また、見易さにも気をつけて、色やフォントなどを選択する必要があります。また、ユーザが別のアプリケーションの動作に慣れていて、例えばEnterキーで次の項目に移動できるようにしたいなどの要望をしてくるかもしれませんので、だれがユーザなのかに配慮して画面インターフェースを決めます。

また、エラーや処理の遅延が起きている場合、適切なメッセージを表示するのも、ユーザに不要な心配を持たせないために必要なことです。

最近はUI構成のパターンも研究されているようです。

Yahoo! Design Pattern Library







この内容についてご意見をください

 
   役に立った    まあまあ    つまらない    難しい    疑問がある

コメント(質問・指摘・要望なんでもどうぞ)
  
メールアドレス: 

  

Copyright Dayan All rights reserved. Last Update: 2006.10.15 Create: 2004.08.31