Статьи

Использование аннотаций Netflix Hystrix в Spring

Я не могу придумать лучшего способа описать конкретную функцию  библиотеки Netflix Hystrix,  чем цитируя ее домашнюю страницу:


Задержка и отказоустойчивость:

прекратите каскадные сбои. Резервы и грациозная деградация. Сбой быстрого и быстрого выздоровления.

Резьба и семафорная изоляция с выключателями.

Я видел  пример,  продемонстрированный Джошом Лонгом ( @starbuxman ), который использует Hystrix, интегрированную со Spring — конкретный код  здесь . В примере используются аннотации для включения в сервисный класс Hystrix.

Моя цель здесь — воссоздать аналогичную настройку в режиме меньшего модульного тестирования. Имея это в виду, рассмотрим следующий интерфейс, который станет отказоустойчивым с помощью библиотеки Hystrix:

package hystrixtest;

public interface RemoteCallService {

    String call(String request) throws Exception;

}

И фиктивная реализация для этого. Фиктивная реализация делегирует фиктивную реализацию, которая, в свою очередь, дает сбой первые два раза, когда она вызывается, и успешно завершается с третьим вызовом:

package hystrixtest;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import static org.mockito.Mockito.*;


public class DummyRemoteCallService implements RemoteCallService {

    private RemoteCallService mockedDelegate;

    public DummyRemoteCallService() {
        try {
            mockedDelegate = mock(RemoteCallService.class);
            when(mockedDelegate.call(anyString()))
                    .thenThrow(new RuntimeException("Deliberately throwing an exception 1"))
                    .thenThrow(new RuntimeException("Deliberately throwing an exception 2"))
                    .thenAnswer(new Answer<String>() {
                        @Override
                        public String answer(InvocationOnMock invocationOnMock) throws Throwable {
                            return (String) invocationOnMock.getArguments()[0];
                        }
                    });
        }catch(Exception e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    @HystrixCommand(fallbackMethod = "fallBackCall")
    public String call(String request) throws Exception {
        return this.mockedDelegate.call(request);
    }

    public String fallBackCall(String request) {
        return "FALLBACK: " + request;
    }
}

Удаленный вызов был аннотирован аннотацией @Hystrixcommand с базовой конфигурацией для возврата к методу «fallBackCall» в случае неудачного удаленного вызова.

Теперь, как вы можете себе представить, в библиотеке Hystrix должно быть что-то, что должно перехватывать вызовы с аннотацией @HystrixCommand и делать ее отказоустойчивой. Это рабочий тест, который объединяет необходимую инфраструктуру — по сути, библиотека Hystrix предоставляет сопутствующую библиотеку на основе AOP, которая перехватывает вызовы. Здесь я использовал поддержку тестирования Spring для начальной загрузки инфраструктуры AOP, для создания HystrixCommandAspect в качестве bean-компонента, вызов переходит к «fallBackCall» для первых двух неудачных вызовов и выполняется в третий раз:

package hystrixtest;

import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TestRemoteCallServiceHystrix {

    @Autowired
    private RemoteCallService remoteCallService ;

    @Test
    public void testRemoteCall() throws Exception{
        assertThat(this.remoteCallService.call("test"), is("FALLBACK: test"));
        assertThat(this.remoteCallService.call("test"), is("FALLBACK: test"));
        assertThat(this.remoteCallService.call("test"), is("test"));
    }

    @Configuration
    @EnableAspectJAutoProxy
    public static class SpringConfig {

        @Bean
        public HystrixCommandAspect hystrixCommandAspect() {
            return new HystrixCommandAspect();
        }

        @Bean
        public RemoteCallService remoteCallService() {
            return new DummyRemoteCallService();
        }
    }
}

Spring-Cloud  предоставляет более простой способ настройки библиотек Netflix для  проектов, основанных на  Spring-Boot, и если бы я использовал эту библиотеку, тест трансформировался бы в это, теперь куча конфигурации закомментирована с помощью Spring-Boot:

package hystrixtest;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;


@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration
public class TestRemoteCallServiceHystrix {

    @Autowired
    private RemoteCallService remoteCallService;

    @Test
    public void testRemoteCall() throws Exception {
        assertThat(this.remoteCallService.call("test"), is("FALLBACK: test"));
        assertThat(this.remoteCallService.call("test"), is("FALLBACK: test"));
        assertThat(this.remoteCallService.call("test"), is("test"));
    }

    @Configuration
    @EnableAutoConfiguration
//    @EnableAspectJAutoProxy
    @EnableHystrix
    public static class SpringConfig {

//        @Bean
//        public HystrixCommandAspect hystrixCommandAspect() {
//            return new HystrixCommandAspect();
//        }

        @Bean
        public RemoteCallService remoteCallService() {
            return new DummyRemoteCallService();
        }
    }
}

Если вы заинтересованы в изучении этого примера дальше, 
здесь  есть 
GitHub репо  с рабочими испытаниями.