次のモデルがあるとします。
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
今、私は本当に人々が私の製品のカタログに追加し、表示できるようにしたいと思います。そこで、次のコントローラーを作成します。
注:静的リストに気を取られないでください。私はそれが間違っていることを知っています。
public class ProductsController : ApiController
{
static readonly List<Product> products = new List<Product>();
// ...
[HttpPost]
public void AddProduct(Product value)
{
products.Add(product);
}
}
お気づきかもしれませんが、私は現在、カスタムId
値を使用して新製品を追加することを許可しています。これは私には正しくないと思います。私は自分の製品の識別子を管理する必要がありますよね?私は人々がそれらを設定できるという考えを人々に与えたくありません。
私が試みた解決策は、製品モデルのDTOを作成し、次に AutoMapper パッケージをインストールすることでした。
DTOは次のとおりです。
public class ProductDto
{
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
そして、これが新しいコントローラーです:
public class ProductsController : ApiController
{
static readonly List<Product> products = new List<Product>();
// ...
[HttpPost]
public void AddProduct(ProductDto value)
{
Product product = Mapper.Map<Product>(value);
product.Id = Guid.NewGuid().ToString();
products.Add(product);
}
}
これは妥当ですか?より単純で、より標準的なアプローチはありますか?
実際...当時(そして今日でも)、1つの単純な理由でクライアント側でIDを生成できないようにすることは非常に一般的でした。IDは自動インクリメントされた整数値であり、データベースエンジンによって生成されていました。
しかし、このアプローチには問題があります:
人々は、IDの難読化を使用したり、システム内のリソースを識別する別の方法を見つけたりして、2番目の問題を修正しようとしましたが、最近では、代わりに Guids/Uuids を使用することがますます一般的になっています。
しかし、明らかな質問が来ました:IDがデータベースエンジンによって生成されなくなり、バックエンドに関連付けられ、Uuids V4が実質的に一意である場合(リスク通常のサイズのプロジェクトでは衝突はほとんどありません)、なぜそれらを生成するためにサーバーが必要なのですか?
そして答えは:そうではありません。
正直なところ、システム全体がオンラインで実行されていてサーバーなしでは機能しない場合は、Uuidを生成するサーバーがまだあるので問題ありません。しかし、RESTクロスプラットフォーム同期用のAPIに裏打ちされた単一ページおよびモバイルアプリケーションは、ますます人気が高まっています。クライアントにUuidを生成させることで、それらを即座に使用できるようになります(クロスリソース参照)は、アプリケーションがオフラインのときでも非常に使いやすくします。
通常のIDを使用してオフラインで動作するアプリケーションを作成し、カスタムローカルIDを効果的に生成してサーバー上のIDと同期できることに反対するかもしれません。 Uuidを使用すると、IDが同じままになるため、この同期とマッピングを完全にスキップして、ローカルをサーバー上にあるかのように扱うことができます。
したがって、リソースIDを制御する必要があると考えても、そうする必要はありません。
ここにカップルポイント:
独自の内部一意識別子を所有している必要があります。これは、追加または表示のためにAPIを介して公開しないでください。識別子は、データベースがキー関係を作成するためのものです。製品を追加するリクエストが受信されると、識別子はシステムによって生成されます(たとえば、IDENTITY
列を使用して)。
エンドユーザーは、CompanyID(おそらくサインオンコンテキストから派生したもの)を持つ製品を送信できる必要がありますplus SKUやUPC code。これはそれらの唯一の参照番号である必要があります。これらの識別子がその企業ID内の他の製品と衝突しないことを確認する責任があります。システムの観点から、このフィールドはExternalProductID
と呼ばれ、CompanyIDと組み合わせた場合にのみ一意のキーを形成します。
私があなただったら、私は製品に価格を含めません。価格は、製品のオファーのプロパティです。製品は永遠ですが、オファーは出入りします(価格が変更された場合)。このようにしないと、更新や製品IDの再利用に問題が発生します。
ここでは、製品IDは実際にはユーザーが使用する必要はなく、APIで使用される内部的なものであるため、それが何であるかは問題ではないと仮定します。
1)シーケンスまたは通常の方法で製品IDを作成します。クライアントにIDを選択させないでください。
2)何らかの理由でカタログまたは製品IDを公開する場合は常に、ユーザーのログインID +塩または他のキーを使用して、製品IDを暗号化(ハッシュしない)します。クライアントがセッション間で製品IDを参照する必要がない場合は、セッションIDを使用できます。
3)誰かが新しい製品を追加したら、それをDBに追加し、シーケンスを使用して新しいIDを作成します。暗号化されたものをクライアントに戻し、継続して使用します。
4)クライアントから製品IDを受け取ったら、必要に応じて復号化します。
5)内部では、IDキーのみを使用します。
これの利点は、
a)再度選択できない一意のキーがあります。
b)クライアントは事実上鍵を推測できない
c)誰かが他の誰かのバージョンを発見したとしても、それは彼らのログイン(またはあなたが決めることに応じてセッション)の外では機能しません。
d)バックエンドに「通常の」データベースがある。
これをかなり長い間考えていたので、私はユーザー@Andyと同じ結論に達しました。
Products APIが少なくとも認証され、承認されることを前提としています。これにより、不正なクライアントがデータベースにランダムな製品オブジェクトを作成するのを防ぐことができます。
ここでの問題は、サーバー側でIDを生成するタスクを処理する必要があるかどうかです。私は先に進み、それが必要ではないと主張します。
IDは(おそらく)データベースの主キーになるため、おそらくクライアントにIDを返す必要があります。クライアントは、作成した製品についてクエリを実行する場合があり、製品名を使用してデータベース全体をDigすると、パフォーマンスが低下する可能性があります。 IDをクライアントに送り返すので、IDに生成させることもできます。
一方、DTOパターンでは、実際に同じことを行うために、個別のクラスを維持する必要があります。これは、オブジェクト指向プログラミングのすべての直観に反します。
クライアントがデータベースの行数を推測できないようにするには、 [〜#〜] guid [〜#〜] を使用して製品オブジェクトを識別することをお勧めします。 GUIDはほとんどの目的でかなり一意であり、ほとんどの言語から生成できます。