Представляем Loco: Rails от Rust

Представляем Loco: Rails от Rust

Содержание
  1. Начало работы с Loco
  2. Маршрутизация в Loco
  3. Модели в Loco
  4. Обработка заданий в Loco
  5. Развертывание Loco
  6. Завершение работы

Хотя Ruby on Rails уже не так популярен, как раньше, во времена своего расцвета он был силой, с которой приходилось считаться. На его основе было построено множество успешных бизнесов - Airbnb и Shopify - два из многих громких имен, появившихся благодаря ему, хотя в последнее время Shopify начала экспериментировать с другими языками и в конце прошлого года объявила, что будет официально поддерживать Rust.

Это привело к тому, что многие фреймворки пытаются подражать философии Rails - Loco.rs не является исключением. Однако в данном случае он призван решить давнюю проблему, связанную с тем, что в Rust не существует по-настоящему включенного в батарею фреймворка. Давайте поговорим об этом.

Почему Ruby on Rails был популярен? Краткая справка

Ruby on Rails стал популярен потому, что это фреймворк, который делает всю тяжелую работу за вас и абстрагируется от многих тяжелых задач - это означает, что между продумыванием бизнес-логики идеи и переходом к полноценной работе проходит совсем немного времени. Это очень хорошо по нескольким причинам, особенно в веб-разработке: вы можете быстрее создавать продукты, не прибегая к помощи шаблонов, вы можете положиться на фреймворк, который сделает за вас все сложные низкоуровневые вещи, и вам не нужно свободно владеть языком Ruby, чтобы использовать его (хотя это очень помогает, если вы им владеете!). Это то, что находит отклик у многих веб-разработчиков, о чем свидетельствует огромное количество разработчиков, использующих Laravel, PHP-фреймворк, который очень похож на Ruby on Rails.

Это достигается за счет того, что все находится в интерфейсе командной строки: вы используете командную строку для запуска самого веб-сервиса, используете ее для миграции и обработки заданий, а также для создания новых контроллеров, моделей и многого другого. Например, вы можете создать модель базы данных с помощью команды rails generate model test, которая сгенерирует модель. Затем вы можете создать контроллер маршрута с помощью rails generate controller test, который сгенерирует контроллер под названием TestController. То же самое можно сделать для миграций с помощью rake generate migration.

Когда речь заходит о Rust, это несколько противоречит друг другу, поэтому Loco.rs и интересен: Rust - это язык, который позволяет вам вникать в суть дела, когда дело доходит до низкоуровневых деталей, а это значит, что он привлекает программистов, которые не против проделать дополнительную работу, потому что они предпочитают, чтобы все было реализовано по их стандарту, или потому что они хотят понять, как все работает, чтобы, когда что-то сломается, они знали, как это исправить. Кроме того, Loco не является самостоятельным фреймворком - в настоящее время он использует axum под капотом, наряду с sidekiq-rs для обработки заданий и sea-orm для миграций.

Начало работы с Loco

Чтобы начать работу с Rust Loco crate, вам нужно использовать его CLI, который вы можете установить, используя следующее:

cargo install loco-cli.

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

Маршрутизация в Loco

Хотя Loco использует axum под капотом, он абстрагирует некоторые вещи в конфигурационных файлах, которые вы можете найти в папке config.

Сервис Axum, запускаемый приложением, реализует функцию Hooks из loco_cli, которая требует использования нескольких функций - перейдя в src/app.rs, вы увидите, что у нас есть функции для регистрации маршрутов, получения имени приложения, подключения рабочих и регистрации задач, усечения таблиц и занесения данных в базу. Мы также можем добавить в маршрутизатор дополнительные функции, которые подключаются к CLI - after_routes() для добавления таких вещей, как промежуточное ПО, и before_run(), которая позволяет выполнять операции до запуска самого приложения. Обратите внимание, что все команды, которые мы используем через CLI проекта для создания вещей, будут автоматически добавлены в файл app.rs - нет необходимости делать это самостоятельно!

Чтобы добавить контроллер, нам нужно выполнить команду cargo loco generate controller test из корня проекта, которая сгенерирует контроллер под названием test и одновременно добавит новый файл в папку controllers. Затем мы можем создать любые необходимые маршруты и добавить их к маршрутизатору в том же файле, и он автоматически будет добавлен в приложение - никакой дополнительной работы не требуется! Ваш новый контроллер должен выглядеть примерно так:

#![allow(clippy::unused_async)]

use loco_rs::prelude::*;

pub async fn echo(req_body: String) -> String {
    req_body
}

pub async fn hello(State(_ctx): State<AppContext>) -> Result<String> {
    // делаем что-то с контекстом (база данных и т.д.)
    format::text("hello")
}

pub fn routes() -> Routes {
    Routes::new()
        .prefix("test")
        .add("https://dev.to/", get(hello))
        .add("/echo", post(echo))
}

Теперь вы можете добавить любые маршруты в этот файл, и они будут помещены под этот контроллер, когда будут добавлены в функцию routes() в файле. С точки зрения маршрутов это означает, что все они будут находиться под одним маршрутом. Вы можете получить доступ к подключению к базе данных from предоставленное State - в отличие от обычного Axum, вам не нужно создавать его самостоятельно.

Самое замечательное в маршрутизации Loco то, что все, что вы знаете об использовании Axum, может быть применено здесь - так что если вы знаете, как писать свои собственные экстракторы, писать промежуточное ПО и другие вещи, все это может быть использовано в Loco, поскольку он, по сути, строится поверх Axum.

После того как вы добавили все контроллеры и маршруты, вы можете использовать cargo loco routes, чтобы отобразить все маршруты, которые есть в вашем приложении.

Модели в Loco

Модели в Loco представляют собой модели баз данных, используемые sea_orm. Чтобы начать работу, выполните следующие действия:

cargo loco generate model <name-of-model>.

В результате будет сгенерирована модель, которую вы можете использовать в своем приложении. Вы также можете инициализировать дополнительные поля, чтобы сгенерировать полную модель:

cargo loco generate model movies title:string rating:int.

Обратите внимание, что если вы хотите инициализировать модель с помощью дополнительных полей, вам нужно будет проверить справочную документацию, чтобы узнать, какие поля вам нужно использовать. После того как вы добавите все необходимые модели, вы можете просто выполнить следующие две команды, чтобы получить необходимые миграции и сущности:

cargo loco db migrate cargo loco db entities.

Когда вы сгенерируете пустую модель, перейдя к файлу модели, вы, вероятно, обнаружите что-то похожее на это:

use sea_orm::entity::prelude::*;
use super::_entities::notes::ActiveModel;

impl ActiveModelBehavior for ActiveModel {
    // расширить activemodel ниже (сохраните комментарий для генераторов)
}

Когда мы используем эту модель в нашем контроллере, обычно мы не ссылаемся на структуру, содержащую саму модель - вместо этого мы ссылаемся на ActiveModel или Entity/Model - пустой файл модели выглядит следующим образом:

use sea_orm::entity::prelude::{ActiveModelBehavior};
use super::_entities::notes::ActiveModel;

impl ActiveModelBehavior for ActiveModel {
    // расширить activemodel ниже (сохраните комментарий для генераторов)
}

Мы можем расширить поведение нашей ActiveModel, добавив метод before_save(), как упоминалось ранее, следующим образом:

use sea_orm::entity::prelude::{ActiveModelBehavior};
use super::_entities::notes::ActiveModel;

impl ActiveModelBehavior for ActiveModel {
    // расширяем activemodel ниже (сохраняем комментарий для генераторов)
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        println!("Это происходит до того, как мы что-то сохраним!");
        Ok(self)
    }
}

Реализация трейта ActiveModelBehaviour (из sea_orm) позволяет нам определить поведение для ActiveModel - более конкретно, мы можем добавить методы до и после сохранения модели, а также до и после удаления модели. Мы также можем расширить поведение нашей модели, добавив к ней дополнительные методы:

impl super::_entities::users::Model { // ... ваши собственные методы }.

Теперь мы можем использовать ее в функции-обработчике, загрузив элемент из базы данных - и затем делать с данными все, что нам нужно:

async fn load_item(ctx: &AppContext, id: i32) -> Result<Model> {
    let item = Entity::find_by_id(id).one(&ctx.db).await?;
    item.ok_or_else(|| Error::NotFound)
}

pub async fn update(
    Path(id): Path<i32>,
    State(ctx): State<AppContext>,
    Json(params): Json<Params>,
) -> Result<Json<Model>> {
    // используем sea_orm для загрузки элемента на основе id
    let item = load_item(&ctx, id).await?;

    // превращаем элемент в ActiveModel, которую затем можем использовать
    let mut item = item.into_active_model();

    // обновляем параметры текущего элемента новыми свойствами
    params.update(&mut item);

    // отправляем новый элемент обратно в базу данных
    let item = item.update(&ctx.db).await?;

    // возвращаем обновленный элемент
    format::json(item)
}

Однако это далеко не все, что может предложить Loco.rs. Мы также можем использовать структуру loco_rs Validator, чтобы иметь возможность проверить новую модель до того, как с ней нужно будет что-то делать! Например, нам нужно проверить, является ли электронное письмо действительным. Вы можете проверить это ниже:

#[derive(Debug, Validate, Deserialize)]
pub struct ModelValidator {
    #[validate(length(min = 2, message = "Имя должно быть не менее 2 символов."))]
    pub name: String,
    #[validate(custom = "validation::is_valid_email")]
    pub email: String,
}

impl From<&ActiveModel> for ModelValidator {
    fn from(value: &ActiveModel) -> Self {
        Self {
            name: value.name.as_ref().to_string(),
            email: value.email.as_ref().to_string(),
        }
    }
}

Обработка заданий в Loco

Как и во всем остальном в Loco, вы также можете генерировать рабочих и задания через CLI. Выполнив команду cargo loco generate task или cargo loco generate worker, вы сможете сгенерировать задание или рабочего по своему усмотрению.

Под капотом Loco использует sidekiq-rs для обработки заданий - это Rust-реализация его Ruby-аналога, sidekiq.rb. Как только рабочий будет создан, перейдите в папку workers и проверьте файл, который вы создали - в нем будет структура для самого рабочего, структура, содержащая аргументы, которые будет принимать рабочий, реализация трейта AppWorker for рабочего, а затем реализацию асинхронного трейта, который позволяет рабочему что-то делать.

Затем вы можете запустить его следующим образом:

ReportWorkerWorker::perform(&boot.app_context, ReportWorkerWorkerArgs{}).await.unwrap();

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

Развертывание Loco

В настоящее время Loco.rs позволяет вам создать развертывание с помощью следующей команды:

cargo loco generate deployment.

Это позволит вам выбрать между Docker и Shuttle. При выборе Docker будет сгенерирован Dockerfile, который вы можете использовать для развертывания в любом месте, а при выборе Shuttle будет автоматически сгенерировано все необходимое для развертывания Shuttle - никакой дополнительной работы не требуется! Затем вы можете использовать Shuttle CLI для запуска нового проекта и его развертывания:

// Please note that if you want to avoid using the --name flag, you should use the name key in the Shuttle.toml file
cargo shuttle start --name <name-of-project>
cargo shuttle deploy --name <name-of-project>

Завершение работы

Спасибо за чтение! Loco - это отличный фреймворк, который подает большие надежды и очень быстро развивается. Создавать Rest API на Rust еще никогда не было так просто!