title: 翻訳: WebAPI 設計のベストプラクティス
tags: api rest 設計 翻訳 WebAPI
author: mserizawa
slide: false


これは Enchant の開発者である Vinay Sahni さんが書いた記事「Best Practices for Designing a Pragmatic RESTful API^1を、ご本人の許可を得て翻訳したものです。

RESTful な WebAPI を設計しようとすると、細かなところで長考したり議論したりすると思います。また、他の API に倣ってやってはみたものの、本当にそれでいいのか、どうしてそうしているのか分からない、何てことも少なくはないと思います。
この記事では、そのようなハマリどころについて Vinay さんなりの答えを提示し、簡潔かつ明快に解説してくれています。
今後 WebAPI を設計される方は、是非参考にしてみてください。

なお、誤訳がありましたら編集リクエストを頂けると幸いです。


まえがき

アプリケーションの開発が進むにつれて、その WebAPI を公開する機会に出くわすかもしれません。ただ、いくらデータ設計がひと段落していたとしても、一度インターフェイスを公開してしまうと、そこからの変更はなかなか自由に出来ず、苦しめられることとなります。
今の時代、ネットで調べれば API 設計に関する情報はいくらでも手に入ります。しかし、幅広く受け入れられているスタンダードかつ万能な設計は存在せず、多くのことに悩むこととなります。フォーマットはどれにするとか、認証はどうやるとか、バージョンはつけるのかとか...

EnchantZendesk に変わるカスタマーサポートツール)の設計を通して、私はこれらの問いに対して実践的な答えを探ってきました。Enchant API を使いやすく、受け入れやすく、また、Enchant の機能を十分に活かせるものにしたかったのです。

API 設計の勘所

ネット上で目にする API 設計に関する議論は、少しアカデミックすぎたり、個々人の曖昧な主張が入り混じっていて、とても現実世界で通用するものではありません。私はこの記事を通して、実用的かつモダンな API 設計について説明しようと思います。誤解を防いだり、理解を早めていただくために、まずは私が思う API の勘所をお伝えします。

  • できる限り Web の標準に従うこと
  • 開発者に親切な作りにすること。また、ブラウザのアドレスバーから叩けるようにすること
  • シンプルで一貫性を持たせ、直感的かつ心地よく使えるようにすること
  • Enchant(本体のサービス)が提供する機能を十分に活用できるものにすること
  • 様々な要件とのバランスを保ちつつ、効率的に開発ができるようにすること

API は開発者のための UI です。他の UI と同じように、UX にもとことんこだわりましょう!

RESTful な URL にしよう

WebAPI の分野で最も市民権を獲得している原則は REST です。REST は、Roy Fieldingネットワークソフトウェア設計に関する論文で言及したのが始まりです。

REST のキーとなる概念は、論理的に分割されたリソースを HTTP メソッド(GET, POST, PUT, PATCH, DELETE)で操作する、ということです。

では、「リソースを論理的に分割する」とはどういうことでしょうか。これは、API 利用者が客観的に見て意味がわかる名詞(動詞じゃありません!)を使って分割する、ということです。内部実装で利用しているモデルがそのままいい具合にリソースに対応するかもしれませんが、これは必ずしも一対一の関係でなくても構いません。重要なのは、内部実装につられて妙なデータ構造を表に晒すべからず、ということです。(Enchant API でもところどころ良くないところはあるのですが...)

リソースが定義できたら、次はそれらに対してどんなアクションが必要で、また WebAPI 上でどのように表現されるかを考えます。REST の原則に従うと、HTTP メソッドを使った CRUD 操作は以下のように定義されます。

  • GET /tickets - チケットのリストを取得する
  • GET /tickets/12 - 指定したチケットの情報を取得する
  • POST /tickets - 新しいチケットを作成する
  • PUT /tickets/12 - チケット #12 を更新する
  • PATCH /tickets/12 - チケット #12 を部分的に更新する
  • DELETE /tickets/12 - チケット #12 を削除する

REST の素晴らしい点は、HTTP メソッドを活用することで /tickets という単独のエンドポイントに対して主要な機能を持たせられることです。命名規則など意識する必要ありませんし、URL 構造は明白かつ整然としたものとなります。これは素晴らしい!

では、エンドポイントの名前は単数形と複数形のどちらを使うのが適切でしょうか。KISS の原則に従えば、答えは「一貫して複数形を使う」です。単数のインスタンスを複数形で表すことに違和感を覚えるかもしれませんが、person/people, goose/geese といった奇妙な単複変換を扱わなくてよくなれば、API 利用者も快適になりますし、API 提供者が実装するのも簡単です(モダンなフレームワークであれば /tickets/tickets/12 を同一のコントローラで扱えますので)。

関連データについてはどうしましょう。もしその関連データが、あるリソースに付随してのみ存在するのであれば、REST によって綺麗に解決されます。以下の例をみてください。Enchant のチケットにはたくさんのメッセージが紐付いているのですが、これらのメッセージは、以下のように表現されるのが自然でしょう。

  • GET /tickets/12/messages - チケット #12 に紐づくメッセージのリストを取得する
  • GET /tickets/12/messages/5 - チケット #12 に紐づくメッセージ #5 を取得する
  • POST /tickets/12/messages - チケット #12 に新しいメッセージを作成する
  • PUT /tickets/12/messages/5 - チケット #12 のメッセージ #5 を更新する
  • PATCH /tickets/12/messages/5 - チケット #12 のメッセージ #5 を部分的に更新する
  • DELETE /tickets/12/messages/5 - チケット #12 のメッセージ #5 を削除する

これとは逆に、関連データがリソースから独立して存在する場合は、リソースのアウトプットに関連データの ID を含めるのが良いです。API 利用者はその内容を見て、関連データのエンドポイントにアクセスすることとなります。
とはいえ、親データと関連データが大体一緒に使われるようであれば、レスポンスに関連データを埋め込めるようにしておいて、何度もリクエストする手間を省いてもいいかもしれません。

最後に、CRUD の概念にフィットしないようなアクションについてはどうしましょうか。

これは難易度の高い問題ではありますが、いくつかのアプローチがあります。

1つは、アクションをリソースの項目の1つとして扱う方法で、これはアクションがパラメータを取らない場合に有効です。例えば、何かをアクティベートするようなアクションであれば、対象となる boolean 項目をサブリソースとして扱って、そこに対して PATCH リクエストを投げると考えます。GitHub API の例を見てみましょう。Gist にスターをつけるアクションを PUT /gists/:id/star としていて、スターを解除するアクションを DELETE /gists/:id/star としています。

一方で、REST の構造にマッチさせられないアクションもあると思います。例えば、複数のリソースを横断的に検索するようなアクションについては、特定リソースのエンドポイントに紐付けるのは、なんとも無理やりな感じがします。このような場合は、/search というエンドポイントを作ることで解決します。ちょっとルール違反な気もしますが、API 利用者から見ておかしくなく、混乱がないようにドキュメントにしっかりと書かれていれば問題ないのです。

いつ何時も、SSL 通信を使おう

通信については、何がなんでも SSL で暗号化しましょう。今の時代、WebAPI は図書館やコーヒーショップ、空港といった様々な場所からアクセスされます。これらのアクセスポイントは必ずしもセキュアではありません。暗号化しないで通信していると、簡単になりすましがされてしまいます。

SSL 通信を使う別のメリットとして、暗号化を保障しておくことで認証処理が簡潔にできることがあります。アクセストークンを使うことで、リクエストごとの認証処理を省略できるのです。

ひとつ気をつけたいのは、API の URL に非 SSL でのアクセスがあるケースです。こうしたアクセスを、対応する SSL の API にリダイレクトしてはいけません。代わりにきちんとしたエラーを投げてください。不適切な設定が行われたクライアントが、暗号化されていないエンドポイントにリクエストを送信して、それが実際の暗号化されているエンドポイントへと暗黙的にリダイレクトされるなんてことは最も避けたい事態でしょう。

かっこいい仕様書を作ろう

API の価値はそのドキュメントの良さが表すと言っても過言ではありません。ドキュメントは誰もが簡単に閲覧できるようにしておく必要があります。ほとんどの開発者は、API の利用を検討する際にまずはドキュメントをチェックしますが、それが PDF だったり、閲覧に何かしらの登録が必要だったりすると、彼らにリーチするのはとても難しくなってしまいます。

また、ドキュメントにはリクエストとそのレスポンスの例を記載しておきましょう。できれば、リクエストはブラウザやターミナルに簡単にペーストできるようにしておくと良いです。GitHubStripe の API 仕様書はこのあたりがとてもよくできています。

一度 API を公開したら、利用者への予告なしに下位互換性のない変更を入れることはできなくなります。ドキュメントには、そういった仕様廃止のスケジュールや、API の更新も掲載しましょう。更新情報については、ブログかメーリングリストでも配信すると良いですね(両方だと最高です!)。

バージョンは URL に含めよう

API は必ずバージョン管理しましょう。バージョン管理することで開発速度を速められますし、廃止された仕様でのリクエストを弾いたり、メジャーアップデートがあるような場合に、移行期間として過去バージョンと共存させることもできます。

API のバージョンに関して議論されることといえば、それを URL に含めるべきか、リクエストヘッダに含めるべきか、ということです。学術的に言えば、ヘッダに含めるのがいいのかもしれません。ただ、ブラウザからバージョンをしたリクエストをすることを考えると、URL に含めるのが妥当といえます(私が冒頭で述べたことを思い出してください)。

バージョン管理について、私は Stripe のアプローチがとても好きです。URL では API のメジャーバージョン(v1)が指定できて、リクエストヘッダでは日付ベースのマイナーバージョンが指定できるのです。これにより、メジャーバージョンで API の安定した動作を担保させつつ、マイナーバージョンで出力項目やエンドポイントの変更といった細かな調整ができるのです。

どんなに頑張っても、API を永らく安定させることは難しく、変更は避けられません。大切なのは、いかにしての変更を管理するかです。何かを廃止したい時は、移行までの期間を十分に確保して利用者に周知すれば、ほとんどの場合は受け入れられるのです。

フィルタ・ソート・検索はリクエストパラメータでやろう

ベースとなる URL はできるだけシンプルにしておくのが良いです。フィルタやソート、検索といった機能はリクエストパラメータで制御するのが良いです(もっとも、これは単一リソースに対するものに限ります)。これらについて、細かく見ていきましょう。

フィルタリング

各フィールドに対して、フィルタリングをするためのパラメータを用意しましょう。例えば、/tickets でチケットのリストを取得する際に、state が open のものだけに絞りたいことがあると思います。このような要望は GET /tickets?state=open のようにして実現させましょう。リソースの項目である state をそのまま、フィルタするためのリクエストパラメータとするのです。

ソート

並び順の指定については、 sort パラメータを用意して処理するようにしましょう。複雑なソートにも応えられるように、ソート対象とする項目をカンマ区切りで指定して、かつ、昇順・降順をネガポジで指定するようにします。いくつか例を挙げてみます。

GET /tickets?sort=-priority - チケットのリストを priority の降順で取得する
GET /tickets?sort=-priority,created_at - チケットのリストを priority の降順、かつ created_at の昇順で取得する

検索

フィルタクエリでは事足りず、全文検索が必要になることもあると思います。おそらく、ElasticSearch や他の Lucene ベースの検索エンジンを使うことになると思いますが、特定リソースに対して投げるクエリには、q パラメータを使いましょう。検索クエリはそのまま全文検索エンジンに伝えられ、API のアウトプットは普段と変わらない形式となります。

以上を組み合わせてみると、以下のような感じでリクエストパラメータが構築できます。

GET /tickets?sort=-updated_at - 最近更新されたチケットを取得する
GET /tickets?state=closed&sort=-updated_at - 最近クローズされたチケットを取得する
GET /tickets?q=return&state=open&sort=-priority,created_at - オープン状態で優先度の高いチケットのうち、「return」という単語を含むものを返す

よく使うクエリのエイリアス

API の UX をより良くするために、よく使われる検索クエリは REST のパスにしてしまうことを考えましょう。例えば、「最近クローズされたチケット」を取得するクエリは、以下のような URL にまとめることができます。

GET /tickets/recently_closed

レスポンスのフィールドを絞れるようにしよう

API 利用者は、常にリソースの全項目を必要としているわけではありません。レスポンスのフィールドを絞る手段を用意することは、API 利用者のネットワーク負荷を下げ、通信速度を向上させることに貢献します。

そのために、出力したいフィールドをカンマ区切りで指定できるパラメータを用意しましょう。例えば、以下のリクエストではオープン状態のチケットを更新日付順で並べて表示するのに必要な、最低限の情報のみを返します。

GET /tickets?fields=id,subject,customer_name,updated_at&state=open&sort=-updated_at

作成・更新の後は変更後の情報をフルで返そう

created_at や updated_at といった項目は、こちらが明示的に指定するものではなく、作成・
更新の際にサーバが自動で挿入するものです。API 利用者が作成・更新後のリソース情報を取得するためにもう一度 API を叩くのは大変なので、POST, PATCH, PUT リクエストのレスポンスには変更後のリソースの情報を含めるようにしましょう。

なお、POST で新しくリソースを作成した際には、ステータスコード 201 を返し、Location ヘッダに作成されたリソースへの URL を含めるのが良いです。

HATEOAS を採用するのは待とう

(API のレスポンスを踏まえて)次に進むべきリンクを API 利用者が構築するか、はたまた API が提供するかについては、賛否両論の様々な意見が飛び交っています。REST を拡張する形で提案された HATEOAS という概念では、エンドポイント同士のインタラクションは API のレスポンスに含めるべきだとされています。

確かに、いわゆる Web サイトはこの HATEOAS の原則通りに動いているように見えます。例えば、私たちはウェブサイトのトップページにアクセスしたら、そこに表示されているリンクを押しますよね。ただ、私はこれを API の世界で展開するには時期尚早だと思います。ウェブサイトではどのような遷移がされるかどうかの判断をアプリ自体ができます。一方、API の場合は次にどのようなリクエストが来るかどうかはその API を使ったアプリに依存するため、API レベルで行うことは難しいのです。もちろん、どうにか工夫をしてその判断を遅延させることはできるかもしれませんが、そこまでして得られるメリットはさほどないと思います。このことから、私は HATEOAS はとても前途有望ではあるものの、現時点ではまだ使うには早すぎると思うのです。これが標準化され、メリットが最大限に生かされるようになるためには、まだまだ議論が必要です。

現時点では API 利用者がドキュメントをちゃんと読むことを前提としつつ、リソースの ID からリンクを構築してもらうのが良いでといえるでしょう。ちなみに、ID にこだわるのにはいくつかの理由があります。まず、ネットワーク上で介されるデータ量が小さくなりますし、API 利用者が保存すべきデータも最小化されるからです(ID を含む URL を保存するのとはわけが違います)。

また、前章で URL はバージョン管理されるべきと言いましたが、これを考慮しても API 利用者には ID
のみを保存してもらう方が良いのです。ID を使えばバージョンに依存しない安定したアクセスができますが、URL ごと保存してしまうとそうもいかないからです。

可能な限り JSON で返そう

API レスポンス形式の分野において、XML の時代は終わりました。XML は冗長ですし、読むのもパースするのも大変です。また、XML のデータの持ち方はほとんどのプログラミング言語が扱うモデルと互換性がありませんし、得意としている拡張性についても、内部モデルを表現する上ではほとんど活用することはありません。

私がとやかく理由を述べるよりも、YoutubeTwitter, Box といった大御所の API が XML を廃止し始めているのを見れば、明白だと思います。

ちなみに、Google トレンドで「XML API」と「JSON API」を比較してみるとこんな感じです。

201305-xml-vs-json-api.png

とはいえ、たくさんのエンタープライズの利用者を抱えていると、XML をサポートしなければいけない状況になることも無きにしも非ずです。そうなった時に、おそらく以下の疑問を抱くと思います

「レスポンス形式の指定はヘッダでやるの?それとも URL でやるの?」

例のごとくブラウザでの叩きやすさを考慮して、URL でやりましょう。URL の最後に .json.xml といった拡張子を追加するのが良いと思います。

フィールドの命名規則を考えよう

もし API のレスポンス形式がデフォルトで JSON であれば、フィールドの名前は JavaScript の命名規則に従うのが「正しい」やり方でしょう。つまり、キャメルケースです。ただ、色々なプログラミング言語でクライアントライブラリを作るのであれば、各言語での命名規則に従うのが良いです。Java や C# であればキャメルケースで、Python や Ruby であればスネークケースといった感じです。

ちなみに、私はずっと、キャメルケースよりスネークケースの方が読みやすいと思っていました。この曖昧な感覚を証明する手段がなかったのですが、近年のアイトラッキングシステムを使った研究により、どうやらスネークケースの方が 20% 読みやすいということがわかったそうです。つまり、スネークケースを使った方が、API の利便性が上がったり、ドキュメントの読みやすさが向上したりするかもしれません。

ほとんどの JSON API ではスネークケースが採用されています。私が思うに、これは各々が利用している JSON シリアライズライブラリがその言語の命名規則に依存しているからです。もしかしたら、命名規則の違いに対応出来る JSON シリアライズライブラリを使った方がいいのかもしれません。

JSON はデフォルトで整形しよう

圧縮された状態の JSON をブラウザ上で見るのは、決して気持ちの良いものではありません。?pretty=true のようなパラメータで PrettyPrint することができるかもしれませんが、それであれば初めから PrettyPrint してある方が良いでしょう。gzip 圧縮してしまえば、余分な空白を転送するコストなど取るに足らないものとなります。

いくつかのユースケースを考えてみましょう。API 利用者がデバッグのために取得したデータを出力したとして、もしそれがそのまま読めたら?または、API 利用者が生成されたリクエスト URL をおもむろにブラウザに投げたとして、それがそのまま読めたら?些細なことではありますが、これらは API の UX を高める大切なポイントです。

とはいえ、余分なデータが転送されることは気になりますよね。

ホンモノのデータを使って検証してみましょう。GitHub API のレスポンスを使いますが、この API はデフォルトで PrettyPrint されているので、比較のためにちょっと加工します。

$ curl https://api.github.com/users/veesahni > with-whitespace.txt
$ ruby -r json -e 'puts JSON JSON.parse(STDIN.read)' < with-whitespace.txt > without-whitespace.txt
$ gzip -c with-whitespace.txt > with-whitespace.txt.gz
$ gzip -c without-whitespace.txt > without-whitespace.txt.gz

この結果、それぞれのファイルサイズは以下のようになりました。

  • without-whitespace.txt - 1252 bytes
  • with-whitespace.txt - 1369 bytes
  • without-whitespace.txt.gz - 496 bytes
  • with-whitespace.txt.gz - 509 bytes

この例では、gzip 圧縮をしない状態だと空白の有無で 8.5% の差が生じ、gzip 圧縮をした状態だと 2.6% の差が生じることがわかります。一方で、gzip 圧縮自体が 60% のサイズ削減をすることもわかります。つまり、PrettyPrint により増加するサイズは数字で見ても比較的少なく、「gzip 圧縮かつ PrettyPrint」の状態で返すのがベストであることがわかります!

さらに付け加えると、Twitter の Streaming API では gzip 圧縮をしたことで 80% のサイズダウンに成功した例もありますし、StackExchange の API は圧縮した状態でしか返さない仕様となっています。

要素はラップせずに返そう

多くの API が、以下のように要素をラップした状態で返しています。

{
  "data" : {
    "id" : 123,
    "name" : "John"
  }
}

なぜラップをするのかというと、こうしておくと付加的なメタデータやページングのデータを埋め込みやすいからです。HTTP クライアントの中には、レスポンスヘッダの参照がしづらいものもあるかもしれませんし、JSONP のリクエストではヘッダを参照する術がありません。しかしながら、世の中のスタンダードは CORS になりつつありますし、RFC 5988 では Link というレスポンスヘッダの仕様が提案されています。つまり、要素をラップする必要はなくなりつつあるわけです。

将来的には、デフォルトの状態ではラップをしていなく、特殊なケースに対応するときのみラップするようになるでしょう。

では、ラップが必要な特殊ケースに出くわした時はどうしましょう。

先にも述べましたが、それが必要なのは JSONP を使ってクロスドメイン制約を解決するときと、HTTP クライアントがどうしてもレスポンスヘッダを扱えないときです。

JSONP のリクエストは、callbackjsonp といったリクエストパラメータと一緒に来ます。API はこのパラメータを見つけ次第、ラップモードへとスイッチが切り替わります。どんな状態であれステータスコード 200 を返し、本来のステータスコードはラップした内側に埋め込みます。さらに、従来レスポンスヘッダに入れていたデータも内包させるのです。

callback_function({
  status_code: 200,
  next_page: "https://..",
  response: {
    ... actual JSON response body ... 
  }
})

HTTP クライアントが対応していないときも同様で、?envelop=true のような何かしらのパラメータをトリガーにラップモードを発動させれば良いです(もちろん、この場合 JSONP コールバックは不要です)。

追加・更新時のリクエストボディには JSON を使おう

ここまでの内容で、あなたはきっと全ての API アウトプットが JSON 形式であることには賛同してくれるでしょう。では、JSON をインプットに使うというのはどうでしょうか。

多くの API では、リクエストパラメータ同様、リクエストボディも key, value 形式のデータが詰められてやりとりされています。これはとてもシンプルですし、幅広く使われていて有効な手段です。

しかしながら、この方法には問題点があります。まず、データ型の概念がないため、受け取った側は文字列から integer や boolean といった値に変換する必要があります。また、階層構造を表現する方法もありません。key に対して [ ] をつけて無理やりに階層を表現する方法もありますが、JSON の表現力に比べたら貧弱なものです。

API がシンプルなものであれば、この方法でも問題ないでしょう。しかし、複雑な API に関して言えば、リクエストボディに JSON を使うことにを検討した方が良いです。いずれにせよ、両方採用するようなことはせず、首尾一貫した仕様にしてください。

JSON で POST, PUT, PATCH のリクエストを受け付ける場合は、Content-Typeapplication/json を指定してもらいましょう。さもなくば、応答するのは 415 Unsupported Media Type です。

ページング情報はレスポンスヘッダに入れよう

先にも述べましたが、ラップするのが大好きな API は往々にしてページング情報をその内側に含めます。私は決してそう言った仕様を責めるつもりはありません。なぜなら、つい最近まではそうするのが普通だったからです。ただ、今は RFC 5988 で提案されている Link ヘッダに倣って、レスポンスヘッダに含めるのが良いでしょう。

Link ヘッダには、API 利用者がいちいち URL を組み立てる必要がないよう「すぐに叩ける状態」の URL を含めましょう。そのためには、ページングはカーソルベースで指定できることがポイントとなります。以下に、GitHub API が返す Link ヘッダを例示します。

Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next", <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

ただ、これだけでは API が返すページングの情報としては不完全です。たとえば、結果のトータル件数がなければ実装に困るでしょう。そういった付加的なものは、X-Total-Count のようなカスタムヘッダを使って返してあげましょう。

関連データを埋め込む手段を作ろう

API 利用者は、往々にしてレスポンス要素の関連データも必要とします。1つのモデルを完成させるのに何回も API を叩いてもらうのは大変ですし、必要に応じて関連データをレスポンスに含めてしまうことは非常に有用に思われます。

ただし、これは REST の原則に背くことでもあります。このルール違反に対する背徳感を少しでも和らげるべく、embedexpand のようなリクエストパラメータでだしわけするようにしましょう。

次の例では、embed パラメータで埋め込む関連データを複数指定し、また、ドット表記を使って取得する項目の指定もしています。

GET /tickets/12?embed=customer.name,assigned_user

これのレスポンスでは、以下のようにチケットに関連データが付与されます。

{
  "id" : 12,
  "subject" : "I have a question!",
  "summary" : "Hi, ....",
  "customer" : {
    "name" : "Bob"
  },
  "assigned_user": {
   "id" : 42,
   "name" : "Jim",
  }
}

もちろん、このようなことを実現するためには、API の内部実装を少なからず複雑にする必要があります。往々にして発生するのは、N+1 問題です。

HTTP メソッドを上書きしよう

HTTP クライアントの中には、GET と POST しか扱えないものもあります。こういった制約を持つクライアントでも不自由なく使ってもらえるよう、HTTP のメソッドを上書きする手段を用意しておきましょう。ただし、このやり方にはこれといったスタンダードが存在しません。強いて言うなら、X-HTTP-Method-Override というリクエストヘッダで上書きするメソッド(PUT, PATCH, DELETE)を指定するのが一般的です。

気をつける点としては、上書き可能なのは POST リクエストのみに限定しておくべき、ということです。REST の原則上、GET リクエストはいかなる場合でもサーバのデータを書き換えてはいけないからです!

リクエスト制限情報はレスポンスヘッダに入れよう

一般的に、API の乱用を避けるためにリクエストの回数には制限が入れられます。RFC 6585 でも、429 Too Many Request という HTTP ステータスコードが提案されています。

リクエスト制限を入れるとなると、API 利用者が現在の制限状態をチェックする方法が求められます。これについても、これといったスタンダードなやり方は存在しないのですが、多くの場合はレスポンスヘッダが利用されます

最低限、以下の情報をヘッダ含めるようにしましょう(ちなみにこれは Twitter の命名規則に従ってします)。

  • X-Rate-Limit-Limit - 一定期間内でリクエストできる最大回数
  • X-Rate-Limit-Remaining - 次の期間までにリクエストできる回数
  • X-Rate-Limit-Reset - 次の期間が来るまでの秒数

ところで、X-Rate-Limit-Reset を日時形式にしないのはなぜでしょう?

日時形式はとても便利ですが、日付やタイムゾーンといった情報はこの用途では余分です。API 利用者が知りたいのは、あとどれくらいでリクエストができるようになるかであり、これに一番簡単に応えるのが秒数なのです。また、端末時計の誤差を無視するという目的もあります。

また、X-Rate-Limit-Reset に UNIX タイムスタンプを使っている API を見かけるかもしれませんが、これは絶対に真似しないでください!

どうしてこれがダメなのかというと、HTTP の仕様としてすでに RFC 1123 が日付のフォーマットについて提案していて、Date, If-Modified-Since, Last-Modified といった HTTP ヘッダで採用されているからです。もし日付の類を扱うヘッダを定義するのであれば、それは RFC 1123 に従う必要があるのです。

トークン認証を使おう

RESTful な API は状態を持ちません。つまり、認証に関して cookie やセッションを使うことはできず、代わりに認証クレデンシャルのようなものを使う必要があります。

SSL の利用を前提とすることで、このクレデンシャルは非常にシンプルなものになります。ランダムに生成された文字列をアクセストークンとして、BASIC 認証のユーザネームフィールドに入力してもらえば良いのです。この方法の優れている点は、ブラウザで扱いやすいことです。未認証の状態であれば、ブラウザはアクセストークンの入力を求めるプロンプトを表示するでしょう。

しかしながら、この BASIC 認証を使った仕組みは、API 利用者がその管理者からトークンをコピーしてもらえる状況でなければ有効ではありません。もしそれができないのであれば、OAuth2 の認証を使って安全に 3rd パーティ開発者用のトークンが発行できるようにしましょう。ちなみに OAuth2 自体も SSL 通信を前提としている点にご注意ください。

JSONP に認証を使う場合は、また別の方法が必要となります。というのも、JSONP のリクエストでは BASIC 認証も Bearer トークンも使えないためです。この場合は、access_token のようなパラメータを使ってトークンの受け渡しをすることになります。ただし、このやり方では、サーバ上のログにトークンが
残る可能性があり、セキュアな方法とはいえない点にご注意ください。

これら3つの方法はトークンの運び方が違うだけであって、本質的にはどれも「トークン認証」と言えます。

キャッシュの情報をレスポンスヘッダに入れよう

HTTP にはキャッシュのための機構が用意されています。API 開発者は、レスポンスにいくつかのヘッダを含め、リクエストのヘッダをバリデーションするだけで、キャッシュの恩恵に与れます。

これについて、ETag を使うやり方と Last-Modified を使うやり方があります。

ETag

レスポンスを送る際に、レスポンス内容のハッシュかチェックサムを ETag ヘッダに含めます。つまりこのヘッダは、レスポンス内容に変更があった際に変わるものとなります。これを受け取ったクライアントは、レスポンス内容をキャッシュしつつ、今後同一 URL のリクエストを投げる際に、ETag の内容を If-None-Match ヘッダに含めるようにします。API は、ETag 値と If-None-Match の内容が一致していれば、レスポンス内容の代わりに 304 Not Modified というステータスコードを返します。

Last-Modified

これは基本的には ETag と同様の概念ですが、タイムスタンプを使う点が異なります。Last-Modified ヘッダには RFC 1123 で提案された形式の日時データが含まれていて、これを If-Modified-Since ヘッダの内容と比較することでキャッシュを制御します。注意点としては、HTTP の仕様では3つの異なる日時形式が定義されていて、API サーバはそのいずれの形式でも許容できるようにする考慮する必要があるということです。

ちゃんとしたエラーメッセージを返そう

Web サイトのエラーページでは、訪問者にとって有益な情報が表示されます。API もこれと同じで、有益なメッセージを、使いやすい形式で返す必要があります。エラーメッセージだからと言って特別にすることはなく、他のリソースと同じように扱えるようにするのです。

HTTP のステータスコードのうち、エラー関連のものは大まかに2種類に分けられます。400 系のクライアント起因のものと、500 系のサーバ起因のものです。最低限、400 系のエラーについては、一通りそれ用の JSON を返すようにしましょう。可能であれば、500 系のエラーについてもそうすると良いです(もっとも、これはロードバランサやリバースプロキシの仕事ですが)。

エラーの内容には、開発者に向けていくつかの情報を含めるようにします。それは、分かりやすいエラーメッセージと、ユニークなエラーコード(これは、ドキュメントで詳細を検索するためのものです)、そして、可能であれば原因の説明です。これらを含めた JSON は以下のようになります。

{
  "code" : 1234,
  "message" : "Something bad happened :(",
  "description" : "More details about the error here"
}

追加・更新時の入力値バリデーションエラーについては、さらにその内容をブレイクダウンした方が良いです。以下の例は、入力値エラーの原因となったフィールドと理由を含めたものです。

{
  "code" : 1024,
  "message" : "Validation Failed",
  "errors" : [
    {
      "code" : 5432,
      "field" : "first_name",
      "message" : "First name cannot have fancy characters"
    },
    {
       "code" : 5622,
       "field" : "password",
       "message" : "Password cannot be blank"
    }
  ]
}

HTTP ステータスコードを有効活用しよう

HTTP ではたくさんのステータスコードが定義されて、これは API でも有効活用できます。これらを使うことは、API 利用者にとってレスポンスを適切に処理するための手助けとなります。是非とも使うべきステータスコードのリストを載せておきます。

  • 200 OK - GET, PUT, PATCH, DELETE リクエストが成功した場合に応答。もしくは、POST リクエストが結果的に何もリソースを作らなかった場合に応答。
  • 201 Created - POST リクエストがリソース作成に成功した場合に応答。なお、そのリソースへのリンクを Location ヘッダに含める必要がある。
  • 204 No Content - 成功したDELETE リクエストで、ボディを返したくない場合に応答
  • 304 Not Modified - HTTP キャッシュが有効な場合に応答
  • 400 Bad Request - パース不可能なリクエストボディが来た場合に応答
  • 401 Unauthorized - 認証がされていない、もしくは不正なトークンの場合に応答
  • 403 Forbidden - 認証はされているが、認可されていないリソースへのリクエストに応答
  • 404 Not Found - 存在しないリソースへのリクエストに応答
  • 405 Method Not Allowed - 認可されていないメソッドでのリクエストに応答
  • 410 Gone - 今は存在しないリソース(廃止されたAPIなど)で空要素を返す場合などに応答
  • 415 Unsupported Media Type - 対応していない MediaType が指定された場合に応答
  • 422 Unprocessable Entity - バリデーションエラーに対して応答
  • 429 Too Many Requests - 回数制限をオーバーしたリクエストに対して応答

まとめ

冒頭で述べた通り、API は開発者のための UI です。機能性だけでなく、使い心地まで、とことん追求しましょう。


追記(2016/03/29)

@ryo88c さんが、本記事の説明で不足している「なぜそうすべきか」の具体的な根拠をまとめてくださいました!
また、アンチパターンとなっている箇所についても言及されています。
本質的な理解のため、ぜひこちらの記事も合わせてご確認いただければと思います。

「WebAPI 設計のベストプラクティス」に対する所感 - Qiita


追記(2016/07/19)

@howdy39 さんが、本記事の内容と Google の WebAPI の設計を比較してまとめてくださいました!
かなり詳細に照らし合わせて検証されています。
ぜひ、こちらの記事も合わせてご確認くださいませ。

GoogleのWebAPI設計とWebAPI設計のベストプラクティスを比較してみる - Qiita

애자일 방법론이란 무엇인가? 애자일 방법론의 핵심은 무엇인가?
https://gmlwjd9405.github.io/2018/05/26/what-is-agile.html

Goal

  • 역사적인 맥락을 통한 애자일의 등장 배경
  • 애자일(Agile)이란 무엇인가
  • 애자일의 핵심은 무엇인가

애자일의 등장 배경

초기 SW의 개발 방법은 계획 중심의 프로세스(SW 개발의 역사)

  • 초기에 SW을 주로 개발했던 분야는 ‘군사’ 쪽의 대형 프로젝트이다.
  • 즉, 계획 중심의 프로세스 로 SW 개발이 진행됐다.
    • 이 계획 중심의 프로세스는 ‘건축(도시 계획)’에서의 방법을 본딴 것이다.
  • 당시에는 이런 프로세스를 하는 것이 적합해보이는 프로젝트가 대부분이었다.

하지만, 지금은 달라졌다. 높아진 SW 개발의 불확실성

  • 90년대를 지나면서 SW분야가 넓어지고 SW의 사용자(end user)들이 ‘일반 대중들’로 바뀌기 시작했다.
  • 또한 비지니스 사이클이 짧아지면서 사람들의 욕구와 트렌드도 빨리 바뀌게 되었다.
    • Q. 비지니스 사이클?
      • 제품이 나오고, 사용하고, 또 다른 제품으로 넘어가서 사용하고, 또 새로운 제품이 나오는 식의 사이클. 예를 들어 사용하는 핸드폰의 수명이 1~2년 정도로 바뀐 것.
  • 이러한 변화에 따라 SW 개발의 불확실성이 높아졌다.

새로운 SW 개발 방법의 등장

  • SW 개발의 불확실성이 높아짐에 따라 예전의 전통적인 개발 방법이 맞지 않다는 것을 느낀 사람들은 각자 자신만의 SW 개발 방법을 만들어서 사용하고 있었다.
    • Q. 왜 전통적인 개발 방법이 맞지 않다는 것을 느꼈나?
      • ‘창의성’이나 ‘혁신’을 계획하는 것이 이상하다고 생각했다.
      • 예를 들어, 보수적인 회사에서 자기들이 생각하기에 미리 계획한 혁신적인 일이 다른 사람들이 보기에는 ‘뻔히 예상가는, 그냥 그 회사같은 것을 해놨네.’ 라고 생각이 든다. 왜냐하면 혁신은 계획에서 나오는 것이 아니기 때문이다.
  • 즉, ‘경량 방법론 주의자(lightweight methodologies)’ 들은 해보면서 고쳐나가는 방식의 방법론을 사용하게 되었다.
    • 규칙이 적고 가볍게 대응을 잘하는 방법
    • Q. 그렇다면 이 방법은 주먹구구식과 뭐가 다른가?
      • 밑에서 더 자세히 설명하겠지만, 이 방법을 아주 잘하는 단계에 이르면 겉에서 보기엔 미리 큰 그림을 만들어놓고 하는 것처럼 보인다.
      • 예를 들어, 즉흥연기를 잘하는 단계에 이르면 겉에서 보기엔 대본대로 하는 것이 아니냐는 생각을 한다.

새로운 SW 개발 방법의 선언

  • 2001년도에 경량 방법론 주의자 17명이 만났다.
    • 17명 각각이 방법론을 만들어 쓰고 있었다.
    • 각각의 방법론에서 서로 간에 추구하는 관점의 공통점을 추려서 ‘애자일 SW 개발 선언문’ (A4로 한 장 정도)을 만들었다.
    • 이 사람들의 뿌리는 스몰토크(Smalltalk)라는 객체 지향 프로그래밍 언어라는 프로그래밍 언어의 문화에서 나왔다. (c언어의 문화와는 다르다.)
      • Q. 스몰토크(Smalltalk)?
        • 동적 형, 반영을 지원하는 객체 지향 프로그래밍 언어
    • 이때부터 애자일이라는 용어에 의미가 생겼다.


애자일(Agile)이란?

1. 사전적인 정의

  • ‘애자일 SW 개발 선언문’에 나와있는 것이 사전적인 정의의 전부다.
    • 시중에 파는 애자일 관련 책들은 ‘애자일 SW 개발 선언문’의 해석 이 대부분이다.
  • 이 선언문의 나열식 내용을 느낌대로 나름의 정리를 하자면, “협력”과 “피드백”이라는 두 단어로 정리를 할 수 있다.
  • 즉, 한 마디로 "협력과 피드백을 자주! 일찍! 더 잘하는 것!" 이다.

2. 현실적인 정의

  • 추가 작성 계획


애자일의 핵심(협력과 피드백)

애자일의 핵심 1. “협력”

  • SW 프로젝트가 망하는 경우는 기술 외적인 것도 크다. 따라서 특히 SW 개발의 불확실성이 높을 때는 “협력”을 잘 해야한다.
  1. 내부적 협력
    • 내부적 협력: SW를 개발한 사람들 안에서의 협력을 말한다. 특히, 직무 역할을 넘어선 협력을 의미한다.
    • 좋은 일은 곱하기!!
      • 1) 혼자 얻은 좋은 통찰을 협력을 통해 다른 사람도 같이 얻을 수 있다.
      • 2) 예상하지 못했던 기회를 잡을 수 있다.
      • 예를 들어, 어떤 사람이 개발을 하다가 2배의 속도로 개발할 수 있다는 것을 발견했다. 협력이 약한 문화에서는 그 사람만 좋은 보상과 칭찬을 받게 되고, 그 사람 코드가 다른 사람들의 코드와 너무 이질적이어서 시스템에 문제가 발생할 수도 있다. 하지만 협력이 강한 문화에서는 그것을 다른 사람들과 공유하여 모두 같이 빠르게 개발할 수 있고 더 나은 발전점을 찾을 수 있다. 즉, 팀 전체의 개선이 일어난다.
    • 안 좋은 일은 나누기!!
      • 1) 문제가 되는 것을 찾기가 쉽다.
      • 2) 예상하지 못했던 문제를 협력이 막을 수 있다.
      • 예를 들어, 내가 생각지 못한 실수를 했는데 아무리 다시 봐도 모르겠거나 더 나은 방법이 생각나지 않을 때가 있다. 이때 나와 항상 같이 일하던 사람에게 물어봐도 나랑 비슷한 생각을 하고 있으므로 더 나은 대안을 제시하지 못하고 비슷한 얘기를 하게 된다. 즉, 서로 다른 사람들과 협력하는 것이 중요하다. 이를 통해 다른 사람이 쉽게 그 문제에 대해 찾을 수 있고 서로 같이 빠르게 해결할 수 있다.
  2. 외부적 협력
    • 외부적 협력: 추가 작성 계획

애자일의 핵심 2. “피드백”

  • 피드백은 학습의 가장 큰 전제조건이다. 내가 어떻게 했는가를 확인하면서 학습해야 한다.
  • 또한, SW 개발의 불확실성이 높을수록 학습 이 중요해진다. 왜냐하면 모르는 것이 많기 때문에 더 빨리 배워나가야하기 때문이다.
  • 일을 잘하는 사람은 feedback seeking 이 뛰어나다. 즉, 이런 사람들은 더 자주 더 많은 사람들에게 피드백을 구한다.
  1. 내부적 피드백
    • 내부적 피드백: 내가 만든 것이 어떻게 됐는지 확인해보는 것
  2. 외부적 피드백
    • 외부적 피드백: 내가 만든 것을 고객이나 다른 부서가 사용해보고 그것을 통해 또 다른 것을 배우는 것


SW 개발의 불확실성과 사업적 가치가 있는 프로젝트

SW 개발의 불확실성

  • 애자일에서는 불확실성이 중요하다.
  • 불확실성이 높을 때 일을 하면 ‘우리가 생각하던 것과는 다르네.’라는 상황이 오게 된다. 이때,
    • 전통적인 방법론을 사용했을 때는 ‘그때 계획을 세울 때 더 잘 세울껄. 그때 이런 리스크까지 생각해놨어야 했는데.’라고 생각하면서도 계획대로 그냥 진행한다.
    • 애자일 방법론을 사용했을 때는 ‘생각대로 안됐네. 계회이 틀어질 수 밖에 없는 게 정상이지. 그럼 지금 빨리 수정하자.’라고 생각하면서 계획을 변경한다.
  • 비지니스 가치(사업적 가치)가 클수록 불확실성도 커진다.

사업적 가치가 있는 프로젝트란

  • 이미 충분한 지식을 가지고 있고 가지고 있는 지식을 활용한 프로젝트의 경우는 대부분 사업적 가치가 떨어진다.
  • 레드오션(Read Ocean)에 뛰어들면 그만큼 경쟁자가 많고 경쟁이 치열해진다.
    • Q. 레드오션(Read Ocean)?
      • 이미 잘 알려져 있는 시장, 즉 현재 존재하는 모든 산업
  • 그렇다면 사업적 가치가 있는 경우는 어떤 경우일까?
    • 우리도 잘 모르는 부분이 있는 것을 하는 것, 즉 학습을 하면서 할 수 있는 것이 사업적으로 가치가 크다.
    • 예를 들어, 카카오뱅크도 이미 다 아는 것을 가지고 하지 않았을 것이다. 분명히 새로운 시도를 했고 이 때문에 가치가 있는 것이다.
    • 따라서 사업적으로 가치 있기 위해서는 배우면서 해야한다.(학습)

학습이란

  • 우리가 생각하는 학습은 너무 정형화되어 있다.
  • 책을 읽는다(독서한다.) = 공부한다. ???
  • 학습에 있어서 독서는 몇 퍼센트를 차지할까를 생각해보자.
  • 학습은 실험, 질문, 검색 등이 모두 포함된다.

관련된 Post

  • 애자일 자격증의 종류와 그 효용 가치에 대해 알고 싶으시면 애자일 자격증을 참고하시기 바랍니다.
  • 애자일 데일리 스크럼의 개념과 도입 방법에 대해 알고 싶으시면 데일리 스크럼이란을 참고하시기 바랍니다.
  • TDD(테스트 주도 개발)의 개념과 효과에 대해 알고 싶으시면 TDD(테스트 주도 개발)란을 참고하시기 바랍니다.
  • 짝 프로그래밍(Pair Programming)의 개념과 효과에 대해 알고 싶으시면 짝 프로그래밍(Pair Programming)이란을 참고하시기 바랍니다.


References


https://doooyeon.github.io/2018/09/10/cookie-and-session.html


쿠키(Cookie)와 세션(Session)

SEPTEMBER 10, 2018

HTTP 프로토콜에서 상태를 유지하기 위한 기술인 쿠키와 세션의 개념과 차이점을 알 수 있다.

HTTP 프로토콜의 특징

  • 비연결 지향(Connectionless)
    • 클라이언트가 request를 서버에 보내고, 서버가 클라이언트에 요청에 맞는 response를 보내면 바로 연결을 끊는다.
  • 상태정보 유지 안 함(Stateless)
    • 연결을 끊는 순간 클라이언트와 서버의 통신은 끝나며 상태 정보를 유지하지 않는다.
  • 쿠키와 세션의 필요성
    • HTTP 프로토콜은 위와 같은 특징으로 모든 요청 간 의존관계가 없다.
    • 즉, 현재 접속한 사용자가 이전에 접속했던 사용자와 같은 사용자인지 아닌지 알 수 있는 방법이 없다.
    • 계속해서 연결을 유지하지 않기 때문에 리소스 낭비가 줄어드는 것이 큰 장점이지만, 통신할 때마다 새로 연결하기 때문에 클라이언트는 매 요청마다 인증을 해야 한다는 단점이 있다.
    • 이전 요청과 현재 요청이 같은 사용자의 요청인지 알기 위해서는 상태를 유지해야 한다.
    • HTTP 프로토콜에서 상태를 유지하기 위한 기술로 쿠키와 세션이 있다.

쿠키(Cookie) 란?

  • 개념
    • 클라이언트 로컬에 저장되는 키와 값이 들어있는 파일이다.
    • 이름, 값, 유호 시간, 경로 등을 포함하고 있다.
    • 클라이언트의 상태 정보를 브라우저에 저장하여 참조한다.
  • 구성 요소
    • 쿠키의 이름(name)
    • 쿠키의 값(value)
    • 쿠키의 만료시간(Expires)
    • 쿠키를 전송할 도메인 이름(Domain)
    • 쿠키를 전송할 경로(Path)
    • 보안 연결 여부(Secure)
    • HttpOnly 여부(HttpOnly)
  • 동작 방식
    1. 웹브라우저가 서버에 요청
    2. 상태를 유지하고 싶은 값을 쿠키(cookie)로 생성
    3. 서버가 응답할 때 HTTP 헤더(Set-Cookie)에 쿠키를 포함해서 전송
       SetCookie: id=doy
      
    4. 전달받은 쿠키는 웹브라우저에서 관리하고 있다가, 다음 요청 때 쿠키를 HTTP 헤더에 넣어서 전송
       cookie: id=doy
      
    5. 서버에서는 쿠키 정보를 읽어 이전 상태 정보를 확인한 후 응답
  • 쿠키 사용 예
    • 아이디, 비밀번호 저장
    • 쇼핑몰 장바구니

세션(Session) 이란?

  • 개념
    • 일정 시간 동안 같은 브라우저로부터 들어오는 요청을 하나의 상태로 보고 그 상태를 유지하는 기술이다.
    • 즉, 웹 브라우저를 통해 서버에 접속한 이후부터 브라우저를 종료할 때까지 유지되는 상태이다.
  • 동작 방식
    1. 웹브라우저가 서버에 요청
    2. 서버가 해당 웹브라우저(클라이언트)에 유일한 ID(Session ID)를 부여함
    3. 서버가 응답할 때 HTTP 헤더(Set-Cookie)에 Session ID를 포함해서 전송
      쿠키에 Session ID를 JSESSIONID 라는 이름으로 저장
       SetCookie: JSESSIONID=xslei13f
      
    4. 웹브라우저는 이후 웹브라우저를 닫기까지 다음 요청 때 부여된 Session ID가 담겨있는 쿠키를 HTTP 헤더에 넣어서 전송
       Cookie: JSESSIONID=xslei13f
      
    5. 서버는 세션 ID를 확인하고, 해당 세션에 관련된 정보를 확인한 후 응답

세션도 쿠키를 사용하여 값을 주고받으며 클라이언트의 상태 정보를 유지한다.
즉, 상태 정보를 유지하는 수단은 쿠키 이다.

  • 세션 사용 예
    • 로그인

쿠키와 세션의 차이점

  • 저장 위치
    • 쿠키 : 클라이언트
    • 세션 : 서버
  • 보안
    • 쿠키 : 클라이언트에 저장되므로 보안에 취약하다.
    • 세션 : 쿠키를 이용해 Session ID만 저장하고 이 값으로 구분해서 서버에서 처리하므로 비교적 보안성이 좋다.
  • 라이프사이클
    • 쿠키 : 만료시간에 따라 브라우저를 종료해도 계속해서 남아 있을 수 있다.
    • 세션 : 만료시간을 정할 수 있지만 브라우저가 종료되면 만료시간에 상관없이 삭제된다.
  • 속도
    • 쿠키 : 클라이언트에 저장되어서 서버에 요청 시 빠르다.
    • 세션 : 실제 저장된 정보가 서버에 있으므로 서버의 처리가 필요해 쿠키보다 느리다.


http://guruble.com/%eb%a7%88%ec%9d%b4%ed%81%ac%eb%a1%9c%ec%84%9c%eb%b9%84%ec%8a%a4microservice-%ec%95%84%ed%82%a4%ed%85%8d%ec%b2%98-%ea%b7%b8%ea%b2%83%ec%9d%b4-%eb%ad%a3%ec%9d%b4-%ec%a4%91%ed%97%8c%eb%94%94/#comment-276


마이크로서비스 아키텍처. 그것이 뭣이 중헌디?

마이크로서비스 아키텍처

마이크로서비스 아키텍처는 하나의 큰 애플리케이션을 여러 개의 작은 애플리케이션으로 쪼개어 변경과 조합이 가능하도록 만든 아키텍처를 말합니다. 애플리케이션을 특화된 기능별로 나누게 되면 자연스럽게 애플리케이션의 추상화(abstraction)가 가능해집니다. 다시 말해, ‘인증’을 담당하는 서비스(예, auth.example.com)는 그 구체적인 구현 내용을 모르더라도 다른 서비스에서 약속된 인터페이스를 이용해 인증 과정을 수행할 수 있습니다. 또한, 검색창의 ‘자동완성’을 담당하는 서비스(예, autocomplete.example.com)는 사용자의 입력을 받아서 자동완성 결과만을 응답해주면 되기 때문에 해당 API를 유지한 상태에서 세부적인 구현내용을 언제든지 손쉽게 개선하고 변경할 수 있습니다.

모든 기술의 발전이 다 그렇듯이 마이크로서비스 역시 기존에 없다가 갑자기 등장한 개념은 아닙니다. 많은 기업들에서는 이미 이와 같은 방식으로 서비스를 분리하여 애플리케이션을 만들었으며 그때 그때 다양한 용어로 이름 붙여져 왔습니다. 그러나 최근 들어, REST API의 일반화, 도커(Docker)와 같은 컨테이너 기술, 클라우드 컴퓨팅 환경의 발전 등에 힘입어 마이크로서비스는 좀 더 손쉽게 구현될 수 있게 되었습니다.

마이크로서비스 아키텍처는 언제 필요한가?

모든 애플리케이션이 마이크로서비스 아키텍처 패턴으로 구성될 필요는 없습니다. 특히, 적은 인원으로 빠르게 시작해야하는 스타트업의 경우 앞으로 어떤 서비스와 컴포넌트가 필요하게 될 지 예측할 수 없는 상황에서 과도하게 시스템을 여러개의 서비스로 쪼갤 필요는 없습니다. (물론, 서비스에 대한 로드맵이 명확하여 초기에 시스템을 마이크로서비스 형태로 구성하는 것도 가능합니다). 그렇다면, 구체적으로 어느 시점에 마이크로서비스 아키텍처에 대해서 고려하는 것이 좋을까요? 일반적으로 다음의 항목들 중에서 대부분이 현재 상황에 해당한다고 생각되면 마이크로서비스 아키텍처 패턴에 대해서 고민을 시작해 보는 것도 나쁘지 않습니다.

  1. 애플리케이션의 배포에 한 시간 이상 소요된다.
  2. 단순한 기능 하나를 수정해도 전체 기능에 대한 QA가 필요하다.
  3. 단순한 버그 수정이 더 중대한 버그를 생산하는 일이 많아졌다.
  4. 현재의 애플리케이션을 기능별로 나눈다고 가정했을 때 수십개의 마이크로서비스가 가능하다.

모놀리틱 아키텍처 (MONOLITHIC ARCHITECTURE)

마이크로서비스 아키텍처를 잘 이해하기 위해서는 먼저, 반대되는 개념인 모놀리틱(Monolithic) 아키텍처에 대해서 살펴볼 필요가 있습니다. 이해를 돕기 위해서, 아마존(Amazon.com)과 유사한 온라인 쇼핑몰을 만든다고 가정해 보겠습니다. 기본적으로 처음 설계되는 애플리케이션의 구조는 그림 1과 같이 비즈니스 로직을 담당하고 있는 애플리케이션이 존재하고, 해당 애플리케이션은 데이터베이스 등 외부 시스템과 특정 프로토콜로 통신을 하게 됩니다. 또한, 사용자에게 인터페이스를 제공하기 위해서 HTML을 렌더링하는 부분과 RESTful API를 제공하는 부분을 갖게 됩니다. 이렇게 구성된 애플리케이션의 소스코드는 하나의 프로젝트로 구성되어 있으며 단일한 패키지로 배포되게 됩니다.

그림 1. 모놀리틱 아키텍처

1

이러한 구성의 애플리케이션은 특별히 이상할 것도 없고 실제로 많은 서비스들이 이와 같은 구성으로 이루어져 있습니다. 이렇게 단순한 구성의 애플리케이션은 로컬 환경에서 개발하기에도 편리하고 통합 시나리오 테스트를 수행하기에도 가장 손쉬운 구성입니다. 또한, 모든 코드가 하나의 묶음으로 구성되어 있기 때문에 배포도 매우 간편해집니다.

하지만 이러한 단순한 애플리케이션의 아키텍처는 서비스가 지속적으로 성장하고 규모가 커질 때 한계에 부딪히게 됩니다. 예를 들어, 3명의 개발자가 몇 가지 핵심 기능을 개발할 때에는 이와 같은 모놀리틱 아키텍처가 최적의 효율성을 보장하지만 개발자의 규모가 수십에서 백명 이상이 되고 서비스의 복잡도가 증가되면 아주 간단한 기능을 하나 추가하기 위해서도 매우 많은 줄의 코드를 수정해야함은 물론, 코드의 변화가 영향을 미치는 범위가 증가되었기 때문에 간단한 변화 하나에도 통합 테스트가 필요하게 됩니다.

많은 회사에서는 이러한 문제를 해결하기 위해서 여러가지 프로세스를 도입하고 애자일 철학을 기반으로 둔 여러가지 방법론을 적용해보고자 노력합니다. 하지만 실제로 이러한 시도가 서비스 구조의 근본적인 원인을 해결하지는 못하기 때문에 좋은 성과를 거두지 못하게 됩니다. 대부분의 경우 근본적인 원인은 서비스의 구조 자체가 너무 복잡하다는 점입니다. 복잡한 구조는 서비스 초창기 부터 함께 개발을 하여 전체 히스토리를 알고 있는 소수의 개발자를 제외하고는 대부분의 개발자들이 전체적인 시스템의 구조를 알지 못하기 때문에 재활용 가능한 모듈을 무시하고 중복된 코드를 생산하게 되며 사용하지 않는 코드가 기술 부채로 계속 쌓이게 됩니다. 또한, 코드가 서로 다양한 방식으로 연관되어 있기 때문에 간단한 버그 수정이 더 큰 버그를 양산하게 되는 결과를 초래합니다.

서비스 복잡도가 증가하면서 모놀리틱 아키텍처가 가지는 문제점들은 배포 시간의 증가, 부분적 스케일 아웃의 어려움, 안정성의 감소 등 여러가지가 있습니다. 그 중에서도 굳이 한가지를 꼽자면 애플리케이션을 구성하는 프로그래밍 언어, 또는 프레임워크의 변경이 거의 불가능에 가까울 정도로 어렵다는 점 입니다. 예를 들어, 애플리케이션에서 사용자의 인증만을 담당하는 요소가 별도의 서비스로 구현되어 있다면 필요에 따라 성능과 안정성을 증가시킬 수 있는 새로운 프레임워크로 변경하는 것을 고려해볼 수 있습니다. 하지만 만약 전체 애플리케이션이 하나로 묶여 있다면 그 동안 개발된 방대한 양의 코드를 새로운 언어, 또는 프레임워크로 전환해야 하기 때문에 대부분 시도조차 할 수 없을 것 입니다.

마이크로서비스 아키텍처 (MICROSERVICE ARCHITECTURE)

마이크로서비스 아키텍처 패턴은 그 이름에서도 유추할 수 있듯이 모놀리틱 아키텍처로 구성된 하나의 큰 서비스를 독립적인 역할을 수행하는 작은 단위의 서비스로 분리하여 설계하는 패턴을 말합니다. 여기에서 말하는 독립적인 역할이란 주로, ‘사용자 관리’, ‘주문 관리’, ‘결제 관리’, ‘알림 관리’와 같이 기능적인 요소를 의미합니다. 각각의 마이크로서비스는 그 크기만 작을 뿐, 자세히 살펴보면 각각이 하나의 모놀리틱 아키텍처와 유사한 구조를 갖습니다. 다만, 하나의 서비스에서 처리해야 하는 기능과 규모가 작기 때문에 이를 마이크로서비스라고 부릅니다.

그림 2 마이크로서비스 아키텍처

2

그림 2는 마이크로서비스 아키텍처의 개략적인 모습을 몇 가지 예시로 나타낸 그림이며 이 예시는 총 4개의 마이크로 서비스로 구성되어 있습니다. 사용자 서비스는 REST API를 이용해서 주문 서비스를 활용하며 API Gateway를 통해 정보를 웹브라우저 화면에 표시하거나 모바일 클라이언트에 데이터를 제공합니다. 또한, 사용자에게 알림이 필요한 경우 실제 알림이 어떤 과정을 통해서 처리되는지 신경쓸 필요없이 알림 서비스(Notification service)를 이용하여 원하는 요청을 호출할 수 있습니다. 그림에 표시된 API Gateway는 뒷쪽에서 자세히 설명하도록 하겠습니다.

마이크로서비스 아키텍처를 나타낸 그림에서 주목할 점은 사용자를 위한 데이터베이스와, 주문을 위한 데이터베이스가 따로 표시되어 있다는 점입니다. 이 부분은 실제로 마이크로서비스 아키텍처를 구현할 때 매우 중요한 부분인데, 전통적인 모놀리틱 아키텍처에서 주로 개발을 했던 경험에 비추어 보면 데이터의 트랜잭션 관리나 정규화 등의 관점에서 매우 비효율적으로 보일 수 있습니다. 물론, 하나의 데이터베이스를 각각의 개별 서비스가 공유해서 사용하는 방식도 가능하지만 마이크로서비스 아키텍처가 가지는 근본적인 장점을 최대한 활용하기 위해서는 이렇게 서비스별로 별도의 데이터베이스를 사용하는 것이 필요합니다. 또한, 데이터베이스(DBMS)의 종류 자체도 반드시 한가지 통일할 필요 없이 데이터의 특성과 서비스가 가지는 특수성에 따라 가장 효율적인 데이터베이스를 선택하여 사용하는 것도 가능합니다. 예를 들어, 어떤 서비스에서 사용하는 데이터는 변경이 적고 주로 읽기(read) 작업만 수행되는 반면, 또 다른 서비스의 데이터는 읽기 작업보다 빠른 속도로 쓰여지는(write) 작업이 대부분이라면 각각의 서비스 특성에 맞게 데이터베이스의 종류를 결정하고 설계할 수 있습니다.

마이크로서비스 아키텍처의 장점

마이크로서비스 아키텍처는 서비스의 규모가 커지고 복잡도가 증가할 수록 여러가지 장점을 갖습니다. 우선 서비스가 개별적으로 독립적인 단위의 애플리케이션이기 때문에 변경이 용이하고 그 변경이 다른 서비스에 미치는 영향이 적습니다. 또한, 개별 서비스 단위의 배포가 가능하기 때문에 하루에도 필요에 따라 여러 번 배포를 하는 것이 가능합니다. 비용적인 측면에서 보자면 마이크로서비스 아키텍처는 부하가 집중되는 특정 서비스를 위해 전체 애플리케이션을 스케일 아웃할 필요가 없기 때문에 불필요한 자원의 낭비를 줄일 수 있습니다. 특히, 서비스의 특성에 따라서 메모리 사용이 많은 서비스도 있을 수 있고, 계산 과정이 많아서 CPU 사용량이 많은 서비스가 있을 경우 서비스의 특성에 맞게 자원을 할당하여 스케일 아웃할 수 있기 때문에 효율적인 자원사용이 가능하게 됩니다.

이 외에도 마이크로서비스 아키텍처는 다양한 장점을 가지고 있지만, 여기에서 가장 강조하고 싶은 장점 중 하나는 시스템의 아키텍처가 개발 조직과 나아가서 회사의 조직 문화에 큰 영향을 미친다는 점입니다. 웹 서비스를 기반으로 하는 대부분의 회사들은 애자일의 사상을 도입하고 여러가지 방법론들을 채택하여 빠르고 유연한 개발 문화를 만들고자 노력합니다. 하지만 서비스의 규모가 커지고 시스템이 복잡해지면 사소한 변경 하나가 발생시킬 수 있는 문제(side effect)가 많아지고, 이 때문에 조직은 복잡한 시스템에 맞는 복잡한 프로세스를 필연적으로 가지게 됩니다. 마이크로서비스 아키텍처의 가장 큰 장점은 특정 서비스의 변경이 다른 서비스에 영향을 미칠 가능성이 적다는 점과 서비스 단위로 독립적인 배포가 가능하다는 점 입니다. 다시 말해, 인증과 관련된 서비스가 독립적으로 분리되어 있다면 해당 서비스의 개선과 수정 작업이 다른 서비스의 이해 당사자들과 독립적으로 진행될 수 있기 때문에 의사결정이 빠르고, 독립적인 테스트의 구축이 용이하기 때문에 품질이 증가하게 됩니다 (다른 서비스와 연계된 통합 테스트는 서비스가 분리될 수록 오히려 복잡도가 높아지게 됩니다). 이것은 다시 말해, 조직의 의사결정 프로세스와 테스트 및 배포 프로세스 등 많은 부분에 영향을 미친다는 것을 의미합니다.

마이크로서비스 아키텍처의 단점

반면, 마이크로서비스 아키텍처가 모든 면에서 장점만을 갖는 것은 아닙니다. 마이크로서비스 아키텍처가 가지는 대표적인 단점으로는 모놀리틱 아키텍처에 비해 서비스 간의 통신에 대한 처리가 추가적으로 필요하다는 점입니다. 이것은 단순히 개발해야 하는 코드의 양이 늘어난다는 점 뿐만 아니라, 사용자의 요청을 처리하기 위한 응답속도의 증가에도 영향을 미칩니다. 뿐만 아니라, 분산된 데이터베이스는 트랜잭션 관리가 용이하지 않기 때문에 데이터의 정합성을 맞추기 위한 노력이 추가적으로 필요합니다. 대부분의 모놀리틱 아키텍처에서는 하나의 데이터베이스를 사용하기 때문에 트랜잭션에 대한 처리가 크게 어렵지 않습니다. 하지만 서로다른 데이터베이스, 심지어 종류도 서로 다른 데이터베이스 내의 데이터의 정합성을 유지하기 위한 트랜잭션 처리는 대부분의 데이터베이스가 자체적으로 지원하지 않기 때문에 애플리케이션의 개발과정에서 항상 고려해야 한다는 어려움이 있습니다.

앞서 설명한 바와 같이 마이크로서비스 아키텍처는 서비스의 특성에 따라 효율적인 자원 사용이 가능하도록 스케일 아웃이 가능한 장점이 있는데 반해, 수 많은 서비스를 배포하고 관리하는 데에 어려움이 있습니다. 넷플릭스(Netflix)와 같은 대규모 서비스들은 보통 수백개 이상의 마이크로서비스로 이루어지는데 이렇게 많은 서비스들은 각각 서로 분산되어 있기 때문에 관리 포인트가 증가하고 통합해서 모니터링하고 운영하는 것이 모놀리틱 아키텍처에 비해 매우 어려워집니다. 이것은 필연적으로 매우 정교한 배포 자동화를 필요로 하며 많은 PaaS(Platform as a Service) 서비스, 또는 도커(Docker)와 같은 컨테이너 기술을 활용하여 도움을 받을 수 있습니다.

API Gateway

그림 3은 우리가 많이 사용하는 검색 포털 사이트의 메인 화면입니다. 사용자는 메인 페이지를 보기 위해서 하나의 URL을 통해 서버에 요청을 보내지만 실제 사용자가 보게 되는 화면에는 다양한 종류의 서비스 결과들 입니다. 그림에서 보여지는 붉은색 상자는 개별 서비스를 나타내며 위에서 부터 순서대로 이름을 붙여보자면, ‘사용자 서비스’, ‘뉴스 서비스’, ‘실시간 급상승 검색어 서비스’, ‘광고 서비스’, ‘날씨 서비스’ 등으로 나눌 수 있습니다.

그림 3. 검색 포털 메인화면에서의 다양한 종류의 서비스

3

만약, 모놀리틱 아키텍처를 이용해서 이와 같은 요청을 처리하는 애플리케이션을 구현하였다면 실제 요청이 도착하여 결과를 반환하기 까지 서버는 다양한 종류의 쿼리를 데이터베이스에 보내게 됩니다. 

그림 4. 모놀리틱 아키텍처에서의 요청 처리

4

이러한 방식은 애플리케이션 아키텍처가 간단하다는 장점은 있지만 특정 서비스에 변경이 있을 경우 이 서비스를 포함하고 있는 모든 코드를 찾아서 수정해 주어야만 하는 어려움이 있습니다.

그렇다면, 마이크로서비스 아키텍처를 갖는 애플리케이션에서는 이와 같은 요청에 대한 처리를 어떻게 수행하게 될까요? 그럼 5는 각각의 마이크로서비스들이 요청에서 필요한 각 영역을 담당하여 처리하는 모습을 보여줍니다.

그림 5. 마이크로서비스 아키텍처에서의 요청 처리

5

그림 5에서 볼 수 있듯이 각각의 마이크로서비스들은 각각이 담당한 내용에 대한 응답을 클라이언트에 보내주어서 임무를 완수합니다. 하지만, 이론적으로는 가능할지 몰라도 이를 실제로 구현하다 보면 몇 가지 어려움이 있습니다.

먼저, 클라이언트(Web UI 또는 모바일 앱)는 메인 페이지를 화면에 표시하기 위해서 연관된 모든 마이크로서비스에 각각 요청을 보내야만 합니다. 우리가 살펴보는 예제에서는 총 5개의 마이크로서비스가 관련되어 있지만 실제로는 이 보다 훨씬 더 많은 요청을 호출해야하는 경우도 있을 수 있습니다. 각각의 마이크로서비스에 요청을 보내기 위해서는 클라이언트가 모든 마이크로서비스의 호스트명은 물론 end_point를 알고 있어야 합니다. 즉, 마이크로서비스가 추가되거나 호스트정보가 변경되면 클라이언트가 가지고 있는 정보 역시 함께 수정해 주어야 합니다. 뿐만 아니라, 클라이언트는 서버에 요청을 보내고 응답을 받기 위해서 네트워크 지연속도(latency)가 필요한데, 요청의 회수가 증가할 수록 이 지연속도는 선형적으로 증가할 수 밖에서 없는 문제를 가지고 있습니다. 즉, 요청을 보내야하는 서비스의 개수가 증가할 수록 응답속도가 늦어진다는 점입니다.

두 번째로, 이와 같은 방식의 요청처리를 클라이언트에서 구현하려면 코드가 매우 복잡해지게 됩니다. 일반적으로 클라이언트는 요청을 보내야하는 서버의 호스트를 명시한 뒤 각각의 상황에 맞게 end_point를 변경하며 요청을 보냅니다. 그러나 이와 같이 여러개의 마이크로서비스에 요청을 보내기 위해서는 모든 마이크로서비스의 주소를 저장해 두어야하며, 요청을 보낼 때마다 해당 요청이 어떤 서비스에 보내는 것인지를 명시해 주어야 하기 때문에 필연적으로 소스코드가 복잡해지는 결과를 초래합니다.

세 번째로, 모든 마이크로서비스가 웹 통신에 적합한 프로토콜로 통신하지는 않는다는 점입니다. 많은 마이크로서비스들은 사용자에게 주는 기능적인 관점에서 나누어져 있기 때문에 HTTP 통신을 제공하지만 일부 서비스들은 서비스 자체가 가지는 특성에 따라 더 알맞는 프로토콜을 사용할 수 있습니다. 예를 들어, 사용자에게 알림을 보내주기 위한 서비스의 경우 요청을 순차적으로 빠르게 처리하기 위해서 메시지 큐와 관련된 프로토콜을 사용할 수도 있습니다. 이러한 방식의 프로토콜들은 모바일 앱 또는 웹 브라우저가 직접 통신하기에는 적합하지 않을 뿐만 아니라, 보안상으로도 방화벽(firewall) 내부에 위치하면서 외부에서 직접 접근하는 것을 차단해야만 합니다.

마지막으로, 이러한 방식의 구현은 추후 두 개 이상의 마이크로서비스가 통합되거나 하나의 마이크로서비스가 두 개 이상으로 분리되는 경우 여기에 맞추어 클라이언트 코드를 수정하는 것이 매우 어렵습니다. 앞에서도 이야기 했지만, 클라이언트는 마이크로서비스에 요청을 보내기 위해서 모든 마이크로서비스의 호스트명은 물론 end_point를 알고 있어야 하며, 호스트정보가 변경되면 클라이언트가 가지고 있는 정보 역시 함께 수정해 주어야하기 때문입니다.

API GATEWAY를 이용한 해결책

API Gateway는 그 이름에서도 유추할 수 있듯이 서비스로 전달되는 모든 API 요청의 관문(Gateway) 역할을 하는 서버입니다. API Gateway는 시스템의 아키텍처를 내부로 숨기고 외부의 요청에 대한 응답만을 적합한 형태로 응답합니다. 즉, 클라이언트는 시스템 내부의 아키텍처가 마이크로서비스 형태로 되어있는지 모놀리틱 아키텍처로 구현되어 있는지를 알 필요가 없으며 서로 약속한 형태의 API 요청만을 서버로 보내면 알맞는 형태의 결과를 받을 수 있습니다.

모든 사용자의 API 요청은 그림 6과 같이 제일 먼저 API Gateway에 도착하게 됩니다. API Gateway는 받은 요청을 기반으로 필요한 마이크로서비스에 개별적인 요청을 다시 보내게 됩니다. 이렇게 각각의 마이크로서비스로부터 받은 응답들을 API Gateway는 다시 취합하여 클라이언트에게 전달하는 역할을 수행합니다. 이 과정에서 API Gateway는 사용자의 HTTP 요청을 마이크로서비스가 받을 수 있는 다른 형태의 프로토콜로 전환하는 역할을 수행하기도 합니다. 앞에서 살펴본 검색 포털의 메인화면을 예로 들어 설명하자면, 사용자의 클라이언트는 메인 페이지에 대한 요청을 API Gateway에 보내고, 이 요청을 받은 API Gateway는 해당 요청의 응답에 필요한 정보들을 사용자서비스, 뉴스 서비스, 실시간 급상승 검색어 서비스, 광고 서비스, 날씨 서비스등에 해당 결과들을 하나의 응답으로 취합한 뒤 클라이언트에게 다시 전달하게 됩니다.

그림 6. API Gateway를 활용한 마이크로서비스 아키텍처에서의 요청 처리

6

API Gateway는 이처럼 클라이언트의 요청을 일괄적으로 처리하는 역할 뿐만 아니라, 전체 시스템의 부하를 분산시키는 로드 밸런서의 역할, 동일한 요청에 대한 불필요한 반복작업을 줄일 수 있는 캐싱, 시스템상을 오고가는 요청과 응답에 대한 모니터링 역할도 수행할 수 있습니다.

이렇게 API Gateway를 이용하여 서비스 요청에 대한 처리를 하게되면 특정 서비스의 변경사항이 생기거나 서비스가 통합/분리 되더라도 클라이언트는 그 사실을 인지할 필요가 없으며 API Gateway 내부의 변경사항만으로 처리가 가능하게 됩니다. 이렇게 API Gateway를 이용하여 서비스 요청에 대한 처리를 하게되면 특정 서비스의 변경사항이 생기거나 서비스가 통합/분리 되더라도 클라이언트는 그 사실을 인지할 필요가 없으며 API Gateway 내부의 변경사항만으로 처리가 가능하게 됩니다. 이렇게 시스템 내부의 아키텍처를 숨길 수 있는(encapsulate) 특성이 API Gateway가 갖는 가장 큰 장점이라고 할 수 있습니다.

API Gateway는 다른 모든 것들과 마찬가지로 장점만을 갖는 것은 아닙니다. API Gateway가 갖는 대표적인 단점으로는 구현하고 관리해야하는 요소가 하나 더 증가한다는 점입니다. 예를 들어, 특정 마이크로서비스에 기능이 추가되는 경우, API Gateway가 포함된 해당 기능을 사용자에게 전달하기 위한 내용을 API Gateway에도 반영해 주어야 합니다. 이러한 이유 때문에 API Gateway를 관리하는 절차가 최소화 되어야만 합니다.

API Gateway가 갖는 또 하나의 단점은 성능상의 병목(bottleneck)지점이 될 수 있다는 점입니다. 앞에서 설명한 바와 같이 API Gateway는 모든 요청을 수용해야 하는 창구 역할을 하기 때문에 이 부분에 병목현상이 발생하면 서비스 전체의 품질에 지대한 영향을 미치게 됩니다. 이러한 이유 때문에 API Gateway를 설계하고 구현할 때에는 항상 성능과 확장성을 고려해야만 합니다. 특히, API Gateway를 비동기(asynchronous)적이고 non-blocking I/O 처리가 가능하도록 구현하는 것이 적은 비용으로 최대의 성능을 발휘할 수 있는 관건이 됩니다.

API GATEWAY 구현시 고려해야할 점들

앞서 살펴본 검색 포털의 메인화면을 잘 살펴보면 각각의 서비스들이 서로 독립적으로 동작가능하다는 점을 알 수 있습니다. 이러한 경우 API Gateway는 동시에 여러개의 요청을 각각의 마이크로서비스에 전달하는 것이 가능합니다. 하지만, 모든 요청이 이와 같이 병렬로 처리될 수 있는 것은 아닙니다. 예를 들어, 검색 포털에서 카페의 메인화면을 표시하기 위해서 API Gateway는 먼저 ‘사용자 서비스’에 요청을 보내어 사용자 정보를 가져와야 하며, 이 정보를 기반으로 해당 사용자가 어떤 까페에 가입화여 활동중인지를 ‘카페 서비스’에 요청해야 합니다. 즉, 개별 마이크로서비스로 보내는 요청의 선후관계가 존재하게 되는 것입니다. 이러한 경우 일반적인 비동기처리를 위해서 콜백(callback) 함수를 이용하게됩니다. 즉, 사용자 서비스로 부터 정보를 받아온 뒤 이 요청의 콜백함수에서 카페 서비스로 다시 요청을 보내게 되는 것입니다. 이러한 방식의 애플리케이션 코드는 흔히 말하는 콜백 지옥(callback hell)을 경험하게 할 수 있으며 유지보수와 소스코드의 가독성을 현저하게 악화시키는 원인이 되곤 합니다.

이러한 문제점 때문에 많은 언어와 프레임워크들은 동일한 처리를 수행하는 코드를 좀 더 직관적이고 서술적으로 표현할 수 있는 Reactive 프로그래밍을 고안하게 되었습니다. 대표적인 reactive 프로그래밍 방식으로는 자바스크립트의 Promise, 스칼라의 Future등이 있습니다. 이러한 reactive 접근방식을 잘 활용하여 API Gateway를 구현하게 되면 비동기 처리의 성능적인 이점은 유지하면서 좀 더 직관적이고 관리하기 쉬운 코드를 작성할 수 있습니다.

API Gateway를 구현할 때 고려해야하는 또 한 가지 중요한 점은 예외에 대한 처리입니다. 마이크로서비스 아키텍처에서는 모놀리틱 아키텍처에 비하여 분산된 서버의 상태와 여러가지 변수들로 인해서 일부 서비스에 장애가 발생하거나 응답속도가 지연될 가능성이 높아지게 됩니다. 이렇게 특정서비스에 문제가 발생했을 때 API Gateway는 서비스 장애의 종류에 따라 적절한 처리가 가능하도록 설계/구현되어야 합니다. 예를 들어, 카페 메인화면에 대한 요청을 처리하는 과정에서 특정 서비스에 장애가 발생하는 상황을 예로 들어보도록 하겠습니다. 카페 메인화면에는 사용자 정보와 사용자가 가입한 카페 목록, 그리고 사용자가 관심을 가질만한 추천 카페 목록을 표시한다고 가정해 보겠습니다. 이 과정에서 ‘추천 카페 목록’을 제공하는 ‘추천 서비스’에서 장애가 발생하는 경우 일반적으로 카페 메인화면은 정상적으로 표시가 되고 추천 목록에는 임시적인 페이지로 대체되는 것이 적당할 것 입니다. 반면, 카페 목록을 제공해야 하는 ‘카페 서비스’에 장애가 발생하는 경우에는 가장 중요한 정보를 전달할 수 없기 때문에 오류 처리를 하는 것이 적합할 것 입니다. 이처럼 API Gateway는 특정 요청에 대하여 개별 서비스의 특수한 장애 상황에 대해서 어떻게 대처할지에 대한 고려가 구현시 포함되어야 합니다.

마지막으로 API Gateway는 모든 요청이 몰리는 지점이기 때문에 동일한 요청에 대하여 중복적으로 마이크로서비스에 요청을 보내는 것 보다 기존의 결과를 캐싱하여 재활용할 수 있도록 설계하는 것이 중요합니다. 예를 들어, 검색 포털의 메인화면에서 ‘날씨 서비스’와 같은 경우, 모든 요청에 대하여 반복적으로 현재 날씨를 가져오는 것 보다 주기적인 시간 단위로 갱신을 하며 한번 가져온 결과를 해당 주기 내에서 재활용하는 것이 불필요한 자원사용을 막는 현명한 방법이라고 할 수 있겠습니다.

https://qiita.com/suin/items/f7ac4de914e9f3f35884


WebAPIでエラーをどう表現すべき?15のサービスを調査してみた

2017-01-05 追記

2016年3月にエラーの標準形式RFC7807「Problem Details for HTTP APIs」が提案され、今日現在proposed standard(標準化への提唱)となっています。こちらも是非ご覧ください。


最近はREST APIを提供しているサービスが増えてきていますね!また公開されるAPIだけでなく、Microservicesなアーキテクチャを採用して、バックエンドがWeb APIで通信するケースも増えてきているように思います。

APIを使うときはあまり気にしたこともなかったですが、いざAPIを設計してみるとどんなインターフェイスがいいのか、どんな形式がいいのかといった疑問が次々と出てきます。

今回、僕の会社で開発しているテスト自動化サービスのShouldBeeのチームでも、Web APIの設計に関して、エラーをどのように表現したらいいかがトピックにあがりました。

そこで、他のサービスではどのように表現しているか参考にしたいと思い、今回調査してみることしました。今回、調査は @t-hiroyoshi に協力してもらいました。ありがとうございます!

調査したサービス

今回、Web APIについて調査したウェブサービスは、次の15のサービスです。ぱっと思いついた順番で調べていったので、特に順番には意味はありません。また、調査範囲も思いつきです。

  1. GitHub
  2. Facebook
  3. Heroku
  4. Toggl
  5. Yahoo!
  6. GREE
  7. Nike
  8. Twitter
  9. Google
  10. Twilio
  11. Foursquare
  12. Flickr
  13. Linkedin
  14. Philips Hue
  15. Qiita

それでは、それぞれのサービスがWeb APIでエラーをJSONでどう表現しているか、個々に見て行きましょう。

GitHub

GitHub Developer

{
   "message": "Validation Failed",
   "errors": [
     {
       "resource": "Issue",
       "field": "title",
       "code": "missing_field"
     }
   ]
 }
  • message: エラーメッセージ
  • errors: 送られたフィールドにエラーがあった場合に追加されます。
    • resource: 対象のリソース
    • field: 対象のフィールド
    • code: エラーコード

典型的なエラーはmessageだけなのでシンプルですが、errorsで複数エラーが返せるのと、詳細なエラーを表現することもできる構造になっています。

Facebook

Using the Graph API

     {
       "error": {
         "message": "Message describing the error", 
         "type": "OAuthException", 
         "code": 190,
         "error_subcode": 460,
         "error_user_title": "A title",
         "error_user_msg": "A message"
       }
     }
  • message: エラーメッセージ
  • type: エラーの型
  • code: エラーコード
  • error_subcode: エラーのサブコード
  • error_user_title: ユーザに直接表示できるメッセージで、翻訳もされるとのことです。ダイアログのタイトルなどに表示するという用途。
  • error_user_msg: これもユーザに直接表示できるメッセージで、翻訳もされるとのことです。

Facebookの特徴は、エンドユーザ向けメッセージが別についてくることです。しかもリクエストのロケールにあわせて翻訳までしてくれるので、アプリを作る側にとったら便利ですね。

Heroku

Platform API Reference | Heroku Dev Center

{
  "id":       "rate_limit",
  "message":  "Your account reached the API rate limit\nPlease wait a few minutes before making new requests",
  "url":      "https://devcenter.heroku.com/articles/platform-api-reference#rate-limits"
}
  • id: エラーコード
  • message: エラーメッセージ
  • url: エラーの詳細が記述されたページのURL

Herokuは極めてシンプルな形式ですが、エラーについての説明があるページのURLがついてくるのが特徴のひとつです。

Toggl

toggl_api_docs/reports.md at master · toggl/toggl_api_docs

  {
    "error": {
      "message":"We are sorry, this Error should never happen to you",
      "tip":"Please contact support@toggl.com with information on your request",
      "code":500
    }
  }
  • message: エラーメッセージ
  • tip: このエラーをどう対処したらいいかの説明
  • code: HTTPステータスコード

Togglの特徴は、エラーをどう対処したらいいか簡単なヒントが付いてくる点です。

Yahoo!

Yahoo!デベロッパーネットワーク:エラーメッセージおよびコード - Yahoo!デベロッパーネットワーク

{
  "Error" : {
    "Message" : "error message"
  }
}
  • Message: エラーメッセージ

Yahoo!のAPIではエラーメッセージだけを返すようになっているようです。

GREE

Error Codes - GREE Developer Center

{
   "code":1001,
   "message":"Message API (batch type) to same user is called 
             several times by official user in a certain period of time. Service unavailable.",
   "ref_url":"http:\/\/docs.developer.gree.net\/error.html"
}
  • code: エラーコード
  • message: エラーメッセージ
  • ref_url: エラーの詳細が記述されたページのURL

GREEのエラーレスポンスも、Herokuと同様にシンプルな形式ですが、エラーについての説明があるページのURLがついてくるのが特徴のひとつです。

Nike

API Error Codes

{
    "requestId": "-1712857370761229397",
    "errors": [
        {
            "code": 90,
            "message": "Invalid format: Start Date must follow format yyyy-mm-dd"
        },
        {
            "code": 100,
            "message": "Invalid format: End Date must follow format yyyy-mm-dd"
        },
        {
            "code": 110,
            "message": "Invalid Format: count must be greater than or equal to 1"
        }
    ]
}
  • requestId: リクエストID
  • code: エラーコード
  • message: エラーメッセージ

Nikeでは、リクエストIDがありこれは開発者がAPIプロバイダに問い合わせしたときに利用されるものと思われます。また、複数のエラーが返せるようになっているのも特徴です。

Twitter

Error Codes & Responses | Twitter Developers

{
  "errors": [
    {
      "message": "Sorry, that page does not exist",
      "code": 34
    }
  ]
}
  • message: エラーメッセージ
  • code: エラーコード

TwitterのAPIのエラーレスポンスは、エラーが配列になっていてエラーが複数返せるように設計されている点が特徴です。

Google

Standard Error Responses - DoubleClick Search API — Google Developers

{
  "error": {
    "errors": [
      {
        "domain": "global",
        "reason": "appNotConfigured",
        "message": "The app with id {appId} does not exist or is not properly configured as a Google Drive app."
      }
    ],
    "code": 403,
    "message": "The app with id {appId} does not exist or is not properly configured as a Google Drive app."
  }
}
  • domain: どの領域のエラーかを特定するためのもののようです。例えばYouTube領域固有のエラーであればyoutube.parameterなどとなるようです。
  • reason: エラーコード
  • message: エラーメッセージ
  • code: HTTPステータスコード

Googleの特徴は、様々なサービスを横断して統一された形式のJSONを使っている点です。そのためサービスを表すためにdomainフィールドが設けられています。また、複数のエラーを返せるようにerrorsフィールドは配列になっています。

Twilio

Twilio Cloud Communications - APIs for Voice, VoIP, and Text Messaging

{
    "status": 400,
    "message": "No to number is specified",
    "code": 21201,
    "more_info": "http:\/\/www.twilio.com\/docs\/errors\/21201"
}
  • status: HTTPステータスコード
  • message: エラーメッセージ
  • code エラーコード
  • more_info: エラーについて詳細が書いてあるページのリンク

codemore_infoはフィールド自体がない場合もあるとのことです。

Foursquare

Responses & Errors

{
    "meta": {
        "code": 400,
        "errorDetail": "Missing access credentials. See https://developer.foursquare.com/docs/oauth.html for details.",
        "errorType": "invalid_auth"
    },
    "response": {}
}
  • code: HTTPステータスコード
  • errorDetail: エラーメッセージ
  • errorType: エラーコード

Foursquareの特徴は、通常のレスポンスとエラーレスポンスで形式の違いがなく、エラーが発生したときはレスポンスのmetaキーにエラーが入ることです。

Flickr

Flickr Services

{
    "code": 96,
    "message": "Invalid signature",
    "stat": "fail"
}
  • code: エラーコード
  • message: エラーメッセージ
  • stat: そのリクエストが成功したか失敗したか

Flickrはシンプルな形式ですが、statがあるのが特徴です。

Linkedin

{
    "errorCode": 0,
    "message": "Unknown authentication scheme",
    "requestId": "PFB5T8NVLO",
    "status": 401,
    "timestamp": 1432283172233
}
  • errorCode: エラーコード
  • message: エラーメッセージ
  • requestId: リクエストID。
  • status: HTTPステータスコード。
  • timestamp: リクエストのタイムスタンプ。

LinkedInのエラーレスポンスは、リクエストIDとリクエストのタイムスタンプがあるのが特徴です。もし開発者から問い合わせがあったときにコミュニケーションしやすそうですね。ただ、リクエストIDなどはヘッダにあったほうがいいのかもとも思います。

Philips Hue

API Core concepts | Philips Hue API

[
  {
    "error": {
      "type": 2,
      "address": "/",
      "description": "body contains invalid json"
    }
  }
]
  • type: エラーコード
  • address: エラーがあったURL
  • description: エラーメッセージ

Qiita

Qiita API v2ドキュメント - Qiita:Developer - Qiita

{
  "message": "Not found",
  "type": "not_found"
}
  • message: エラーメッセージ
  • type: エラーコード

まとめ

エラー内容の比較

以上のサービスのAPIが提供している情報を表にまとめるとこのようになります。

APIのエラー形式のサービス比較表_-_Google_スプレッドシート.png

今回調査した範囲では全てのサービスが、人が読んで理解できる形式のメッセージを必ずエラーに含めていました。

エラーコードはプログラムがそのエラーをハンドルしやすいように、intまたは英数字からなるstringでした。なお、エラーコードを返すAPIの中には、エラーコード一覧表を提供しているサービスとそうでないサービスがありました。クライアントとしてはエラーコードの一覧表があれば、適切なエラーハンドリングをプログラミングしておくことができるので、APIプロバイダが一覧表を公開しておくと良いと思います。

サービスの性質によっては複数エラーを返せたほうがいいかもしれません。クライアントがアプリで入力欄が複数あるUIでは、一度に複数のエラーを把握できたほうが、いいユーザ体験になることがあります。

フィールド名の比較

それぞれのサービスで使われているフィールド名をまとめたのが次の表です。

APIのエラー形式のサービス比較表_-_Google_スプレッドシート.png

メッセージについては、多くのサービスがmessageを採用していました。エラーコードについては、codeが最も多く、typeと名付ける例もありました。複数エラーを返す可能性があるサービスでは、errorsと複数形のフィールド名になっています。ステータスコードについては、codeないしはstatusが使われていました。詳細URLを提供しているサービスは、今回の調査範囲では少数派でしたが、urlrel_urlmore_infoが使われていました。

APIを設計するときは、他のサービスで使われているネーミングに倣っておくと、他のAPIの経験がある開発者とって、そのフィールドが何を表現しているのか理解しやすくなるのではないでしょうか?

最後に調査していて、APIドキュメントに、エラーレスポンスのボディについて説明を設けているサービスとそうでないサービスがあることに気づきました。開発者としては、APIがどのような形式のエラーを返すのかが分かりやすく書いてあると、クライアントを実装しやすいと思います。また、ドキュメントにはエラーに含まれるフィールド名のリストや表だけではなく、具体的にJSONの例もあると親切だと感じます。

エラーレスポンスで考慮したいこと

以上の調査を終えて、僕なりにエラーに表現するときに考慮したいことをまとめてみます。

  • エラーメッセージ: 開発者が読んで分かるメッセージを入れる。
  • エラーコード: クライアントのプログラムがエラーハンドリングの手がかりになる情報を与える。加えて、ドキュメントではエラーコードの一覧を公開する。
  • 複数エラー: サービスによってケースバイケースだが、複数のエラーが表現できるようになっている。
  • 詳細URL: 公開APIでドキュメントが整っているなら、開発者のググる手間がなくなる。
  • 一貫性: どのエンドポイントでも同じ構造のJSONになっている。
  • シンプル: 複雑な構造がなくネストが浅いJSONのほうがクライアントが複雑な型を定義しなくて済む。

おわり

Web APIでエラーをどのように表現したらいいか、15のサービスの例を見ながら考えてみました。どのサービスでも共通する部分がある一方、独自の工夫がこらされているAPIもありました。Web APIを実装する機会があればぜひ参考にしてみてください。

関連シリーズ





https://heropy.blog/2017/09/30/markdown/



MarkDown 사용법 총정리

html asciidoc md markdown

마크다운(MarkDown)에 대해서 알고 계신가요?
파일 확장자가 .md로 된 파일을 보셨나요?
웹 개발을 하면서 아마 README.md라는 이름의 파일을 한 번은 보셨을텐데, 이 파일이 마크다운 문법으로 작성된 파일 입니다.

사용법이 매우 쉽고, 빠르게 문서를 정리할 수 있습니다.
물론 모든 HTML 마크업을 대신할 수 없기 때문에 지나친 의존보다는 쉽고 빠르게 작성하는 용도로 사용하세요.
마크다운과 비슷한 형태로 문법이 좀 더 복잡하지만 확장자가 .adoc인 AsciiDoc 문법도 있습니다. 좀 더 다양한 형태의 문서를 만들 수 있지만, 문법이 좀 더 복잡하고 지원 플랫폼이 적습니다.

우선 문법이 쉽고 다양한 플랫폼을 지원하는 마크다운 문법을 배우세요.
30분이면 충분합니다.

마크다운(MarkDown)에 대해서

마크다운의 장점

  1. 문법이 쉽다.
  2. 관리가 쉽다.
  3. 지원 가능한 플랫폼과 프로그램이 다양하다.

마크다운의 단점

  1. 표준이 없어 사용자마다 문법이 상이할 수 있다.
  2. 모든 HTML 마크업을 대신하지 못한다.

마크다운의 사용

메모장부터 전용 에디터까지 많은 곳에서 활용할 수 있습니다.
문법이 쉽기 때문에 꼭 전용 에디터를 사용할 필요는 없습니다만, 마크다운 코드의 하이라이트 효과를 원한다면 전용 에디터가 좋은 선택이 될 것 같네요.
저는 평소 Atom을 사용하고 있습니다.
혹은 마크다운 문법을 지원하는 모든 곳에서 사용할 수 있으며, 일반 블로그나 워드프레스 외 Slack이나 Trello 같은 서비스에서 메세지를 작성하듯 사용할 수도 있습니다.
화면에 표현되는 스타일(CSS)은 설정에 따라 달라집니다.
HTML과 마찬가지로 눈에 보이는 스타일은 무시하고 각 문법의 의미로 사용하세요.

마크다운 문법(syntax)

제목(Header)

<h1>부터 <h6>까지 제목을 표현할 수 있습니다.

# 제목 1
## 제목 2
### 제목 3
#### 제목 4
##### 제목 5
###### 제목 6

제목1(h1)과 제목2(h2)는 다음과 같이 표현할 수 있습니다.

제목 1
======

제목 2
------

강조(Emphasis)

각각 <em><strong><del> 태그로 변환됩니다.

밑줄을 입력하고 싶다면 <u></u> 태그를 사용하세요.

이텔릭체는 *별표(asterisks)* 혹은 _언더바(underscore)_를 사용하세요.
두껍게는 **별표(asterisks)** 혹은 __언더바(underscore)__를 사용하세요.
**_이텔릭체_와 두껍게**를 같이 사용할 수 있습니다.
취소선은 ~~물결표시(tilde)~~를 사용하세요.
<u>밑줄</u>`<u></u>`를 사용하세요.

이텔릭체는 별표(asterisks) 혹은 언더바(underscore)를 사용하세요.
두껍게는 별표(asterisks) 혹은 언더바(underscore)를 사용하세요.
이텔릭체와 두껍게를 같이 사용할 수 있습니다.
취소선은 물결표시(tilde)를 사용하세요.
밑줄은 <u></u>를 사용하세요.

목록(List)

<ol><ul> 목록 태그로 변환됩니다.

1. 순서가 필요한 목록
1. 순서가 필요한 목록
  - 순서가 필요하지 않은 목록(서브) 
  - 순서가 필요하지 않은 목록(서브) 
1. 순서가 필요한 목록
  1. 순서가 필요한 목록(서브)
  1. 순서가 필요한 목록(서브)
1. 순서가 필요한 목록

- 순서가 필요하지 않은 목록에 사용 가능한 기호
  - 대쉬(hyphen)
  * 별표(asterisks)
  + 더하기(plus sign)
  1. 순서가 필요한 목록
  2. 순서가 필요한 목록
    • 순서가 필요하지 않은 목록(서브)
    • 순서가 필요하지 않은 목록(서브)
  3. 순서가 필요한 목록
    1. 순서가 필요한 목록(서브)
    2. 순서가 필요한 목록(서브)
  4. 순서가 필요한 목록
  • 순서가 필요하지 않은 목록에 사용 가능한 기호
    • 대쉬(hyphen)
    • 별표(asterisks)
    • 더하기(plus sign)

링크(Links)

<a>로 변환됩니다.

[GOOGLE](https://google.com)

[NAVER](https://naver.com "링크 설명(title)을 작성하세요.")

[상대적 참조](../users/login)

[Dribbble][Dribbble link]

[GitHub][1]

문서 안에서 [참조 링크]를 그대로 사용할 수도 있습니다.

다음과 같이 문서 내 일반 URL이나 꺾쇠 괄호(`< >`, Angle Brackets)안의 URL은 자동으로 링크를 사용합니다.
구글 홈페이지: https://google.com
네이버 홈페이지: <https://naver.com>

[Dribbble link]: https://dribbble.com
[1]: https://github.com
[참조 링크]: https://naver.com "네이버로 이동합니다!"

GOOGLE

NAVER

상대적 참조

Dribbble

GitHub

문서 안에서 참조 링크를 그대로 사용할 수도 있습니다.

다음과 같이 문서 내 일반 URL이나 꺾쇠 괄호(< >, Angle Brackets)안의 URL은 자동으로 링크를 사용합니다.

구글 홈페이지: https://google.com
네이버 홈페이지: https://naver.com

이미지(Images)

<img>로 변환됩니다.
링크과 비슷하지만 앞에 !가 붙습니다.

![대체 텍스트(alternative text)를 입력하세요!](http://www.gstatic.com/webp/gallery/5.jpg "링크 설명(title)을 작성하세요.")

![Kayak][logo]

[logo]: http://www.gstatic.com/webp/gallery/2.jpg "To go kayaking."

대체 텍스트(alternative text)를 입력하세요!

Kayak

이미지에 링크

마크다운 이미지 코드를 링크 코드로 묶어 줍니다.

[![Vue](/images/vue.png)](https://kr.vuejs.org/)

Vue

코드(Code) 강조

<pre><code>로 변환됩니다.
숫자 1번 키 왼쪽에 있는 `(Grave)를 입력하세요

인라인(inline) 코드 강조

`background`혹은 `background-image` 속성으로 요소에 배경 이미지를 삽입할 수 있습니다.

background혹은 background-image 속성으로 요소에 배경 이미지를 삽입할 수 있습니다.

블록(block) 코드 강조

`를 3번 이상 입력하고 코드 종류도 적습니다.


```html
<a href="https://www.google.co.kr/" target="_blank">GOOGLE</a>
```

```css
.list > li {
  position: absolute;
  top: 40px;
}
```

```javascript
function func() {
  var a = 'AAA';
  return a;
}
```

```bash
$ vim ./~zshrc
```

```python
s = "Python syntax highlighting"
print s
```

```
No language indicated, so no syntax highlighting. 
But let's throw in a tag.
```

<a href="https://www.google.co.kr/" target="_blank">GOOGLE</a>
.list > li {
  position: absolute;
  top: 40px;
}
function func() {
  var a = 'AAA';
  return a;
}
$ vim ./~zshrc
s = "Python syntax highlighting"
print s
No language indicated, so no syntax highlighting. 
But let's throw in a <b>tag</b>.

표(Table)

<table> 태그로 변환됩니다.
헤더 셀을 구분할 때 3개 이상의 -(hyphen/dash) 기호가 필요합니다.
헤더 셀을 구분하면서 :(Colons) 기호로 셀(열/칸) 안에 내용을 정렬할 수 있습니다.
가장 좌측과 가장 우측에 있는 |(vertical bar) 기호는 생략 가능합니다.

| 값 | 의미 | 기본값 |
|---|:---:|---:|
| `static` | 유형(기준) 없음 / 배치 불가능 | `static` |
| `relative` | 요소 자신을 기준으로 배치 |  |
| `absolute` | 위치 상 부모(조상)요소를 기준으로 배치 |  |
| `fixed` | 브라우저 창을 기준으로 배치 |  |

값 | 의미 | 기본값
---|:---:|---:
`static` | 유형(기준) 없음 / 배치 불가능 | `static`
`relative` | 요소 **자신**을 기준으로 배치 |
`absolute` | 위치 상 **_부모_(조상)요소**를 기준으로 배치 |
`fixed` | **브라우저 창**을 기준으로 배치 |
의미기본값
static유형(기준) 없음 / 배치 불가능static
relative요소 자신을 기준으로 배치
absolute위치 상 부모(조상)요소를 기준으로 배치
fixed브라우저 창을 기준으로 배치

인용문(BlockQuote)

<blockquote> 태그로 변환됩니다.

인용문(blockQuote)

> 남의 말이나 글에서 직접 또는 간접으로 따온 문장.
> _(네이버 국어 사전)_

BREAK!

> 인용문을 작성하세요!
>> 중첩된 인용문(nested blockquote)을 만들 수 있습니다.
>>> 중중첩된 인용문 1
>>> 중중첩된 인용문 2
>>> 중중첩된 인용문 3

인용문(blockQuote)

남의 말이나 글에서 직접 또는 간접으로 따온 문장.
(네이버 국어 사전)

BREAK!

인용문을 작성하세요!

중첩된 인용문(nested blockquote)을 만들 수 있습니다.

중중첩된 인용문 1
중중첩된 인용문 2
중중첩된 인용문 3

원시 HTML(Raw HTML)

마크다운 문법이 아닌 원시 HTML 문법을 사용할 수 있습니다.

<u>마크다운에서 지원하지 않는 기능</u>을 사용할 때 유용하며 대부분 잘 동작합니다.

<img width="150" src="http://www.gstatic.com/webp/gallery/4.jpg" alt="Prunus" title="A Wild Cherry (Prunus avium) in flower">

![Prunus](http://www.gstatic.com/webp/gallery/4.jpg)

마크다운에서 지원하지 않는 기능을 사용할 때 유용하며 대부분 잘 동작합니다.

Prunus

Prunus

수평선(Horizontal Rule)

각 기호를 3개 이상 입력하세요.

---
(Hyphens)

***
(Asterisks)

___
(Underscores)

(Hyphens)


(Asterisks)


(Underscores)

줄바꿈(Line Breaks)

동해물과 백두산이 마르고 닳도록 
하느님이 보우하사 우리나라 만세   <!--띄어쓰기 2번-->
무궁화 삼천리 화려 강산<br>
대한 사람 대한으로 길이 보전하세

동해물과 백두산이 마르고 닳도록
하느님이 보우하사 우리나라 만세
무궁화 삼천리 화려 강산
대한 사람 대한으로 길이 보전하세

일반 줄비꿈이 동작하지 않는 환경(설정 및 버전에 따라)의 경우, ‘2번의 띄어쓰기’나 <br>를 활용할 수 있습니다.


가상화


가상화는 전통적으로 하드웨어에 종속된 리소스를 사용해 유용한 IT 서비스를 만들 수 있는 기술입니다. 가상화를 사용하면 물리적 머신의 기능을 여러 사용자 또는 환경에 배포해 물리적 머신을 최대한 활용할 수 있습니다.

구체적인 예를 들자면, 각각 고유한 역할을 가진 3개의 물리 서버가 있다고 가정해 보세요. 하나는  메일 서버이고, 다른 하나는 웹 서버이고, 나머지 하나는  내부 레거시 애플리케이션을 실행하는 서버입니다. 각 서버는 잠재적인 실행 용량의 일부에 불과한 30% 용량만 사용되고 있습니다. 그러나 내부 운영을 위해서는 레거시 애플리케이션이 계속 필요하므로 레거시 애플리케이션과 이를 호스팅하는 세 번째 서버를 유지해야 합니다.

Server usage

전통적으로는 위의 이론을 따릅니다. 1개의 서버와 1개의 운영 체제, 1개의 태스크와 같이 개별 서버에서 개별 태스크를 실행하는 것이 더 쉽고 안정적인 경우가 많습니다. 1개의 서버로 여러 개의 태스크를 처리하기란 쉽지 않았습니다. 그러나, 가상화를 사용하면 메일 서버를 2개의 고유한 서버로 분할해 독립적인 태스크를 처리하고 레거시 애플리케이션을 마이그레이션할 수 있습니다. 마찬가지로, 하드웨어도 더 효율적으로 사용할 수 있습니다.

Server usage: virtualization

보안을 고려하여 첫 번째 서버를 다시 분할해 다른 태스크를 처리하면 사용률을 30%에서 60% 또는 90%까지도 높일 수 있습니다. 이렇게 하고 나면 이제 빈 서버를 재사용해 다른 태스크를 처리하거나 모든 서버를 사용 중지해 냉각 및 유지관리 비용을 줄일 수 있습니다.


간단하게 알아보는 가상화의 역사

가상화 기술은 1960년대부터 시작되었으나 널리 도입된 것은 2000년대 초입니다. 하이퍼바이저와 같은 가상화 지원 기술이 수십 년 전에 개발되어 일괄 처리를 수행하는 컴퓨터에 여러 명의 사용자가 동시에 액세스할 수 있게 되었습니다. 일괄 처리는 급여와 같은 루틴 태스크를 매우 빠르게 수천 번 실행하는 사업 부문에서 널리 사용되는 컴퓨팅 방식이었습니다

그러나 이후 몇십 년간 단일 머신에서 여러 사용자가 작업할 수 있게 하는 솔루션은 점차 인기를 얻은 데 비해, 가상화는 빛을 보지 못했습니다. 즉, 시간 공유 솔루션으로 인해 운영 체제 내에서   격리된 사용자는UNIX등의 운영 체제로 옮겨가게 되었고 결국 Linux®가 주류로 떠올랐습니다. 그러는 동안 가상화는 대중적인 인기를 얻지 못하는 틈새 기술로 남아 있었습니다.

이제 시간을 뛰어넘어 1990년대로 가 보겠습니다. 대부분의 기업이 물리 서버와 단일 벤더 IT 스택을 사용하고 있었기 때문에 다른 벤더의 하드웨어에서 레거시 애플리케이션을 실행할 수 없었습니다. 다양한 벤더가 제공하는 저렴한 상용 서버, 운영 체제, 애플리케이션으로 IT 환경을 업데이트하면서 기업은 사용률이 낮은 물리 하드웨어에 종속되었으며 각 서버에서 벤더별 태스크를 1개만 실행할 수 있었습니다.

가상화는 이러한 문제를 해결할 수 있었습니다. 이 2가지 문제에 대해 적절한 해결책을 제시하는 가상화를 통해 기업은 서버를 파티셔닝하고 여러 유형 및 버전의 운영 체제에서 레거시 애플리케이션을 실행할 수 있게 되었습니다. 서버를 더 효율적으로 사용하거나 아예 사용하지 않아도 되었으므로 구매, 셋업, 냉각, 유지관리와 관련된 비용이 줄어들었습니다.

또한 가상화는 광범위하게 적용할 수 있어 벤더 종속을 줄이고 클라우드 컴퓨팅의 기반을 다지는 데 도움이 되었습니다. 오늘날 가상화는 종종 전체 추적을 위해 전문적인  가상화 관리 소프트웨어가 필요한 기업들에서 사용되고 있습니다.


가상화는 어떻게 작동되나요?

하이퍼바이저라는 소프트웨어가 물리 리소스를 필요로 하는 가상 환경으로부터 물리 리소스를 분리합니다. 하이퍼바이저는 노트북 등의 운영 체제에 배포하거나 서버 등의 하드웨어에 직접 설치할 수 있으며 대부분의 기업들은 이러한 방식으로 가상화합니다. 하이퍼바이저가 물리 리소스를 가져와서 분배하면 가상 환경에서 그러한 리소스를 사용합니다.

How virtualization works

리소스는 필요에 따라 물리 환경에서 여러 가상 환경으로 파티셔닝됩니다. 사용자가 가상 환경(일반적으로 게스트 머신 또는 가상 머신이라고 함)과 상호 작용하고 가상 환경 내에서 계산을 실행합니다. 가상 머신은 단일 데이터 파일과 같이 기능합니다. 디지털 파일과 같이 한 컴퓨터에서 다른 컴퓨터로 이동할 수 있고 어느 쪽 컴퓨터에서든 열어 동일하게 작동할 수 있습니다.

가상 환경이 실행 중이고 사용자 또는 프로그램이 물리 환경에서 추가 리소스를 요구하는 명령을 내리면 하이퍼바이저가 그 요청을 물리 시스템에 전달하고 변경사항을 캐시합니다. 이 모든 작업은 네이티브 속도에 가깝게 이루어집니다(특히 요청이 커널 기반 가상 머신상에서 오픈소스 하이버파이저를 통해 전송되는 경우).


가상화 유형

데이터 가상화

여러 곳에 분산되어 있는 데이터를 단일 소스로 통합할 수 있습니다. 데이터 가상화를 통해 기업은 데이터를 동적 공급 요소로 다루어 여러 소스에서 데이터를 동시에 가져오고 새로운 데이터 소스를 손쉽게 통합하며 사용자의 요구에 따라 데이터를 변환할 수 있는 처리 역량을 확보할 수 있습니다. Red Hat® JBoss® Data Virtualization와 같은 데이터 가상화 툴은 여러 데이터 소스를 대표하여 이와 같이 다양한 데이터 소스가 단일 소스로 처리될 수 있도록 함으로써 필요한 데이터를 필요한 포맷으로 적시에 애플리케이션 또는 사용자에게 제공합니다.

데스크탑 가상화

데스크탑 가상화는 단일 머신에서 여러 운영 체제를 배포할 수 있는 운영 체제 가상화와 혼동하기 쉬우며, 시뮬레이션된 데스크탑 환경이 중앙 관리자 또는 자동화된 관리 툴을 통해 수백 개의 물리 머신에 동시 배포되도록 지원합니다. 각 머신에서 물리적으로 설치, 설정, 업데이트되는 전통적인 데스크탑 가상화 환경과 달리 데스크탑 가상화는 관리자가 모든 가상 데스크탑에서 설정, 업데이트, 보안 점검을 대규모로 수행할 수 있습니다.

서버 가상화

Server virtualization

서버는 대량의 특정 태스크를 매우 효과적으로 처리해 노트북 및 데스크탑 등의 다른 컴퓨터가 다양한 태스크를 처리할 수 있도록 하는 컴퓨터입니다. 서버를 가상화하면 서버가 이러한 특정 기능을 더 많이 수행할 수 있으며 서버 파티셔닝을 통해 구성 요소로 여러 기능을 지원할 수 있습니다.

운영 체제 가상화

Operating system virtualization

운영 체제 가상화는 운영 체제의 중앙 태스크 관리자인 커널에서 이루어집니다. 이렇게 하면 Linux 환경과 Windows 환경을 함께 실행할 수 있습니다. 또한 기업은 가상 운영 체제를 컴퓨터에 푸시해 다음과 같은 이점을 얻을 수 있습니다.

  • 컴퓨터에 고도의 OOTB(Out Of The Box) 기능이 필요하지 않으므로 하드웨어에 많은 비용이 소모되지 않습니다.
  • 모든 가상 인스턴스를 모니터링하고 격리할 수 있으므로 보안이 강화됩니다.
  • 소프트웨어 업데이트와 같은 IT 서비스에 소요되는 시간이 절약됩니다.

네트워크 기능 가상화

Network function virtualization

NFV(네트워크 기능 가상화)는 디렉터리 서비스, 파일 공유, IP 설정과 같은 네트워크의 주요 기능을 분리하여 이러한 기능을 환경에 배포합니다. 소프트웨어 기능이 속해 있는 물리 머신으로부터 기능을 분리하면 특정 기능을 새 네트워크에 함께 패키징하고 이를 환경에 할당할 수 있습니다. 네트워크를 가상화하면 스위치, 라우터, 서버, 케이블, 허브 등 여러 개의 독립적인 네트워크를 생성하는 데 필요하며 특히 통신 산업에서 일반적으로 사용되는 물리 구성 요소의 수가 감소합니다.


+ Recent posts