電話:
cargo run
リポジトリルートから、準備ができています。
また、APIドキュメントを探索して、すべてがどこに住んでいるのかを知りましょう。
cargo doc --open
このリポジトリには、オンラインストアのサンプルラストアプリケーションが含まれています。目標は、Rust言語を活用してスケーラブルで保守可能なアプリケーションを構築するいくつかの設計パターンを探索することです。
それはさまざまなアイデアの遊び場であり、それらのいくつかは実際にはパンアウトしないかもしれません。ここに何かについてのフィードバックがある場合は、お気軽に問題を開いてください!
ソフトウェアを真空に設計することは困難です。重要なものを駆動する実際のドメインがない場合、設計上の決定はarbitrary意的に感じることができます。私は決定とその背後にある理由を文書化する努力をしましたが、注文項目を注文から分割すべきかのような質問?または、注文のクエリが製品のデータベーステーブルにアクセスできるようにする必要がありますか?純粋に技術的な観点からは本当に答えることはできません。彼らはプロジェクトの目標についても視点を必要とします。このコードを読んでいる人なら誰でも、それらの任意の設計上の決定に基づいてそれを精査することをお勧めします。あなたがあなた自身の環境で直面している制約と、錆でアプリケーションを構築するときにあなた自身の決定をどのように通知するかを考えます。
特定の錆フレームワークやライブラリや、オンラインショッピングアプリケーションに固有の問題を解決することではありません。
次のセクションでは、アプリケーションの一部について説明し、なぜそれらがそれらの状態にまとめられているのかを説明します。
プロジェクトレイアウトは、プライバシーに焦点を当てています。特定のアイテムの範囲を制限することにより、潜在的な破損の範囲も制限します。特定のアイテムの範囲を制限することにより、アプリケーション状態を維持する負担の範囲も制限します。さびにおいて、モジュール内のプライベートなアイテムは、そのモジュールのすべての子供に表示されます。それは悪いことのように聞こえるかもしれませんが、それを活用して、ドメインAPIがシリアル化やストレージなどの外部の懸念のために実装の詳細を漏らしないようにします。
アプリケーションの各コアビジネスコンセプトは、 productsやcustomersなどの独自の(ほとんど)自己完結型フォルダーに分割されます。各モジュールは、特定のエンティティセットについて知る必要があるすべてのものをカプセル化します。
/store )/queries )/commands )エンティティは、製品を追加する際のProductに応じてOrderなど、別のモジュールのエンティティに依存できます。各ドメインモジュールにはプライバシー階層があります。
from_dataメソッドを持っていますfrom_dataに依存してエンティティを水補給しますこれらのモジュールは少し重量ですが、適切なアプリケーションでは、新しいドメインモジュールを追加するとマクロを使用して簡素化できます。このアプリケーションでマクロを使用していないため、コードは簡単に従うことができます。
完全に作成されたモジュール階層の1つの問題は、現在のレイアウトに単に収まらない概念になったときにすべてバラバラになる可能性があることです。これが頻繁に発生するほど、それがどうあるべきかを伝えることが不可能になるため、以前に存在したレイアウトに準拠することがより困難になります。
これらのモジュールに独自の運命を管理してもらいたいのですが、個別のサービスに分割できるように自己完結させたくありません。これは、物事をシンプルに保つためです。これをやりたい場合は、個別のモジュールではなく、個別の木枠を使用することをお勧めします。
アプリケーションは、単純なコマンドクエリ責任の分離設計に従います。これは、多くの複雑なロジックなしでデータ駆動型アプリケーションにうまく機能するアプローチです。コマンドはいくつかのドメインの相互作用をキャプチャし、エンティティで直接動作しますが、クエリは完全にarbitrary意的です。このアプリケーションは、CQRを実現するための特別なインフラストラクチャを使用していません。これらは、依存関係噴射パターンを使用して実装された単純な特性です。基本的に:
Result<()>Result<T>を返します&mut selfレシーバーが必要です&selfレシーバーが必要です可変性の違いは、コマンドがクエリを呼び出すことができることを意味しますが、クエリはコマンドを呼び出すことはできません。
エンティティはアプリケーションの中心です。実際のビジネスが不足しているにもかかわらず、私はドメインモデルを豊かに保つために努力しました。エンティティは、Cruddy Stateの袋だけではありません。彼らです:
.to_data()を呼び出すことにより、エンティティの読み取り専用ビューを取得できます。エンティティを表示している間、動作を変更することはできません。これは、Rustの借入システムによって保証されています。エンティティは.into_data()を使用して、所有権を読み取り専用データに移動できます。これは一方向の操作であるため、州に加えられた変更は店に戻ることはできません。エンティティの目標は、いくつかの重要なドメイン概念の不変剤をカプセル化することです。ここのエンティティは、モックインメモリストアまたは外部データベースのいずれかで使いやすいです。あるエンティティは、同じソースを指しているため、あるエンティティが別のエンティティに反映されている状態の変更に依存しないように注意する必要があります。
また、データが実際に有効であるという保証がないため、エンティティは別のエンティティのデータ型に依存しないように注意する必要があります。代わりに、彼らはエンティティに依存し、必要に応じてデータに変換するため、状態が有効であることを常に知っています。
エンティティの状態を保護するために、次の錆機能を使用します。
SerializeまたはDeserialize実装しません。これはトラックに変更される可能性がありますが、逆方向の互換性のためにシリアル化可能な状態を迅速にゆっくりと維持する方が簡単です。エンティティは、一部の状態またはデータをカプセル化し、そのデータに加えられた変更が、データが保持すると予想される不変性を破らないようにします。ゲッターを実装するのではなく、データの読み取り専用ビューを構造として公開します。利点は、Getter Methodのように、DataStructuresを使用するためのRustの素晴らしい機能を放棄する必要がないことです。このビューは読み取り専用であるため、変更を構造に直接書き戻すことはできません。エンティティは、そのためのセッターメソッドを引き続き提供しています。
このように状態を公開することは、公開されていないversionのように、実装の詳細をリークすると主張することができます。これはおそらく真実です。それを回避するために、読み取り専用ビューの寿命をフィールドに移動し、潜在的に異なる国家のビューを構成し、エンティティによって管理されているデータ構造をプライベートに保つことができます。
また、それらを保管していない構造に不変剤を保持することは脆弱であると主張することもできます。これは、C#のように、一部のフィールドのプライバシー境界がオブジェクトレベルにある場合に理にかなっています。錆は少し違っています。最も厳しいプライバシーの境界は、モジュールとその子供にあります。したがって、特定のフィールドの不変性を維持する負担は、定義されているモジュールのすべてのアイテムに加えて、そのモジュールのすべての子供に該当します。
これはひどいリークのように聞こえるかもしれませんが、このアプリケーションはそれを活用して、抽象化されたストレージを構築します。 ORMをサポートするためにAPIに穴を開ける必要があるのではなく、不変剤の状態を維持することは、単にモデルストアに広がっています。
IdとVersionのタイプには、両方ともPhantom Genericパラメーターがあります。このパラメーターは、 Id<ProductData>やId<OrderData>などの互換性のないタイプのIDを表現できるように純粋に存在しますが、他の実装の詳細を共有しています。
これは、マクロを使用してボイラープレートを削減するよりも、従うのが簡単なパターンです。
各永続性エンティティにはversionフィールドがあります。このフィールドは、特定の時点でエンティティの状態に対応する非シーケンシャル識別子です。エンティティがストアから取得されると、バージョンを水分補給します。これは、更新する直前にチェックされ、それらが一致しない場合は、私たちはalkします。
バージョンのチェックは、データに独占的なロックがあるため、メモリストアでは正常に機能します(一度に状態を変更できるのは1つの発信者のみ)が、適切なDBには別のアプローチが必要です。おそらく、IDとバージョンが一致する場所を更新し、更新されたレコードの数を選択し、0の場合はbalk(バージョンが一致しなかった、または存在しないことを意味します)。
ストレージレイヤーは、独立したデータストアがトランザクションに参加できるようにする簡単なトランザクションスキームを使用します。中央リポジトリは、アクティブなトランザクションを追跡し、データがデータストアから取得されたときにコンサルティングを行い、使用する準備ができていることを確認します。データの楽観的な同時性により、複数のアクティブトランザクションが同時に同じ値を設定することを試みることができないことが保証されます。これは真の隔離に違反しますが、物事をシンプルに保ち、保存される各値に必要な状態を最小限に抑えることができます。
依存関係は、アプリケーションを設計する際に頼る慣行として有益です。依存関係解決の懸念をAppロジックから分離することができます。また、アプリケーションをスケーリングする明白な方法も提供します。このアプリケーションは、多くのインフラストラクチャなしでこれらの利点を提供する単純なパターンを採用しています。
このアプリケーションは、.NETアプリケーションを記述した場合に慣れているような制御コンテナの反転を使用しません。これは、主に錆のために実際にはないためです。それは難しい問題です。洗練されたコンテナがなくても、コマンドとクエリを構成するために、単純な依存噴射パターンを利用します。
ここでの依存噴射の主な目標は、モッキングをサポートすることではありません。個々のコンポーネントの論理から遠く離れて周辺の懸念を押し上げることにより、複雑さを減らすことです。
注射可能なコンポーネントは独自のモジュールに住んでいます。そのモジュールには:
Resolverタイプのインプルブロック。impl Traitを返す機能。このデフォルトの実装が使用する具体的なタイプはわかりません。Arc 、 Boxなどのいくつかのスマートポインター用に実装されたブランケットであるコンポーネントを記述する特性。共有Resolver少しサービスロケーター-Yに聞こえますが、それはそうですが、依存関係の解像度は、 Resolver自体のインプルブロックに完全に含まれているため、アプリロジックの魔法のグローバル状態に依存するという問題を回避します。
ボイラープレートを減らすために、単一の方法のみを備えたコンポーネントの場合、 Fn特性のためにそれらをブランケット実装します。これにより、すべての依存関係よりも一般的な構造を宣言することを避けることができます。 Rust Compilerがあなたのためにそれの世話をします。
このパターンは散文で説明するのが難しいので、それを見る必要があります。職場でのこの依存関係注入パターンの例についてはdomain/products/commands/create_productモジュール、またはdomain/products/model/storeモジュールをご覧ください。
Resolver 「神のオブジェクト」ではありませんか?神のオブジェクトは、すべての重要なロジックを、神のオブジェクトを介して作業することなくコンポーネントを操作できないポイントにすべての重要な論理を収集するオブジェクトです。これらは、構築または変更が困難になるため問題です。ここのResolverパターンは、個々のコンポーネントであるが、 Resolverコンポーネントを扱うだけで、コンポーネントを作業する必要はありません。