Комментарии 73
MVC это вообще не паттерн, а обобщенный принцип, реализацию которого каждый видит по-своему. Есть, конечно, его описание еще из 80-х, но оно имеет очень отдаленное отношение к реализации в Rails-подобных фреймворках. Я предпочитаю избегать этого термина вообще и использовать более конкретные (скажем, hexagonal architecture).
А вот с термином ORM все нормально, просто надо вспомнить, как он определен у того же Фаулера. Active Record (или Data Mapper) — это способы реализации ORM. Вручную написанный (скажем, с SQL запросами и Reflection-ом) маппер для конкретной сущности — это тоже ORM.
По вопросу нарезки согласен: тут из DDD важно одно из ключевых его понятий "Bounded Context".
ОRМ не уважаю категорически, когда нужна производительность и отлаживаемость — пишу SQL только вручную, и не разу за 25 лет не пожалел.
Нужно просто знать, где остановиться. Пытаться натянуть сову на глобус и делать «как в питоне» — заведомо обреченная на неуспех задача. Однако, если использовать ORM в меру, как просто продвинутый конструктор запросов — можно получить много профитов, фактически не отказываясь от ручного написания запросов. По крайней мере, в go.
ОRМ не уважаю категорически, когда нужна производительность и отлаживаемость — пишу SQL только вручную
Еще раз повторюсь: вручную написанный SQL и ORM вполне совместимы.
Вот на условном псевдокоде:
class MyEntity {
constructor(public final id: int, public final title: string);
}
interface MyEntityMapper {
findById(id: int) -> MyEntity;
persist(myEntity: MyEntity);
}
MyEntityMysqlMapper.findById(id: int) -> MyEntity = {
row := db.query("select id, title from my_table where id = $1", id).fetchOneOrFail();
return MyEntity(row.id, row.title);
}
MyEntityMysqlMapper.persist(myEntity: MyEntity) = {
db.query("insert into t (id, title) values ($1, $2) on duplicate key update set title=$2", myEntity.id, myEntity.title);
}
Это тоже ORM.
Я чаще всего пока слышу такую точку зрения, что один микросервис на go == Bounded context и получается что код в рамках сервиса уже сам по себе сгруппирован по предметной области.
Ну и прикрываясь этим в project structure самого проекта творится лютая дичь с папками вроде controllers, dbQuery, services и т.д Чего только стоят официальные рекомендации на этот счет.
Т.е внутри микросервиса файлы группируются по технологическим признакам, т.к считается что вся кодовая база одного сервиса обслуживает небольшую узкую часть предметной области
github.com/go-kit/kit/tree/master/examples/shipping
Тут вопрос, насколько узка область сущностей.
они разбились о сложность
реального мира, и более неактуальны.
Как по мне, очень спорное и слишком категоричное утверждение. Может поясните?
В конце концов, MVC — это всего навсего способ организации обработки http запроса, и он нисколько не противоречит DDD. А, наоборот, чаще всего с ним сочетается.
Если MVC — это неправильная архитектура организации приложения, то как тогда в go надо? Чем он так принципиально отличается от всего остального back-end мира?
MVC — это всего навсего способ организации обработки http запроса
MVC к HTTP не имеет отношения.
Имхо, логика все-таки должна быть разделена по слоям, и даже компонентный подход этому не противоречит. А MVC в свое время как раз и возник, как способ разгрести ту кашу, в которую очень быстро превращается реализация предлагаемого вами паттерна.
Впрочем, всему свое время и место, наверное.
Например, в России студентов натаскивают на ООП и MVC, поэтому у нас в каждой щели ООП и МВЦ, даже когда от них вообще никакого толку.
Я к тому, что всегда будет существовать толстенный класс разработчиков, которые особенно ни о чём не задумываются, и просто штампуют как-то работающий код.
И работодателю очень сильно кажется, что как раз такие люди для него особенно эффективны. А они ещё и не шибко дороги.
А раз работодателю так кажется, значит он им и платит, и они такой код генерят в больших количествах, и это становится чуть ли не нормой.
Я всё это к чему. Если миллион мух делает что-то ужасное, это совершенно не значит, что что-то в этом мире не так. Главное, не работать с этими мухами вместе, чтобы не приходилось иметь дело с тем же, что и они.
ООП нисколько не устарело. Устарело то понимание ООП, которому учат студентов (при этом в основном учат те, кто сам толком не понимает ООП).
Модный ныне DDD — это по сути и есть реализация принципов SOLID. Давно уже модные микросервисы — тоже, по сути, ООП: если каждый микросервис рассматривать как объект, получается 1 в 1 определение Алана Кея.
И это не такая уж мелочь, потому что, напомню, в Go package == folder. И если тест находящийся в том же package'е может вызвать private method, то тест находящийся в другом package — уже нет.
Ну, если ставить так вопрос, то это совсем не проблема, потому что тестировать приватные методы — моветон в любом языке. Если никак без этого не обойтись, значит, что-то не так.
</sarcasm>
Тестировать нужно на всех уровнях: через API делаются инеграционные тесты, а через внутренние методы — низкоуровневые тесты. И они друг друга нифига не заменяют.
Ага. Именно поэтому во всех железках, от автомобилей до микропроцессоров есть специальные тестовые подключения, к которым обычный потребитель доступа не имеет. Идионы, наверное, всё это проектируют.
Плохой пример. Ибо тут точно так же есть публичный API с подключением по «правам/ролям».
Тестировать нужно на всех уровнях: через API делаются инеграционные тесты, а через внутренние методы — низкоуровневые тесты.
Интеграционные тесты, это тесты, где тестируется ВЗАИМОДЕЙСТВИЕ между системами.
Внутренние методы != приватные.
P.S. Интеграционное тестирование конечно не взаимозаменяемое с юнит тестированием. Я такого и не говорил и не утверждал.
Внутренние методы != приватные
Возвращаемся к тому, что в Go в целом нет private method, только package private. Если не использовать Beego, конечно, с которым все будет private.
Плохой пример. Ибо тут точно так же есть публичный API с подключением по «правам/ролям».Какой же он «публичный», если там пломба и вам нужно быть «в системе», чтобы безболезненно её менять?
По мне — так полный аналог пакетов в Go…
Глобальное состояние это в целом зло, и сильно усложняет тестирование. Импорт сайдэффектов неочевиден и его поведение невозможно поменять.
Как пример — библиотека логирования glog регистрирует глобальные флаги командной строки при импорте, и требует парсинга этих флагов. Переименовать флаги нельзя, при конфликте возникает panic
. Чтобы решить проблемы с glog в Kubernetes, ее в итоге форкнули в https://github.com/kubernetes/klog/blob/master/README.md
Пример описания проблем с glog: https://github.com/kubernetes/kubernetes/issues/61006
Да много где стандартная библиотека кривая — сначала не все продумали, а потом не меняли из соображений обратной совместимости. В PHP, например. Или в Java местами. Это не значит, что надо ждать языка своей мечты до пенсии :)
1. Эту библиотеку постоянно меняют и ломают совместимость (см. D).
2. Язык ещё невероятно молодой и на нём мало чего написано, отчего и непонятно — что именно сделано криво в стандартной библиотеке (см. 100500 новых и новейших языков).
Я ни разу не пользователь beego, и на go уже достаточно давно ничего не писал, но могу отметить, что большая часть пунктов притянута за уши.
>Структура папок
Сильная субъективщина на несколько абзацов текста. Либо фрэймворк позволяет создать свою структуру приложения (пусть даже подпакетами внутри controllers\models\view и т.д.), либо нет. Маловероятно, что в beego есть жесткий лок на структуру директорий.
>ORM
>Но основная проблема с Beego ORM даже не в том, что нужно бороться с proprietary языком, а в том, что он использует все худшие практики Go, будь то import sideffect'ов
Неправда. Подключение драйверов БД через сайдэффект — это штатный способ подключения в go.
Вот синтаксис запросов — более чем странный, тут можно согласиться.
>Bee tool
>Вот только если в мире RoR был rails и был rake, то bee — это такая мусорка для всего
Неправда. Если я правильно помню, в RoR5 команды «rails command» полностью заменяют rake, а в RoR4 почти всё было через rake. Да и в целом разделение не принципиальное. И непонятно, где у автора проблема с filewatcher возникла.
>Automatic routing
>Ну, а как работает весь routing? При запуске пресловутого bee генерируется еще один файл, commentsRouter_controllers.go
Тут можно согласиться, что аннотации в go выглядят несколько странно. Но вот генерация дополнительно файла (от кодогенератора) — так делают все кодогенераторы в go.
>Component testing
>И если тест находящийся в том же package'е может вызвать private method, то тест находящийся в другом package — уже нет.
Если вы хотите постоянно тестировать приватные методы, то у вас более крупные проблемы, чем война с beego.
По моему опыту, я использовал его — от middleware до back-end. По бек скажу за MVC — как раз благодаря этому, сначала пренес логику с Java на Beego, ну и потом еще раз логику с обычного контейнера на AWS Lambda
Проблема такого подхода заключается в том, что приложение по продаже носков «сверху» будет выглядеть точно так же, как приложение для заказа еды.
Да в общем то по бизнес логике, это практически одинаковые процессы, отличающиеся нюансами предметной области.
Тогда будьте готовы к уродцам вроде apiv1
И в чем проблема в поддержании версий апи живыми? Очень похоже реализована версионность в AWS
А насчет кодогенерации, то что есть в Beego гораздо помягче Dagger'a
это практически одинаковые процессы, отличающиеся нюансами предметной области
Так предметную область и надо выставлять на показ.
И в чем проблема в поддержании версий апи живыми?
Проблема не в поддержании API, проблема в том, что теряется вся семантика.
А насчет кодогенерации, то что есть в Beego гораздо помягче Dagger'a
С Dagger не работал. Там тоже приходится сгенерированный код коммитить?
С Dagger не работал. Там тоже приходится сгенерированный код коммитить?
Я не работал с Go и Beego, но работал с многими системами где было много кодогенерации. Весь этот процесс успешно встраивался в Build.
То есть генерируемый файл не коммитился в репозиторий, а генерировался в процессе создания Build-а в Bamboo, который впоследствии деплоился на сервер.
Чем вызвана нужда генерировать файл локально?
Там тоже приходится сгенерированный код коммитить?
В Go, насколько я помню, принято коммитить сгенерированный код
Так предметную область и надо выставлять на показ.
Вот тут можно подробней. На мой взгляд, достоинство систем с солид -подходом (mvc, IMHO, это проекция Single Responsibility Principle с выделением отображений. Просто логичный способ разделения логики приложения) это то что их просто изменять и рефакторить. Вот как тут более на показ выставить предметную область?
проблема в том, что теряется вся семантика
Вот тут можно подробней. У себя я семантику апи реализовывал как json на вход. Ну если не лень, можно и полный GraphQL распарсить.
ну и чтоб два раза не вставать,
эту архитектуру, как сову на глобус, они стали натягивать на Go
mvc, IMHO, это проекция Single Responsibility Principle на вот это все с отображением. Просто логичный способ разделения логики приложения. И как то без разницы, на каком языке.
Вот как тут более на показ выставить предметную область?
Вместо:
root/
--controllers/
----userController
----orderController
--models/
----user
----order
--views/
----user.view
----order.view
Можно иметь
root/
--user/
----user
----userController
----user.view
--order/
----order
----orderController
----order.view
У себя я семантику апи реализовывал как json на вход
А как быть с семантикой внутри приложения? В том же JavaScript'е хотя бы namespacing при импорте нормальный:
import { UsersController } from './api/v1/usersController.js'
В Go так не выйдет.
Можно иметьИ если в order.view используется user.model, а в user.view — order.model, то у нас получается спагетти. С классическим разделением контроллеров-вьюшек-моделей такая проблема проблема проявляется значительно меньше.
Ну вот у вас есть модели Ученик и Урок:
root/
--student/
----student
----studentController
----student.view // <== личная страница ученика
--lesson/
----lesson
----lessonController
----lesson.view // <== на этой странице у нас информация про урок, кто его преподает и в каком классе
в какую из ваших папочек вы бы закинули табель результатами, где по вертикали — список учеников, а по горизонтали — список уроков?
И куда бы вы отнесли какие-то совмещенные вьюшки? Ну типа таблица orders <=> users.
Зависит от того, где это будет применяться. Скорее всего пользователь хочет видеть свои заказы, а не наоборот.
в какую из ваших папочек вы бы закинули табель результатами, где по вертикали — список учеников, а по горизонтали — список уроков?
Табель — вообще отдельный domain, который я бы занес в какую-нибудь /grading
Зависит от того, где это будет применяться. Скорее всего пользователь хочет видеть свои заказы, а не наоборот.По такому принципу в папке orders вообще не должно ничего лежать. Или у вас бывает, когда заказы хотят что-то увидеть?
Табель — вообще отдельный domain, который я бы занес в какую-нибудь /gradingАга, значит список папок — это, по сути, список вьюшек, в которых иногда лежат модели. Проблема в том, что создавая новую вьюшку — как вы можете посмотреть модели, которые могли бы в ней использовать?
По такому принципу в папке orders вообще не должно ничего лежать. Или у вас бывает, когда заказы хотят что-то увидеть?
Конкретный заказ должен видеть скорее всего тот, кто его выполняет. Но в целом, может быть ситуация, когда в одном package'е будет несколько моделей.
значит список папок — это, по сути, список вьюшек, в которых иногда лежат модели
Domain — это не view.
Если посмотреть на те проекты, с которыми я сейчас работаю, у меня в domain'е будет скорее всего аналог controller'а, а может и не один, и какой-то набор service class'ов, причем произвольное количество слоев, в зависимости от сложности.
В итоге в игрушке есть сущность автомата в модели, которая используется и на карте, и в руках воина и в инвентаре и в каждом месте по разному. А модель — одна. И получается нам тогда надо WeaponDomain, в нее набросать все вьюшки, которые будут использовать уже другие компоненты. Что часто ужасно неудобно, т.к. крайне размазывает ответственность. У тебя всё зависит от всего, куча перекресных ссылок.
Намного удобнее, когда тебе дали задание изменить размещение элементов в инвентаре — ты зашел в view/inventory, а там — все элементы. А потом тебе дали задание иначе баллистику пули и способ рассчета повреждений реализовать — пошёл в модель, поменял там это.
Просто лично я сам когда писал лет 8 назад свой фреймворк — старался делить именно на сущности, а внутри уже на модель и отображение, но как-то со временем понялось, что это плохой подход и модель значительно удобнее на практике как отдельная сущность.
Хотя я предполагаю, что на каких-то примитивных задачах этот подход будет работать, но там любой подход будет работать.
А вообще мы как раз изначально говорили о фреймворке, разве нет? (с него мы начали, а не к нему пришли) — авторы целятся на более универсальное решение, которое не превратится в лапшу, стоит сделать шаг в сторону.
Если под «крупным сайтом» подразумевается какое-нибудь монолитное приложение, а не набор микросервисов, делим его на модули. Каждый модуль — domain. Ко всему есть подход. Но если DDD не нравится — заставить его полюбить не смогу, да и не нужно :)
А по поводу конкретного фрейморка — авторы никуда особо не целились, а тупо скопировали то, что было в Ruby on Rails.
как в Go принято — максимально плоско и explicit
Это верно подмечено, иначе просто не получится. Да и вообще понравились мысли в заключении. Я вот перехожу с java like языков на golang ну идеологически
При написании кода складывается ощущение что вернулся лет на 5 назад — ни нормального ddd, grasp, rich domain models, hexagonal architecture — ничего из этого не требуется, и более того, порицается со словами «это не go way»
Вообще я вижу go как язык с довольно узкой нишей(в которой он чертовски хорош). И очень грустно от того, что все почему то увидели в нем серебряную пулю, способную заменить enterprise языки. Из за попыток таких замен и получаются такие недоразумения как фреймворк, которому посвящена статья.
как только нужно что-то монструозно большое, я думаю что на Go, что на Java, что на .NET будут и фасады, и микросервисы, и вся эта братия
Безусловно. Нет в Go никакой магии, хоть некоторые в нее и веруют.
Так они на любом языке хорошо пишутся
Не совсем. В Java до последнего времени не было удобного HTTP Client «из коробки», к примеру. Какой-нибудь command line на Python'е потребует предустановленный интерпретатор, в то время как в Go получаем единый бинарник «из коробки», опять же.
Но не всё так плохо, изучал другие фреймворки, во многих разработка вообще встала 3-4 года назад.
В Beego мне нравится роутинг, шаблоны, получение параметров запроса, файлов запроса и тп, контроллеры содержат кучу полезных мелочей для облегчения разработки, получается не думать про организацию и работу кода, а сосредоточится только на реализации, при том вес фреймворка маленький.
Для примера переписал свой средний сайт с аудиофайлами, личный кабинет полностью, управление файлами, панель администратора, весь фронт — за 2 недели (вечерами от основной работы) и сайт весит теперь 10Мб RAM против ~500Мб на php (+ memcache столько же), ну а скорость работы просто самолёт )) Поэтому можно закрыть глаза на временное отсутствие нормальных DDD фреймворков.
По поводу фреймворков и Go, есть же знаменитая статья о том, что для Go не нужны фреймворки, мы используем библиотеки по необходимости, а так написать лишние несколько строчек, и то не всегда, — это на много меньшая проблема, чем потом разбираться как сделать то, что нужно в рамках фреймворка.
Главная проблема в современном программировании, что большинство читает всяких Фаулеров и Мартинов, но почти никто не читает Таненбаума, Кернигана и других умных людей, которые реально учёные и инженеры, а не "консультанты".
Вот реально, судя из комментариев, много народу считает, что название и иерархия папок — это проблема, которая требует серьезнейшего решения, но потом начинаешь общаться с людьми и оказывается, что они даже не знают, что такое кеш-линия.
Beego — это уже не Go