Back-End

14 jun, 2018

Implementando o Circuit Breaker Pattern – Parte 02

Publicidade

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(quot;{threshold} deve ser maior que zero");
            if (timeout <= 0)
                throw new ArgumentOutOfRangeException(quot;{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(quot;Circuito Closed");
                ChangeState(CircuitBreakerState.Closed);
                this.Timer.Stop();
            }
        }
        #region Machine State Handles
        private void Trip()
        {
            if (this.State != CircuitBreakerState.Open)
            {
                Trace.WriteLine(quot;Circuito Aberto");
                ChangeState(CircuitBreakerState.Open);
            }
        }
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            lock (monitor)
            {
                try
                {
                    Trace.WriteLine(quot;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!