乳母レベルのチュートリアル!Golang マイクロサービスのシンプルなアーキテクチャの実践

はじめに | この記事は、簡潔なアーキテクチャの理論から始まり、trpc-go ディレクトリ仕様に依存し、コード アーキテクチャ全体の分割方法、trpc-go サービス コードの具体的な実装の詳細、および実装手順を簡単に説明し、議論します。 DDDとの違い。この記事は、私たちのグループが始めた go マイクロサービスのベスト プラクティスの最初の部分から作成されました.開発と読み取り学習から一連の go マイクロサービス開発方法論を要約し、それらを求める過程での思考と思考を互いに共有したいと考えています.ベストプラクティス トレードオフのプロセス。今回は主にディレクトリの編成方法について説明します. ディレクトリの編成は実際にはアーキテクチャの設計です. 一連の一般的なアーキテクチャの設計により, 開発者は特定のシナリオのロジック設計とコード設計に集中できます. 標準的な操作は.シンプルで、状況に応じて段階的に実装でき、操作性が高い。

序章

効率的な go 言語、成熟した trpc-go フレームワーク、および一連のミドルエンド SDK とパブリッシング プラットフォームにより、初心者でもチュートリアルを通じて簡単な機能を備えたマイクロサービスをすばやく作成し、go でマイクロサービス開発を開始することができます。ほとんどの開発ニーズを処理します。

しかし、いったん開始すると、需要が増加するにつれて、コードの保守、既存のロジックの変更、継続的な抽象化、および一般的に使用される機能のスケーラビリティの改善に多くの時間を費やす必要があることがわかります.同じマイクロサービス コードを作成することは、抽象スタイルが異なるだけでなく、抽象標準、モジュール分割、データ フロー、階層化されたロジックなどによって、ますます難しくなっています。異なる表現。

さまざまな形式のコード ベースは私たちが望んでいるものではありません.コード アーキテクチャの可読性,スケーラビリティ,保守性を維持したい. このように、コードの詳細 (コード標準) の一貫性に加えて、これにより、開発は特定のシナリオのロジック設計とコード設計に集中し、サービス関連のコンテンツを膨大な量の知識を使用して適切に処理できます。各サービスのアーキテクチャが簡潔明瞭であると、面倒なことを解決したり、リファクタリングしたりできます. チーム内の各ウェアハウスはそれ自体で書かれているようで、すぐに開始でき、チームの効率は幾何学的に向上します. .

1.開発状況

異なるビジネス シナリオは異なります. 付加価値のあるビジネス シナリオでは, ほとんどの需要境界またはすべてのサービス機能を最初に決定することはできません. 一般に, それは小さな需要から始まるマイクロサービスであり, ビジネスが成長するにつれて徐々に変化する可能性があります. . まるで小さな苗木から豊かな枝を持つ大きな木に徐々に成長しているかのように、非常に複雑です. 最初は、このサービスの責任は非常に単純で、非常に単純で、ロジックのあるサービスで問題ないかもしれませんが、後々追加された様々な依存関係があると、ロジックが複雑になり始めます.さらに恐ろしいのは、1 つの要件は 1 つの要件であるため (最悪の場合、製品の需要を予測できないことを前提として)、後方開発モデルの場合、またはアーキテクチャの概念がなければ、もう 1 つの要件は、機能の追加、ブランチの追加、必要なもののインポートにすぎません。徐々に、ほとんどのサービスは次のようになります。

  • 合理的な下請けがない、または論理的な責任の下請けのみ (サブカテゴリ)

  • プロシージャ指向プログラミングの場合、関数呼び出しチェーンは非常に長く、さまざまなパッケージに散在しています。

  • 依存性注入がなければ、依存性はグローバル導入の形で存在し、大きな範囲と潜在的な並行性の問題を伴います。

最終的には次のようになります

  • 普遍的ではなく、すべてが普遍的ではありません。ロジック部分の各変更には、パラメーターの関係を変更するための複数の関数呼び出しが含まれる場合があります。

  • テストケースを書くのが難しい インポートされた関数はモックかどうか? モック関数を1つずつ書く. モンキーモックは妥当か?

  • 各モジュールは独立しておらず、論理的には order_hanlder、conf、XXX_helper、database などのモジュールに分割されているように見えますが、明確な上下関係はありません.各モジュールには、構成の読み取り、外部サービス呼び出し、プロトコルが含まれている場合があります変換等。

まず、現在のマイクロサービス コードのステータスを見て、いくつかの一般的なマイクロサービス ディレクトリ編成スタイルを切り取ってみましょう。

現在一般的な 4 つのマイクロサービス ディレクトリ編成方法は、左から右に 1、2、3、4 です。

  • サービス 1 は、メインを除いてすべてロジックに配置され、ロジックは実際にその責任を失いました。

  • サービス 2 はすべてフラットです。作成者はなぜこれを行ったのですか。彼は多くのサル func モックを作成したためです。抽象化がないため、異なる関数間の呼び出しにより多くの関数モックが再利用されますが、テスト ファイルの内容はそうではありません。 import をサポートしているため、基礎となるロジック関数が異なるパッケージでモックを繰り返し書き込むことを避けるために、単純にタイル化されています。

  • サービス 3 の一般的な編成方法は、ロジックを単位としてモジュールをサブコントラクトおよびデカップリングすることであり、基本的に単一責任の原則に準拠していますが、この種のマイクロサービスは、需要が増大するにつれてネットワーク呼び出しの問題を引き起こします。

  • サービス 4 は、外部呼び出しに対して一定の抽象化されたディレクトリ設計を備えていますが、構成方法が一見明確ではなく、合理的な下請けがなく、ロジック コードがアクセス層に記述されています。

(1) 構造なし

上記の例のように、ほとんどのサービスにはアーキテクチャの概念がなく、ほとんどのサービスは論理的な単位の形でパッケージ (サブカテゴリ) に分割されます. 各パッケージ間の関係は横の関係であり、各パッケージのロジックは理論的には、パッケージ機能を使用する場合は、サービスの成長に合わせてインポートするだけです。

  • サービスのさまざまなパッケージ機能間の呼び出しは、ゆっくりとネットワーク構造に発展しました。

  • データフローの流れ方向とロジックの組み合わせはますます複雑になっています。

  • コード呼び出しを見ずに、データがどこに行くのかを理解するのは困難です。

これは現在の一般的な現実問題であり、ビジネスの成長の過程で、マイクロサービスは簡単にゴミの山に成長し、開発は疲れ果てて変更できなくなります。

いわゆるコードの腐敗とは、コードのインクリメントが一定のレベルに達すると、サービス内の関数呼び出し組織が階層構造を持たないネットワーク構造になることを意味します。マクロ レベル. DDD 思考などのデザインは、すべてそのような問題を解決することです。

(2)層別化なし

一般的なマイクロサービスは、レイヤー化せずに下請けするという概念しかなく、データ フローはレイヤー化されていません. 合理的なレイヤー化がないため、当然、上下の呼び出しに関係はありません. レイヤーのないシステムは、混乱、インターフェースの混乱、これは、将来のメンテナンスとデバッグにとって悪夢です。

2. アーキテクチャのベスト プラクティスを探る

(1) シンプルな構造

「The Way of Clean Architecture」より、このアーキテクチャモデルは、フロントエンドとバックエンドを区別しない広い意味での抽象的なアーキテクチャであり、各マイクロサービスのコードも、マイクロレベルでの簡潔なアーキテクチャに準拠することを願っています。レベル。

バックグラウンド サービスのシナリオでは、trpc-go ディレクトリ仕様からピラミッド構造を抽象化できます。

この構造の利点は、次の点に反映されています。

  • 標準構造:レイヤー+モジュール

  1. 構造はレイヤー化されており、各レイヤー間でモジュールが分割されています。

  2. データ フローの方向は固定されており、上から下への単一の方向があります。

  3. 構造は明確で、需要コードの成長は構造化されており、組織的な関係は絡み合っていません。

  • 一貫性

  1. アーキテクチャは共通であり、統一および標準化が可能です。

  2. 異なるサービスのアーキテクチャは共同開発中も同じであり、理解するためのコストはかかりません。

  • 操作が簡単

  1. 関連する概念は単純で操作が簡単で、開発の直感に沿っており、コードの正しい分類を容易にします。

  2. ドメイン モデリングなどの追加の問題は関係ありません。

  • コードの肥大化を遅らせる

  1. コードを上下に階層化すると、3 層構造により、各層のコード展開速度がある程度低下する可能性があります。

(2) カタログ仕様

階層化は、データの流れの方向に応じて、インターフェイス層 (ゲートウェイ層)、ロジック層、および外部依存層に分割されます. 分割方法と理解のコストはそれほど高くありません. 詳細は次のとおりです.

  • ゲートウェイ

  • サービスインターフェースの入り口で、インターフェースが実装されている場所がtrpc-goのサービスに対応しています。

  • ビジネスロジックを介さずに、プロトコルの分析と変換、およびプロトコルの配置のみを実行します。

  • 論理

  • サービスのコア ビジネス ロジックが実装される場所。

  • サブモジュールの下請けを内部的に実装します。

  • レポ

  • 外部データベース、RPC 呼び出しなどを含む外部依存層。

  • 各パッケージは、外部データを呼び出して整理し、インターフェイスの形式でロジックに提供する抽象インターフェイスを提供します。

  • 外部呼び出しとデータ照合のみを行い、ビジネス ロジックは含まれません。

  • 実在物

  • 定数やエラー コードに似た、サービス全体のデータ構造。

  • 貧血モデル、つまりデータ構造の読み取りおよび書き込みメソッドのみを含むオブジェクト。

  • 防食層

  • 各層は抽象的なインターフェースの形で外界に露出されており、各層の間の防食は依存関係の逆転によって実現されています。

  • 抽象インターフェースは gomock を使用してスタブ コードを自然に生成でき、上層は下層に対応するスタブ コードを使用して、上層のテスト時に下層の依存関係をモックするだけで済みます。

3. 仕様を実装する

実際には, 標準に従ってコードディレクトリを分類することは最初のステップにすぎません. 重要なことはレイヤー間の分離とモジュール間のデカップリングです. したがって, 依存関係の反転, 依存関係の注入, カプセル化, テスト仕様が必要です.テスト仕様書は特定のコードの場合、逆にコード設計が適格かどうかをチェックするための定規であり、各インターフェイスが gomock パイルを使用できない場合、依存関係の反転がうまく行われていません。

(1) 依存関係の逆転とインターフェースの分離

  • 依存関係の逆転

  • 上位レベルのモジュールは下位レベルのモジュールに依存してはならず、すべて抽象化に依存する必要があります。

  • 抽象化は詳細に依存すべきではなく、詳細は抽象化に依存すべきです。

  • インターフェイス分離

  • クライアントは、必要のないインターフェースに依存すべきではありません。

  • モジュール間の依存関係は、最小限のインターフェイスに基づく必要があります。

実装要件:異なるレイヤー間の外部インターフェイスはすべてインターフェイスの形式で提供され、単一責任設計、インターフェイスは可能な限りシンプルで明確であり、インターフェイスファイルは特定の実装ファイルではなく個別に保存されます。従属パラメーター定義とインターフェース宣言は一緒に配置されます。

たとえば、msg パッケージの下の api.go は、メッセージ インターフェイスを定義します。

(2) 依存性注入

依存性注入 (DI、依存性注入) は、制御の反転 (IOC、制御の反転) を実現する方法を指します. 実際、内部依存とは、内部で作成するのではなく、外部から注入することをよく理解しています.パラメータを介して。例:

  • 内部パッケージ

  • 高い凝集性と低いカップリング。

  • 合理的な抽象関数、分子関数、クラスタリングなど

例:

(3) gomock 以外のモックパッケージを導入しない

関数をパイルするためにモンキー モックを使用する必要がある場合、それはコードがインターフェイスの原則に準拠していないことを意味します。また、Monkey モックのモック関数はエクスポートできず、呼び出されるこの関数のパッケージ内の単一のテストごとにモックを書き直す必要があります。

Gomock スタブ コードは自動生成でき、上位層が下位層の依存関係をモックする必要がある場合は、モック スタブを依存関係として注入するだけで済みます。

(4) 設定(リモート設定)

フレームワークの構成に加えて、ほとんどすべてのサービスがリモート構成 (カラフルな石の構成) にアクセスするようになり、ほとんどすべてのサービスのリモート構成を読み取るロジックを再実装する必要があります。各サービスの異なる必要があります) ので、一連のコードで解決することは困難です. ここでは、パッケージ置換メソッドを使用して、エクスポートされた構造に異なる構成エンティティ定義を導入し、共通のコードを実現します (それは普遍的であり、ゼロコピーを達成することはできません)

  • サービスごとに 1 つのリモート構成。

  • リモート構成はjson形式です(yamlと同じ、内部で統一されています)

  • リモート構成は entity/config パッケージで定義され、構造は Config です。

このようにして、次のリモート構成の実装を再利用できます。

サービスに複数の構成がある場合:

例: このサービスはリファクタリングされており、以前は仕様がなかったので、3 つの異なるリモート構成を作成しました (実際には 1 つで十分です)。

Get によって返される構造が異なるため、異なる構成が異なるインターフェイス インスタンスを使用して実装されます. それぞれの異なる構造の構成は、解析時に固定構造であり、get の戻り値も固定構造です. go テンプレート機能の場合はサポートされていません 各異なるファイルの構成は、異なる impl 実装で解析されます. コードにはいくつかの重複があるようですが、この表現により明確で理解しやすくなります. 通常、サービス ビジネス構成は 1 つのファイルに配置されます.

サービスごとに 1 つの構成は、構成の初期化などのコードを削減するのに非常に役立ちます。

(5) 構成の使用

インターフェイスベースの構成は、依存性注入を実装するのに非常に便利です。構成パッケージを導入してグローバル構成を読み取るという以前の方法を放棄し、依存性注入によって構成の範囲を縮小し、多くの同時実行の問題を回避します。

4.着陸方法

理想は非常に充実している、現実は非常に薄い、要求の進行とコードの品質の間の矛盾、それを 1 つのステップで達成したい場合、実際には、それは 1 つのステップで実装できないことを意味します.

実際の状況では、需要が非常に緊急であり、コードを設計して最適化するための開発時間があまりない場合が多いため、最初のステップで開発時間がかかりすぎず、最適な時間配分ができることを願っています。 1:9 から開始し、どの段階でも、要件の迅速な完了を優先できます (つまり、全体を破壊することなく、ある程度の不適合を許容します)。つまり、独自の古いスタイルを維持できます。最初は 90% の自由度、10% の時間を抽出するように設計されているため、仕様の実装はそれほど苦痛ではありません。

全体的な着陸ステップは3つの段階に分けることができます(通過する必要はありません。時間がきつくない場合は、標準に従って直接実現できます)

現在のニーズの緊急性と個人的なスケジュールに応じて、段階的に練習してください。

V. まとめ

マイクロサービス コード アーキテクチャの一貫性と実装仕様の一貫性は、多くの利点をもたらします。

(1) なぜDDDしないのか

実際、なぜDDDが挙げられているのかというと、避けられない問題だからですが、答えはすでに存在しています.DDDは中規模および大規模プロジェクトを制御するための切り札ですが、DDDを使用しても新しいプロジェクトの開発が速くなったり、より便利. これは、巨大なシステムをより高速に反復および更新できるようにするため、つまり、新しいプロジェクトがドメイン駆動設計にあまり注意を払う必要がなく、新しいプロジェクトでもドメインなしで開始できるようにするための今後の検討事項です。駆動設計。

DDD の長所と短所:

さまざまなビジネスがさまざまな問題に直面する可能性があります.多くの実用的な要件は、最初からトップレベルの設計を伴う大きな要件や大規模なプロジェクトではないことが多く、多くのマイクロサービスでさえ、自分の分野の要素をまだ決定しておらず、ビジネスとともに死んでいます.サービス作成当初はドメインモデルや境界が明確ではない. インターフェースを持った新しいサービスの最初からイベントストームを設計したり, 要素やサブドメインを分割したりすることは現実的ではない. したがって, サービスが小さければ小さいほど, DDD は必要ありません。多くの場合、チームの新しいメンバーの急速な成長を考慮する必要があります. 新しいクラスメートやインターンのクラスメートがすぐに DDD を開始し、すべてのサービスで DDD を実装することは困難です. このように,ニーズの異なるサービスにはギャップがあり、同僚のサービスを引き継ぐ場合、構造を理解するという精神的負担が依然として存在します。

あとがき

全体的なルールは大まかに記述しますが、実践の過程で、内部の詳細、関数の抽象化、クラスタリング、およびサブモジュールの分割については、経験と実践の蓄積であり、やはりコードのスキルがテストされます。このアーキテクチャ仕様は役に立ちません。

優れたアーキテクチャやカタログのデザインは、ごみを分類するためのごみ箱のようなものです. 事前に設定された分類ルールにより、ごみは簡単に分類され、分類されたごみは宝物や利用可能なリソースに変わることができます. したがって, コードに直面してゴミの山のように、リファクタリングするときは、まず正しいアーキテクチャに従ってゴミを分類する必要があります。

効果的な階層化が行われていますが、論理層でのモジュール分割は厳密には要求されていません. つまり、抽象的なインターフェイスが提供された後、具体的な実装は細部の問題です. 需要が高まるにつれて、実際には複雑さがもたらされます.ただし、外部呼び出しはレポに分割され、データ インスタンスはエンティティにあるため、マイクロサービスの最終的なロジック コードはそれほど急速に拡張されません.3 層構造は、複雑さの拡張をある程度遅くすることができます. ある日の拡張が大きい場合は、DDD を使用したリファクタリングが別の解決策になる場合があります。

この記事は、ベスト プラクティスを模索する過程での思考プロセスとトレードオフを記録するものです. 結局, マイクロサービス コード アーキテクチャの実践に特効薬はなく, より良い状況はありません. 比較的簡単な方法しかありません.シンプルで効果的なソリューションを実装するには、より一般的です

おすすめ

転載: blog.csdn.net/m0_72650596/article/details/126231039