当前位置:网站首页>ACL: an effective shield Manuel L ó pez Torrent

ACL: an effective shield Manuel L ó pez Torrent

2021-05-04 16:51:34 Jiedao jdon

I heard about the anti-corruption layer for the first time (ACL) In a word Eric Evans The book of “ Domain-driven design ” in . Those days ,DDD It's a new area I'm exploring , I'm very excited about all these new concepts , But I didn't implement most of the concepts .

In recent years , In almost all the development I do , I had to deal with legacy code , Data repository or “ The third party ” Subsystem , also ACL already “ formation ”, When we deal with other subsystems, we have to pay special attention to it .

stay wiki.c2.com(http://wiki.c2.com/?AnticorruptionLayer) in , We can find ACL The definition of :

“ If your application needs to handle databases or other applications , The models for these applications are not appropriate or applicable to the models needed in your own applications , Please use The anti-corruption layer Convert to or from your model to your model ”

stay Eric Evans In the book , He wrote two very interesting sentences :

“ But when the other side of the border starts to leak , The transition layer may take a more defensive tone .”

“ When systems based on different models come together , The adaptation of the new system to the semantic requirements of other systems may lead to the damage of the new system itself ”

The basic concept is very clear . When we need to work with other systems , Data repositories or legacy code ( Even “ our ” Legacy code )“ conversation ” when , We should prevent our model from being different from others “ Outside the model ” blend .

We These external systems or data repositories must be treated as different bounded contexts , Of course , It will have its own model , It will relate to our model .

Most of the time , This mapping would be customer/supplier( Publish subscribe ) type , We'll usually be the consumer subscriber .

Even if you don't implement it in your development DDD,ACL It's also a very good “ Tools ”.

As you can see ,ACL It's not just a simple translation layer . If we develop ACL, We should design :

  1. Translation model
  2. Prevent failure
  3. monitor
  4. Improve our integration testing

An example

Imagine , We are developing a banking related application . We need to process the credit card attached to the account .

To get this information , We need to work with old and unreliable SOAP Server communication , To retrieve the credit card information attached to the account .

Realization ACl There are many ways to layer , But we always use design patterns , Such as appearance , Adapters and translators .

Translation model

This is our model . We have a basic credit card information , We can test it with some well-known algorithms .

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

Here we have a service , It can be downloaded from SOAP Restore the credit card from the server :

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

As you can see in the code , We'll recover the credit card information and transform it into our model .

The point here is , We always provide an account number , We will restore the credit card list attached to this account , If something goes wrong, we catch exceptions .

If the third party system changes , We just need to change our translator , Or even if the service agreement changes , We only need to modify one class to keep the integrity of the model .

Be prepared to fail

If the credit card server drops , What will happen to our application ? Our application will see errors 500 Do you ? Maybe the warning page is easier for our users , Or hide this part of the page and show the rest , Or we can use “cirtuit vandals ” And provide cache response .

Maybe you need more than one exception king to add more granularity to the fault ( namely CardServiceConnectionError,CardServiceTranslationError)

Improve our service

The existence of this abstraction layer allows us to decorate it and add more features , To increase the defense of our system . Maybe we need to record all the transactions for some audit . Maybe , We can measure the time response or response code and send all telemetry to ELK Stack .

Let's decorate our service :

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

According to my experience , When we develop a “ front end ” when , It could be a good practice , Because we are the visible part of the iceberg . Most of the time , The monitoring subsystem will help us detect the cause of the error in the application .

Integration testing

When we create integration tests , We need to start with the well-known state of the system . for example , If we want to test the integration between the repository and the database , We need to create tables , Fill in some specific data , Execute the test and check that the state of the database changes as we expect .

Now , Using virtualization ( Such as Docker) It's very easy to replicate the database , We can do it in CI Run it in an integration test in a pipeline .

If the system is not under our control , It could be more complicated or even impossible , And we can't make sure that we always have the same state when we run tests .

ACL Allow us to simulate this system , such , We can test our code to react before it fails .

We can simulate subsystem response and test how to use this new partner to run our bounded context .

Conclusion

Use ACL, We will get :

  1. Translation information
  2. Prevent other subsystem failures
  3. Record and monitor relationships
  4. Good integration testing

版权声明
本文为[Jiedao jdon]所创,转载请带上原文链接,感谢
https://chowdera.com/2021/05/20210504164657517p.html

随机推荐