Сразу извиняюсь за свою манеру повествования, ибо она похоже больше на поток сознания, но главное уловить суть, которую я хочу донести.
Я хочу подготовить небольшой цикл публикаций про работу с данными:
Общий обзор FOR-архитектуры (эта статья)
Взгляд на валидацию данных и частные применения
Разбор FOR со стороны поставщика данных (ключевая часть, нужная для понимания всей картины)
Гомогенность данных в больших распределенных системах (идея, выросшая из валидации и использующая те же механизмы)
Начнем с определения кому надо
Если рассматривать разработку веб приложений, то я бы выделил 2 вектора:
Первый - будем называть студийная, когда глубина проработки ниже и обусловлена срочностью бизнеса, ему "надо продавать сегодня".
Второй - enterprise, это когда есть 1 продукт (или больше, что не важно в данном контексте) и он должен быть сделан на уровне, когда поддержка должна быть простой и легкой десятилетиями.
Если мы рассматриваем студийный подход - то имеем более низкую вовлеченность в предметную область (domain driven) и подходы ограничены более простым и менее проработанным ТЗ (как правило, судя по моему опыту, не пытаюсь сказать, что в студиях пишут хуже, просто там как правила оплата не по часам, а по проектно, а с большими студиями я не сталкивался) а вот в энтерпрайзе уже надо сделать более качественно, потому что нам (писателям кода) и надо будет поддерживать и расширять код под постоянно изменчивый бизнес (и лишь такой бизнес хорошо живет, а не тот, кто может позволить быть в статике).
Данный параграф обозначил аудиторию моего подхода и моих идей, я полагаю не стоит в "быстрой" разработке использовать то, что я предлагаю, потому что это будет сильно дольше и, соответственно, дороже.
Начнем с ситуации. Рассмотрим 2 кейса.
Первый:
У вас react/vue/что-то иное
Есть некий компонент (форма создания какой-то элемента)
Поля формы сильно отличаются от контекста и типа этого элемента (к примеру, товары в магазине)
Программист в начале имел всего 3 типа и всё было хорошо. Он за каждым ходит на бек, прямо внутри всего чудного компонента
Где-то в дебрях:
let form_data = axios.get(element_type);
По моему видению, это всегда должно быть иначе
let form_data = FormProvider.get_by_type(element_type);
Плюсы очевидны по мне.
Низкая связанность
Не зависимость от поставщика
Легкий рефакторинг (данных)
Мы можем добавить ttl-кеш в наш провайдер (логи, единая точка валидации отправляемых и получаемых данных по роута и многое другое), формы редко меняются. В таком случае форма будет уже мгновенно вызываться
Концепция работы Data Providers на фронте:

На бекенде, это выглядит примерно также:

Итого мы имеем определение Data Provider - это интерфейс работы с данными внутри вашего приложения
И второе нужное нам определение FOR - это аббревиатура filters+options+response
Моё видение реализации
Data Provider имеет 2 механизма работы:
DataProvider::search($filters, $options, $response) статичный метод на входе, который покрывает 80% задач. Если всё в вашей системе представлено в виде элементов - то данный метод даст высший уровень гибкости вашему API, потому что позволит находить что угодно в рамках свои полномочий.
Также мы тут добавляем некий массив примитивных операций со статичным интерфейсом, когда фильтры избыточны, их может быть достаточно много и они также могут скрывать внутри себя фильтры, обеспечивая единообразие и одновременно упрощенный синтаксис.
Примеры:
CompanyProvider::get_by_id()
JobProvider::get_actual()
Конкретика
Архитектура состоит из 2 частей: пользователь и поставщик.
Поставщика я хочу детальнее описать позже, пока приведу пример реализации, который более нагляден
(код размещен в папке: DataProvider ElementSearch, эта часть будет подробно разобрана в другой статье) https://gitlab.com/dev_docs/software_architecture/for-architecture/-/blob/main/code_examples/DataProvider%20ElementSearch%20%5Bprototype%5D/BaseFilters.php
Концепция его работы:

:
Теперь взгляд со стороны использования:
Filters - определяет условия поиска (что и где ищем)
Options - определяет область поиска (все что не filter и не response)
Response - определяет модификацию / формат ответа (как обработаем ответ)
Примеры запроса будут размещены тут: https://gitlab.com/dev_docs/software_architecture/for-architecture/-/tree/main/code_examples/DataProvider%20ElementSearch%20%5Bprototype%5D/Examples
Реализация внутри системы (back / php)
(код размещен в папке: DataProvider ElementSearch, эта часть будет подробно разобрана в другой статье) https://gitlab.com/dev_docs/software_architecture/for-architecture/-/tree/main/code_examples/DataProvider%20ElementSearch%20%5Bprototype%5D
Фильтр - массив условий, фильтр состоит из:
object - объект поиска, состоит из:
element_type_id (element_type) - сама сущность (user, sku, company, news, properties ...)
element_id - подсущность. к примеру, id свойства или поле, к примеру: user_name или если props, то 69
operator_id (operator) - оператор поиска (у меня они все зарегистрированы и есть в виде констант, это лучше, чем их строковое представление) https://gitlab.com/dev_docs/software_architecture/for-architecture/-/blob/main/code_examples/DataProvider%20ElementSearch%20%5Bprototype%5D/Operators.php
value - значение поиска (может быть любой тип, все зависит от контекста)
Примеры фильтров:
DataProvider.search( { filters : [ { object : { element_type_id : _CONSTANTS.ELEMENT_TYPES.SOME_ELEMENT_TYPE_ID, element_id : 'some_id' }, operator_id : _CONSTANTS.OPERATORS.EQUAL, value : options.element_id } ] } )
PaymentProvider.search( { filters : [ { object : { element_type_id : _CONSTANTS.ELEMENT_TYPES.PAYMENT, element_id : 'payment_id' }, operator_id : _CONSTANTS.OPERATORS.EQUAL, value : this.element_id } ], response : { structure_mode : 'listing' } })
И немного посложнее
{ filters : [ { object : { element_type_id : _CONSTANTS.ELEMENT_TYPES.CATEGORY, element_id : null }, operator_id : _CONSTANTS.OPERATORS.EQUAL, value : 27 }, { object : { element_type_id : _CONSTANTS.ELEMENT_TYPES.ITEM_STATUS, element_id : null }, operator_id : _CONSTANTS.OPERATORS.IN, value : [103, 101] } ], response : { structure_mode : 'listing', add : { relations : [ { relation_id : 160, // связь - подчинённые relation_field : 'master', data_fields : 'IDS' }, { relation_id : 125, // связь с должностью relation_field : 'slave', data_fields : { general : ['item_full_name'] } }, { relation_id : 163, // связь с физическим лицом relation_field : 'slave', data_fields : { property : [ 18, // фамилия 389 // инициалы ] } } ] } } }
Именно это часть из архитектуры идёт в модель, а затем в БД (если речь про бек) или на апи (если речь про фронт).
Options - это специальные параметры, которые будут уникальны в каждом отдельно взятом продукте. Пояснение: В не релизном состоянии, когда код все ещё пишется и нет абсолютно точного описания задачи и конечной цели, когда не проработана вся комбинаторика - есть кейсы, когда в запрос надо добавить некие опции, чтобы получить результат (назовем это прототипный код, к примеру). Это место, куда вы можете пихать, пока не поймете как с этим быть.
Response - определяет тот формат (модель, дата-класс, ...) который вы хотите получить или, к примеру, вы хотите получить только ids элементов.
Дополнительный плюс этого подхода (как и любого другого, где есть сразу заложенная абстракция) - вы в любой момент без изменения бизнес логики сможете отказаться от реализации (которая в примере) и заменить её на более оптимизированный код (или даже отдельный сервис)
Немного рефлексии, для цельности картины:
Основные тезисы, которых точно стоит придерживаться в работе с данными
Абстракции - наше всё
Принцип единой ответственности (в чуть более широкой интерпретации) - должен быть в основе каждого вашего технического и не очень решения
Простота - во всём, простой код - легче понять и развить (исправить)
Бизнес логика и отдельные модули - не должны зависеть от поставщика решений (низкая связанность)
Каждое разрабатываемое приложение должно иметь четкую, единообразную структуру (но бывает много исключений)
