Найти тему
Small Entropy

Велосипеды и костыли: история одной идеи

Оглавление

Как и любой другой разработчик я активно работаю над своими pet-проектами. Первая задача, которая встаёт перед любым программистом, выбор стека технологий. Естественно, любому в своём pet-проекте хочется сделать идеальное решение. И вот тут начинаются проблемы.

Стартапы...pet-проекты... Источник - Яндекс.Картинки
Стартапы...pet-проекты... Источник - Яндекс.Картинки

Идея для pet-проекта

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

Я хочу качественный продукт

Первое что говорит себе любой разработчик: "Я хочу получить качественный продукт". Почему это проблема?

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

Как только вы хотите делать pet-проект найдите или выделите человека, который будет оценивать результат со стороны пользователя. Скажем, сервер авторизации бесполезен без удобной формы. Функционал пользовательских параметров не нужен, пока UI не учитывает их. И так во всём.

Весь код, который не решает проблемы пользователя - бесполезный для проекта код (на этапе старта). И это надо осознать, иначе вы, как и мы, убьёте огромное количество времени "впустую", т.к. будете решать свои проблемы, а не удовлетворять потребности пользователя.

Я хочу поддерживаемый код

Желание сделать поддерживаемый код - для опытного инженера-программиста тоже большая проблема. Чем опытнее человек - тем больше он знает разных паттернов, практик, подходов. И часто - применяет не самый простой и понятный подход.

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

Я хочу автоматизировать процесс разработки

Бич красивых реализаций, особенно когда вы работает с языками, которые поддерживают меттапрограммирование. Ни один junior не сможет вам провести полноценный debug макроса и кода, который в итоге попал в программу. Это слишком сложно для среднестатистического специалиста!

Так же - чаще всего рутина дешевле. Автоматизировать процесс в разы дороже, чем сделать рутинную задачу.

Я хочу использовать крутой стек

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

Итоги моих ошибок

Итогом моих попыток сделать крутой продукт стал перевод всего проекта на NodeJS и фреймворка Nest. Пришёл я к этому после того, как отказался от Julia, Racket и CommonLisp.

Использование Dlang + Vibe.d для требовательного API и Ruby + Roda для не нагруженного API

Dlang - очень удобный язык. Это C++ с удобством Python, не плохим пакетным менеджером, компиляцией и возможностью управления памятью. Одним из главных фреймворков Dlang для web'а является Vibe.d. Главный недостаток, который я смог выявить - дикие утечки при компиляции достаточно простого сервиса (сервис авторизации). Почему так - коллега, который занимался DevOps толком не разбирался и просто докинул ресурсов. Возможно у нас просто кривые руки.

Что до Ruby и Roda - отличный стек, но нам казалось, что он будет не достаточно производительным. А количество строк кода немного пугало (ведь у нас везде были микросервисы, что - для команды из 3 человек мягко говоря зря). Вот с Ruby проблем не возникло от слова совсем.

Отказ от Racket

Lisp, который просто приятно использовать. Источник - Яндекс.Картинки
Lisp, который просто приятно использовать. Источник - Яндекс.Картинки

В одно утро я нашёл достаточно молодой фреймворка Vela для Racket. Понравилось то, что код выходил очень компактным, а сам язык поддерживал прекрасную возможность использовать макросы для генерации исходного кода сервера. Почему-то нам идея это понравилась. Мы хотели описывать декларативно в YAML-файлах под сервера и логики API (если она была на уровне CRUD) с возможностью подключения handler'ов там где нужно что-то сложнее.

Сами же handler'ы мы планировали реализовывать на DSL (так мы хотели решить процесс вливания junior-разработчика), который должен был быть чем-то вроде python, с ограниченными возможностями и стандартной библиотекой для взаимодействия с платформой.

Начали длительный процесс реализации идеи.

Сразу хочу сказать, что от Racket я отказался исключительно потому, что инструменты выбранные мной просто плохо работали (скажем, пришлось делать доработку драйвера для MongoDB для того, чтобы я мог использовать необходимый мне функционал). Язык Racket является одним из наиболее красивых из стеков с которыми я поработал (и именно в этот момент я пожалел, что не стал использовать Clojure), но количество батареек удручает. Хотя, если бы я не пытался писать универсальное нечто - было бы всё намного проще и удобнее. В свою очередь хочется сказать спасибо всему сообществу языка Racket за отзывчивость. При работе над платформой на основе Racket, MongDB, Vela я получил бесценный опыт, который останется со мной навсегда. Всем советую хотя бы раз попробовать сделать такой продукт.

Отказ от Racket в сторону Common Lisp и работа под Woo

Лучший маскот для самого гибкого языка программирования. Источник - Яндекс. Картинки
Лучший маскот для самого гибкого языка программирования. Источник - Яндекс. Картинки

В какой-то момент я устал бороться с недостатком батареек под Racket и посмотрел в сторону Common Lisp. Требовалось не так много для переноса кода с одного диалекта на другой. Аналог Vela можно было реализовать достаточно быстро самому. В качестве сервера был выбран не блокирующий Woo. В качестве реализации был взять Steel Bank Common Lisp (SBCL).

Начался новый цикл переноса кодовой базы. Он шёл достаточно быстро и просто (благо Emacs в качестве IDE мне нравится, а REPL для CL просто прекрасен). Почему ушли? В момент, когда сервер научился собирать из YAML-файлов базовый сервер я понял, что я просто не найду людей на поддержку продукта. В одиночку - уже не вытягивал. Как раз прошло серьёзное повышение по работе, которое отъело много свободного времени (освоение новых областей ответственности).

Следовательно мне было надо передавать часть работы junior'у, который до этого просто разбирался с базовыми принципами архитектуры. И тут я могу сказать - порог вхождения в Common Lisp выше, чем в Racket. Просто потому, что для Racket есть очень удобная и качественная, современная документация. Даже сам Emacs как IDE стал проблемой.

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

Julia и проблемы ФП

Самый приятный python-подобный язык. Источник - Яндекс.Картинки
Самый приятный python-подобный язык. Источник - Яндекс.Картинки

И после работы над попыткой написать платформу на достаточно функциональном, но в тоже самое время гибком язык Julia могу сказать - мне не понравилось. Julia... привкус python в нём силён на столько, что возникают вопросы, а почему не Python?

Решалась работа junior'a тут просто, кстати. Julia умеет вызывать Python и получать из него результат выполнения кода. Да только мы так и не добрались до этой фичи, т.к. погрязли в куче разных доработок функционального кода. Почему так вышло?

Именно с переходом на Julia и получения быстрого начального результата мы задумались о бизнес-сценариях и поняли, что кодовая база перенесённая с Lisp просто не выдерживает никакой критики. Мы делали продукт красиво выглядящий для программиста-теоретика и отвратительный для прикладника. Решения, которые мы предлагали - было очень сложным, выглядело монструозным. А ещё - именно тут мы осознали на сколько же в бизнес-кейсах удобно ООП. Без него мы были вынуждены придумывать массу сложных обёрток и действий, которые позволяли получить нам нужный результат. В итоге фича которая делается в ООП языках за счёт простого наследования превращалась в новую задачу по продумыванию архитектуры. Не спорю, что возможно просто я плохой ФП программист или начал переносить на Julia код из Lisp "в лоб" (там я проблему наследования легко решал макросами).

В итоге, мы взяли паузу и начали переносить кода на NodeJS. И итогом стала тестовая архитектура и свой фреймворк Raccoon, которые сейчас болтаются в NPM...

NodeJS, Raccoon, Nest

Лучший фреймворк для NodeJS. Источник - Яндекс.Картинки
Лучший фреймворк для NodeJS. Источник - Яндекс.Картинки

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

Тогда я снова обратился к фреймворку Nest, который когда-то использовал на прошлой работе (тогда он был в зачаточном состоянии) и увидел...они реализовали всё, что мне было нужно и как мне было нужно.

Итоге - за 3 недели была перенесена вся кодовая база. Микросервисы переехали в монолит, а в основу легла луковичная архитектура. На данный момент кодовая база растёт, а фичи - просто начинают работать после того, как появилась потребность.

Так же, за счёт луковичного подхода мы перешли к понятию "дистрибути", когда у нас сервер приложения собирается из набора готовых модулей. При необходимости - мы легко сможем выделить нужный функционал в отдельный сервер.

Итоги

Закончился 2020 год и мы отказались от своего pet-проекта. Сейчас он трансформировался в работу над 2 более простыми сервисами без замашек на разработку универсальной платформы. С одной стороны - это очень печальный опыт. С другой - мы наелись много кактусов и получили большой опыт в разработки по настоящему сложного продукта очень малой командой.

Выводы которые мы сделали для себя:

  • маленькая команда должна брать монолитную архитектуру, но делать из неё луковицу. Таким образом не будут тратиться ресурсы на связывание микросервисов с тобой и проще выпустить нужный функционал. С точки зрения DevOps монолит - самый простой вариант в принципе.
  • сложные языки (Lisp, Haskell) не просто так не стали популярными. Порог вхождения в них очень высок, в то же время - программистов способных их освоить для написания полезного кода не так много. Беря такой стек - вы обрекаете проект на проблемы с поддержкой и трудностями в разработке (батареек для них в разы меньше).
  • компилируемые языки в любом случае потребуют вменяемый build-сервер, иначе чехарда с контейнерами будет достаточно узким местом. К тому же - в реальных задачах скорее всего вашим узким местом станет канал или СУБД, а не сервер приложения. Количество же запросов дешевле решать горизонтальным масштабированием.
  • не стоит изобретать велосипед на не популярном стеке. Стек не просто так не получил распространения, вероятнее всего где-то есть проблемы и связаны они именно с реализацией бизнес-задач.
  • применимость стека можно понять только при работе над бизнес-задачами. В остальных случаях вы работаете со "сферическим конём в вакууме" и с ним всё будет хорошо.
  • пишите тесты. Только написание тестов позволило нам быстро переносить код со стека на стек и не терять функциональность.

Удачи вам в разработке pet-проектов! Надеюсь эта заметка будет кому-то полезна.