Beego — это уже не Go

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

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

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

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

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

Давайте разберем несколько основных аспектов Beego, и почему они противоречат различным best practices в Go, да и в индустрии в целом.

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

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

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

Но проблема даже серьезней. Для Go нет разделения между структурой папок и структурой пакетов (package’ей). Потому в Beego и UsersController и OrdersController будут под одним package’ем — controllers. А если у вас controller’ы двух типов, те, что сервят UI и те, что используются для API, причем последние в приличном обществе принято версионировать? Тогда будьте готовы к уродцам вроде 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 {string} 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? Нет, не слышали.

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

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.

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

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

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

import (
_ «github.com/../../routers»
)

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

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

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

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

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

Оставить комментарий