Comments 19
Мне одному кажется, что это всё (как и ООП) взялись применять и к месту, и (что чаще) не к месту?
Не одному.
Применение ООП ни к месту для меня звучит как оксюморон. Что же, делать проекты в процедурном стиле и задумываться над тем, достоин ли вот этот проект применения ООП?
На счет понимания самих принципов: бьюсь в каждой команде, где случалось работать, за то, чтобы ребята начали лучше их понимать. Скажу, что это окупается и отражается на коде в повседневной работе. Не понимаю, что такое ООП ни к месту.
На счет паттернов: да, здесь можно находить кейсы применения ни к месту. Но обычно это связано с непониманием самого паттерна и того же полиморфизма. Чаще это ситуация, когда человек заявляет, что у него реализован паттерн, а на самом деле его там и в помине нет. Такого, чтоб паттерн реализован правильно, и это было ни к месту, нечасто видел.
У них тогда просто еще лямбд не было!
в данном случае сильная типизация вам только мешает
да и чужеродная она на 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. Query отдельно, с разными представлениями, собираемыми из разных агрегатов и/или уже хранящихся собранными. И кстати возможно в разных bounded contexts, то есть возможно Вам просто надо разные варианты сущности из разных контекстов вместо разных представлений. А в командной части будут лежать честные агрегаты. Отвечающие только за свою консистентность. Агрегат это термин программирования. Что в ddd что в uml. Так же как ассоциация. Это тип связи. Иначе возникает много вопросов и придется пояснять за агрегат каждому встречному)
Паттерны «Банды четырех»: примеры применения в реальном проекте