Pull to refresh

Comments 23

Ваш репозиторий должен возвращать простую коллекцию объектов (один объект), вместо коллекции моделей (одной модели), т.к. модели потом гуляют у вас по коду и разработчик может в обход репозитория удалить/сохранить модель. Т.е. теряется суть репозитория как слоя взаимодействия с моделью, с тем же успехом можно напрямую из сервиса модель дергать

Спасибо за обратную связь, попозже изучу внимательнее ваш пример.


Эта статья — перевод.

Мы понели, что мопед — не твой.
Вот хорошая статья, подробнее объясняющая мой комментарий: habrahabr.ru/post/316836

Есть такая хорошая архетиктура, обделенная статьей на хабре — Proto, реализованная на Laravel в проекте Apiato


Суть примерно та же. Голый MVC в том виде в котором он есть в Laravel недостаточен.

Спасибо большое за ссылки. Интересно будет посмотреть.

У Вас тут явно масса проблем. Этот подход не применим в приложениях хотя бы мелкого размера. Возможно только в совсем крохотных, где весь код можно уместить в голове.


По пунктам:


  • Почему ваш код находится в пространстве App\Http\Controllers ?


  • Если речь идёт про DDD. То надо было реализовать всю логику приложения в отдельном пространстве. Вообще не в папке с Laravel. Где то в /src/. Вот туда надо было складывать ваши абстрактные репозитории. А на Laravel лишь реализовывать адаптеры, по интерфейсам. А вообще, делать универсальный репозиторий — идея так себе. На практике это не работает. Хоть они и похожи, всегда есть отличия. Тут надо событие бросить, тут что то не сохранять. В итоге каждый репозиторий будет иметь методы, которые будут перегружать методы родительского класса. Лучше делать отдельный, несвязанный реп, к каждой из сущностей. Так будет проще впоследствии.


  • У вас репозиторий сетит в себя модель при инстанцировании, что потом приведет к проблемам. Что делать если вы захотите создать сразу 3 объекта Pizza?

В create должна быть фабрика.


$class = get_class($this->model); 
$newObj = new $class();
$newObj->fill($data);
return $newObj;

  • Сортировка как свойство класса? Really. Ну на вкус и цвет конечно, но видать вы еще не усвоили, что использование явных параметров в функциях лучше, чем свойства объектов. Все потому что у вас в классе 6 методов, а сортировку используют лишь 2. Подумайте об этом.


  • $this->model->hydrate, опять же, используйте статичные фабричные методы. {Model}::hydrate() или по аналогии с п.3


  • Ну и ответ на вопрос: — как справиться с жадной загрузкой?
    Ответ: Не использовать. Да как бы привлекательно это ни было.
    На край — не должна покидать пределы репы.


  • Трейты с сортировкой в мусорку.

Добрый вечер.


У меня масса проблем с переводом статьи? Или вы имели ввиду, что вам не нравится подход автора оригинальной статьи?


Попробую ответить на ваши вопросы:
1) не весь, а только код одного контроллера. Ещё два во вложенном namespace.
Сервисы, репозитории и трейты находятся в отдельных пространствах. Уточните, в чем на ваш взгляд ошибка и как бы вы сделали?


2) почему вы считаете, что логику приложения нужно делать не в папке с фреймворком? Не могли бы какой-нибудь пример подобной реализации показать. Пока не очень хорошо понимаю преимущества данного подхода.
Насчёт абстрактных классов и трейтов- даже Фреймворк их активно использует и далеко не часто приходится перезагружать. Возможно у нас разный опыт, но я применял подобные паттерны с трейтами и абстрактными классами на не маленьких проектах и особых проблем не испытывал. Это не золотая пуля и многое зависит от изначального дизайна приложения. Согласен, что это может быть не оправданным.


3) статья рассказывает о подходе, а не о конечном дизайне.
От себя могу добавить, есть гипотеза, что на небольших проектах уровень репозитория не нужен — его вполне закрывает eloquent. А на больших проектах эту реализацию лучше заменить на doctrine.
В любом случае, я считаю, что нужно на каждом проекте заниматься проектированием в начале проекта.


4) я бы не хотел принимать это на свой счёт и быть категоричным. Есть количество способов решить эту задачу > 1. В каждой конкретной ситуации лучшими могут быть разные.


5) поясните, почему вы так считаете?


6) а как вы обычно решаете проблему n+1? А разве в указанных примерах сортировка вне репозитория?
Не уверен, что понимаю вашу мысль. Не могли мы это замечания описать детальнее?


7) почему вы так считаете?

Опять. Снова пропустил тег перевод.

1 и часть второго: Я несколько раз вкорячивался с разными библиотеками, и фреймворками. С laravel дваджы, когда менялось куча всего, и надо было много править. Теперь я не пишу бизнес-логику с внутри проекта на фреймворке. Бизнес-логика — отдельно, фреймворк как инфраструктура — отдельно.

2. В долгую, копипаста лучше, чем 1 абстрактный и потомки с перегрузкой методов. Про трейты п.7.

3. Eloquent закрывает только микропроекты. Так как это active record, просто он по сути предназначен для CRUD, тут да это прям инь и янь, но все остальное — это не очень удачные попытки заменить SQL синтаксис, не удобным(когда начинают монструозные запросы на нём писать) и бажным(issues по eloquent открыты всегда десятками) PHP кодом. И Doctrine конечно вроде мощнее, но та же проблема. Многословно, новый синтаксис.

4. Ну потому как репозиторий обрастает запросами вроде getByXXX, и содержащиеся публичные свойства, и методы, будут мозолить глаза, а новичков в коде, заставлять думать, что эти свойства влияют сразу на все методы, что вряд ли будет являться правдой.

5. $this->model это что, это уже какой-то объект, кто его туда положил, почему он там, в каком он состоянии? Вы не можете сказать. Но ожидаете что он «чистый», чтобы породить новый объект. Вызов же статичного метода, гарантировано вернёт вам новый объект с установленными значениями. Короче это метод класса, не метод объекта. Ну по-крайней мере обычно все это предполагают.

6. Я думаю, что если у вас по коду возникает проблема N+1, значит походу, пора отложить Eloquent(Он про CRUD) и перейти на SQL. Возможно DataMapper вас спасет. А может и нет.

7. Сортировка — это свойство запроса, а не класса. Ну вот просто, что такое сортировка? может она быть у репозитория? Вот мне кажется нет, но это только моё мнение. Та же история с отношениями.
Всё это про «магию». Вечная борьба прямого кода и «магии», которую считают рефакторингом. Но последний делается по факту, а вот «магия» — это часто преждевременная оптимизация.

Автор, переведённой статьи, изобрёл один из небольших разделов «магии» согласно того с чем ему приходится сталкиваться. Она работает на простых проектах. Где модели не содержат сколько-то сложных агрегаторов.

Если принять то, что это «магия» и она может работать в какой-то группе приложения, то я соглашусь с её применением. Когда есть простые модели (контекст laravel, а не DDD) и их связи. Если разработчик один. Пусть.

Но бывает всё сложнее. Бывает нужно передать в сервис несколько репозиториев, а то ещё и другой сервис. После сервиса нужно выполнить нечто инфраструктурное, например, отправить почту и/или пуши, логировать и т.д. на основе успешности выполнения сервиса. Добавим в абстракцию сервиса подписку на события?
Может в сервисе потребоваться дополнительная валидация, так как она нужна не везде, а там где нужна и валидируются не только входящие данные запроса. Её то же повесим абстракцией на сервис?

Но главная проблема — это когда в проекте джуниор или условный мидл начинает писать код по аналогии увиденной «магии», не думая о её востребованности в его части задачи. Ведь понимание чьей-то «магии» так завораживает. Хочется то же стать «магом». Это приятно и побочные эффекты сразу не видны. Причём когда джуниор видит, что «магия» в конкретном случае не работает, то начинается его магия, например, с прямым доступом к моделям (контекст laravel).

Вот тогда задумываешься. С одной стороны можно быстро сделать прототип большой части проекта, а с другой загубить его в самой сложной части.

И опять же, серебряной пули нет.

ИМХО, вы сделали слишком сложную систему, убив простоту Laravel.


Я бы смело выкинул слой репозитория, потому что всё, что реализовано в нём, уже есть в классе Model (по-сути, репозитории уже встроены в классы моделей), а инкапсуляцию бизнес-логики обеспечивают классы сервисов.

А в интернетах кто-то понимает, как использовать Журавель?

Есть нормальный мануал? Или все расчитаны на дебилов?
После прочтения этой статьи я понял что я никак не работаю с Laravel
Ну куда, зачем Laravel не для этого придумали. На самом деле, я сталкиваюсь с разработчиками (такие есть и в комментариях) которые я пару раз правил код в Laravel и он вообще имеет не правильную архитектуру и вообще давайте не юзать Eloqent, навернем три слоя абрстракции сверху и будем радоваться жизни.
На самом же деле для небольших проектов достаточно ничего не мудрить, а просто писать код как вам предложили (даже если вы супер мего крутой Senior Software Engineer) и побольшей части для сайта заказа пиццы никакие Сервисы даже не нужны.

Все эти современные стандарты и крутые абривиатуры, на самом деле всех бесит что Laravel ломает их, к примеру:
someControllerMethod(int $id) {
    $pizza = Pizza::findOrFail($id);
    return view('pizza.show', ['pizza' => $pizza])
}

Или, о боги, даже так:
someControllerMethod(Pizza $pizza) {
    return view('pizza.show', ['pizza' => $pizza])
}

И что же, он найдет пиццу, а если не найдет у юзера высвититься красивая 404 ошибка (из коробки, Карл, просто установил laravel написал 10 строк кода и оно работает!)

А нам предлагают, делать так:
class PizzaRepository {
// какие то 100 строк кода
}
class PizzaService {
// какие то 100 строк кода
}
someControllerMethod(PizzaService $pizzaService) {
    $pizza = $pizzaService->find($pizza);
    if (!$pizza) {
         throw new SomeHttpException('Pizza not found...');
    }
    
    return view('pizza.show', ['pizza' => $pizza])
}


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

У меня довольно много проектов на Laravel, есть и одиночные, есть и с небольшими командами 3-6 человек, и пока мы не сталкивались с тем что у нас что то пошло не туда, с самого начала пути (есть проекты долгожители, которые живут с 3 версии Laravel, сейчас он уже на версии 5.5) мы стараемся просто использовать ту парадигму которую предлагает Laravel.
И все это время читаю на разных форумах об архитектуре Laravel что Eloqent слабоват и не позваляет писать сложные запросы, красиво. На самом деле, решение простое, не писать сложные запросы, использовать простые связи, избегать вложенности и писать код с удовольствием. У вас много сложных запросов? Вы где то свернули не туда. Уже прошли те времена, когда считалось крутым одним запросом вытащить пол базы данных.
На самом же деле для небольших проектов достаточно ничего не мудрить,

А для больших как вы предлагаете? Статья описывает подходы для более-менее серьезных проектов. При создании лендинга на laravel эту часть, безусловно, можно пропустить.
И все это время читаю на разных форумах об архитектуре Laravel что Eloqent слабоват и не позваляет писать сложные запросы, красиво. На самом деле, решение простое, не писать сложные запросы, использовать простые связи, избегать вложенности и писать код с удовольствием. У вас много сложных запросов? Вы где то свернули не туда.

Разберитесь в разнице между подходами Active Record и Doctrine (Data Mapper, Gateway) и возможно вам станет понятнее.
Почитайте больше про паттерны.
не тратили время на изучение очередного модного архитектурного паттерна и пытались придумать свой велосипед

Если вы считаете, что паттерны, предлагаемые доктриной или подходы, описываемые в данной статье — это новый модный паттерн, то у меня для вас плохие новости.
1. Как правило, достаточно сервиса, на самом деле любой лишний слой, добавляет сложность, а управление сложностью, очень важно для большого проекта.
2. При чем тут разница меж ними? Вы используете Laravel? Пользуйтесь предложенным подходом, он работает. Не нужно к метле педали прикручивать.
3. Если вы стремитесь в одном проекте совместить все паттерны сразу, то я вас тоже, огорчу, это так не работает.

Суть моего сообщение в том, что, не стоит недооценивать технологии не попробовав ее сполна, а потом говорить что я использовал паттерн1, паттерн2 и паттерн3 как на всех своих старых проектах в новом проекте на Laravel и он мне не понравился :) Это называется: «в чужой монастырь со своим уставом»

Проблема Laravel в том, что если делать так как предлагаете вы, т.е. не использовать паттернов и просто писать код, то вскоре вы с удивлением обнаружите, что не вы диктуете фреймворку как вы хотите чтобы это работало, а он вам.


Мое мнение бизнес-логика отдельно, инфраструктурный код отдельно, написано на фреймворке, и там никакой логики, только адаптеры между кодом бизнеса и фреймворка.
Но этот подход даёт профит когда у вас большой проект.


Большой проект в моем понимании — это когда пару сотен контроллеров, 200+ таблиц со сложными моделями данных, админка с аналитикой и подобные веши, версионированное API, фоновые задачи, интеграция с внешними источниками или хранилищами данных. Все это должно быть покрыто юнит и интеграционными тестами. Вот тогда вся эта магия ларавела, начинает выходить боком.


Посмотрите код ваших приложений, и задайте следующие вопросы:


  • Есть ли такое, что Eloquent модели попадют в представление ?
  • Используются ли в представлении Eloquent связанные отношения, например:
    @foreach($user->somerelations as $relation)
    {{ $relation->someattr }}
    @endforeach
  • Путешествуют ли ваши сущности внутри других сущностей ?
  • Есть ли у такие сущности, которые изменяются(вызывается сохранение в БД) в разных местах кода?
  • Используете ли вы Observers для изменения данных сущности перед сохранением в БД ?

Если вы ответили да, то эти решения убьют ваш проект, если он захочет расти. И это только, первое что пришло на ум.

В одном из проектов, у нас куда больше 200 таблиц, более того, он работает с 7 разными базами данных и имеются связи как меж. таблицами одной базы так и меж таблицами разных баз данных. Тем не менее, у меня нет сложных запросов, а приложение работает быстро. Так же оно работает с разнообразными хранилищами типа s3, ftp, sftp и яндекс.диска. Так же есть очереди задач, Task Scheduling, прикручный elasticsearch, websocket сервер для real-time взаимодействий(на node.js общается с сайтом через бродкасты). Интеграциями с разными рекламными API для сборов статистики и управления рекламой, дашборд с аналитической информацией и многое другое, кол-во контроллеров не считал.

Что значит попдают? Передаю ли я их туда? Да, передаю, но никакой логики там, нет, просто вывод. На самом деле, я конечно, могу форматировать вывод или приобразовывать это все в массив, но толку с этого мало.

Да использую связанные отношения, и не вижу в этом ничего плохого, за 4 года работы проекта, пока не возникало глобальных проблем ни с обновлениями, ни с рефакторингом.

Есть такие.

Да, например в сущности новость, перед сохранением генерируется slug если название было изменено.

ЗЫ: На самом деле, вы можете написать хоть 10 слоев абстракций для общение с фреймворком, вы можете сказать что это дает вам неимоверный профит и возможность в любой момент сменить фреймворк, но вам все равно придется менять часть кода. Я же использую другой подход, мой код интегрируется с фреймворком, у меня нет никаких слоев меж ним и бизнес логикой, это безусловно имеет один минус — обновления проходят не так гладко, как в вашем случае, хотя, у вас проблем возникнут тоже. В моем случае используются практически все возможности фреймворка, когда мне их не хватает, что то даже модифицируется, в этом кстати Laravel преуспел как никто другой, а вы пытаеться от них отказаться в силу того, что в дальнейшем можете сменить фреймворк на другой.

Мне довелось поработать к на стартапах, так и в ентерпрайзе, на стартапе ваш способ так себе, потому что требует некотрых временных затрат на архитектуру, а в ентерпрайзе вам врятли кто то позволит через 4 года успешной работы на фреймворке Х перейти на фреймворк Y без крайне везких причин.

Вынос бизнес логики из кода под фреймворком, несет не тот смысл, смысл не в том чтобы сменить фреймворк. Смысл в том чтобы доменный слой(бизнес-логика) была отделена от инфраструктуры(фреймворк), это позволяет сделать логику прозрачной для следующих программистов которые будут сопровождать и развивать вашу систему. А не так что, ну мы тут сохранили модель, в мидлвере изменили модель, обсервер выбросил событие в rabbitmq и с другой стороны получили это событие и что то изменили в БД. Все это будет почти невозможно понять тем кто придёт обслуживать вашу систему послезавтра, когда вы и ваши текущие коллеги уволитесь.
Пример, так себе, это можно сделать и в вашем случае, и никто не говорил что так делается в моем случае, вы все равно не сможете сделать бизнес логику на 100% прозрачной, а время на это потратите неимоверное.

Нет какого-то единого подхода… многое зависит от проекта. Иногда достаточно стандартного eloquent-crud, а доменно-специфичные обработки завязываются на события. Иногда вместо общего репозитория удобнее использовать отдельно Finder и Storage. Часто вижу ошибку, когда всю логику пытаются воткнуть в единый сервис и вместо god-model создается god-service, когда гораздо уместнее делать несколько более контекстно-зависимых сервисов
Наиболее частая структура — Acme — набор независимых библиотек и логики
App — всё фреймворко-приложение-специфичное
Bundles — слабо-связанные модули

Sign up to leave a comment.

Articles