Kireev
20 subscribers
41 photos
28 videos
14 links
Download Telegram
Знакомьтесь, Анна Ивановна

У меня есть пет-проект — [Анна Ивановна](https://github.com/kireevys/anna_ivanovna), Личный Бухгалтер.

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

В частности, основной постулат проекта — «Защита ядра от изменений»: какие бы сценарии я ни выдумывал, мой бизнес-функционал должен оставаться неизменным как можно дольше. Открытым к расширению, но закрытым к изменениям. И я всячески стараюсь усложнить себе задачу.

Как основу я беру принципы чистоты — Functional Core / Imperative Shell ( #FCIS ) — по сути отделение бизнес-логики (БЛ) от IO, а также слоистую архитектуру. Мне не нравится обзывать такие подходы луковыми/гексагональными и прочими — скажу, что я стараюсь быть ближе к Границам Контекстов из #DDD по Эрику Эвансу.

Если интересен проект — заходите на гитхаб, почитайте ридми. В дальнейшем я буду добавлять подробности о проекте и его истории.

#buh_anna #аннаивановна
Это была подводка. Хочу поделиться НАКОНЕЦ ТО технической статьей 🙂
Переезд на самописный TEA (The Elm Architecture) движок

Сегодня — заметка о том, как я затащил #FCIS - Functional Core / Imperative Shell на фронт #аннаивановна. Выделил под Yew собственный #TEA движок, чтобы получить чистое разделение Core/Shell. Рассказываю, что сработало, что заболело и за что пришлось заплатить.
ПР: https://github.com/kireevys/anna_ivanovna/pull/66/changes

Немного истории.

По задумке, Анна Ивановна может работать в нескольких режимах:
- Персональное настольное приложение — только локальная база данных, без синхронизации и с полным контролем конфиденциальности
- Веб-сервис — только облако, доступ через браузер, все данные на стороне владельца сервиса — классический веб-сервис
- Комбинированный режим — доступ через настольное приложение с облачной синхронизацией — это позволяет работать и с веб-сервисом через браузер, и без интернета через настолку, и с веб-сервисом с настолки
- Бонусом — веб-сервис из телеграма

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

В #rust есть Yew-фреймворк — для меня он был вне конкуренции: минималистичный дизайн, полнота, и прямая заявка на чистый TEA:

Developers who have experience with frameworks like React and Elm should feel quite at home when using Yew.

Ради приличия я посмотрел на Leptos и Dioxus, но в моменте больше понравился Yew.

Я затащил его на проект, сделал фронтенд, ещё практически read-only — мне понравилось. server-side render, html-макрос, компоненты — очень удобно.

При имплементации фичи Редактирования Плана я заметил, что корневой метод обработки сообщений trait Component::update принимает мутирующую ссылку на &mut self. В моём понимании TEA мы не должны мутировать стейт — суть компонента в том, что мы каждый раз выдаём новое состояние на базе текущего и поступивших команд.
Я не устоял перед искушением — в первой имплементации Редактора Плана я безжалостно мутировал стейт.

Мутации — коварный паттерн. Реализуются элементарно: берёшь и изменяешь. Это просто работает. Однако это усложняет отслеживание потока выполнения — никогда не знаешь, что и где должно мутировать, а что мутировать не должно. Именно поэтому TEA имеет ровно одно место для изменения стейта — после вычисления новой модели, когда решение уже принято. Только так можно отделить IO от принятия решения и сохранить бизнес-логику (БЛ) в чистоте.
Также мутирующий стейт очень сложно тестировать — никогда не знаешь, где, что и когда мутирует.

Мне, конечно, не нравилось, фича была сложной и объемной, имплементация была довольно грязной, тесты мне было писать противно. Базовая задача - редактирование плана - была решена!

Любая грязь требует вернуть должок.

Недавно мой брат нашёл баг — можно было сделать отрицательное пополнение.
У меня давно выработался алгоритм работы с багами:
1. Воспроизвести на автоматизированном тестировании
2. Исправить

Но писать тесты к грязному коду — занятие для ценителей.
Требовался рефакторинг.

Мне абсолютно не понравился подход yew к мутациям, и я решил выделить свой простенький движок для TEA — по сути два трейта: один для моделей и Functional Core, второй для IO — Imperative Shell.


pub trait Model: Clone {
type Msg;
type Cmd;

fn handle(self, msg: Self::Msg) -> (Self, Vec<Self::Cmd>);
}

pub trait Shell<M: Model> {
fn execute(&self, cmd: M::Cmd);
}


Model::handle принимает и возвращает себя по значению (никаких `&mut`), плюс отдаёт список команд для Shell. Shell — это IO: получает команды и выполняет их в реальном мире. Классический Elm — ядро не делает IO, ядро описывает, что должно произойти.

⬇️
⬆️

Всю фронтовую логику изменения стейтов я перенёс на этот движок, модели отделил от yew и их презентации, а yew оставил как рантайм — теперь он запускает движок, роутит в него сообщения и рендерит получившийся стейт с помощью презентера.
Это позволило мне написать generic-стенд для тестирования как каждой модели в отдельности, так и всего App в сборе — там получилась матрёшка, которая роутит сообщения вглубь себя.
Тестирование сводится к проверке движка этим стендом и сериализации/десериализации Модели в html и Msg в структуры.

Дополнительно я получил возможность заменить yew на что угодно без изменения движка — в любой момент можно сделать tui/tauri-ipc/gpui. Я не собираюсь этого делать, но мне греет душу такая возможность.

Бонусом — чистый borrowing: app.model = handle(app.model, msg). Стейт не копируется, а передаётся во владение. С yew-овским &mut self так было нельзя — там можно только править на месте, а забрать и вернуть нельзя. По сути получился zero-cost handling.

За это я заплатил потенциальной потерей стейта в Option<AppModel> и take(), чтобы вытащить модель по владению. Если handle запаникует, модель останется None, и view покажет render_fatal. Неидеально, но честно: паника в чистой функции — это баг, который должен быть виден, а не замят.

Сейчас всё ещё довольно грязновато — презентеры сами по себе делают валидации полей, но у меня есть тикет https://github.com/kireevys/anna_ivanovna/issues/64 с идеей исправления этой грязи.

TEA без owned-семантики — это вообще не TEA. Суть подхода в том, что handle(self) → Self передаёт модель во владение и возвращает новую: стейт меняется ровно в одном месте, и больше нигде это невозможно. Rust тут ничего не «чинит» — он просто удобная среда для такой семантики: borrow checker сам не даст никому другому дотянуться до модели. Yew этого не позволяет, пришлось выделить свой движок. Плата — один Option в рантайме; выигрыш — чистый Core, тестируемый в изоляции, и zero-cost handling. Для меня это был очередной шаг в сторону FCIS — подхода, в который я верю и на котором обкатываю Анну Ивановну.

Это первый большой пост в этом канале. Расскажите, как вы относитесь к #FCIS? Применяли на своих проектах — где именно и с каким результатом? Будет интересно почитать в комментариях.

Спасибо, что дочитали до конца!

#buh_anna #аннаивановна
🔥4
В чем надо разобраться чтобы стать синьор-программистом
(Любой курс за 200к)
🙈1
В чем надо разобраться, чтобы стать джуном-программистом (любая вакансия)


Там ещё был postgress для профессионалов, но на меня уже начали странно смотреть
🗿2
Отыграли летний отчетник с @red_mettallurg_krd и Дима Чернат уже собрал альбом 🐱

https://dmitrychernat.wfolio.pro/disk/25-07-2026-8-oy-otchetnyy-kontsert-krasnyy-metallurg-srjswg
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
This media is not supported in your browser
VIEW IN TELEGRAM
Похоже, моя Лиза от меня устала и решила отправить в мир по настоящему легендарной и тяжёлой литераторы 🫦

6 издание, 2026 год
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5
И вот только теперь я понял🙈
🙈1
🔥4
Please open Telegram to view this post
VIEW IN TELEGRAM
🙈1🙉1🙊1
Я не мастер фотографии, но вот вам немного веловайба.

Вообще, кажется это лучший подарок в моей жизни)
🔥6
Я, пока Слопчик пилит мне софт по ночам
Сашка как всегда великолепна, распутала все что нужно, запутала там где надо
This media is not supported in the widget
VIEW IN TELEGRAM
🔥4🗿1