Как стать автором
Обновить

Комментарии 73

НЛО прилетело и опубликовало эту надпись здесь

MVC это вообще не паттерн, а обобщенный принцип, реализацию которого каждый видит по-своему. Есть, конечно, его описание еще из 80-х, но оно имеет очень отдаленное отношение к реализации в Rails-подобных фреймворках. Я предпочитаю избегать этого термина вообще и использовать более конкретные (скажем, hexagonal architecture).


А вот с термином ORM все нормально, просто надо вспомнить, как он определен у того же Фаулера. Active Record (или Data Mapper) — это способы реализации ORM. Вручную написанный (скажем, с SQL запросами и Reflection-ом) маппер для конкретной сущности — это тоже ORM.


По вопросу нарезки согласен: тут из DDD важно одно из ключевых его понятий "Bounded Context".

НЛО прилетело и опубликовало эту надпись здесь
ОRМ не уважаю категорически, когда нужна производительность и отлаживаемость — пишу SQL только вручную, и не разу за 25 лет не пожалел.

Нужно просто знать, где остановиться. Пытаться натянуть сову на глобус и делать «как в питоне» — заведомо обреченная на неуспех задача. Однако, если использовать ORM в меру, как просто продвинутый конструктор запросов — можно получить много профитов, фактически не отказываясь от ручного написания запросов. По крайней мере, в go.
Уж как минимум нужно использовать prepared statements. Без них как-то вообще грустно.
О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.

Интересно, а есть успешные кейсы следования идеям DDD при написании приложух на go?
Я чаще всего пока слышу такую точку зрения, что один микросервис на go == Bounded context и получается что код в рамках сервиса уже сам по себе сгруппирован по предметной области.
Ну и прикрываясь этим в project structure самого проекта творится лютая дичь с папками вроде controllers, dbQuery, services и т.д Чего только стоят официальные рекомендации на этот счет.
Т.е внутри микросервиса файлы группируются по технологическим признакам, т.к считается что вся кодовая база одного сервиса обслуживает небольшую узкую часть предметной области
они разбились о сложность
реального мира, и более неактуальны.


Как по мне, очень спорное и слишком категоричное утверждение. Может поясните?

В конце концов, MVC — это всего навсего способ организации обработки http запроса, и он нисколько не противоречит DDD. А, наоборот, чаще всего с ним сочетается.

Если MVC — это неправильная архитектура организации приложения, то как тогда в go надо? Чем он так принципиально отличается от всего остального back-end мира?
MVC — это всего навсего способ организации обработки http запроса

MVC к HTTP не имеет отношения.
Я так понял, что автор комментария имеет ввиду «MVC в контексте бекенда web-приложения — это всего навсего способ организации обработки http запроса»
НЛО прилетело и опубликовало эту надпись здесь
От solid при таком подходе придется отказаться в первую очередь. А вообще, представляю эти «божественные объекты», хранящие в себе логику обработки запросов, обращения к бд и вывода. Вот уж гибкая, читаемая и прозрачная структура получится. :)
Имхо, логика все-таки должна быть разделена по слоям, и даже компонентный подход этому не противоречит. А MVC в свое время как раз и возник, как способ разгрести ту кашу, в которую очень быстро превращается реализация предлагаемого вами паттерна.
Впрочем, всему свое время и место, наверное.
НЛО прилетело и опубликовало эту надпись здесь
В любом языке будут школьники, которые молятся на устаревшие подходы.
Например, в России студентов натаскивают на ООП и MVC, поэтому у нас в каждой щели ООП и МВЦ, даже когда от них вообще никакого толку.

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

И работодателю очень сильно кажется, что как раз такие люди для него особенно эффективны. А они ещё и не шибко дороги.

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

Я всё это к чему. Если миллион мух делает что-то ужасное, это совершенно не значит, что что-то в этом мире не так. Главное, не работать с этими мухами вместе, чтобы не приходилось иметь дело с тем же, что и они.

ООП нисколько не устарело. Устарело то понимание ООП, которому учат студентов (при этом в основном учат те, кто сам толком не понимает ООП).


Модный ныне DDD — это по сути и есть реализация принципов SOLID. Давно уже модные микросервисы — тоже, по сути, ООП: если каждый микросервис рассматривать как объект, получается 1 в 1 определение Алана Кея.

НЛО прилетело и опубликовало эту надпись здесь
И это не такая уж мелочь, потому что, напомню, в Go package == folder. И если тест находящийся в том же package'е может вызвать private method, то тест находящийся в другом package — уже нет.

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

В Go в целом нет такой вещи, как private method, все package private.
С чего вы взяли что тестирование приватных методов это моветон.
Наверно исходя из того, что тестировать надо поведение и результат, а не «внутренности». Иначе, тест будет по сути тем же, что и тестируемое… только написано сложнее и корявее :)
Ну просто не всегда возможно проверить тестоми которые используют только публичные методы все граничные условия. Точнее наверно это можно сделать но сложность этих тестов может быть гораздо выше, чем тестов которые могут вызывать приватные методы. И опять смотря что приватный метод делает.
Эту проблему я решаю так: Если мне надо протестировать что-то «приватное», то выделяю это в отдельную сущность, с публичным API.

Это кстати так же помогает например с логированием через декоратор, а не прямо в методе, посреди функциональности вызывать логер. Все становится чистым и приятным.
Ага. Именно поэтому во всех железках, от автомобилей до микропроцессоров есть специальные тестовые подключения, к которым обычный потребитель доступа не имеет. Идионы, наверное, всё это проектируют.

</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

НЛО прилетело и опубликовало эту надпись здесь
Глобальное состояние в целом не зло, особенно в Go, где правила несколько изменились и повсеместное применение глобальных package-level переменных и прочего работает и проблем не создает в большинстве случаев. Все таки это идиоматичный Go, а не какая-то редкая практика. Единичные примеры тут мало что значат и говорят, в первую очередь, о странно написанном glog. Либа, которая лезет во флаги командной строки — вот где проблема, а не в глобальном состоянии.
То, что так сделано в стандартной библиотеке — не значит, что это хорошая практика.
Безусловно не значит, но, с другой стороны, может быть тогда вообще не стоит писать на языке, где стандартная библиотека не отражает хорошие практики :)?
Писать fullstack приложения на Go, как это предлагает делать Beego, явно не стоит.

Да много где стандартная библиотека кривая — сначала не все продумали, а потом не меняли из соображений обратной совместимости. В PHP, например. Или в Java местами. Это не значит, что надо ждать языка своей мечты до пенсии :)

Я бы даже сказал сильнее: на языке, где в стандартной библиотеке все «прямо и красиво» ничего писать не стоит. Потому что вариантов ровно два:
1. Эту библиотеку постоянно меняют и ломают совместимость (см. D).
2. Язык ещё невероятно молодой и на нём мало чего написано, отчего и непонятно — что именно сделано криво в стандартной библиотеке (см. 100500 новых и новейших языков).
Ну Go не такой молодой язык, чтобы ему требовалось из соображений совместимости что-то оставлять :). Не думаю, что авторы языка считают "_" импорт ради побочных эффектов за плохую практику.

Обычно импорт "_" делается либо для того, чтобы сработал init() пакета, либо чтобы достать что-то импортированное через рефлексию.


Сомневаюсь, что в общем случае это хорошая идея.

Да, я собственно про вариант с init() и говорил :).
Так же не означает, что практика плохая, если есть плохие примеры ее использования. Практика вполне хорошая, полезная, удобная. Когда применяется правильно проблем не создает, что и показывает пример стандартной библиотеки.
Достаточно надуманная критика, которая явно исходит из личных предпочтений и своего взгляда на best practics. И ещё есть постоянные болезненные отсылки к RoR.
Я ни разу не пользователь 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.
Если вы хотите постоянно тестировать приватные методы, то у вас более крупные проблемы


Не могли бы вы пояснить свою мысль.

Мне вот как то наоборот, 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, то у нас получается спагетти. С классическим разделением контроллеров-вьюшек-моделей такая проблема проблема проявляется значительно меньше.
С чего бы? Количество классов в обоих примерах одинаковое, зависимости те же.
А от пересечений между неймспейсами — больше. И куда бы вы отнесли какие-то совмещенные вьюшки? Ну типа таблица orders <=> users.

Ну вот у вас есть модели Ученик и Урок:
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'ов, причем произвольное количество слоев, в зависимости от сложности.
Просто на практике, а не в сферических примерах обычно модели и вьюшки относятся к разным доменным зонам, то есть не пересекаются напрямую, как вы написали и просто их соотносить вот модель, а вот её вьюшка — крайне неудобно. Получается, пишется модель, их взаимосвязи, возможно даже модель дублируется и на сервере, и на клиенте. Потом отдельно на эти модели направляются вьюшки. Они могут быть разными. Скажем, mobileView и desktopView. А модель — она одна. И часто на эти направления нужны разные люди. Кто-то лучше верстает и шрифты подгоняет, а кто-то лучше предметную область и взаимодействие компонентов понимает.

В итоге в игрушке есть сущность автомата в модели, которая используется и на карте, и в руках воина и в инвентаре и в каждом месте по разному. А модель — одна. И получается нам тогда надо WeaponDomain, в нее набросать все вьюшки, которые будут использовать уже другие компоненты. Что часто ужасно неудобно, т.к. крайне размазывает ответственность. У тебя всё зависит от всего, куча перекресных ссылок.

Намного удобнее, когда тебе дали задание изменить размещение элементов в инвентаре — ты зашел в view/inventory, а там — все элементы. А потом тебе дали задание иначе баллистику пули и способ рассчета повреждений реализовать — пошёл в модель, поменял там это.

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

Хотя я предполагаю, что на каких-то примитивных задачах этот подход будет работать, но там любой подход будет работать.
Мы как-то перескочили на написание игрушек и собственных фреймворков.
Да дело в том, что разделение на модель/вьюшку — универсально, и хорошо расширяется. А разделение по доменам — как повезет. В каких-нибудь средне-крупных сайтах будут те же проблемы, просто пример игрушки — очевиднее.

А вообще мы как раз изначально говорили о фреймворке, разве нет? (с него мы начали, а не к нему пришли) — авторы целятся на более универсальное решение, которое не превратится в лапшу, стоит сделать шаг в сторону.
Возвращаемся к тому, что разделение по доменам не отменяет разбиение на модели и view.

Если под «крупным сайтом» подразумевается какое-нибудь монолитное приложение, а не набор микросервисов, делим его на модули. Каждый модуль — domain. Ко всему есть подход. Но если DDD не нравится — заставить его полюбить не смогу, да и не нужно :)

А по поводу конкретного фрейморка — авторы никуда особо не целились, а тупо скопировали то, что было в Ruby on Rails.
Но если DDD не нравится — заставить его полюбить не смогу, да и не нужно
То есть аргументов нету и надо поверить и полюбить наивной любовью?
То есть у меня в целом нет цели убеждать любить DDD кого-то, с кем я не работаю :)
как в Go принято — максимально плоско и explicit

Это верно подмечено, иначе просто не получится. Да и вообще понравились мысли в заключении. Я вот перехожу с java like языков на golang ну идеологически ахереваю переход дается очень тяжело.
При написании кода складывается ощущение что вернулся лет на 5 назад — ни нормального ddd, grasp, rich domain models, hexagonal architecture — ничего из этого не требуется, и более того, порицается со словами «это не go way»
Вообще я вижу go как язык с довольно узкой нишей(в которой он чертовски хорош). И очень грустно от того, что все почему то увидели в нем серебряную пулю, способную заменить enterprise языки. Из за попыток таких замен и получаются такие недоразумения как фреймворк, которому посвящена статья.
Go очень легко продавать менеджменту — «низкий порог входа, нанимаем джунов, concurrency, 100K RPS, все дела».
а в какой нише он чертовски хорош? Просто не в теме, интересно
У Go есть одно хорошее применение — инфраструктура. Какая нибудь command line utility или reverse proxy на нем действительно пишутся хорошо. Все остальное — так себе.
Ха! Так они на любом языке хорошо пишутся, а весь этот оверхэд, который так поносится в статье и, я так понимаю, Go-разработчиками, тоже не на пустом месте возник — как только нужно что-то монструозно большое, я думаю что на Go, что на Java, что на .NET будут и фасады, и микросервисы, и вся эта братия
как только нужно что-то монструозно большое, я думаю что на Go, что на Java, что на .NET будут и фасады, и микросервисы, и вся эта братия

Безусловно. Нет в Go никакой магии, хоть некоторые в нее и веруют.

Так они на любом языке хорошо пишутся

Не совсем. В Java до последнего времени не было удобного HTTP Client «из коробки», к примеру. Какой-нибудь command line на Python'е потребует предустановленный интерпретатор, в то время как в Go получаем единый бинарник «из коробки», опять же.
Использую Beego постоянно, про DDD и ORM согласен, ужас ужасный.
Но не всё так плохо, изучал другие фреймворки, во многих разработка вообще встала 3-4 года назад.
В Beego мне нравится роутинг, шаблоны, получение параметров запроса, файлов запроса и тп, контроллеры содержат кучу полезных мелочей для облегчения разработки, получается не думать про организацию и работу кода, а сосредоточится только на реализации, при том вес фреймворка маленький.
Для примера переписал свой средний сайт с аудиофайлами, личный кабинет полностью, управление файлами, панель администратора, весь фронт — за 2 недели (вечерами от основной работы) и сайт весит теперь 10Мб RAM против ~500Мб на php (+ memcache столько же), ну а скорость работы просто самолёт )) Поэтому можно закрыть глаза на временное отсутствие нормальных DDD фреймворков.
можно закрыть глаза на временное отсутствие нормальных DDD фреймворков


Не думаю, что временное.

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


Главная проблема в современном программировании, что большинство читает всяких Фаулеров и Мартинов, но почти никто не читает Таненбаума, Кернигана и других умных людей, которые реально учёные и инженеры, а не "консультанты".


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

Боюсь предположить, как знания внутреннего устройства процессора помогут написателю кода на какой-нибудь Java, которому нужно туда-сюда гонять JSON'ы по сети.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории