Data Transfer Object Is a Shame

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

DTO, насколько я понимаю, является основой паттерна проектирования ORM, который я просто “обожаю”. Но давайте перейдем к делу: DTO - это просто позор, и человек, который его изобрел, просто неправ. Нет оправдания за то, что он сделал.

Кстати, насколько мне известно, его звали Мартин Фаулер. Возможно, он не был единственным изобретателем DTO, но он сделал его законным и рекомендовал его использование. Со всем уважением, он просто ошибался.

Основная идея объектно-ориентированного программирования заключается в том, чтобы скрыть данные за объектами. У этой идеи есть название: инкапсуляция. В ООП данные не должны быть видимыми. Объекты должны иметь доступ только к данным, которые они инкапсулируют, и никогда к данным, инкапсулированным другими объектами. Нельзя спорить об этом принципе - это то, в чем состоит ООП.

Однако, DTO полностью противоречит этому принципу.

Давайте рассмотрим практический пример. Предположим, что это сервис, который получает JSON-документ из некоторого RESTful API и возвращает DTO, который мы затем можем сохранить в базе данных:

Book book = api.loadBookById(123);
database.saveNewBook(book);

Я думаю, что внутри метода loadBookById() произойдет следующее:

Book loadBookById(int id) {
  JsonObject json = /* Load it from RESTful API */
  Book book = new Book();
  book.setISBN(json.getString("isbn"));
  book.setTitle(json.getString("title"));
  book.setAuthor(json.getString("author"));
  return book;
}

Я правильно понимаю? Я уверен в этом. Мне это уже кажется отвратительным. В любом случае, продолжим. Вот что, скорее всего, произойдет в методе saveNewBook() (я использую чистый JDBC):

void saveNewBook(Book book) {
  Statement stmt = connection.prepareStatement(
    "INSERT INTO book VALUES (?, ?, ?)"
  );
  stmt.setString(1, book.getISBN());
  stmt.setString(2, book.getTitle());
  stmt.setString(3, book.getAuthor());
  stmt.execute();
}

Эта Book является классическим примером паттерна проектирования передачи данных. Все, что она делает, это передает данные между двумя участками кода, двумя процедурами. Объект book довольно глупый. Он знает только… ничего. Он ничего не делает. На самом деле, он вообще не является объектом, а скорее пассивной и анемичной структурой данных.

Какой правильный дизайн? Их несколько. Например, этот мне кажется хорошим:

Book book = api.bookById(123);
book.save(database);

Это то, что происходит в bookById():

Book bookById(int id) {
  return new JsonBook(
    /* RESTful API access point */
  );
}

Вот что происходит в Book.save():

void save(Database db) {
  JsonObject json = /* Load it from RESTful API */
  db.createBook(
    json.getString("isbn"),
    json.getString("title"),
    json.getString("author")
  );
}

Что произойдет, если в JSON будет много других параметров книги, которые нельзя легко передать как параметры в один метод createBook()? Как насчет этого:

void save(Database db) {
  db.create()
    .withISBN(json.getString("isbn"))
    .withTitle(json.getString("title"))
    .withAuthor(json.getString("author"))
    .deploy();
}

Есть и другие варианты. Но основная точка в том, что данные никогда не выходят за пределы объекта book. После создания объекта данные не видны и не доступны никому другому. Мы можем только попросить наш объект сохранить себя или напечатать себя на какой-то носитель, но никогда не получим от него какие-либо данные.

Сама идея DTO неправильна, потому что она превращает объектно-ориентированный код в процедурный код. У нас есть процедуры, которые манипулируют данными, и DTO - это просто контейнер для этих данных. Не думайте так и не делайте так.

PS. Есть еще несколько названий DTO: бизнес-объекты, объекты домена (не в DDD), объекты сущностей, JavaBeans.

Translated by ChatGPT gpt-3.5-turbo/36 on 2023-10-01 at 08:13

sixnines availability badge   GitHub stars