Dev (Back & Front)ARTIGO

Implementando o Circuit Breaker Pattern – Parte 02

O autor apresenta um passo a passo ensinando a implementar o padrão Circuit Breaker.

Fala, galera!

Continuando a série de artigos sobre o padrão Circuit Breaker, na qual falamos sobre os conceitos desse padrão, tratamentos de exceções e sua máquina de estado, vamos falar agora sobre como implementar o padrão Circuit Breaker. Como vimos, esse padrão utiliza uma máquina de estado para controlar suas operações.

Perdeu a primeira parte? Clique aqui.

Vamos ao código!

Para este exemplo, devemos criar toda a estrutura necessária para o nosso Circuit Breaker. Adicionaremos uma Interface chamada “ICircuitBreaker”; ela será responsável pela exposição dos métodos necessário do circuito.

public interface ICircuitBreaker
{
        CircuitBreakerState State { get; }
        void Reset();
        void Execute(Action action);
        bool IsClosed { get; }
        bool IsOpen { get; }
}

Depois vamos adicionar nossa máquina de estado: um enumerador dos estado do circuito.

public enum CircuitBreakerState
{
    Closed,
    Open,
    HalfOpen
}

Também criaremos as exceções que serão tratadas pelo o circuito.

public class OpenCircuitException : Exception
{
    public OpenCircuitException(string message) : base(message)
    {
    }
}
public class CircuitBreakerOperationException : Exception
{
    public CircuitBreakerOperationException(string message, Exception innerException) : base(message, innerException)
    {
    }
}

Agora vem a parte boa: a implementação do nosso circuito. Adicionaremos uma classe chamada “CircuitBreaker”. Ele implementará a interface ICircuitBreaker.

public class CircuitBreaker : ICircuitBreaker
{
        public event EventHandler StateChanged;
        private object monitor = new object(); 
        public int Timeout { get; private set; }
        public int Threshold { get; private set; }
        public CircuitBreakerState State { get; private set; }
        private Timer Timer { get; set; }
        private int FailureCount { get; set; }
        private Action Action { get; set; }
        public bool IsClosed { get { return State == CircuitBreakerState.Closed; } }
        public bool IsOpen { get { return State == CircuitBreakerState.Open; } }
        public CircuitBreaker(int threshold = 5, int timeout = 60000)
        {
            if (threshold <= 0)
                throw new ArgumentOutOfRangeException($"{threshold} deve ser maior que zero");
            if (timeout <= 0)
                throw new ArgumentOutOfRangeException($"{timeout} deve ser maior que zero");
            this.Threshold = threshold;
            this.Timeout = timeout;
            this.State = CircuitBreakerState.Closed;
            this.Timer = new Timer(timeout);
            this.Timer.Enabled = false;
            this.Timer.Elapsed += Timer_Elapsed;
        }
        public void Execute(Action action)
        {
            if (this.State == CircuitBreakerState.Open)
            {
                throw new OpenCircuitException("Circuit breaker is currently open");
            }
            lock (monitor)
            {
                try
                {
                    this.Action = action;
                    this.Action();
                }
                catch (Exception ex)
                {
                    if (this.State == CircuitBreakerState.HalfOpen)
                    {
                        Trip();
                    }
                    else if (this.FailureCount <= this.Threshold)
                    {
                        this.FailureCount++;
                        //Ativa o Retry
                        if (this.Timer.Enabled == false)
                            this.Timer.Enabled = true;
                    }
                    else if (this.FailureCount >= this.Threshold)
                    {
                        Trip();
                    }
                    throw new CircuitBreakerOperationException("Operation failed", ex);
                }
                if (this.State == CircuitBreakerState.HalfOpen)
                {
                    Reset();
                }
                if (this.FailureCount > 0)
                {
                    this.FailureCount--;
                }
            }
        }
        public void Reset()
        {
            if (this.State != CircuitBreakerState.Closed)
            {
                Trace.WriteLine($"Circuito Closed");
                ChangeState(CircuitBreakerState.Closed);
                this.Timer.Stop();
            }
        }
        #region Machine State Handles
        private void Trip()
        {
            if (this.State != CircuitBreakerState.Open)
            {
                Trace.WriteLine($"Circuito Aberto");
                ChangeState(CircuitBreakerState.Open);
            }
        }
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            lock (monitor)
            {
                try
                {
                    Trace.WriteLine($"Retry, Execução nº {this.FailureCount}");
                    Execute(this.Action);
                    Reset();
                }
                catch
                {
                    if (this.FailureCount > this.Threshold)
                    {
                        Trip();
                        this.Timer.Elapsed -= Timer_Elapsed;
                        this.Timer.Enabled = false;
                        this.Timer.Stop();
                        
                    }
                }
            }
        }
        private void ChangeState(CircuitBreakerState state)
        {
            this.State = state;
            this.OnCircuitBreakerStateChanged(new EventArgs() { });
        }
        private void OnCircuitBreakerStateChanged(EventArgs e)
        {
            if (this.StateChanged != null)
            {
                StateChanged(this, e);
            }
        }
        #endregion
}

Com essa implementação, nosso Circuit Breaker está pronto. O mapa do nosso código ficou assim:

Para nossos testes, utilizei um Console Application com o código abaixo. Note que estou estourando uma exceção de propósito para forçar o circuito em estado aberto.

static void Main(string[] args)
 {
      var cb = new CircuitBreaker.CircuitBreaker(5, 3000);
      try
      {
         cb.Execute(() =>
         {
              throw new Exception(); 
         });
      }
      catch(CircuitBreakerOperationException ex)
      {
          Trace.Write(ex);
      }
      catch(OpenCircuitException openEx)
      {
          Console.Write(cb.IsOpen);
      }
      Console.Write(cb.IsClosed);
      Console.Read();
}

Na janela de saída do Visual Studio temos a seguinte mensagem, conforme a imagem abaixo:

E ai, o que achou da implementação? No terceiro e último artigo da série vamos utilizar componentes que implementam o padrão Circuit Breaker.

Abraços e até a próxima!

Rafael CruzAutor

Rafael Cruz é Partner e Lead Software Architect na Electric Dreams, Professor Universitário, Most Valuable Profissional (MVP), Instrutor Oficial Microsoft (MCT), Fundador do Coders in Rio, Pós graduado em Engenharia de Software pela UFRJ. Atua como palestrante e autor em portais voltado para tecnologia Microsoft com foco em Web, Cloud e Mobile

Ver perfil