Я обожаю тупой код. Тупой в том смысле, что даже через 10 лет, если заказчик попросит тебя внести изменения в него, ты сможешь сделать это не вникая во всю логику, даже будучи после пятничного корпоратива, ничего в старом коде не сломав. И тупой в том смысле, что не нужно прикладывать никаких когнитивных усилий, чтобы его понять. Но есть в Laravel Eloquent ORM одно архитектурное решение, которое заставляет меня плакать. Интересно? Заходите под кат.
Умные люди уже давным давно все придумали за нас: OOP, Паттерны проектирования, SOLID, DDD и другие страшные словечки, которые так пугают по-началу, а потом применяются по наитию.
Этим мне нравится Laravel и Symfony. Они позволяют писать максимально тупой и защищенный код прямо из коробки. Да. У каждого из них есть свои недостатки… Но в Laravel есть один, который меня раздражает больше всего. Это использование Active Record Pattern (AR) для работы с моделями.
Для начала — немного об этом паттерне. Что это вообще такое? Для понимания, следует пойти к родителю этого опуса проектирования приложений — паттерну Repository. Этот паттерн представляет собой коллекцию. Коллекцию сущностей (Entity), которые может извлекать, изменять, сохранять, удалять, в общем, управлять ими в абстрактном месте хранения. В 90 из 100 процентах случаев, таким местом хранения являются различные базы данных. Но может быть и файловая система, и какой-то кэш, и даже внешнее API.
Такой подход полностью соответствует принципу единой ответственности и подходу DDD. Кроме того, благодаря этому подходу, реализуется слабая связанность — нам не важно, каким именно образом в приложении хранится что-либо, мы работаем с Entity, когда хотим работать непосредственно с объектным представлением данных и работаем с Repository, когда нам нужно взаимодействовать с хранилищем.
Но в Laravel решили пойти по пути использования AR, что бесспорно круто и невероятно удобно, когда тебе нужно сделать быстрый прототип, но становится невероятной головной болью, когда нужно взаимодействовать с несколькими источниками данных и оперировать с ними в рамках системы.
AR — паттерн, который сопоставляет Entity и Repository в одну Model. То есть объект становится представлением конкретной записи в базе данных. И… Что? К чему это приводит и почему это так раздражает?
Во первых, мы нарушаем тот самый принцип единой ответственности — логика работы с хранилищем в одном месте, а логика работы с сущностью в другой. Это важно, ведь в рамках моей системы я не хочу передавать по цепочке вызовов строчку из бд в объектном представлении. Я хочу передавать модель. Мне должно быть наплевать, как она получается, изменяется и сохраняется. Мне нужно иметь те методы, которые позволяют взаимодействовать только с моделью, а не со строками в БД.
Во вторых, мы не можем (как следствие того, что Persistent слой — слой хранения — связан со слоем сущности), простым образом изменить место хранения модели. Да, мы можем сделать это в конфигурации, изменив это сразу для всех, в рамках поддерживаемых баз данных. Или изменить только для конкретной модели (при всем при этом, мы не убираем никакие методы query builder’а, ведь нельзя избавиться от методов базового класса) и напороться на тонну вероятных ошибок в коде или, не дай Бог, если его кто-то другой поддерживать будет (а такое случается сплошь и рядом).
В третьих. Мне хочется тестировать свои сущности. Мне хочется, черт возьми, быть уверенным, что изменения, которые я вношу не сломают мою бизнес-логику. А, как показывает практика, в случае с AR ты этого сделать не можешь, потому что нарушен чертов принцип единой ответственности! Хотя тут я немного лукавлю. Тестировать модели можно, просто… Немного не просто.
Тем не менее, нельзя не сказать о плюсах этого паттерна. Хотя весь его плюс в том, что это «быстро, просто, не задумываясь». Сливая два близких по логике своего действия и постоянно использующихся вместе паттерна, мы получили удобное средство, которое немного сокращает объем кода (в сторону усложнения, мы же помним о «тупом коде»?). Это также, позволяет избавиться от лишних проблем на этапе формирования MVP, который обязательно (практика показывает, что такое случается редко, но все же) планируется переписать. Это позволяет сместить мысли с вопроса «как мы делаем» на вопрос «что мы делаем», то есть избавиться от лишних вопросов о технологиях и перейти к бизнес-логике.
К какому же выводу я пришел за несколько лет использования Laravel Eloquent ORM? Active Record зло во плоти? Да нет, это крутейший инструмент для некоторых ситуаций, особенно для этапа, когда пишется простенькое приложение или прототип такого приложения. Но это невозможная для работы вещь, когда твое приложение вырастает и тебе приходится работать с большим количеством источников данных, писать код со 100% покрытием тестами, начинаются большие проблемы.
Да, придумываются новые фишки (Trucker?), да идем на ухищрения. Но все же хочется немного больше свободы от фреймворка, тем более, что он так многим хорош!