Обновить

Комментарии 58

Почему вы считаете, что на Go и Rust лучше писать сложный бэк?

Go = PHP (или node.js/next.js) + компиляция (скорость и статическая типизация) + отличный сетевой рантайм

Rust = Go + более выразительные типы - рантайм - GC (но часто это и плюс)

C# / Java где-то посередине: богатые библиотеки, GC есть, но предсказуемый, типы богаче Go, но без Rust'овской борьбы с borrow checker

Rust, конечно, молодой и не про веб больше. Но я накатал приложение на 5000 строк, мне понравилось

Те преимущества, что вы описали - это про скорость. Микросервисы на Go - милое дело, но вот сложные штуки, где важна скорость разработки, но не в ущерб архитектуре, и есть множество готовых решений для ее ускорения, Rust и Go по-моему в пролете. А PHP с Node.js - вполне себе оправданные решения.

Про Java и C# не сильно в курсе, но мой вопрос и не про них.

Видел очень плохие приложения на PHP причем именно из-за языковых особенностей. Травмирующий опыт и всё такое.

Представьте себе единый бизнес процесс (визард), описанный в нескольких файлах на 4-5000 строк без типов, только словари и понять логику что это за данные и каким правилам они отвечают невозможно не прочитав весь код.

А теперь представьте ещё всё приложение целиком на и150-200.000 строк, где система согласования на 20 состояний, 50 переходов и десяток ролей равномерно размазана по жирным контроллерам-рендерам (ну, как PHP это любит). Да, можно было написать лучше. Но динамическая типизация провоцирует такое

Жирные контроллеры-рендеры-запросы к БД в одной функции видел только в пхп

В других языках такое физически неудобно писать.

PHP :Барьер входа в говнокод нулевой

<?php
if($_POST) {
    mysqli_query($db, "UPDATE orders SET status='ok' WHERE id=".$_POST['id']);
    echo "<script>alert('OK')</script>";
}
?>
<h1>Orders</h1>
<?php foreach(mysqli_query($db, "SELECT * FROM orders") as $r): ?>
    <form method=post>
        Order #<?=$r['id']?> $<?=$r['sum']?>
        <input type=hidden name=id value=<?=$r['id']?>>
        <button onclick="return confirm('Sure?')">Approve</button>
    </form>
<?php endforeach ?>

HTML, JS, SQL, логика — всё тут. SQL injection в комплекте

В Rust я бы на автомате написал так

// main.rs
#[tokio::main]
async fn main() {
    let db = PgPool::connect(&env::var("DATABASE_URL")?).await?;
    let svc = OrderService::new(db);

    let app = Router::new()
        .route("/", get(orders))
        .route("/approve", post(approve))
        .with_state(svc);

    axum::Server::bind(&"0.0.0.0:3000".parse()?)
        .serve(app.into_make_service())
        .await?;
}
// controller.rs
use axum::{Router, routing::{get, post}, extract::{State, Form}};
use askama::Template;

#[derive(Template)]
#[template(path = "orders.html")]
struct OrdersTemplate { orders: Vec<Order> }

async fn orders(State(svc): State<OrderService>) -> OrdersTemplate {
    OrdersTemplate { orders: svc.get_pending().await.unwrap() }
}

async fn approve(State(svc): State<OrderService>, Form(params): Form<HashMap<String, String>>) 
    -> axum::response::Redirect {
    if let Some(id) = params.get("id").and_then(|s| s.parse().ok()) {
        svc.approve(id).await.unwrap();
    }
    axum::response::Redirect::to("/")
}

// service.rs
impl OrderService {
    async fn get_pending(&self) -> Result<Vec<Order>> {
        sqlx::query_as!(Order, "SELECT * FROM orders WHERE status='pending'")
            .fetch_all(&self.db).await
    }

    async fn approve(&self, id: i32) -> Result<()> {
        sqlx::query!("UPDATE orders SET status='ok' WHERE id=?", id)
            .execute(&self.db).await?;
        Ok(())
    }
}
// HTML 
<!-- templates/orders.html -->
<h1>Orders</h1>
{% for order in orders %}
<form method="post" action="/approve" onsubmit="return confirm('Sure?')">
    Order #{{ order.id }} ${{ order.sum }}
    <input type="hidden" name="id" value="{{ order.id }}">
    <button>Approve</button>
</form>
{% endfor %}

Да, кода больше. Но читать просто, достаточно прочитать сигнатуры. В Rust они не врут в отличие от PHP, JS, python

Это вы лукавите, сравнивая лапшичный код на PHP уровня сайта школы 2001 года и упорядоченный код на Rust, который использует фреймворк Axum и шаблонизатор Askama (им обоим не больше 5 лет).

На Rust так же можно написать грязный код уровня

Скрытый текст
use axum::{
    extract::Extension,
    response::Html,
    routing::get,
    Form, Router,
};
use serde::Deserialize;
use sqlx::{MySql, Pool, query};
use std::net::SocketAddr;
use tokio;
use tokio::net::TcpListener;

#[derive(Deserialize)]
struct ApproveForm {
    id: i32,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pool = sqlx::mysql::MySqlPoolOptions::new()
        .connect("mysql://lamer:weak_password@localhost/my_poor_db")
        .await?;

    let app = Router::new()
        .route(
            "/",
            get({
                let pool = pool.clone();
                move || {
                    let pool = pool.clone();
                    async move {
                        let orders = query!("SELECT id, sum, status FROM orders")
                            .fetch_all(&pool)
                            .await
                            .unwrap_or_default();

                        let html = format!(
                            r#"
                            <h1>Orders</h1>
                            {}
                            "#,
                            orders
                                .into_iter()
                                .map(|r| {
                                    format!(
                                        r#"
                                        <form method="post">
                                            Order #{} ${}
                                            <input type="hidden" name="id" value="{}">
                                            <button type="submit" onclick="return confirm('Sure?')">Approve</button>
                                        </form>
                                        "#,
                                        r.id, r.sum, r.id
                                    )
                                })
                                .collect::<Vec<_>>()
                                .join("\n")
                        );

                        Html(html)
                    }
                }
            })
            .post({
                let pool = pool.clone();
                move |Form(form): Form<ApproveForm>| {
                    let pool = pool.clone();
                    async move {
                        // sql-инъекция бережно сохранена
                        let query_str = format!("UPDATE orders SET status='ok' WHERE id={}", form.id);
                        let _ = sqlx::query(&query_str).execute(&pool).await;

                        Html(r#"<script>alert('OK')</script>"#.to_string())
                    }
                }
            }),
        )
        .layer(Extension(pool));

    // Запуск сервера
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    let listener = TcpListener::bind(&addr).await.unwrap();
    println!("Сервер запущен на http://{}", addr);

    axum::serve(listener, app).await.unwrap();

    Ok(())
}

Но в 2025 году код из вашего примера для PHP и моего примера для Rust будут одинаково ругать коллеги.

Лукавство, конечно. Вы меня поймали.

Тем не менее, PHP провоцирует на говнокод. На Rust я бы просто не додумался до такого.

Тем не менее, PHP провоцирует на говнокод

Тейки из 2010 до сих пор живы хех. Если что, в PHP есть такая вещь как фреймворки (laravel, symfony и иногда yii2 на легаси), которые являются просто базой. Вы выше просто навалили какой-то отрывок кода. Да, так можно написать, теоретически. Но так никто и не делает. Я бы мог в пример навалить такой же гавнокод на джаве или плюсах из своих первых задач в шараге. Но это не имеет смысла

С вашего позволения беру ваш прекрасный код, чтобы пугать коллег ;)

>Видел очень плохие приложения на PHP причем именно из-за языковых особенностей.

Откровенный обман, так как языковые способности PHP никак не заставляют писать говнокод.

Говнокод можно написать на любом языке.

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

типы богаче

Это как?

Go

// Слишком просто - теряем ошибки
func ProcessOrder(orderID string) {
    payment, _ := chargeCard(orderID)
    inventory, _ := reserveStock(orderID)
    delivery, _ := scheduleDelivery(orderID)
    // что если payment прошёл, а inventory упал?
}

// Честный Go - verbose
func ProcessOrder(ctx context.Context, orderID string) error {
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fmt.Errorf("begin tx: %w", err)
    }
    defer tx.Rollback()

    paymentID, err := chargeCard(ctx, orderID)
    if err != nil {
        return fmt.Errorf("charge card: %w", err)
    }

    if err := reserveStock(ctx, tx, orderID); err != nil {
        // откатить платёж - но это внешний API!
        go refundPayment(context.Background(), paymentID)
        return fmt.Errorf("reserve stock: %w", err)
    }

    if err := scheduleDelivery(ctx, orderID); err != nil {
        // и тут откатывать всё...
        return fmt.Errorf("schedule: %w", err)
    }

    return tx.Commit()
}

Rust это красиво!

// Rust - типы кодируют состояния
struct Order<S> {
    id: OrderId,
    state: S,
}

struct Unpaid { amount: Money }
struct Paid { payment_id: PaymentId }
struct Stocked { reservation_id: ReservationId }
struct Scheduled { delivery_id: DeliveryId }

impl Order<Unpaid> {
    fn pay(self) -> Result<Order<Paid>, PaymentError> {
        let payment_id = charge_card(&self.id, self.state.amount)?;
        Ok(Order { id: self.id, state: Paid { payment_id } })
    }
}

impl Order<Paid> {
    fn reserve_stock(self, tx: &mut Transaction) -> Result<Order<Stocked>, StockError> {
        let reservation_id = inventory::reserve(tx, &self.id)?;
        Ok(Order { id: self.id, state: Stocked { reservation_id } })
    }
}

// Использование - компилятор не даст пропустить шаг
let order = Order::new(id, amount)
    .pay()?           // -> Order<Paid>
    .reserve_stock(&mut tx)?  // -> Order<Stocked>  
    .schedule()?;     // -> Order<Scheduled>

JS не сильно отличается от Go

// Node.js - async/await, динамическая типизация
async function processOrder(orderId) {
    let order = { id: orderId, status: 'CREATED' }; // Состояние - просто строка

    try {
        const paymentResult = await chargeCard(order.id);
        order.status = 'PAID';
        order.paymentId = paymentResult.paymentId;
        console.log(`Order ${order.id} paid. Payment ID: ${order.paymentId}`);

        const inventoryResult = await reserveStock(order.id);
        order.status = 'STOCK_RESERVED';
        order.reservationId = inventoryResult.reservationId;
        console.log(`Order ${order.id} stock reserved. Reservation ID: ${order.reservationId}`);

        const deliveryResult = await scheduleDelivery(order.id);
        order.status = 'DELIVERED'; // Или 'SCHEDULED'
        order.deliveryId = deliveryResult.deliveryId;
        console.log(`Order ${order.id} delivery scheduled. Delivery ID: ${order.deliveryId}`);

        // Сохранение финального состояния в БД
        await saveOrder(order);

        return order;
    } catch (error) {
        console.error(`Error processing order ${order.id}:`, error.message);
        order.status = 'FAILED'; // Обновляем статус на FAILED
        await saveOrder(order); // Сохраняем состояние ошибки
        // Здесь потребуется логика компенсации/отката, как и в Go
        if (order.paymentId) {
            console.log(`Attempting to refund payment ${order.paymentId}`);
            // await refundPayment(order.paymentId); // Откат платежа
        }
        throw error; // Перебрасываем ошибку дальше
    }
}

На JS никто уже не пишет ни бэк, ни фронт, а система типов TS побогаче и Go, и Java, и пожалуй даже C#. Хоть и не без проблем, вроде отсутствия нормальной ковариантности и контрвариантности. Правда к TS есть вопросы к серверному рантайму (вроде реализации многопоточности) и экосистеме бэкенда.

Почему вы считаете, что на JS не стоит писать бек?

Потому что выстрелить себе в колено, открыв кучи уязвимостей, на JS в разы легче, чем на Rust (который за такое сильно бьёт по рукам).

Вот не надо пинять на язык, ладно)

Преимуществ, когда один стэк и на фронте и на бэке - куча, нода уже взрослая и отлично показала себя в проде, библиотек на все случаи жизни, а если что-то не так - всегда можно залезть и поправить.

Все проблемы у людей от головыи рук, не надо инструменты винить.

При безграмотной арзитектуре, погоне за хайпом и борще в голове вас нечего не спасёт. Выбирать надо под задачу, под вектор развития и под приемственность (сходите ваканчии под go/rust помониторьте).

Тоже раньше считал js дырявым, пока не открыл для себя typescript. Эта штука с линтером точно не даст вам накостылить ошибок и заодно сильно поможет при разработке (подсказками и предупреждениями).

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

Согласен, что сеньор сможет написать относительно безопасный и быстрый код (ну тут скорее просто быстро написать код, чем быстрый код), на фронте и на бэке, но то же самое сможет сделать на расте миддл.

тупо стать сеньором

Путём надра.. гриндинга

Я много лет программирую на JS, и не могу вспомнить, что именно неясно работает и какие особенности надо знать, чтобы написать безопасный и быстрый код. Скорее всего, теперь мне эти особенности кажутся обычными, вот я и не могу вспомнить.

Сможете привести примеры?

Rust (который за такое сильно бьёт по рукам)

Я думал в раст комьюнити больше принято класть руку на коленку товарища

Как вам удалось в одной фразе смешать чисто серверный PHP и выполняющийся в браузере (по крайней мере придуманный для браузера) JS?

Догадайся

Какая разница для чего он придуман, по сути оба интерпретируемые, оба через апи работают I/O. Ну а так очевидно шутливый либо тупой наброс)

Иногда неплохо бы загуглить незнакомый фреймворк / библиотеку, чтобы не раздавать советы писать SPA с SSR на языках, на которых слегка проблематично реализовать SPA, да ещё с SSR (кроме C# с Blazor, но удачи набрать специалистов).

А вы воспользуйтесь своим советом. Вдруг окажется SSR был изначально без этих инструментов))) Понимате мы "натягивали" верстку на пхп, которая где рендерилась?)

В моих словах где-то есть отрицание того факта, что SSR возможен без Next.js? Если есть, приведите пожалуйста. Я вполне успешно реализовывал SSR с React до того как появился Next.js ещё в далеком 2016 году.

Пост о проблемах конкретного фрейморка Next.js, автор комментария выше предлагает писать backend на Go, Rust, C#, Java, из чего можно предположить, что он не понимает, что такое Next.js и для чего он нужен.

Я даже подчеркну, что особой любви не питал и не питаю к Next.js. Весь фреймворк сделан так, что шаг влево, шаг вправо, и он начинает вставлять в палки в колеса. Так было в 2018 году, когда взять react + react-router + loadable-components + webpack было безопаснее с точки жизни продукта. Так и осталось в настоящее время, если не стало хуже. Но я не буду отрицать, что Next.js имеет преимущества, особенно в скорости разработки на начальных этапах, когда продукт должен был выйти ещё вчера.

Понимате мы "натягивали" верстку на пхп, которая где рендерилась?)

Как и в ASP.NET, Django, Ruby on Rails, и даже, прости Господи, Nitrogen. Только какое это имеет отношение к SPA?

Партишины так же по апи возвращались, как например это делает сейчас htmx, там даже есть атрибут на всю сраницу для aync навигации. Вполне себе SPA. А еще помните был hashtag для роутинга до history api

Так же у edge темплейтов сейчас есть асинхронный функционал, вполне себе можно сходить за частями в момент регдера темплейта и сделать себе микрофронты даже. И все на базе технологий 20 летней давности)

Собственно, что даёт nextjs чего не может дать другой стек? Изоморфность? А она действительно нужна?

Было дело, в RoR даже Stimulus на замену Turbolinks завезли. Но бизнес проголосовал деньгами: UX стал сложнее, микросервисы, backend'еры в своей массе заниматься frontend не хотят, а всё чаще и чаще не умеют, и уж тем более сложным UX, frontend'еры backend трогать не хотят и не умеют (дайте мне React/Vue/Angular/Svelte/Solidjs). Поэтому я бы всё таки констатировал смерть классического подхода, где backend рендерит свои шаблоны, а рядом прикрученный JavaScript добавляет UX логику. "Миром" сейчас правят react, vue, angular. И уже даже новостные сайты это приложения на React / Vue.

Я не буду вступать в полемику хорошо это и плохо. Боюсь, скоро в Web ещё и Flutter войдёт (уже есть попытки), где для выделения текста вставляются элементы поверх Canvas. Просто констатирую, что SPA на сегодня это React / Vue / Angular / e.t.c.

Ну просто у нас например есть проект на стеке Adonis, Alpine, Edge. И там шаблоны. Это супер, думаю статейку как то напишу. Убрали кучу астракций, а по возможностям все тоже самое

Next.js уже во времена нового роутера и папочки php app для серверных компонентов шагнул не туда.

Главный недостаток next.js - он дико тормознутый. Когда они добавили SSG, это означало "ребята, мы не знаем, как ускорить это г-но, поэтому вот вам генератор статики". Проблема изначально была в том, что никому не нужен реактивный фронтенд на бэке. На бэке на странице нет и не может быть никаких событий, поэтому абсолютное большинство фреймворков, независимо от языка, используют строковые шаблонизаторы.

  1. Next.js не дает вам возможность чейнить мидлвары, потому что это все-таки технология для разработки интерфейсов, а не логики веб-приложения. Не надо писать огромную логику в миддлварах на next.js, прикрутите себе бек и там чейните мидлвары сколько хотите :)

  2. Лог "Logging from the page!" не отобразился в браузерной консоли потому, что Next.js - это технология серверного рендеринга. Рендеринг был выполнен на сервере, там и нужно искать лог :) (в данном случае лог будет в билдтайме). В целом по рассказу такое ощущение, что автор ищет логи не там, где их возможно увидеть :)

  3. Клиентские компоненты называются таковыми, потому что там доступно клиентское апи (хуки реакта, обработчики событий)

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

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

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

"промежуточное ПО"? серьезно?

Трудности перевода)

А я на этом дерьме написал проект на 100К строк. Миддлвару использовал только для CSP. Она там вообще чисто чтоб было, а проку ноль. Rest маршруты тупо декорировал функциями с логированием, авторизацией и прочими делами. А вот с тем, как код пересекает границу сервер-клиент я дофига проблем словил. Так что второй раз пожалуй ни для чего серьезного я nextjs не возьму

У нас тоже дикие траблы с Next.js. Корни всех проблем, в том, что React, по сути заутсорсил развития фреймворка и новых фичей таким вот инструментам. Они за него имплементят SSR и прочее. И все оно полусырое. Не говоря уже в целом о том, что React'у пора на покой

И что же Вы можете предложить взамен реакта? Vue? 🤣

Почему нет?

Ну каждому свое)

Vue хороший вариант. В сравнении с реактом это прям небо и земля по удобству.
Но есть еще много других. Самое главное брать современный инструмент, который использует например сигналы, а не абсолютно не нужный вирутал дом, который так и не справился с задачей обещанной еще в 2013.  Мы за вас рендерим все эффективно и вы об этом не думаете. До сих пор на собесах приходится спрашивать про разницу между useCallback от useMemo и тд и тп

Ну кому как)

Так что взамен реакту есть актуальное?

Так Вью и Свелт

Но реакт умеет использовать сигналы, правда не стараниями разработчиков реакта. Причём релизной версии пакета уже 3 года, работает библиотека прекрасно, и я даже писал об этом пост. А до меня в ру пространстве об этом рассказывал АйТи Синяк у себя на канале на ютубе. До него ещё минимум два зарубежных фронтенд-разработчика, ведущие свои каналы, тоже рассказывали про сигналы в реакте. Только массовой аудитории почему-то до сих пор пофиг, они вон mobX'ом своим обмажутся и рады) хотя счётчик скачиваний в неделю неуклонно растёт, что не может не радовать. В общем сложилось впечатление, будто реакт разработчикам такое не нужно, ибо дальше "о, прикольно" ни в одном коммерческом проекте по моему личному опыту дело не дошло

Mobx или любую другую библиотеку для реактивности вместо реактовского кор стейта я как понимаю запрещает использовать какая-то религия?

Эм, возьмите тот же nuxt. В нём реально всё сделано для людей, которым нужно сразу приступить к реализации задачь и не греть голову чем ещё обмазать компоненты и какой реакт квери подключить. Реально всё из коробки. Ну и в целом вью быстрее реакта, который превращается в пхп с его не совсем понятными сервер сайд компонентами.

Кстати, не подсппжите как реакт бутстрап в апп роутере запустить, чтобы навбар на серверсайде отработал?

А нормальный ui не судьба взять для проекта на реакт, а не бутстрап?))) Зачем навбар в серверсайде вообще нужен? Вы явно что-то не то делаете. xD

Есть стойкое ощущение, что 99% приложений, которые пишут на всяких Next.js можно спокойно наговнокдить на обычной vanilla js + пара тройка специфичных библиотек, которые решают узкие задачи...
Проблема в том, что порог входа очень низкий. Поэтому на старте берем новый хайповый распиаренный фреймворк, который работает почти как надо из коробки. Вот в этом почти и кроется дьявол.
Потому что дальше дальше остается лишь уповать на то, что создатели фреймворка (которые нам ничего не должны) хорошо сделали свою работу (не факт), продумали все сценарии использования, которые нам могут понадобиться (вряд ли) и будут осуществлять качественную поддержку и развитие своего продукта, добавляя нужные вам фичи (с чего бы?).
Даже вендоры коммерческих приложений за много денег с оплаченной премиальной поддержкой не особо стремятся допиливать свой продукт под ваши хотелки, а тут что?
Остается лишь ворчать в хабрике, показывая каких диких костылей пришлось наворотить, чтобы очередная гравицапа делала как надо.

Может автору стоило хотя бы разобраться в инструменте прежде чем обсирать его? Зачем серверные логи в браузере искать?

Не понимаю автора кода. Выглядит как велосипед безумца. Существуют же решения для хранения локальных данных, данных пользователя в рамках одного запроса, например? Хранить логгер в локальном сторе - странно. Ну хорошо придумал записывать поле в хеадере, что возмущаться? Сам же придумал. Философия Next.js не предполагает такое использование. Вместо того чтобы принять что это не так должно работать и найти стандартное решение, не разобравшить в философии фреймворка, обвиняет что что-то не работает. Откуда столько положительных кликов?

Нашел баг, недостаток? Придумал как решить? Пошел на гитхаб и предложил апдейт.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds