[Nodejs] インターフェース仕様とビジネス階層化

ここに画像の説明を挿入

1. インターフェース仕様 - RESTful アーキテクチャ


1.1 RESTとは

REST の正式名称は Representational State Transfer で、中国語で表現的状態転送を意味します。これは 2000 年に、HTTP 仕様の主な著者の 1 人である Roy Fielding の博士論文で初めて登場しました。同氏は論文の中で次のように述べている:「この記事を書いた目的は、アーキテクチャ原則に準拠することを前提として、ネットワークベースのアプリケーションソフトウェアのアーキテクチャ設計を理解し、評価し、強力で優れたパフォーマンスを獲得し、 「コミュニケーション。REST とは、一連のアーキテクチャ上の制約と原則を指します。」 アーキテクチャが REST の制約と原則に準拠している場合、それを RESTful アーキテクチャと呼びます。

REST 自体は新しいテクノロジ、コンポーネント、またはサービスを作成するものではありませんが、RESTful の背後にある考え方は、Web の既存の機能と機能を使用し、既存の Web 標準のガイドラインと制約をより適切に使用することです。REST 自体は Web テクノロジーの影響を深く受けており、理論的には REST アーキテクチャ スタイルは HTTP に拘束されませんが、現時点では HTTP が REST に関連する唯一のインスタンスです。したがって、ここで説明する REST も REST over HTTP です。

理解RESTful

RESTful アーキテクチャを理解するには、Representational State Transfer というフレーズの意味と、その各単語の意味を理解する必要があります。

以下では、REST 原則に基づいてリソースについて説明し、リソースの定義、取得、表現、関連付け、状態遷移の観点からいくつかの主要な概念を列挙して説明します。

  • リソースとURI
  • 均一なリソースインターフェイス
  • リソース表現
  • リソースへのリンク
  • 状態遷移

1.2 リソースと URI

RESTの正式名称はRepresentational State Transferですが、どのような表現を指すのでしょうか?実際にはリソースのことを指します。参照する必要があるものはすべてリソースです。リソースは、エンティティ (携帯電話番号など) または単なる抽象概念 (値など) にすることができます。リソースの例をいくつか示します。

  • ユーザーの携帯電話番号
  • ユーザーの個人情報
  • ほとんどのユーザーが注文した GPRS パッケージ
  • 2 つの製品間の依存関係
  • 特定のユーザーが扱える割引パッケージ
  • 携帯電話番号の潜在的な価値

リソースを識別できるようにするには一意の識別子が必要ですが、Web ではこの一意の識別子が URI (Uniform Resource Identifier) です。

URI は、リソースのアドレスとリソースの名前の両方とみなすことができます。一部の情報が URI で表されていない場合、それはリソースとみなされず、リソースの一部の情報にすぎません。URI の設計は、アドレス指定可能性の原則に従い、自己記述的である必要があり、人々に形式上の直感的な接続を提供する必要があります。ここでは、github Web サイトを例として、非常に優れた URI をいくつか示します。

URI 設計に関するヒントをいくつか見てみましょう。

  • URI を読みやすくするには、_ または - を使用します

Web 上の URI は以前はコールドな数字や意味のない文字列でしたが、現在では _ または - を使用して単語を区切る Web サイトが増えており、URI がより人間らしく見えるようになりました。たとえば、中国の有名なオープンソースの中国語コミュニティ ( oschinaなど) は、この形式のニュース アドレスを採用しています。

  • / を使用してリソースの階層関係を示します

たとえば、上記の /git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08 は、git ユーザーの git プロジェクトの特定の送信レコードを参照するマルチレベル リソースを表します。たとえば、/orders/2012/10 を使用して表すことができます。 2012 年 10 月の注文記録のリソース。

  • リソースをフィルタリングするには?を使用します

多くの人は ? を単なるパラメータ転送としか考えていないため、URI が複雑になりすぎて理解しにくくなる可能性があります。? を使用してリソースをフィルタリングできます。たとえば、/git/git/pulls は git プロジェクトのすべてのプッシュ リクエストを示すために使用され、/pulls?state=closed は git プロジェクトでクローズされたプッシュ リクエストを示すために使用されます。通常、URL はクエリ結果または特定の条件のアルゴリズム演算結果に対応します。

  • 、または ; を使用して、同じレベルのリソースの関係を示すことができます。

同じレベルでリソースの関係を表現する必要がある場合、 、 、 ; を使用して分割できることがあります。たとえば、いつか github で、おそらく /git/git/block-sha1/sha1 を使用して、ファイルの 2 つのランダムな送信レコードの違いを比較できるようになります。ただし、現在、github はこれを行うために ... (/git/git/compare/master...next など) を使用します。

1.3 統一リソースインターフェイス

RESTful アーキテクチャは、統合インターフェイスの原則に従う必要があります。統合インターフェイスには、限られた事前定義された操作セットが含まれています。リソースの種類に関係なく、リソースは同じインターフェイスを通じてアクセスされます。インターフェイスでは、GET、PUT、POST などの標準 HTTP メソッドを使用し、これらのメソッドのセマンティクスに従う必要があります。

リソースが HTTP メソッドのセマンティクスに従って公開される場合、インターフェイスにはセキュリティと冪等性の特性があります。たとえば、GET および HEAD リクエストは安全であり、リクエストが何度行われてもサーバーの状態は変化しません。かわった。ただし、GET、HEAD、PUT、および DELETE リクエストはすべて冪等であり、リソースが何回操作されても、結果は常に同じであり、後続のリクエストは最初のリクエスト以上の影響を及ぼしません。

GET、DELETE、PUT、POST の代表的な使用法を以下に示します。
① GET

  • 安全で冪等
  • 代表者を得る
  • 変更時の表現を取得 (キャッシュ)
  • 200 (OK) - 応答として送信されることを意味します
  • 204 (コンテンツなし) - リソースに空の表示があります
  • 301 (永久に移動) - リソースの URI が更新されました
  • 303 (その他を参照) - その他 (負荷分散など)
  • 304 (変更されていません) - リソースは変更されていません (キャッシュされています)
  • 400 (不正なリクエスト) - 不正なリクエストを指します (例: 間違ったパラメータ)
  • 404 (見つかりません) - リソースが存在しません
  • 406 (受け入れられません) - サーバーは必要な表現をサポートしていません
  • 500 (内部サーバーエラー) - 一般的なエラー応答
  • 503 (サービス利用不可) - サーバーは現在リクエストを処理できません

②ポスト

  • 安全ではなく冪等ではない
  • サーバー管理の (自動生成された) インスタンス番号を使用してリソースを作成する
  • サブリソースの作成
  • 部分的に更新されたリソース
  • 変更されていない場合、リソースは更新されません(楽観的ロック)
  • 200 (OK) - 既存のリソースが変更された場合
  • 201 (作成) - 新しいリソースが作成された場合
  • 202 (受理) - 処理要求は受理されましたが、まだ完了していません (非同期処理)
  • 301 (永久に移動) - リソースの URI が更新されました
  • 303 (その他を参照) - その他 (負荷分散など)
  • 400 (不正なリクエスト) - 不正なリクエストを指します
  • 404 (見つかりません) - リソースが存在しません
  • 406 (受け入れられません) - サーバーは必要な表現をサポートしていません
  • 409 (競合) - 一般的な競合
  • 412 (前提条件が失敗しました) - 前提条件が失敗しました (条件付き更新の実行時の競合など)
  • 415 (サポートされていないメディア タイプ) - 受信した表現はサポートされていません
  • 500 (内部サーバーエラー) - 一般的なエラー応答
  • 503 (サービスを利用できません) - サービスは現在リクエストを処理できません

③置く

  • 安全ではないがべき等
  • クライアントが管理するインスタンス番号でリソースを作成する
  • 置き換えによるリソースの更新
  • リソースが変更されていない場合は更新します (楽観的ロック)
  • 200 (OK) - 既存のリソースが変更された場合
  • 201 (作成) - 新しいリソースが作成された場合
  • 301 (永久に移動) - リソースの URI が変更されました
  • 303 (その他を参照) - その他 (負荷分散など)
  • 400 (不正なリクエスト) - 不正なリクエストを指します
  • 404 (見つかりません) - リソースが存在しません
  • 406 (受け入れられません) - サーバーは必要な表現をサポートしていません
  • 409 (競合) - 一般的な競合
  • 412 (前提条件が失敗しました) - 前提条件が失敗しました (条件付き更新の実行時の競合など)
  • 415 (サポートされていないメディア タイプ) - 受信した表現はサポートされていません
  • 500 (内部サーバーエラー) - 一般的なエラー応答
  • 503 (サービスを利用できません) - サービスは現在リクエストを処理できません

④削除

  • 安全ではないがべき等
  • リソースを削除する
  • 200 (OK) - リソースが削除されました
  • 301 (永久に移動) - リソースの URI が変更されました
  • 303 (その他を参照) - その他、負荷分散など
  • 400 (不正なリクエスト) - 不正なリクエストを指します
  • 404 (見つかりません) - リソースが存在しません
  • 409 (競合) - 一般的な競合
  • 500 (内部サーバーエラー) - 一般的なエラー応答
  • 503 (サービス利用不可) - サーバーは現在リクエストを処理できません

実際によくある問題をいくつか見てみましょう。

  • リソースの作成に使用される場合の POST と PUT の違いは何ですか?

リソース作成における POST と PUT の違いは、作成するリソースの名前 (URI) がクライアントによって決定されるかどうかです。たとえば、ブログ投稿に Java カテゴリを追加するには、生成されるパスがカテゴリ名/カテゴリ/java である場合、PUT メソッドを使用できます。ただし、多くの人は、たとえば、Rails によって実装された典型的な RESTful アプリケーションで、POST、GET、PUT、および DELETE を CRUD に直接マッピングします。

これは、Rails はデフォルトでサーバーが生成した ID を URI として使用しており、多くの人が Rails 経由で REST を実践しているため、この誤解が生じやすいためだと思います。

  • クライアントは必ずしもこれらの HTTP メソッドをサポートしているわけではありませんか?

これは、特に GET メソッドと POST メソッドしかサポートできない一部の古いブラウザベースのクライアントに当てはまります。

実際には、クライアントとサーバーの両方である程度の妥協が必要になる場合があります。たとえば、rails フレームワークは、パラメーター _method=DELETE を非表示にすることで実際のリクエスト メソッドの受け渡しをサポートしますが、Backbone などのクライアント側 MVC フレームワークでは、_method 送信を渡し、X-HTTP-Method-Override ヘッダーを設定してこの問題を回避できます。

  • インターフェースが統一されているということは、特別なセマンティクスを持つメソッドを拡張できないということですか?

メソッドがリソース操作に対して特定の識別可能なセマンティクスを持ち、インターフェイス全体の均一性を維持できる限り、統合インターフェイスによってメソッドの拡張が妨げられることはありません。

たとえば、WebDAV は HTTP メソッドを拡張し、LOCK や UPLOCK などのメソッドを追加しました。github API は、問題を更新するための PATCH メソッドの使用をサポートしています。次に例を示します。

PATCH /repos/:owner/:repo/issues/:number
ただし、PATCH は標準の HTTP メソッドではないため、サーバーはクライアントがそれをサポートできるかどうかを考慮する必要があることに注意してください。

  • URI にとって、Uniform Resource Interface は何を意味しますか?

統一リソース インターフェイスでは、リソースを操作するために標準の HTTP メソッドを使用する必要があるため、URI はリソースの操作ではなく、リソースの名前のみを表す必要があります。

平たく言えば、URI はアクションを使用して記述すべきではありません。たとえば、統合インターフェイスの要件を満たさない URI をいくつか示します。 |
-- GET /getUser/1
|-- POST /createUser
|-- PUT /updateUser/1
|-- DELETE /deleteUser/1

GET リクエストによってカウンターが増加するとセキュリティ違反になりますか?

セキュリティとは、リクエストに副作用がないことを意味するものではなく、たとえば、多くの API 開発プラットフォームではリクエストのトラフィックが制限されています。github と同様、認証なしのリクエストは 1 時間あたり 60 リクエストに制限されます。

ただし、クライアントは副作用を目的としてこれらの GET または HEAD リクエストを発行するのではなく、副作用はサーバーによって「自己評価」されます。

さらに、サーバーの設計時には、クライアントはこれらのリクエストが副作用を引き起こさないと考えるため、副作用が大きくなりすぎないようにする必要があります。

  • キャッシュを直接無視した方がよいでしょうか?

動詞を意図どおりに使用した場合でも、キャッシュ メカニズムを簡単に無効にすることができます。これを行う最も簡単な方法は、HTTP 応答にヘッダー Cache-control: no-cache を追加することです。ただし、同時に、効率的なキャッシュと再検証 (Etag などのメカニズムを使用) のサポートも失われます。

クライアントの場合、RESTful サービスのプログラム クライアントを実装するときは、表現を毎回再フェッチすることを避けるために、既存のキャッシュ メカニズムも最大限に活用する必要があります。

  • レスポンスコードの処理は必要ですか?

HTTP 応答コードはさまざまな状況で使用でき、これらのステータス コードを正しく使用することは、クライアントとサーバーがより豊富なセマンティクスのレベルで通信できることを意味します。

たとえば、201 (「Created」) 応答コードは、新しいリソースが作成され、その URI が Location 応答ヘッダーに含まれていることを示します。

HTTP ステータス コードの豊富なアプリケーション セマンティクスを活用しなければ、再利用性、相互運用性、疎結合を改善する機会を逃すことになります。

これらのいわゆる RESTful アプリケーションがエラー情報を提供するために応答エンティティを渡す必要がある場合、SOAP は次のようになり、これを満たすことができます。

1.4 リソースの表現

上で述べたように、クライアントは HTTP メソッドを通じてリソースを取得できますよね? いいえ、正確に言えば、クライアントが取得するのはリソースの表現にすぎません。外界におけるリソースの特定の表現は、複数の表現形式 (または表現、表現) を持つことができ、クライアントとサーバーの間で送信されるものも、リソースそのものではなくリソースの表現です。たとえば、テキスト リソースは html、xml、json などの形式で、画像は PNG または JPG で表示できます。

リソースの表現は、データと、そのデータを記述するメタデータで構成されます。たとえば、HTTP ヘッダーの「Content-Type」は、そのようなメタデータ属性の 1 つです。

では、クライアントはサーバーが提供する表現形式をどのようにして知るのでしょうか?

その答えは、HTTP コンテンツ ネゴシエーションを通じて、クライアントは Accept ヘッダーを通じて特定の形式での表現を要求でき、サーバーは Content-Type を通じてクライアントにリソースの表現を伝えることができるということです。

github を例として、組織のリソースをリクエストする json 形式:
画像-20221108163406411
① URI にバージョン番号を含める
実際の一般的な設計をいくつか見てみましょう。いくつかの API には、URI にバージョン番号が含まれています。

http://api.example.com/1.0/foo
http://api.example.com/1.2/foo
http://api.example.com/2.0/foo

バージョン番号をリソースのさまざまな表現として理解する場合は、1 つの URL を使用し、それを Accept ヘッダーで区別するだけで済みます。github を例にとると、その Accept の完全な形式は次のとおりです。application/vnd.github[.version].param[+json]

v3 バージョンの場合は、Accept: application/vnd.github.v3 です。上記の例では、次のヘッダーを同じ方法で使用できます。

Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.2
Accept: vnd.example-com.foo+json; version=2.0

②URI 接尾辞を使用して表現形式を区別する
Rails フレームワークと同様に、異なる形式を区別するために /users.xml または /users.json の使用をサポートしています。この方法は間違いなくクライアントにとってより直観的ですが、リソースの名前とリソースの表現形式が混乱します。個人的には、コンテンツ ネゴシエーションを最初に使用してプレゼンテーション形式を区別する必要があると考えています。

③ サポートされていない表現形式への対処方法
要求された表現形式をサーバーがサポートしていない場合、どうすればよいでしょうか。サーバーがこれをサポートしていない場合は、HTTP 406 応答を返し、要求の処理を拒否する必要があります。以下では、例として github を使用して、XML 表現リソースをリクエストした結果を示しています。

画像-20221108163547723

1.5 リソースへのリンク

REST が標準の HTTP メソッドを使用してリソースを操作することはわかっていますが、それを CURD を使用した Web データベース アーキテクチャとして理解するには単純すぎます。

このアンチパターンは、「アプリケーション状態のエンジンとしてのハイパーメディア」という中心的な概念を無視しています。ハイパーメディアとは何ですか?

Web ページを閲覧するときに、ある接続からあるページにジャンプし、さらに別の接続から別のページにジャンプすることは、リソースを 1 つずつリンクするというハイパーメディアの概念を使用することになります。

この目標を達成するには、クライアントをガイドするためのリンクを式形式で追加する必要があります。『RESTful Web Services』という本では、著者はこのリンク機能を接続性と呼んでいます。いくつかの例を詳しく見てみましょう。

以下は組織配下のプロジェクト一覧を取得するgithubのリクエストですが、レスポンスヘッダーに次のページと最後のページのレコードへのアクセス方法をクライアントに伝えるLinkヘッダーが追加されていることがわかります。応答本文で、URL を使用してプロジェクト所有者とプロジェクト アドレスをリンクします。

画像-20221108163612111

別の例は、注文を作成した後、リンクを介して支払い方法をクライアントに案内する次の例です。

画像-20221108163626264

上の例は、ハイパーメディアを使用してリソースの接続を強化する方法を示しています。多くの人は、RESTful アーキテクチャを設計するときに、ハイパーメディアを無視して、美しい URI を探すことに多くの時間を費やします。したがって、「リソースの CRUD」に焦点を当てるのではなく、リソース表現へのリンクを提供することにもっと時間を費やす必要があります。

1.6 状態遷移

上記の伏線があると、REST での状態遷移を議論するときに理解しやすくなります。

ただし、最初に REST 原則におけるステートレス通信の原則について説明しましょう。一見矛盾しているように見えますが、国家がないのに、国家の移転についてどうやって語ることができるのでしょうか。

実際、ここで述べたステートレス通信の原則は、クライアント アプリケーションが状態を持つことができないという意味ではなく、サーバーがクライアントの状態を保存すべきではないという意味です。

① アプリケーションの状態とリソースの状態
実際には、アプリケーションの状態とリソースの状態は区別されるべきであり、クライアントはアプリケーションの状態を維持する責任を負い、サーバーはリソースの状態を維持します。

クライアントとサーバー間の対話はステートレスである必要があり、各リクエストにはリクエストの処理に必要なすべての情報が含まれている必要があります。

サーバーはリクエスト間でアプリケーションの状態を保持する必要はなく、実際のリクエストを受信したときにのみアプリケーションの状態に注意を払います。

このステートレスな通信原理により、サーバーと仲介者は独立したリクエストと応答を理解できるようになります。

複数のリクエストにおいて、同じクライアントが同じサーバーに依存する必要がなくなり、拡張性と可用性の高いサーバーの実現が容易になります。

ただし、Cookie を使用して特定のサーバー (J2EE の JSESSIONID など) のセッション状態を追跡するなど、ステートレス通信の原則に違反する設計を行う場合があります。

これは、各リクエストとともにブラウザーによって送信される Cookie がセッション状態の構築に使用されることを意味します。

もちろん、サーバーがセッション状態に依存せずに検証できる情報 (認証トークンなど) が Cookie に保存されている場合、そのような Cookie は REST 原則にも準拠します。

② アプリケーションの状態の転送
ここでの状態の転送はよく理解されており、「セッション」の状態はリソースの状態としてサーバーに保存されるのではなく、アプリケーションの状態としてクライアントによって追跡されます。クライアント アプリケーションの状態は、サーバーが提供するハイパーメディアのガイダンスに従って変化します。サーバーは、ハイパーメディアを通じて、その後のどの状態が現在の状態に入ることができるかをクライアントに伝えます。

「次のページ」などのリンクは、現在の状態から次の可能な状態に移行する方法を案内する、この進行状態の役割を果たします。

2. ビジネスの階層化


画像-20220620210337550

モデル フォルダーにはデータベース モデルが保存されるため、M レイヤーはサービス フォルダーで置き換えることができます。

3. ビジネス階層のプレゼンテーション


3.1 オリジナルコードの書き方

画像-20221108162718013
config/db.config.js

const mongoose = require('mongoose')

mongoose.connect('mongodb://127.0.0.1:27017/ds')
//插入集合和数据,数据库ds2会自动创建

// 监听mongodb数据库的连接状态
// 绑定数据库连接成功事件
mongoose.connection.once('open', function () {
    
    
  console.log('连接成功')
})
// 绑定数据库连接失败事件
mongoose.connection.once('close', function () {
    
    
  console.log('数据库连接已经断开')
})

model/UserModel.js

const mongoose = require('mongoose')

const userType = new mongoose.Schema({
    
    
  username: String,
  password: String,
  age: Number,
})

const UserModel = mongoose.model('UserModel', userType, 'users')

module.exports = UserModel

app.js

var express = require('express');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

app.use('/', indexRouter);
app.use('/users', usersRouter);

module.exports = app;

routes/user.js

var express = require('express')
var router = express.Router()
const UserModel = require('../model/UserModel')

// 获取用户
router.get('/', (req, res) => {
    
    
  const {
    
     page, limit } = req.query
  UserModel
    .find({
    
     $where: 'obj.username !== ""' })
    .sort({
    
    
      age: -1,
      name: -1,
    })
    .then(data => res.send(data))
})

// 添加用户
router.post('/', function (req, res, next) {
    
    
  const {
    
     username, password, age } = req.body
  new UserModel({
    
     username, password, age }).save((err, docs) => {
    
    
     res.send({
    
    
       code: 200,
       data: {
    
    
         id: docs._id,
       },
     })
   })
})

// 修改用户
router.put('/:id', function (req, res, next) {
    
    
  const {
    
     username, password, age } = req.body
  UserModel
    .updateOne(
      {
    
    
        _id: req.params.id,
      },
      {
    
    
        username: '更新',
      },
    )
    .then(data => {
    
    
      res.send({
    
    
        ok: 1,
      })
    })
})

// 删除用户
router.delete('/:id', function (req, res, next) {
    
    
  const {
    
     username, password, age } = req.body
  UserModel
    .deleteOne({
    
    
      _id: req.params.id,
    })
    .then(data => {
    
    
      res.send({
    
    
        ok: 1,
      })
    })
})
module.exports = router

3.2 ビジネス階層化を使用してコードを変更する

画像-20221108162652289

config/db.config.js、model/UserModel.js、app.js は変更なし

router/user.js

var express = require('express')
var router = express.Router()
const userController = require('../controllers/userController')

router.get('/', userController.getUser)

router.post('/', userController.addUser)

router.put('/:id', userController.updateUser)

router.delete('/:id', userController.deleteUser)

module.exports = router

controllers/userController.js

const userService = require('../services/userService')

const userController = {
    
    
  async getUser(req, res, next) {
    
    
    const {
    
     page, limit } = req.query
    let data = await userService.getUser(page, limit)
    res.send(data)
  },
  async addUser(req, res, next) {
    
    
    const {
    
     username, password, age } = req.body
    let data = await userService.addUser({
    
     username, password, age })
    res.send(data)
  },
  async updateUser(req, res, next) {
    
    
    let data = await userService.updateUser(req.params.id)
    res.send(data)
  },
  async deleteUser(req, res, next) {
    
    
    let data = await userService.deleteUser(req.params.id)
    res.send(data)
  },
}

module.exports = userController

services/userService.js

const userModel = require('../model/userModel')

const userService = {
    
    
  getUser(page, limit) {
    
    
    return userModel
      .find({
    
    }, {
    
     _id: 0 })
      .sort({
    
    
        age: -1,
      })
      .skip((page - 1) * limit)
      .limit(limit)
  },
  addUser({
     
      username, password, age }) {
    
    
    return userModel.create({
    
    
      username,
      password,
      age,
    })
  },
  updateUser(_id) {
    
    
    return userModel.updateOne(
      {
    
    
        _id,
      },
      {
    
    
        username: '更新',
      },
    )
  },
  deleteUser(_id) {
    
    
    return userModel.deleteOne({
    
    
      _id,
    })
  },
}

module.exports = userService

おすすめ

転載: blog.csdn.net/weixin_43094619/article/details/131932202