Последние месяцы я тратил (почти) все свое свободное время, работая над своим дополнительным бизнесом — JustFax. Все началось с миграции с LemonSqueezy на Stripe (интересно узнать почему? Подпишитесь на мою другие блоги информационный бюллетень, так как я планирую написать подробный анализ, почему вы можете захотеть выбрать платежный шлюз вместо Merchant of Record). Но, как и каждый рефакторинг или переписывание, он стал намного больше, чем я ожидал. Простое изменение поставщика платежей потребовало от меня реализации очереди обработки заданий поверх SQL, а также создания крошечной системы учета. Все на Rust, конечно. Это привело к одному из самых больших merge-реквестов, которые я когда-либо объединял.
Этот merge-request пришел как раз вовремя и напомнил мне, что я начал JustFax около года назад (может быть, 11 месяцев). В дополнение к этому, я работаю с Rust в production в моем $MAIN_JOBпоэтому я решил дать обзор того, как использовать Rust в производстве.
Если он скомпилирован, он запустится
Я кратко упомянул об этом в своем первое впечатление от написания веб-приложения на Rustи я хочу подчеркнуть это еще раз.
Обычно я не запускаю свой код после каждой измененной строки (если только я не делаю раздражающие корректировки CSS). Большую часть времени я заканчиваю большой кусок рефакторинга или реализации функции, а затем запускаю все для тестирования. Более того, с Rust вы не можете запустить код, который не компилируется, поэтому если в вашем коде есть ошибки, вам нужно их исправить, в отличие от JavaScript. И поэтому я увлекся рефакторингом, который длился бесконечное количество вечеров в течение нескольких месяцев. Одно привело к другому, и код никогда не был в состоянии, когда он мог бы компилироваться, до самого последнего момента, когда все было сделано.
Я был в страхе, управляя всем, но после настройки каждого компонента системы и выдачи первого (местный) запрос, я был удивлен, увидев, что все работает! Я знаю, что я хороший разработчик, но я не обманываю себя, что могу писать код без ошибок. И за исключением мелких проблем с динамическими аспектами кода, такими как неправильные форматы сериализации/десериализации, код работал безупречно.
Первый слой этого успеха — типобезопасная, компилируемая природа Rust. Вы не можете случайно назначить i64 к uuidкомпилятор Rust не позволит вам сделать это. Вы также не можете получить доступ к несуществующему полю в структуре, в отличие от JavaScript. И конечно, есть люди, которые утверждают, что никогда не сталкивались cannot access property "foo" of undefined ошибки времени выполнения, мой опыт, как и опыт моих коллег — не одно и то же. Одна из причин, по которой мы используем Rust в моем $MAIN_JOBпотому что существующий бэкенд, написанный на динамическом языке, стал неуправляемым. Конечно, вы можете держать контекст небольшого приложения в голове, но в какой-то момент он перерастет вашу память. Я помню, в старые времена, когда я занимался PHP, в какой-то момент нам приходилось прибегать к выгрузке содержимого массива на экран, чтобы узнать, какие у него ключи, потому что некоторые ключи были snake_caseв то время как другие camelCaseи т. д.
Второй слой, и это своего рода расширение первого, это одержимость людей безопасностью типов в сообществе Rust. И это хорошая одержимость, потому что она рождает такие инструменты, как sqlx — типобезопасная оболочка SQL, работающая во время компиляции, которая запускает ваши запросы к реальной базе данных и не позволяет вашему коду скомпилироваться, если ваш запрос синтаксически неверен или вы пытаетесь вставить i32 в TEXT столбец без явного приведения его типа. Раньше я боялся SQL, потому что это обычно большая часть каждого приложения, и легко ошибиться. Неправильно напишите запрос или перепутайте аргументы, и удачи вам в производстве. Многие люди пишут модульные тесты вокруг SQL-запросов, в то время как другие прибегают к использованию ORM или генераторов кода из вашей схемы. Но с sqlx Мне больше не о чем беспокоиться. Я получаю всю мощь SQL, не прибегая к высокоуровневому, очень абстрактному ORM или не прибегая к генерации кода из схемы базы данных. Конечно, есть недостатки, и я упомяну некоторые из них далее в блоге.
Другой набор инструментов, которые я могу отнести к этой категории, — это шаблонизаторы с безопасными типами, такие как askama или maud. Они не нашли своего места в моем проекте, хотя я и играл с ними, но они, тем не менее, удаляют еще один элемент непредсказуемости из вашего кода. Одна из вещей, которая заставляет меня смеяться, это когда я получаю письмо, которое начинается с Hello {{userName}}. Смешно получать его от неизвестного сервиса; стыдно получать его от банка. С типобезопасными, скомпилированными шаблонами такие ошибки не существуют.
Единственное, чего мне не хватает, так это типобезопасного интерфейса стороннего API. Конечно, вы можете сгенерировать клиент OpenAPI из спецификации OpenAPI/Swagger, но вы все равно остаетесь во власти стороннего поставщика API, и тот факт, что у них есть спецификация, не обязательно означает, что они будут ей следовать и не сломают свой API, так что я не уверен, что мы сможем решить эту проблему.
И когда он работает, он очень стабилен.
Я никогда не сталкивался с крахом процесса Rust. Я сталкивался с крахом процесса Node. Если только вы не загрязняете свой код .unwrap() (что по сути означает «если результат — ошибка, крах»), есть большая вероятность, что ваш процесс никогда не рухнет. У меня есть несколько .unwrap() вызывается в моем коде, в основном во время инициализации, поэтому если файл конфигурации отсутствует или переменная окружения не определена, я ничего не могу сделать, кроме как аварийно завершить процесс. Но в целом Rust требует от вас явной обработки ошибок. Все, что возвращает Resultтребует, чтобы вы либо подняли ошибку через ? оператора или выполнить match заявление об этом. В большинстве случаев это имеет смысл. А в некоторых случаях это немного раздражает (например, когда вы знаете, что преобразование из i64 к i32 будет успешным, потому что он содержит число, которое находится в пределах границ i32вам все равно необходимо сделать try_from() который возвращает Result). Но даже в досадных случаях я стараюсь не прибегать к .unwrap()и предпочел бы зарегистрировать предупреждение и вернуть какое-то разумное значение по умолчанию. Программирование непредсказуемо, так что, по крайней мере, так моя программа может продолжать работать, регистрируя место, где я мог быть глупым.
JustFax — это не просто Rust, у него также есть внутренние системы, написанные на TypeScript и других фреймворках JS. И я клянусь, что каждый раз, когда я создаю новый проект TypeScript, что-то меняется. Либо файл конфигурации изменился для какого-то инструмента (смотрю на вас eslint); или есть новый шаблон для машинописного текста; или express больше не круто и теперь все используют fastify; или express является снова круто; или ts-node плохо, теперь мы используем tsx; или этот модуль esm только, пока этот cjs так что пойди разберись. Всегда есть что-то с TypeScript. То, как ты работаешь, как ты линтуешь свой код, как работают рабочие пространства в vanilla JS по сравнению с Машинопись.
Но не с Растом.
cargo init – бум, и вы получаете новый проект. Нужны рабочие пространства? Никаких проблем, работает безупречно. Линтинг?
clippy вас охватили. Просто гораздо меньше шаблонов и умственной усталости.
Время компиляции все еще PITA
Несомненно, самый большой недостаток Rust — это время компиляции. Особенно, если прибегнуть к использованию таких инструментов, как sqlx или maud которые в значительной степени полагаются на макросы, как LSP во время разработки, так и компилятор — начинают бороться. По мере роста проекта я собираю больше сторонних зависимостей. И пока я пытаюсь оптимизировать их, помещая общие зависимости между несколькими пакетами, в корне Cargo.tomlа также агрессивно выбирая только необходимые функции зависимостей, я все еще борюсь со временем компиляции. Я упоминал, что первоначальная компиляция заняла около 6 минут внутри CI/CD, что теперь занимает около 20 минут. Согласно различным блогам в Интернете, ее можно оптимизировать, но для этого требуется хирургическая процедура, чтобы агрессивно кэшировать зависимости в многоэтапном контейнере Docker. У меня сейчас нет на это времени, и мне нужно больше сосредоточиться на деловой стороне, но я вернусь к этому позже.
Локальная компиляция на Mac M2 управляема, особенно с инкрементальной сборкой, но это стоит денег на хранение. Время от времени я запускаю cargo cleanчто приводит к удалению десятков ГБ кэша. Однако даже при инкрементальной компиляции это все еще далеко от мгновенной горячей перезагрузки, которую вы получаете в мире JS. Итак, возвращаясь к моему первому пункту, цикл разработки «измените небольшой фрагмент кода, немедленно протестируйте» как бы прерывается. Вы не получаете этой мгновенной обратной связи, которую вы получаете с JS/TS, и именно поэтому некоторые внутренние и внешние инструменты для JustFax по-прежнему написаны на TypeScript. Rust как бы направляет вас к потоку «напишите много кода, скомпилируйте, проверьте», а не к рабочему процессу «измените одну строку, alt-tab браузер», который у вас есть с Node/JavaScript. Меня это в основном устраивает. Я получаю многое взамен, например, настоящую безопасность типов. Я считаю, что конвейер CI можно исправить, мне просто нужно больше времени, чтобы над ним поработать.
Есть вещи, в которых Rust преуспевает, например, чистый бэкэнд. Фреймворки, такие как axum предоставляют множество необходимых частей для создания сервера API, и большинство (популярных) внешних API имеют клиента для своего API на Rust. Конечно, они обычно не поддерживаются компанией, которая создает эти API, но, по крайней мере, мы можем их использовать. Однако большинство примеров в сети не включают Rust. И поэтому вам остается только самому разбираться, как интегрироваться с тем или иным API.
И это повторяющаяся тема с разработкой на Rust, по крайней мере для веба, я не могу комментировать другие типы приложений. Я часто обнаруживаю, что читаю исходный код или просматриваю GitHub issues для проблем, похожих на ту, с которой я столкнулся. LLM редко помогают с правильным решением, так как большинство пакетов являются своего рода нишевыми. Сообщество Rust уделяет большое внимание документации и примерам для своих пакетов, что здорово, но неизбежно вы столкнетесь с каким-то крайним случаем, который не документирован и не имеет примера для него, и, скорее всего, вам придется прибегнуть к чтению исходного кода, чтобы разобраться.
И некоторые приложения не очень хорошо подходят для Rust, особенно если вы пришли из сред быстрого прототипирования. Я все еще предпочитаю писать frontend на TypeScript с использованием Astro или Svelte. Rust DX просто не очень хорош в этом отношении. С необходимостью перекомпилировать код при каждом изменении переменной, он слишком медленный для быстрой итерации при frontend-разработке.
В заключение, я рад, что выбрал Rust год назад. Он не только помог мне получить $MAIN_JOB что мне очень нравится, но также помогло мне создавать лучшее программное обеспечение. И я с нетерпением жду второго года разработки Rust.
2024-09-24 13:32:24
1727368062
#Год #Rust #производстве
Читайте также

