Search
Write a publication
Pull to refresh

Comments 19

Мне одному кажется, что это всё (как и ООП) взялись применять и к месту, и (что чаще) не к месту?

Применение ООП ни к месту для меня звучит как оксюморон. Что же, делать проекты в процедурном стиле и задумываться над тем, достоин ли вот этот проект применения ООП?

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

На счет паттернов: да, здесь можно находить кейсы применения ни к месту. Но обычно это связано с непониманием самого паттерна и того же полиморфизма. Чаще это ситуация, когда человек заявляет, что у него реализован паттерн, а на самом деле его там и в помине нет. Такого, чтоб паттерн реализован правильно, и это было ни к месту, нечасто видел.

Не понимаю, что такое ООП ни к месту.

Ну вот некоторые люди, команды, конторы — пишут на языках, в которыцх классов нет. Про WhatsApp слышали когда-нибудь? — Ну вот.

Можно почитать SICP

У них тогда просто еще лямбд не было!

когда зубришь паттерны банды 4 начинаешь ненавидеть ООП
как только появляется необходимость паттерна их этой книжки я вижу варианты

  • либо ошибка проектирования

  • либо я по быстрому сваливаю на ФП

не устарели ли решения банды 4х?
не устарели ли решения банды 4х?

Отвечают ЗнаТоКи©

вопросы не устарели, ответы устарели

либо я по быстрому сваливаю на ФП

Я лично из ФП и не выхожу никуда уже лет 10.

в данном случае сильная типизация вам только мешает

да и чужеродная она на PHP
попробуйте так

/*Вот возможный упрощённый пример реализации подхода одновременно простого, 
JSON-ориентированного, модульно-плагинного (функционально-декларативного) способа, 
достаточно близко соответствующий исходной задаче для удобного сравнения:

Общее ядро (CampaignAggregator)
Простой агрегатор собирает данные кампании из разных источников в общую JSON-структуру:
*/
  class CampaignAggregator {

    public function aggregate(string $campaignId): array {
        return [
            'id' => $campaignId,
            'status' => $this->loadStatus($campaignId),
            'prices' => $this->loadPriceSegments($campaignId),
            'creatives' => $this->loadCreatives($campaignId),
            'metadata' => $this->calculateMetadata($campaignId),
            // сюда легко добавляются новые поля
        ];
    }

    private function loadStatus(string $campaignId): string {
        // загружаем статус из базы или сервиса
        return 'active';
    }

    private function loadPriceSegments(string $campaignId): array {
        // получаем цены из репозитория
        return ['segmentA' => 100, 'segmentB' => 200];
    }

    private function loadCreatives(string $campaignId): array {
        // загружаем креативы
        return [['id' => 'creative1', 'title' => 'Creative One'], ['id' => 'creative2', 'title' => 'Creative Two']];
    }

    private function calculateMetadata(string $campaignId): array {
        // считаем некоторые вычисляемые поля
        return ['CTR' => 0.12, 'ROI' => 1.5];
    }
}

interface CampaignTransformerInterface {
    public function transform(array $campaignData): array;
}

//Примеры плагинов-трансформеров (для разных потребителей)
// Потребитель А (ему нужна простая структура id/status):
class SimpleCampaignTransformer implements CampaignTransformerInterface {
    public function transform(array $campaignData): array {
        return [
            'campaign_id' => $campaignData['id'],
            'campaign_status' => $campaignData['status']
        ];
    }
}

// Потребитель B (ему нужны цены и креативы):
class PricesCreativesTransformer implements CampaignTransformerInterface {
    public function transform(array $campaignData): array {
        return [
            'campaignId' => $campaignData['id'],
            'prices' => $campaignData['prices'],
            'creatives' => array_map(fn($creative) => $creative['title'], $campaignData['creatives']),
        ];
    }
}

// Потребитель С - нужен другой комбинированный формат:
class DetailedCampaignTransformer implements CampaignTransformerInterface {
    public function transform(array $campaignData): array {
        return [
            'id' => $campaignData['id'],
            'fullInfo' => [
                'status' => $campaignData['status'],
                'metadata' => $campaignData['metadata'],
                'prices' => $campaignData['prices']
            ],
            'creativeCount' => count($campaignData['creatives'])
        ];
    }
}

//использование
$aggregator = new CampaignAggregator();
$campaignData = $aggregator->aggregate('campaign-12');

// плагин выбирается каждым потребителем независимо
// или через конфигурацию, зависит от системы
$consumerA = new SimpleCampaignTransformer();
$consumerB = new PricesCreativesTransformer();
$consumerC = new DetailedCampaignTransformer();

// выводим полученные JSON-данные (пример)
echo json_encode($consumerA->transform($campaignData), JSON_PRETTY_PRINT);
echo json_encode($consumerB->transform($campaignData), JSON_PRETTY_PRINT);
echo json_encode($consumerC->transform($campaignData), JSON_PRETTY_PRINT);
  • ✅ Архитектура стала более прозрачной и декларативной.

  • ✅ Решение не требует глубокого погружения для понимания.

  • ✅ Легко добавляются изменения, т.к. JSON и простые функции — это максимально понятный подход.

  • ✅ Отражение «естественной простоты» самой предметной области в коде — не вводятся абстракции, которых не существует в реальности.

у декларативного подхода есть неприятное свойство: он является антонимом слову "контракт". Если в массиве отсутствуют какие-то ключи - он не перестаёт быть массивом, но результат его обработки может отличаться кардинально.

Я в курсе, кэп

Просто нужно перенести ответственность за корректность данных туда кто лучше об этом знает

Спасибо за ваш вариант решения. Разумеется, любую задачу можно решить множеством способов, и каждый имеет право на жизнь.) Что лично меня здесь настораживает:

  • Разные потребители требуют разный состав данных, то есть не для каждого случая есть необходимость загружать весь агрегат. Лучше иметь возможность управлять тем, какие именно части агрегата будут загружены в зависимости от потребности потребителя.

  • Подход "один потребитель - один трансформер" - это как раз то, чего я стремился избежать, чтобы не дублировать код. Хотелось иметь набор переиспользуемых мини-трансформеров и из них уже набирать конечное решение для каждого потребителя.

Я в целом согласен с тем, что итоговое решение не блещет простотой. Но зато оно блещет очень большой гибкостью, что и было целью создания этого микросервиса.)

Не знаю, мне давно хотелось рассказать об этом интересном кейсе: как пошаговое решение задачи само по себе приводило к добавлению все новых паттернов.)

переиспользуемых мини-трансформеров

Переиспользование, DRY, приводит к появлению сложных искусственных абстракций, у которых нет аналогов в предметной области и поэтому сложных для понимания. Так что вам ещё и документацию писать на свой код придётся)

Но да, тут нужен баланс. Если таких случаев много, то вложившись в создание общей абстракции можно в сумме сэкономить.

В вашем подходе дублируется код в трансформерах. Учитывая, что автор писал о сотнях полей, очень высок шанс допустить ошибку в написании или пропустить что-нибудь в случае появления новых полей.

Было интересно почитать, спасибо.

Есть вопрос про SubLevelTransformer - зачем в методе transform вызывается supports? Ведь ранее при проверке этого звена цепочки уже вызывается supports, разве нет?

Спасибо за ваш отзыв! Метод supports будет первым вызван в самом "внешнем" трансформере-декораторе. Чтобы это представить наглядно, вы можете обратиться к последней схеме в статье: там где цепочка трансформеров. Если дорисовывать эту схему декораторами, то мы могли бы какой-то один из эллипсов на схеме обвести еще одним эллипсом. Вот этот второй, внешний эллипс - и есть трансформер-декоратор.

Соответственно, именно самый внешний трансформер примет запросы на выполнение операций supports() и transform(). Поскольку, при написании каждого класса трансформера, мы не знаем, как именно он будет использован в клиентском коде, то есть, будет он единственным трансформером, или в составе цепочки, будет он снаружи или внутри "многослойного" звена, нам следует реализовать методы supports() и transform() в каждом классе.

Вдобавок, своей реализацией метода supports() трансформер-декоратор дополнительно подчеркивает, что не может обходиться без nested трансформера и служит лишь для добавления каких-то дополнительных возможностей, а не в качестве самостоятельного компонента.

CQRS? Агрегат это граница консистентности, а не одно большое представление вроде бы как. Если он не меняется транзакционно весь целиком, то без разницы что многие ассоциации включены в представление. Да ещё и в разные. Короче, этот монстр не агрегат.

Да, согласен с вами. Единственно, про CQRS не понял, причем он здесь. В статье слово агрегат применялось не в смысле доменного агрегата, а скорее как агрегируемое из разных источников представление кампании.

Вот при этом и CQRS. Query отдельно, с разными представлениями, собираемыми из разных агрегатов и/или уже хранящихся собранными. И кстати возможно в разных bounded contexts, то есть возможно Вам просто надо разные варианты сущности из разных контекстов вместо разных представлений. А в командной части будут лежать честные агрегаты. Отвечающие только за свою консистентность. Агрегат это термин программирования. Что в ddd что в uml. Так же как ассоциация. Это тип связи. Иначе возникает много вопросов и придется пояснять за агрегат каждому встречному)

Sign up to leave a comment.

Articles