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!




