Pull to refresh
46
-5.5
Alex Gusev@flancer

Я кодирую, потому что я кодирую…

Send message
Я рассматривал ситуацию, когда я — разработчик базового функционала. Мне нужно предложить разработчикам плагинов такую модель взаимодействия, которая бы позволила им независимо ни от меня, ни друг от друга дополнять базовый функционал всем, чем им заблагорассудится. Если отталкиваться от нашего примера, то я ввожу в приложение сущность Клиент и обеспечиваю какие-то базовые функции (например, аутентификацию по логину и паролю), а разработчики плагинов уже сами добавляют свое (разработчик плагина 1 добавляет реферальную программу и новый атрибут ref к сущности Клиент, разработчик плагина 2 — аутентификацию по email'у и новый атрибут email). Я, как разработчик базового функционала ничего не знаю какие плагины разрабатываются сейчас или будут разрабатываться в будущем. Разработчики плагинов также не знакомы друг с другом и не имеют представления, какие еще плагины будут стоять рядом с их собственными. Но, разумеется, все разработчики имеют представление о базовом функционале и базовых структурах данных (сущностях/атрибутах). В какой комбинации весь этот зоопарк будет собран на стороне клиента — зависит от клиента, его постоянно меняющихся запросов и выхода новых плагинов. Количество сущностей (таблиц в БД) и набору их атрибутов (колонки таблиц) изменяется с установкой каждого нового плагина. Единая команда разработчиков есть только у базисного функционала, остальные разработчики могут строить свои плагины либо на основании базисного функционала, либо на основании других плагинов (по большому счету независимы друг от друга). Вот в такой среде и функционирует DataObject.
1. Ваш «семантически эквивалентный код» ни в коем разе не опровергает мой демонстрационный пример, что предложенный подход сокращает кол-во строк кода минимум в 2 раза для программ на PHP.
2. Если это невозможно сделать на PHP, то попробуйте изложить ваши мысли на Java/C/JavaScript/Python. У меня есть кое-какой опыт в этих языках и, надеюсь, я смогу понять, что вы хотите донести.
3.Т.е., вы утверждаете, что название «функциональная парадигма» не отражает сути?
4. Согласен, разница есть. Считайте, что я привел пример для случая, когда разработчик базового функционала ничего не знает о разработчиках плагинов, а разработчики плагинов знают только базовый функционал и не могут никаким образом повлиять ни на него, ни друг на друга.
5. Спешу вас расстроить а) PHP вполне позволяет строго использовать типизацию, б) вы все еще не поняли, что даже самая расстрогая типизация не дает возможности создавать изолированным командам разработчиков общее приложение, даже наоборот — мешает.
6. В самом общем случае вообще ничего нет — есть и такая теория.
Дополнение по пункту 3: первых 4 плагина как раз и занимаются поиском подобных конфликтов. Которые возникают как раз вследствие того, что большинство разработчиков плагинов друг с другом элементарно не знакомы.
1. Нет, подход не исключительно для PHP. Но я не могу сказать, какая разница, потому что я не понимаю, что вы написали. Раз уж вы задаете вопросы по статье, я полагаю, вы ее прочитали. Вопросов по PHP у вас не возникло — я делаю вывод, что этот язык вам знаком. А раз знаком, значит вы можете изложить свои мысли на нем. В конце концов, джентельмены во время дуэли оружие не меняют, а мы начали с PHP.
2. Я сделал вывод о том, что «функциональная парадигма делает акцент на вычислениях» на основании названия этой самой парадигмы, вы в свою очередь сказали, что «название обманчиво» и привели в качестве примера LISP. Я обратил ваше внимание на то, что мы говорим не про LISP, а именно про «функциональную парадигму» и в данном конкретном случае название вполне соответствует сути:
Функциона́льное программи́рование — раздел дискретной математики и парадигма программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании).

А теперь вы предлагаете мне показать функциональный язык (из мейнстрима), в котором нет обширной поддержки структур данных.. Похоже, вы просто потеряли нить обсуждения.
3. Потому что при распределенной разработке вполне возможен вариант, когда разработчики не могут договориться друг с другом просто в силу того, что они не знакомы друг с другом. Более того, я спорадически интегрирую такие решения друг с другом. Это Magento.
4. Но это и не означает обратного.
5. Если вы не видите выигрыша, то вам не и стоит этого делать. Если вдруг увидите выигрыш — тогда и применяйте.
Спасибо за пример.

Вот в этом месте лишние атрибуты. Разработчик базового функционала заложил только $id. Атрибут $ref заложил разработчик плагина 1, атрибут $email — разработчик плагина 2. Разработчики друг с другом не знакомы. Поэтому базовый класс выглядит по идее как-то так:
class Customer implements CustomerInterface
{
    private $id;

    public function getId(): int
    {
        return $this->id;
    }

    public function setId(int $id)
    {
        $this->id = $id;
        return $this;
    }
}

После чего у интегратора и начинается свистопляска с различными переопределениями. Акцентирую еще раз — весь фокус в том, что разработчики плагинов ничего не знают друг про друга, каждый из них знает только структуру базового объекта и свои собственные дополнения.

По поводу сохранения. В Magento при обращении к БД считывается структура таблиц со всеми атрибутами и при сохранении фильтруются «лишние» из $data. Это дело кэшируется, поэтому иногда получается весьма забавно, когда поля в таблице есть, а сохранить в них ничего нельзя. Но все лечится путем удаления кэша.
Смотрите, в базовой имплементации есть объект Customer, есть два плагина, разработчики которых не знают друг о друге, но знают, что есть базовая имплементация Customer'а. Каждый из разработчиков добавляет по одному атрибуту к базовой сущности Customer:
CREATE TABLE Customer (
  Id int NOT NULL AUTO_INCREMENT COMMENT 'this is attribute from base implementation',
  Ref varchar(255) DEFAULT NULL COMMENT 'this attribute is added by plugin 1',
  Email varchar(50) DEFAULT NULL COMMENT 'this attribute is added by plugin 2',
  PRIMARY KEY (Id)
)

и создает свои собственные расширения базового объекта CustomerRef & CustomerEmail:
/**
 * This is base object.
 *
 * @method int getId()
 * @method void setId(int $data)
 */
class Customer extends DataObject {
}

/**
 * This is extended customer (plugin 1).
 *
 * @method string getRef()
 * @method void setRef(string $data)
 */
class CustomerRef extends Customer {
}

/**
 * This is extended customer (plugin 2).
 *
 * @method string getEmail()
 * @method void setEmail(string $data)
 */
class CustomerEmail extends Customer {
}

допустим, есть внешний класс для выполнения операций с БД (разделение инструкций и данных, детали его реализации на данный момент не важны). В этом случае в базовой имплементации подгружается объект со всеми своими атрибутами и используется в таком виде в базовом workflow. В местах, где включаются обработчики плагинов (допустим, по событию), они преобразовывают базовые данные в понятный для себя вид и работают со «знакомыми» атрибутами, игноря атрибуты незнакомые. В конце базового workflow происходит сохранение объекта в БД:
        // data loader (base impl.)
        $base = $repo->load('Customer', 21);
        
        // plugin1 code on event 1
        $cust1 = new CustomerRef($base);
        $id1 = $cust1->getId();
        $ref = $cust1->getRef();

        // plugin2 code on event 2
        $cust2 = new CustomerEmail($base);
        $id2 = $cust2->getId();
        $cust2->setEmail('any@email.com');

        // data saver (base impl.)
        $repo->save('Customer', $cust2);

По сути дела производный от DataObject класс в некотором роде и является для среды выполнения адаптером к данным, хранимым в ассоциативном массиве. Все то же самое, при желании, можно изобразить и просто на ассоциативном массиве, только без autocomplete'а в IDE и без возможности поиска Find Usages.
1. Сравнил. Это точно не PHP.
2. Я имел в виду не LISP, я имел в виду именно «функциональное программирование».
3. «Строго типизированный» DTO не удовлетворяет условию «распределенной разработки» — там по определению не может быть строгой типизации.
4. Практика «игнорирую то, чего не знаю» весьма хорошо сочетается с «универсальным контейнером».
5. То, что это ассоциативный массив никак не отменяет того, что он может использоваться в качестве «универсального контейнера данных».
Ну вот! Так это то, о чем я и писал!!! Делая класс на базе DataObject вы делаете его:
а) типизируемым;
б) дополняемым;

Меня в Magento весьма сильно напрягало, что в таких структурах никогда точно не знаешь, что лежит, и сильно радовало, что в них всегда можно положить все, что угодно. Проблема в том, что в некоторых случаях множественное наследование не работает. Например, когда два-три расширения переопределяют один и тот же класс основного функционала. Разработчик каждого плагина не знает о существовании других плагинов, да и не должен. А я, как интегратор, должен сам решать в каком порядке мне выстроить иерархию наследования в конечном приложении. И иногда это бывает довольно забавной задачей, если учесть, что порой приходится совмещать в одном приложении по 15-20 сторонних плагинов.

В подобной ситуации вот такой DataObject + «гарвардский» подход (отделение данных от инструкций, функциональное программирование, если кому удобнее) может дать весьма ощутимые бонусы в виде конвейеризации обработчиков некоей «структуры данных».
По поводу использования обработчиков (SQL-процедур/функций и триггеров) рядом с данными есть различные мнения. Нет универсального решения — «каждому решению присуще сожаление» (с) Применяемый способ решения зависит от условий задачи. Обычный массив можно использовать в качестве «универсального контейнера», а можно для тех же целей его доработать (как это было сделано в той же Magento) и использовать его более эффективно. Сравните:
    $transId = $data['Sales'][3]['Payments'][0]['Transactions'][0]['Id'];
    $transId = $data['/Sales/3/Payments/0/Transactions/0/Id'];

Во втором случае адрес элемента в массиве данных напоминает привычные пути (файловые, XPath, JSONPath), что позволяет использовать уже существующие методы работы с подобной информацией. При помощи аннотаций его можно слегка «дотипизировть», оставляя тем не менее дополняемым на уровне исполнения.

Вы правы в том, что все данные одной не очень большой базы можно воткнуть в обычный ассоциативный массив. И да, это будет универсальный контейнер. А если его использовать без добавления инструкций по обработке, то это и будет универсальный контейнер данных. Вот только не будет отражена структура «известных данных», что изрядно усложнит разработку. Хотя можно сделать «типизатор» данных, который для любого произвольного массива будет извлекать данные нужной структуры. Это тоже путь.
1. А разве это не очевидно?
/**
 * @method array getBaseCalcData()
 * @method void setBaseCalcData(array $data)
 */

вместо
    private $_baseCalcData;
    public function getBaseCalcData() {
        return $this->_baseCalcData;
    }
    public function setBaseCalcData(array $data) {
        $this->_baseCalcData = $data;
    }

Даже визуально видно, что сложность уменьшилась минимум в 2 раза.

2. На основании названия.

3. Если DTO полностью удовлетворяет условиям, значит его можно использовать в качестве «универсального контейнера данных».

4. Если есть расширяемость на уровне данных, то значит в SOA уже используется концепция «универсального контейнера данных». Да, я знаю, как расшифровывается XML.

5. Нет, это вы не поняли. Если вы используете любой способ структурирования данных для формирования «посылки» с произвольным содержимым, то вы таким образом создаете «универсальный контейнер данных». Даже если сами об это не подозреваете. А я просто обращаю ваше внимание на этот аспект «обработки структурированных данных». Вы могли всю жизнь программировать в определенном стиле, не подозревая об этом, а потом я пришел и сообщил, что для вашего стиля характерны определенные черты, и дал ему название «функциональное программирование».

6. Если он полностью удовлетворяет, то является.
1. Я решал задачу уменьшения сложности при производстве ПО.
2. Функциональная парадигма делает акцент на вычислениях, я же делаю ацент на данных. В обоих случаях рассматривается один и тот же посыл — «отделение данных от инструкций», который был предложен еще 70-80 лет назад Говардом Эйкеном.
3. В таком случае DTO нельзя использовать как «универсальный контейнер данных».
4. Не отличается, но дополняет — вводит расширяемость на уровне данных в «чётко определённые интерфейсы» SOA.
5. «Универсальный контейнер данных» — это более узкий термин, чем «любой способ обработки структурированных данных». «Контейнер» подразумевает краткосрочное хранение при передаче, «универсальный» подразумевает независимость от содержимого, «данные» подразумевают данные. Это все равно спросить «Зачем вводить понятие 'функционального программирования', когда уже есть всем понятный термин 'программирование'».
6. В таком случае, типизируемый объект не может являться универсальным контейнером данных.
7. См. п.2
Спасибо на добром слове. Посмотрел. JSONNET — это именно то, о чем я написал (разделение кода и данных), вывернутое наизнанку (совмещение данных и их обработки).
1. Да. Теперь мои мысли приобрели форму ссылки. Это удобно.
2. В некотором смысле этот подход является следствием функциональной парадигмы.
3. В таком случае «DTO — как паттерн» можно использовать в качестве универсального контейнера данных;
4. Знаю.
5. Чтобы не смешивать данные и обрабатывающий их код — как в DTO.
6. Если типизируемый объект позволит добавлять к себе любую структуру данных и выдавать ее — то он и будет являться универсальным контейнером. Именно это и позволяют делать объекты, наследующие от DataObject.
Если отталкиваться от аббревиатуры MVC, то M — это «чистые данные», а V & C — это типизация, наследование и все прочее. В «коде» (VC) можно и нужно использовать все, что позволяет уменьшить сложность и увеличить управляемость, а в данных (M) — только данные (аналог POJO). Потому что именно это уменьшает сложность и увеличивает управляемость. Код в данных — это как SQL-процедуры в БД.
1. Да. Причем попытаться уложиться в несколько экранов.
2. Слышал.
3. Если DTO не зависит от типа переносимых данных — да, это он и есть.
4. Buzzword SOA — не понимаю, о чем это.
5. Самое главное в этой концепции: мухи (код обработчиков) отдельно, а котлеты (обрабатываемые данные) — отдельно. А если «мухи» и/или «котлеты» будут в виде типизируемых объектов — так оно даже и лучше.
1. Суть подхода вы уловили — в качестве контейнера можно использовать и массив, и ArrayObject. Самое главное, чтобы функционал, который реализован в контейнере, не зависел от типа данных, которые в нем содержатся. Как в том же ArrayObject.
2. Можно при сериализации использовать и внешнюю функцию — это не принципиально. Принципиально, что сериализуются любые данные — и те, которые уложил в контейнер разработчик основного функционала, и те, которые в этот же контейнер уложил разработчики плагинов для основного функционала.
3. Один входной объект для функции и один выходной — это принципиальный вопрос для конвейеризации. Если вы посмотрите на web-сервисы, то увидите, что именно так и есть — на вход подается одна структура данных (request), на выходе получается другая, но тоже одна (response). В request'е объединены воедино все данные, необходимые для выполнения операции сервисом (как и в запросе) — сервис сам может выбрать из запроса нужные ему данные (а плагин к сервису может выбрать из этого же запроса нужные ему).
1. Я сравнивал с архитектурой Эйкена не акцессоры, а именно сам подход, когда данные отделяются от обработчиков.
2. Любая DBMS является довольно-таки универсальным контейнером данных.
3. Я использую подход с акцессорами только лишь из-за привычки ожидать подсказки от IDE после набора префикса get/set. Мне так удобнее. Чтобы не путать с другими методами при использовании автодополнения. Но если мы работаем с контейнером данных, то там не должно быть других методов, кроме акцессоров, так что можно обойтись и самими свойствами.
4. В моем примере A, B и C — это представители различных уровней приложения, разных «вселенных». Это как запросить данные из программы на JavaScript у сервиса, написанного на Java. Java-сервис может изменять запрос сколько угодно — он работает с копией, созданной на основании транскода (XML в случае с SOAP).
5. Менять входные данные — не очень хорошая практика, даже если ты создаешь весь код сам, а уж если ты работаешь в команде, то надо много раз подумать, чтобы решится на это.
6. Если все-таки приходится сталкиваться с тем, что моя или чья-то еще функция/метод изменяет входные данные, то — да, меня это напрягает.
Люблю я хабровчан за их неравнодушие! Этим-то и ценно общение — обменом информацией или эмоциями, на худой конец. В общем, никто не уйдет обиженным.

Для себя я решил задачу экстрагирования в статье небольшого размера сути подхода по отделению обрабатываемых данных от обработчиков, что, на мой взгляд, является одним из краеугольных камней при распределенной разработке (слабосвязанными командами разработчиков) гетерогенных программных систем (типичный пример — web-приложение), интегрированных с внешними сервисами посредством SOAP/REST и адаптируемых конечным пользователем под изменяющиеся условия применения.

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

IMHO, в наше стремительно летящее время приложения должны разрабатываться в том виде, в каком девелоперу удобно их создавать и изменять (второе даже важнее первого). И если девелоперу удобнее создавать и изменять (второе важнее) приложение с использованием JS — он будет это делать с использованием JS. Более того, этого будет требовать сам заказчик разработки. Ну а пользователи… 80% пользователей приложения проапргрейдятся, 20% будут выкинуты на обочину курить бамбук.

Я не имею в виду, что JS «захватит мир», я имею в виду, что «одноядерный 32-битный Pentium 4 Northwood» это если и не 20%, то уже очень близко к нему.

Information

Rating
Does not participate
Location
Рига, Латвия, Латвия
Date of birth
Registered
Activity

Specialization

Фулстек разработчик
Ведущий
From 3,000 €
JavaScript
HTML
CSS
Node.js
Vue.js
Веб-разработка
Progressive Web Apps
PostgreSQL
MySQL
GitHub