Implementando o Circuit Breaker Pattern – Parte 2

11 jun

Fala Galera,

Continuando a série de posts sobre o padrão Circuit Breaker, na primeira parte falamos sobre os conceitos desse padrão, tratamentos de exceções e sua maquina de estado.

Perdeu a primeira parte clique aqui.

Nesta segunda parte da série de posts, vamos falar como podemos implementar o padrão do Circuit Breaker, como vimos esse padrão utiliza uma maquina de estado para controlar as suas operações.

Vamos ao código =]

Show me the code!!!

Para este exemplo, devemos criar toda a estrutura necessária para o nosso Circuit Breaker. Vamos adicionar 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 que será um enumerador dos estado do circuito.

public enum CircuitBreakerState
{
    Closed,
    Open,
    HalfOpen
}

Também vamos criar 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. Vamos adicionar uma classe chamada CircuitBreaker ele irá 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 o nosso testes, utilizei um Console Application com o seguinte 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 imagem abaixo:

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

Abs e até a próxima

One Reply to “Implementando o Circuit Breaker Pattern – Parte 2”

  1. Pingback: Implementando o Circuit Breaker Pattern – Parte 3 - Rafael Cruz

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *