Хабрахабр

Beego — это уже не Go

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

Хайп Go пришелся где-то на 2014ый год, когда авторы приложений имевших от силы 1000RPM (requests per minute) вдруг как один решили, что им срочно нужен concurrency, потому что вот-вот их 1000RPM превратиться в 1000RPS (что тоже не так много, на самом деле).

И эту архитектуру, как сову на глобус, они стали натягивать на Go. Результатом хайпа стало то, что к Go приобщилось много людей, привыкших к MVC архитектуре приложения, буть то Spring, Django или Ruby on Rails. Revel благополучно сдох, хотя его и пытаются все еще откачать. Так появились кадавры вроде Beego и Revel. А вот о Beego хочется поговорить отдельно.

Практически «Евангелие от Ричарда». Немалый вклад в продвижение Beego среди масс вложил Richard Eng своим циклом статей «A word the Beegoist». Иронично, что не смотря на то, что Ричард оголтело продвигает Go, сам он на нем не пишет.

И могу сказать, что это явно не тот путь, которым должна идти разработка на Go.
Давайте разберем несколько основных аспектов Beego, и почему они противоречат различным best practices в Go, да и в индустрии в целом. В свою очередь я с Go, а еще хуже, с Beego, проработал не мало.

Структура папок

Robert C. Martin, более известный как Uncle Bob, неоднократно озвучивал идею, что структура приложения должна передавать его суть. Он крайне любит приводить пример с кафедральным собором, на который можно посмотреть сверху, и сразу понять, что это кафедральный собор.

Проблема такого подхода заключается в том, что приложение по продаже носков «сверху» будет выглядеть точно так же, как приложение для заказа еды. Роберт неоднократно критиковал Ruby on Rails за его структуру папок — controllers, models, views, вот этого всего. И для того, чтобы понять суть приложения, нужно будет забраться в какую-нибудь папку models, и посмотреть, а с какими же сущностями мы в итоге имеем дело.

В то время как тот же Spring ушел в сторону Domain Driven Design и структуры папок передающей суть, Beego навязывает использование структуры, ставшей уже antipattern'ом. Именно это больное поведение Beego и копирует.

Для Go нет разделения между структурой папок и структурой пакетов (package'ей). Но проблема даже серьезней. А если у вас controller'ы двух типов, те, что сервят UI и те, что используются для API, причем последние в приличном обществе принято версионировать? Потому в Beego и UsersController и OrdersController будут под одним package'ем — controllers. Тогда будьте готовы к уродцам вроде apiv1.

ORM

Довольно странно, что Beego, будучи неудачным клоном Ruby on Rails, при этом не использует ActiveRecord pattern. Его ORM представляет собой крайне странное зрелище. Если для совсем базовых операций, вроде прочесть строку/записать строку, он еще годится, то вот, к примеру, как выглядит простенькая выборка (здесь и далее примеры взяты напрямую из документации):

qs.Filter("profile__age__gte", 18) // WHERE profile.age >= 18

Но основная проблема с Beego ORM даже не в том, что нужно бороться с proprietary языком, а в том, что он использует все худшие практики Go, будь то import sideffect'ов:

import ( _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3"
)

Или регистрация моделей в init():

func init(){ orm.RegisterModel(new(User))
}

Сделайте себе одолжение, даже если вы все же решите по какой-то необъяснимой причине работать с Beego, не используйте Beego ORM. Если вам без ORM жизнь не мила (а что вы делаете в мире Go, милейший?), пользуйтесь GORM. Он хотя бы поддерживается. Иначе, «database/sql» вам в помощь.

Bee tool

Из Ruby on Rails скопирован так же command line tool, который зовется просто Bee. Вот только если в мире RoR был rails и был rake, то bee — это такая мусорка для всего. Он и MVC приложение за'boostrap'ит, и миграции прогонит, и file watcher запустит. В последнем и кроется еще одна проблема. Ведь в чем одно из основных достоинств Go? То, что запускается локально, максимально близко к тому, что запустится в production'е. Если вы не используете bee, конечно.

Automatic routing

Go — строго типизированный язык, который при этом не поддерживает ни generics, ни annotations. Как на таком слепить MVC фреймворк? Путем чтения комментов и генерации файлов, конечно.

Выглядит это примерно так:


// @Param body body models.Object true "The object content"
// @Success 200 models.Object.Id
// @Failure 403 body is empty
// @router / [post]
func (this *ObjectController) Post() { var ob models.Object json.Unmarshal(this.Ctx.Input.RequestBody, &ob) objectid := models.AddOne(ob) this.Data["json"] = map[string]string{"ObjectId": objectid} this.ServeJson()
}

Очевидность, как можно видеть, — нулевая. Функция Post() вообще ничего не получает и не возвращает. http.Request? Нет, не слышали.

При запуске пресловутого bee генерируется еще один файл, commentsRouter_controllers.go, который содержит пример такого замечательного кода: Ну, а как работает весь routing?

func init() { beego.GlobalControllerRouter["github.com/../../controllers:ObjectController"] = append(beego.GlobalControllerRouter["github.com/../../controllers:ObjectController"], beego.ControllerComments{ Method: "Post", Router: `/`, AllowHTTPMethods: []string{"post"}, MethodParams: param.Make(), Filters: nil, Params: nil})
...
}

Смотрите, не забудьте перегенерировать и за'commit'ить этот файл после каждого изменения. До последнего времени ситуация была еще печальней, и во время тестов этот файл генерировался автоматически, так что о проблемах вы узнавали уже в production'е. Кажется в последних версиях это странное поведение было исправлено.

Component testing

И так мы подходим к теме тестирования. Go, в отличие от большинства других языков программирования, приходит с тестовым фреймворком «из коробки». В целом, философия Go в том, что тест должен сидеть рядом с тестируемым файлом. Но мы же в мире MVC, плевать на философию Go, верно? Потому будьте добры все свои тесты разместить в папочке /test, как завещал нам DHH.

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

Код Beego в принципе очень сложно тестировать, поскольку в нем все на свете — это side effect. Но ладно бы все ограничивалось структурой папок.

Вот так Beego запрашивает routers:

import ( _ "github.com/../../routers"
)

Та же история и с middleware'ами, и с controller'ами, которые я уже упоминал раньше.

Документация

Это для меня как software architect'а вишенка на торте. Документация в BeeGo хороша настолько, насколько хорош ваш китайский. Нет, от комментов на китайском внутри кода за последние года два уже вроде избавились.

Теперь на китайском остались только некоторые pull request'ы:

image

И в особенности в issues:

Вместо заключения

Если у вас есть команда написателей кода на Ruby/PHP/Python, и вы срочно хотите перевести их на Go, худшее, что вы можете для них сделать — это заставить их перейти на MVC фреймворк на Go. MVC в целом так себе архитектурный паттерн, а в Go он вообще не к месту. Либо, если вы уж совсем уверены, что ничто кроме Go вас не спасет, пусть переучиваются и пишут так, как в Go принято — максимально плоско и explicit. Либо, быть может им видней, при помощи какого инструмента решать поставленные им задачи?

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть