Если DTO используются для передачи данных между слоями приложения (база данных, бизнес-логика, представления), то, по Фаулеру, это называется LocalDTO. Некоторые разработчики (включая самого Фаулера) негативно относятся к локальным DTO. Основным отрицательным моментом локального применения DTO является необходимость маппинга данных из одной структуры в другую при их передаче от одного слоя приложения к другому.
Тем не менее, DTO являются важным классом объектов в приложениях и в этой статье я покажу JS-код, который на данный момент считаю оптимальным для DTO (в рамках стандартов ECMAScript 2015+).
Структура данных
Во-первых, в коде должна быть отражена сама структура данных. Лучше всего это делать с использованием классов (аннотации JSDoc помогают ориентироваться в типах данных):
class ConfigEmailAuth { /** @type {string} */ pass; /** @type {string} */ user; }
Это пример простой структуры данных, где каждый атрибут является примитивом. Если некоторые атрибуты сами являются структурами, то класс выглядит примерно так:
class ConfigEmail { /** @type {ConfigEmailAuth} */ auth; /** @type {string} */ from; /** @type {string} */ host; /** @type {number} */ port; /** @type {boolean} */ secure; }
Создание объектов
Как правило, создание экземпляра DTO в половине случаев связано разбором имеющейся структуры данных, полученной «по проводам» с «другой стороны». Поэтому конструктор DTO получает на вход некоторый JS-объект, из которого пытается извлечь знакомые ему данные:
/** * @param {ConfigEmailAuth|null} data */ constructor(data = null) { this.pass = data?.pass; this.user = data?.user; }
В конструкторе структуры со сложными атрибутами используются конструкторы для соответствующих атрибутов:
/** * @param {ConfigEmail} data */ constructor(data = null) { this.auth = (data?.auth instanceof ConfigEmailAuth) ? data.auth : new ConfigEmailAuth(data?.auth); this.from = data?.from || 'default@from.com'; this.host = data?.host || 'localhost'; this.port = data?.port || 465; this.secure = data?.secure || true; }
Если какой-то атрибут представляет из себя массив, то в конструкторе его разбор выглядит примерно так:
class ConfigItems { /** @type {Item[]} */ items; /** * @param {ConfigItems} data */ constructor(data = null) { this.items = Array.isArray(data?.items) ? data.items.map((one) => (one instanceof Item) ? one : new Item(one)) : []; } }
Если какие-то данные должны быть сохранены в атрибуте без разбора, то это тоже возможно (хотя к DTO имеет такое себе отношение):
class SomeDto { /** @type {Object} */ unknownStruct; /** * @param {SomeDto} data */ constructor(data = null) { this.unknownStruct = data?.unknownStruct; } }
Метаданные
Метаданные — это информация о коде. Метаданные позволяют отследить, где используются соответствующие атрибуты объекта:
class SaleOrder { /** @type {number} */ amount; /** @type {number} */ id; } SaleOrder.AMOUNT = 'amount'; SaleOrder.ID = 'id';
Например, при выборке данных из БД:
const query = trx.from('sale'); query.select([ {[SaleOrder.ID]: 'saleId'}, {[SaleOrder.AMOUNT]: 'totalAmount'}, // ... ]);
Результирующую выборку можно напрямую передавать в конструктор SaleOrder, а затем получившийся DTO выкидывать на web в качестве ответа.
Резюме
Если сводить воедино все три составляющих DTO (структура, конструктор, метаданные), то получается примерно такой es-модуль:
import ConfigEmailAuth from './ConfigEmailAuth.mjs'; export default class ConfigEmail { /** @type {ConfigEmailAuth} */ auth; /** @type {string} */ from; // ... /** * @param {ConfigEmail} data */ constructor(data = null) { this.auth = (data?.auth instanceof ConfigEmailAuth) ? data.auth : new ConfigEmailAuth(data?.auth); this.from = data?.from || 'default@from.com'; // ... } } ConfigEmail.AUTH = 'auth'; ConfigEmail.FROM = 'from'; // ...
Подобный подход позволяет извлекать знакомые подструктуры данных из больших структур (конфигурационных файлов, ответов от сервисов и т.п.), непосредственно относящиеся к текущему контексту, а IDE, за счёт аннотаций, имеет возможность помогать разработчику ориентироваться в этой подструктуре.
«Вот и всё, что я могу сказать об этом.» (с)
Информационные системы предназначены для обработки данных, а DTO (Data Transfer Object) является важным концептом в современной разработке. В “классическом” понимании DTO являются простыми объектами (без логики), описывающими структуры данных, передаваемых “по проводам” между разнесенными процессами (remote processes). Зачастую данные «по проводам» передаются в виде JSON.
ссылка на оригинал статьи https://habr.com/ru/post/567040/
Добавить комментарий