Рубрики
Без рубрики

Принцип надежности

Принцип надежности. Помеченный как java, дизайн.

“Будьте консервативны в том, что вы отправляете, будьте либеральны в том, что вы принимаете”

Этот мудрый совет, называемый также принципом надежности или законом Постеля, оказывается действительно полезным во всех тех случаях использования, когда приложения отправляют сообщения между собой. Часто эти сообщения содержат полезную нагрузку Json, отправляемую по протоколу HTTP. Типичный сценарий включает в себя:

  1. Клиент сериализует модель в формате Json и отправляет ее по протоколу HTTP на сервер.
  2. С другой стороны, сервер получает сообщение, извлекает тело запроса, которое является нашим Json, десериализует его обратно в модель (которая может отличаться от модели клиента), а затем выполняет с ним операции.

Здесь нет ничего действительно нового. Даже в этом простом и распространенном сценарии необходимо принять довольно важное решение: формат данных. В данном случае используется структура сообщения в формате json. Давайте предположим, что мы разрабатываем конечную точку rest, которая выполняет операции с пользователем. Основываясь на требованиях, которые у нас есть Похоже, что для того, чтобы смоделировать нашего пользователя, нам нужны только имя и фамилия. Json для нашего пользователя может выглядеть следующим образом:

{ "firstname" : "Diego", "lastname" : "Maradona"}

По сути, мы определили неявный контракт между двумя нашими частями системы (клиентом и сервером). Для того чтобы они работали как единое целое, им необходимо согласовать формат данных. Несмотря на то, что наше решение выглядит разумным, мы нарушили еще один важный принцип:

“Увеличьте количество не принятых решений”

Определяя структуру сообщения, мы решили, что json, который будет отправлен клиентом на сервер, имеет два поля (“имя” и “фамилия”) и может содержать только эти два поля. Это как раз вторая часть предложения, которая звучит как ненужное решение. Действительно, мы затрудняем изменение нашей системы без веской причины. С этим связана пара минусов:

  1. Если в будущем нам придется добавить новое поле для нашего пользователя, это можно сделать, только изменив клиент и сервер одновременно и развернув их вместе.
  2. Либо клиент, либо сервер могут изменить свою собственную модель, что приведет к ошибке, обнаруживаемой только во время выполнения (если мы не проведем дополнительную работу по тестированию нашего контракта).

Эволюция схемы

Проблема довольно распространена и проходит под общим названием Schema evolution, определение, которое включает также такие вещи, как эволюция схемы базы данных. Так что нет ничего удивительного в том, что существуют библиотеки, которые помогают в этом (например, Protocol buffer или Avro). Несмотря на то, что я немного поэкспериментировал с ProtoBuf, у меня редко возникала необходимость использовать его (в основном потому, что он содержит надмножество функций, которые мне действительно были нужны). Действительно, большинство Java-приложений, над которыми я работал, уже имели Jackson или Gson в качестве зависимости. Оказывается, что оба они могут быть настроены так, чтобы стать толерантным читателем/писателем и поддерживать эволюцию схемы. Если вы программист Java, вы, вероятно, знакомы с Jackson или Gson, которые являются двумя популярными библиотеками для преобразования Java POJO из/в Json. В случае, если это не так, вы можете ознакомиться с этим учебным пособием . В любом случае, следовать примерам должно быть довольно легко.

Пример

В следующих примерах показано, как настроить Jackson на устойчивость к изменениям схемы. Примеры написаны на Java, и вы можете найти весь код, используемый в статье, в github.com/napicella/java-jackson-tolerant-reader .

Вот как выглядит модель для нашего пользователя для нашего rest api:

public class User {
    private String name = "";
    private String surname = "";

    public User() {
    }

    public User(String name, String surname) {
        this.name = name;
        this.surname = surname;
    }
    // setters and getters …     
}

Сервер получает json и десериализует его в пользовательский тип.

public void handle(Request request) throws IOException {
  ObjectMapper mapper = new ObjectMapper();  
  User user = mapper.readValue(request.body(), User.class);
   // do something with User
}

В приведенном выше примере Jackson object mapper сопоставляет строку в запросе.тело к пользовательскому классу. Он выдает исключение, если json не соответствует классу. Например, предположим, что клиент отправляет нам следующий json:

{ "firstname" : "Diego", "lastname" : "Maradona", "middlename": "Armando" }

Джексон собирается выдать исключение, потому что свойство “middlename” не определено в классе User.

Терпимый читатель – 1

Что мы можем с этим поделать? Давайте попробуем увеличить количество не принятых решений, определив, что json должен содержать по крайней мере “firstname” и “lastname”. Джексон позволяет определить это двумя способами: программно и с помощью аннотаций Java. Без потери общности мы собираемся сделать это с помощью аннотаций:

@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
//…

Параметр @JsonIgnoreProperties(ignoreunknown) указывает Джексону игнорировать любые неизвестные свойства во входных данных JSON без исключения.

    @Test
    public void test() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        User user =
                mapper.readValue("{ \"name\" : \"Diego\", \"surname\" : \"Maradona\", \"middlename\" : \"Armando\"}",
                        User.class);

        assertThat("TOLERANT-READER. The json might contain property that are not defined in the pojo. " +
                        "Ignore them!" +
                        "How: use @JsonIgnoreProperties(ignoreUnknown = true) annotation on the POJO",
                user.getName(), is("Diego"));
    }

Делая это, сервер больше не будет жаловаться на поле, которое не определено в классе User.

Терпимый читатель – 2

Во многих случаях сервер может справиться с тем фактом, что свойство отсутствует, присвоив ему значение по умолчанию. Для примера предположим, что сервер может принять пустого пользователя, и в этом случае он присвоит значения по умолчанию для имени и фамилии. Для этого необходимо настроить две вещи: 1 – Установить значения по умолчанию для имени и фамилии в модели

@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
    private String name = "Dries";
    private String surname = "Mertens";
    // rest of the class as before

2 – Скажите Джексону, чтобы он не сериализовал нулевые значения, используя @JsonInclude(JsonInclude. Включать. NON_NULL) аннотация.

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
    private String name = "Dries";
    private String surname = "Mertens";
    // rest of the class as before

Json Включает В Себя. Включать. NON_NULL гарантирует, что нулевые значения не будут сериализованы, поэтому json, полученный сервером, вообще не будет содержать этих свойств. Установка значений по умолчанию приводит к тому, что Джексон использует это значение в случае, если свойство отсутствует в Json. В результате следующий тест имеет зеленый цвет:

    @Test
    public void test2() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        User user = new User("Michael", null);
        String json = mapper.writeValueAsString(user);

        User deserializedUser = mapper.readValue(json, User.class);

        assertThat("TOLERANT-WRITER. Don't serialize null values. A tolerant reader prefers no value at all, " +
                        "because in that case can provide a default." +
                        "If you serialize null, there is no way for the reader to understand that actually the property" +
                        "is missing." +
                        "How: use @JsonInclude(JsonInclude.Include.NON_NULL) annotation on the POJO",
                deserializedUser.getSurname(), is("Mertens"));

        assertThat(deserializedUser.getName(), is("Michael"));
    }

Выводы

Определенно, есть гораздо больше, что можно сказать об эволюции схемы в мире rest api. Также интересны способы, которые мы можем использовать для тестирования таких контрактов, но это обсуждение в другой раз. Конечно, мы будем очень признательны за любую обратную связь!

Оригинал: “https://dev.to/napicella/robustness-principle-with-jackson-3h5”