反腐層ACL - 一個有效的盾牌 - Manuel López Torrent

banq發表於2019-08-30

我第一次聽說反腐敗層(ACL)一詞是在Eric Evans的書“領域驅動設計”中。那些日子,DDD是我正在探索的一個新領域,我對所有這些新概念感到非常興奮,但我沒有實現大部分概念。
近年來,在我所做的幾乎所有開發中,我不得不處理遺留程式碼,資料儲存庫或“第三方”子系統,並且ACL已經“形成”,當我們處理其他子系統時必須特別注意它。
在wiki.c2.com(http://wiki.c2.com/?AnticorruptionLayer)中,我們可以找到ACL的定義:
“如果您的應用程式需要處理資料庫或其他應用程式,這些應用程式的模型不合適或不適用於您自己的應用程式中所需的模型,請使用反腐敗層轉換為該模型或從您的模型轉換為您的模型”

在Eric Evans的書中,他寫了兩句相信非常有趣的句子:
“但是當邊界的另一邊開始洩漏時,轉換層可能會採取更加防禦的語氣。”
“當基於不同模型的系統結合在一起時,新系統適應其他系統語義的需求可能導致新系統自身的損壞”

基本概念非常清楚。當我們需要與其他系統,資料儲存庫或遺留程式碼(甚至是“我們的”遺留程式碼)“交談”時,我們應該阻止我們的模型與其他“外面模型”混合。
我們必須將這些外部系統或資料儲存庫視為不同的有界上下文,當然,它將擁有自己的模型,它將與我們的模型建立關係。
大多數情況下,這種對映將是customer/supplier(釋出訂閱)型別,我們通常將是消費訂閱一方。
即使您沒有在您的開發中實施DDD,ACL也是一個非常好的“工具”。

如您所見,ACL不僅是一個簡單的翻譯層。如果我們開發ACL,我們應該設計:
  1. 翻譯模型
  2. 防止失敗
  3. 監控
  4. 改進我們的整合測試


一個例子
想象一下,我們正在開發一個銀行相關的應用程式。我們需要處理附加到帳戶的信用卡。
要獲取此資訊,我們需要與舊的且不太可靠的SOAP伺服器通訊,以檢索附加到帳戶的信用卡資訊。
實現ACl層的方法有很多種,但我們總是使用外觀設計模式,如外觀,介面卡和翻譯器。

翻譯模型
這是我們的模型。我們有一個基本的信用卡資訊,我們可以用一些眾所周知的演算法驗證它。

class CreditCard
  attr_accessor :card_number, :card_holder, :expiration_year, :expirartion_month, :type

  def initialize(args)
    @card_number = args[:card_number]
    @card_holder = args[:card_holder]
    @expiration_year = args[:expiration_year]
    @expirartion_month = args[:expirartion_month]
    @type = args[:type]

    validate_card_number
  end

  private
    def validate_card_number
      CreaditCardValidator::validate(self)
    end

end

class CreaditCardValidator
    def self.validate(credid_card)
      # Validate or raise CreditCardInvalidNumber
    end
end

class CreditCardInvalidNumber < StandardError; end


這裡我們有一個服務,可以從SOAP伺服器中恢復信用卡:

class SoapClientWrapper

  def get_card_data(account_number)
    foreign_cards = get_fixtures(account_number)
    foreign_cards.map { |c| yield c }
  end

  Customer = Struct.new(:name, :surname, :number, :date, :type)

  def get_fixtures(card_number)
    [
      Customer.new("Dave", "Foo", "371449635398431", "02/20", "American Express"),
      Customer.new("Jane", "Bar", "5555555555554444", "10/21", "MasterCard"),
      Customer.new("Micha", "Jar", "4111111111111111", "14/23", "visa" )
    ]
  end

end

class CreaditCardService

  attr_reader :cards

  def initialize(soap_client)
    @soap_client = soap_client
  end

  def get_card_from_account(account_number)
    begin
      @cards = @soap_client.get_card_data(account_number) do |card|
        CreditCard.new(
          :card_holder => card.name + card.surname,
          :card_number => card.number,
          :expiration_year => card.date.split('/')[1],
          :expirartion_month => card.date.split('/')[0]
        )
      end
    rescue Exception => e
      raise CreaditCardServiceError
    end
  end
end

class CreaditCardServiceError < StandardError; end


正如您在程式碼中看到的,我們會恢復信用卡資訊並轉換為我們的模型。
這裡的要點是,我們總是會提供一個帳號,我們會恢復附加到此帳戶的信用卡列表,如果出現問題我們會捕獲異常。
如果第三方系統發生變化,我們只需要更改我們的翻譯器,或者即使服務協議發生變化,我們也只需要修改一個類來保持模型的完整性。

準備好失敗
如果信用卡伺服器掉落,我們的申請會怎樣?我們的應用程式會看到錯誤500嗎?也許警告頁面對我們的使用者來說更加容易,或者隱藏這部分頁面資訊並顯示其餘部分,或者我們可以使用“cirtuit破壞者”並提供快取響應。
也許你需要不止一個例外之王來為故障新增更多粒度(即CardServiceConnectionError,CardServiceTranslationError)

改善我們的服務
這個抽象層的存在允許我們裝飾它並新增更多功能,以增加我們系統的防禦。可能我們需要記錄所有交易以進行一些審計。也許,我們可以測量時間響應或響應程式碼並將所有遙測傳送到ELK堆疊。
我們來裝飾我們的服務:

class TimeCreaditCardService

  def initialize(credit_card_service)
    @credit_card_service = credit_card_service
  end

  def get_card_from_account(account_number)
    measure { @credit_card_service.get_card_from_account(account_number) }
  end

  def measure
    start = Time.now
    result = yield
    finish = Time.now
    @delta = finish - start # in seconds
    return result
  end

  def get_time
    puts "Time elapsed #{(@delta)*1000} milliseconds"
  end

end


根據我的經驗,當我們開發一個“前端”時,這可能是一個很好的實踐,因為我們是冰山的可見部分。大多數時候,監控子系統將幫助我們檢測應用程式中的錯誤原因。

整合測試
當我們建立整合測試時,我們需要從眾所周知的系統狀態開始。例如,如果我們想測試儲存庫和資料庫之間的整合,我們需要建立表,填充一些具體資料,執行測試並檢查資料庫狀態是否按照我們預期的方式更改。
如今,使用虛擬化(如Docker)複製資料庫非常容易,我們可以在CI管道中的整合測試中執行它。
如果系統不在我們的控制之下,這可能會更復雜甚至不可能,並且我們無法確保在執行測試時我們始終具有相同的狀態。
ACL允許我們模擬這個系統,這樣,我們可以測試我們的程式碼在失敗前做出反應。
我們可以模擬子系統響應並測試如何使用這個新夥伴來執行我們的有界上下文。

結論
使用ACL,我們將獲得:

  1. 翻譯資訊
  2. 防止其他子系統故障
  3. 記錄和監視關係
  4. 良好的整合測試

相關文章