玉ねぎ建築🧅


玉ねぎはおいしい野菜であり、世界中の料理の中心的な成分です.おそらく、あなたは疑問を持っています、なぜ、我々はソフトウェア工学の文脈で彼らを議論していますか?最初に導入Jeffrey Palermo 一連のブログ記事では、タマネギのアーキテクチャガイドのソフトウェアエンジニアは、データベースの選択などの外部の懸念、またはどのようにユーザーインターフェイスが動作しないとのコアコレクション内のビジネスロジックをモデル化する.
玉ねぎの建築はどんな感じですか.それは驚きとして来るかもしれない.

それは中央のコアの周りに包まれた層を持つタマネギに非常に似ています.これらの層の各々は、サービスの全体的な機能の範囲内で特定の義務を表す.タマネギに似て、あなたは外側の最も多くの層を通って行くことによって芯にアクセスすることができます、そして、それはアーキテクチャ目的の我々に通知する物語です-外側から中心へのカップリングの流れを指示するために.
だから、典型的なタマネギのように、コアに私たちの方法を働かせてうまくいけば道に沿って任意の涙を避けましょう.つの外側の層は、直接私たちのビジネスロジックに関連していないが、それは自分の目的を満たすに依存しています.彼らは頻繁に変更することができますので、我々のコアアプリケーションロジックから分離されます.
これらの層があります:インフラストラクチャ、我々のデータベース、ファイルシステム、または我々が住んでいる任意の外部Webサービスです.テスト:ユニット、統合、エンドツーエンド.どのように我々はビジネスケースを検証します.最後に、ユーザーインターフェイス、どのように我々のユーザーは、我々が構築したコードと対話します.これらの層は私たちの「アプリケーションコア」の最初の層と相互作用するものです、そして、それはアプリケーションサービス層(時々輸送層として知られています)です.この層の中で、私たちは一連の契約を通して私たちのサービスができることを定義します.
内部の移動、ドメインサービス層に遭遇します.この層では、我々のビジネスロジックの大半が住んでいる、それは、Bに入力を有効にする操作を実行し、出力に入力し、鶏肉に卵.これは、最後の層との相互作用を介して、我々は使用する高レベルのデータオブジェクトの表現であるドメインモデル層との相互作用を実現します.
我々が金融取引を処理することのような現実の仕事を解決する方法の例を通して歩きましょう.外から.

例-コーヒーを買う
多くを驚かせるかもしれない1つの外層は、基盤です.我々が使用するデータベースまたは外部の依存性は、我々のドメインモデル層の一部ではありませんか?非常に有効な質問ので、さらに探検しましょう.
例えば、いくつかのNOSQLデータベースにおけるユーザアカウントの定義を以下に示します:
{
    "id": "some_user_id",
    "balance": 500.00,
    "currency": "GBP",
}
したがって、問い合わせを行い、これと対話することを望むなら、このJSONをマーシャリングするためのモデルオブジェクトを作成するのが一般的です.
type Account struct {
    ID       string    `json:"id"`
    Currency string    `json:"currency"`
    Balance  float64   `json:"balance"`
}
意味を正しくするか.しかし、チームがnoSQLがゼロまででないと決定すると言います、そして、若干の関係良さは月の傾向です.さて、タマネギのアーキテクチャの力のおかげで、新しいリレーショナル構造がドメインモデル契約で定義されるように必要なフィールドを提供する限り、アプリケーションのビジネスロジックは変更する必要はなく、ユーザーが必要とする機能を提供し続けることができます.これは外部の依存関係やデータストレージに適用され、アプリケーションはキーテイクアウトと対話します.
Externalise your dependencies and decouple them through contracts.
今、私たちはアカウントのドメインモデル表現を持っています.金融取引のユースケースを定義する必要があります.

ダイアグラムからわかるように、「ジェネリックコーヒーショップ」では購入しているコーヒーのコストに対してアンバランスのバランスをとることができます.「ジェネリックコーヒーショップ」がHTTP POSTリクエストを通じて我々と通信すると仮定しましょう.
  • ANDREのユーザID.
  • コーヒーを買う量.
  • この要求のJSON表現は次のようになります.
    {
        "userID": "some_user_id",
        "amount": 100.00,
    }
    
    このユースケースの命名は、ユーザーのバランスを課金するものであり、前述のように、アプリケーションサービス層は、これらを定義するところです.これをコードで表現するには、HTTPハンドラコード内で具現化する必要があります.
    package application
    
    import (
        "net/http"
        "encoding/json"
    )
    
    // ChargeRequest is our representation of an incoming request.
    type ChargeRequest struct {
        UserID string  `json:"userID"`
        Amount float64 `json:"amount"`
    }
    
    // ChargeUserHandler takes an incoming HTTP POST request and decodes
    // the body into a ChargeRequest so that we can carry out charging
    // the users balance for the cost of the transaction.
    func ChargeUserHandler(w http.ResponseWriter, r *http.Request) {
        var cr ChargeRequest
    
        err := json.NewDecoder(r.Body).Decode(&cr)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
    
        if cr.UserID == "" {
            http.Error(w, "user id empty", http.StatusBadRequest)
            return
        }
    
        // Call our domain services layer
    
        return
    }
    
    すごい!我々は現在、我々のアプリケーションサービス層内のトランザクションのためのユーザーを充電したいと思っている人に期待を設定している.しかし、我々はこのようにトランザクションからこれまで有用な何かをしていません、そして、タマネギアーキテクチャの層に続いて、我々はドメインサービス層を定義する必要があります.
    エンジニアとして、私たちの流れをレイアウトするために少し時間をかけましょう.
  • チェックAndres口座残高とそれが取引のコストをカバーするのに十分な資金があるならば.
  • そうでない場合やANDREのアカウントがない場合は、エラーを返します.
  • 今、我々はアカウントを持って、アンドレスバランスからトランザクションのコストを差し引いて、我々のデータベースの中でそれを更新します.
  • 更新が失敗した場合、エラーを返す必要があります.我々は誰も無料コーヒーを取得したくない.
  • 今すぐバランスは、このようにトランザクションを完了し、アンドレは彼のおいしいコーヒーを飲むことができます“ジェネリックコーヒー会社”に戻って確認を更新されています.
  • ありがたいことに、我々のアカウントドメインモデルを既に定義しているので、このコードのすべてをカプセル化するのは、次のようになります.
    package service
    
    import (
        "database/sql"
        "fmt"
    )
    
    // DB interface sets out the operations allowed on our database.
    type DB interface {
        StartTransaction() (*sql.Tx, error)
        GetUserAccount(tx *sql.Tx, userID string) (Account, error)
        UpdateUserAccount(tx *sql.Tx, userID string, account Account) error
    }
    
    // ChargeService carries out the business logic to charge users balances
    type ChargeService struct {
        db DB
    }
    
    // ChargeUser takes a user ID and an amount then deducts that amount from the
    // balance of that user, if they have enough.
    func (cs *ChargeService) ChargeUser(userID string, chargeAmount float64) error {
        transaction, err := cs.db.StartTransaction()
        if err != nil {
            return fmt.Errorf("error starting transaction")
        }
        defer func() {
            _ = transaction.Rollback()
        }()
    
        account, err := cs.db.GetUserAccount(transaction, userID)
        if err != nil {
            return err
        }
        if account.Balance < chargeAmount {
            return fmt.Errorf("insufficient funds")
        }
    
        account.Balance = account.Balance - chargeAmount
        err = cs.db.UpdateUserAccount(transaction, userID, account)
        if err != nil {
            return err
        }
    
        err = transaction.Commit()
        if err != nil {
            return fmt.Errorf("error committing transaction")
        }
    
        return nil
    }
    
    上記の例を参照すると、ドメインサービスレイヤコードで必要とされるビジネスロジックを捕捉したことがわかります.
    // ChargeUserHandler takes an incoming HTTP POST request and decodes
    // the body into a ChargeRequest so that we can carry out charging
    // the users balance for the cost of the transaction.
    func ChargeUserHandler(w http.ResponseWriter, r *http.Request) {
        var cr ChargeRequest
    
        err := json.NewDecoder(r.Body).Decode(&cr)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
    
        if cr.UserID == "" {
            http.Error(w, "user id empty", http.StatusBadRequest)
            return
        }
    
        // New code here
        db := DatabaseAdapter{}
        chargeService := service.ChargeService{db: db}
        err := chargeService.ChargeUser(cr.UserID, cr.Amount)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    
        return
    }
    
    さて、もし我々がこのサービスを展開するなら、アンドレがコーヒーを買うことを決定するならば、我々は我々が我々の調査において設定された要件を満たして、我々のロジックがきちんと層に分類されていると確信していることを確信することができます.これは単純なユースケースですが、質問されている本当の質問は理由です.

    なぜタマネギのアーキテクチャを使用しますか?
    しかし、タマネギのアーキテクチャを使用する利点は何ですか?
    それは我々が定義した各層の間の契約であり、また、タマネギの構造が大きく依存する依存関係の反転原理としても知られている.我々の層が我々のコードでセットされた契約/インターフェースに付着する限り、我々は我々のNOSQLまたはSQL議論で言及したように彼らを利用することができます.絵は千の言葉を言います.

    契約を使用すると、各レイヤーは、次の上にその期待を設定し、それが必要なものだけにカップルを設定することができます.そのうえ、彼らが変化に反応して、我々のコードのテスト容易性を増やすことをより簡単にしている彼らの隣人と彼らの契約上の義務を満たす限り、各層の実装仕様はどんな点ででもリファクタリングされることができます.
    しかし、このアーキテクチャパターンは、あらゆる問題に銀弾ではありません.すべてのソフトウェア問題と同様に、多くのエンジニアがそれに取り組んでいるより大きなアプリケーションにより適しているので、我々はこの追加の抽象を必要とするかどうか評価する必要があります.エンジニアとして、我々は全体的に手で仕事に利益をもたらすかどうか決定するために批判的思考を適用する必要があります.さらに、契約/インターフェースを定義して、宗教的に彼らを強制することの複雑さの更なる複雑さは、パターンの強い理解を必要とします.よく実行されるならば、利点は生産性を過充電して、大いに開発されているアプリケーションの柔軟性を増やすでしょう.
    あなたがオニオン建築について何を考えているかを私に知らせてください、そして、私はあなたがこのポストを楽しむことを望みます.
    次まで.
    BM
    さらに私の個人で読むことができますblog