Статьи

Не разбирайте, используйте синтаксический анализ объектов

Традиционный способ интеграции объектно-ориентированного бэкэнда с внешней системой — через объекты передачи данных , которые сериализуются в JSON перед выходом и десериализуются при возвращении. Этот способ так же популярен, как и неправильный. Часть сериализации должна быть заменена принтерами , которые я объяснил ранее. Вот мой взгляд на десериализацию, которая должна быть сделана — угадайте, что — объектами.

La science des rêves (2006) Мишеля Гондри

Скажем, есть внутренняя точка входа, которая должна зарегистрировать новую книгу в библиотеке, поступающую в JSON:

1
2
3
4
5
{
  "title": "Object Thinking",
  "isbn: "0735619654",
  "author: "David West"
}

Кроме того, есть объект класса Library , который ожидает, что объект типа Book будет передан его методу register() :

1
2
3
4
5
class Library {
  public void register(Book book) {
    // Create a new record in the database
  }
}

Скажем также, что тип Book имеет простой метод isbn() :

1
2
3
interface Book {
  String isbn();
}

Теперь вот точка входа HTTP (я использую Takes and Cactoos ), которая принимает запрос POST multipart/form-data и регистрирует книгу в библиотеке:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class TkUpload implements Take {
  private final Library library;
  @Override
  public Response act(Request req) {
    String body = new RqPrint(
      new RqMtSmart(new RqMtBase(req)).single("book")
    ).printBody();
    JsonObject json = Json.createReader(
      new InputStreamOf(body)
    ).readObject();
    Book book = new BookDTO();
    book.setIsbn(json.getString("isbn"));
    library.register(book);
  }
}

Что не так с этим? Ну, несколько вещей.

Во-первых, это не для повторного использования. Если бы нам понадобилось нечто подобное в другом месте, нам пришлось бы снова написать эту обработку HTTP и анализ JSON.

Во-вторых, обработка ошибок и валидация также не подлежат повторному использованию. Если мы добавим его в метод выше, нам придется копировать его везде. Конечно, DTO может инкапсулировать это, но это не то, для чего обычно DTO.

В-третьих, приведенный выше код является довольно процедурным и имеет много временных связей .

Лучшим дизайном было бы скрыть этот разбор внутри нового класса JsonBook :

01
02
03
04
05
06
07
08
09
10
11
12
class JsonBook implements Book {
  private final String json;
  JsonBook(String body) {
    this.json = body;
  }
  @Override
  public String isbn() {
    return Json.createReader(
      new InputStreamOf(body)
    ).readObject().getString("isbn");
  }
}

Тогда точка входа RESTful будет выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
public class TkUpload implements Take {
  private final Library library;
  @Override
  public Response act(Request req) {
    library.register(
      new JsonBook(
        new RqPrint(
          new RqMtSmart(new RqMtBase(req)).single("book")
        ).printBody()
      )
    );
  }
}

Разве это не элегантнее?

Вот несколько примеров из моих проектов: RqUser из zerocracy / farm и RqUser из yegor256 / jare .

Как видно из приведенных выше примеров, иногда мы не можем использовать implements потому что некоторые примитивы в Java — это не интерфейсы, а final классы: String — «идеальный» пример. Вот почему я должен сделать это:

1
2
3
4
5
6
class RqUser implements Scalar<String> {
  @Override
  public String value() {
    // Parsing happens here and returns String
  }
}

Но помимо этого, эти примеры прекрасно демонстрируют принцип «разбора объектов», предложенный выше.

Опубликовано на Java Code Geeks с разрешения Егора Бугаенко, партнера нашей программы JCG . См. Оригинальную статью здесь: не анализируйте, используйте синтаксический анализ объектов

Мнения, высказанные участниками Java Code Geeks, являются их собственными.