Статьи

Проверка запросов и фильтрация по флагам – Редизайн и рефакторинг

Общие положения
В предыдущих постах я начал описывать структуру валидации / фильтрации, которую мы создаем.
Показывая код, я пытаюсь показать чистый код, ориентацию теста и эволюцию кода.
У этого есть некоторая ловкость в процессе; Мы знаем конечные требования, но точные детали меняются со временем.

В ходе разработки мы изменили код, сделав его более общим, поскольку в нем мы увидели некоторые шаблоны.
Код развивался по мере развития потока.

Поток, как мы его понимаем.
Вот схема потока, который мы реализуем.

Запросить последовательность

Запросить последовательность

Шаблон
На каждом шаге последовательности (проверка, фильтрация, действие) мы распознавали один и тот же шаблон:

  1. У нас есть конкретные реализации (фильтры, проверки)
  2. У нас есть движок, который оборачивает конкретные реализации
  3. Нам нужно отобразить реализации по флагу, и по флагам запроса выбрать соответствующие реализации.
  4. Нам нужно иметь класс, который вызывает маппер, а затем двигатель

Диаграмма, показывающая рисунок

Шаблон

Шаблон

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

Примеры кода
Давайте посмотрим, что пришло из шаблона mapper.


MapperByFlag
public interface MapperByFlag<T> {
List<T> getOperations(Request request);
}

AbstractMapperByFlag
public abstract class AbstractMapperByFlag<T> implements MapperByFlag<T> {
private List<T> defaultOperations;
private Map<String, List<T>> mapOfOperations;
public AbstractMapperByFlag(List<T> defaultOperations, Map<String, List<T>> mapOfOperations) {
this.defaultOperations = defaultOperations;
this.mapOfOperations = mapOfOperations;
}
@Override
public final List<T> getOperations(Request request) {
Set<T> selectedFilters = Sets.newHashSet(defaultOperations);
Set<String> flags = request.getFlags();
for (String flag : flags) {
if (mapOfOperations.containsKey(flag)) {
selectedFilters.addAll(mapOfOperations.get(flag));
}
}
return Lists.newArrayList(selectedFilters);
}
}

Mapper Реализации
public RequestValidationByFlagMapper(List<RequestValidation> defaultValidations,
map<String, List<RequestValidation>> mapOfValidations) {
super(defaultValidations, mapOfValidations);
}
public ItemFiltersByFlagMapper(List<Filter> defaultFilters, Map<String, List<Filter>> mapOfFilters) {
super(defaultFilters, mapOfFilters);
}

Я создал тест для абстрактного класса, чтобы показать сам поток.
Тесты реализаций используют Отражение Java, чтобы проверить, что правильные введенные параметры отправлены супер.
Я также показываю  импорт  здесь. Чтобы иметь некоторые ссылки на статический импорт, пакеты и классы mockito и hamcrest.


AbstractMapperByFlagTest
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Map;
import org.eyal.requestvalidation.model.Request;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@RunWith(MockitoJUnitRunner.class)
public class AbstractMapperByFlagTest {
private final static String FLAG_1 = "flag 1";
private final static String FLAG_2 = "flag 2";
@Mock
private Request request;
private String defaultOperation1 = "defaultOperation1";
private String defaultOperation2 = "defaultOperation2";
private String mapOperation11 = "mapOperation11";
private String mapOperation12 = "mapOperation12";
private String mapOperation23 = "mapOperation23";
private MapperByFlag<String> mapper;
@Before
public void setup() {
List<String> defaults = Lists.newArrayList(defaultOperation1, defaultOperation2);
Map<String, List<String>> mapped = ImmutableMap.<String, List<String>> builder()
.put(FLAG_1, Lists.newArrayList(mapOperation11, mapOperation12))
.put(FLAG_2, Lists.newArrayList(mapOperation23, mapOperation11)).build();
mapper = new AbstractMapperByFlag<String>(defaults, mapped) {
};
}
@Test
public void whenRequestDoesNotHaveFlagsShouldReturnDefaultFiltersOnly() {
when(request.getFlags()).thenReturn(Sets.<String> newHashSet());
List<String> filters = mapper.getOperations(request);
assertThat(filters, containsInAnyOrder(defaultOperation1, defaultOperation2));
}
@Test
public void whenRequestHasFlagsNotInMappingShouldReturnDefaultFiltersOnly() {
when(request.getFlags()).thenReturn(Sets.<String> newHashSet("un-mapped-flag"));
List<String> filters = mapper.getOperations(request);
assertThat(filters, containsInAnyOrder(defaultOperation1, defaultOperation2));
}
@Test
public void whenRequestHasOneFlagShouldReturnWithDefaultAndMappedFilters() {
when(request.getFlags()).thenReturn(Sets.<String> newHashSet(FLAG_1));
List<String> filters = mapper.getOperations(request);
assertThat(filters, containsInAnyOrder(mapOperation12, defaultOperation1, mapOperation11, defaultOperation2));
}
@Test
public void whenRequestHasTwoFlagsShouldReturnWithDefaultAndMappedFiltersWithoutDuplications() {
when(request.getFlags()).thenReturn(Sets.<String> newHashSet(FLAG_1, FLAG_2));
List<String> filters = mapper.getOperations(request);
assertThat(filters, containsInAnyOrder(mapOperation12, defaultOperation1, mapOperation11, defaultOperation2, mapOperation23));
}
}

RequestValidationByFlagMapperTest
@RunWith(MockitoJUnitRunner.class)
public class RequestValidationByFlagMapperTest {
@Mock
private List<RequestValidation> defaultValidations;
@Mock
private Map<String, List<RequestValidation>> mapOfValidations;
@InjectMocks
private RequestValidationByFlagMapper mapper;
@SuppressWarnings("unchecked")
@Test
public void verifyParameters() throws NoSuchFieldException, SecurityException, IllegalArgumentException,
IllegalAccessException {
Field defaultOperationsField = AbstractMapperByFlag.class.getDeclaredField("defaultOperations");
defaultOperationsField.setAccessible(true);
List<RequestValidation> actualFilters = (List<RequestValidation>) defaultOperationsField.get(mapper);
assertThat(actualFilters, sameInstance(defaultValidations));
Field mapOfFiltersField = AbstractMapperByFlag.class.getDeclaredField("mapOfOperations");
mapOfFiltersField.setAccessible(true);
Map<String, List<RequestValidation>> actualMapOfFilters = (Map<String, List<RequestValidation>>) mapOfFiltersField.get(mapper);
assertThat(actualMapOfFilters, sameInstance(mapOfValidations));
}
}

Задачи
Есть и другие классы, которые могут быть кандидатами на рефакторинг.
RequestFlowValidation  и  RequestFilter  похожи.
And
RequestValidationsEngineImpl  и  FiltersEngine

To Do 2
Создайте Matcher для отражающей части.

Код
Как всегда, весь код можно найти по адресу:

Тег для этого поста:  все компоненты в

Заключение
Инфраструктура почти готова.
В течение этого времени мы также реализуем фактические классы для потока (проверки, фильтры, действия).
Они не описаны ни в постах, ни в GitHub.
Инфраструктура будет подключена к сервису, который мы используем с помощью Spring.
Это будет объяснено в следующих постах.