EvApps – Telegram
EvApps
203 subscribers
1.23K photos
50 videos
1 file
238 links
IT-aутстафферы из Тулы💚
https://evapps.ru/

Здесь пишем про веб- и мобильную разработку

▶️ Наш чат для системных аналитиков: https://news.1rj.ru/str/pro_sa_evapps

▶️ Посмотреть, как мы живём: https://vk.com/evapps
Download Telegram
Всем привет!
На связи снова разработчики EvApps💪
Сегодня поговорим про DTO (Data Transfer Objects), а именно разберем, зачем и как использовать его в Laravel.

⭕️ DTO (Объект Передачи Данных) - это шаблон проектирования, используемый для передачи данных между уровнями в программной архитектуре. Основная цель DTO - отделить различные уровни или компоненты приложения, позволяя им взаимодействовать друг с другом, не вникая в детали реализации друг друга.

Итак, DTO - это простой объект передачи данных, который помогает отделить и изолировать компоненты друг от друга.

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

❇️ Например, код контроллера сохранения статьи:

public function store(Request $request): JsonResponse
{
return response()->json([
$this->service->createArticle($request->all()),
Response::HTTP_CREATED
]);
}


В данном примере мы не знаем, что вернет метод $request->all().

Мы можем создать свой класс CreateArticleRequest и, при необходимости, смотреть, что он возвращает. Но теперь валидация данных связана с HTTP Request, и если нам нужно вызвать метод createArticle в другом месте кода, то нужно будет вручную передавать объект CreateArticleRequest. К тому же, класс CreateArticleRequest вернёт массив полей и мы не можем принудительно указать тип данных передаваемых в метод createArticle.

Используя DTO мы решим проблемы описанные выше.

⭕️ Как использовать?
Как мы сказали выше, DTO - это простой объект для сопоставления свойств.

❇️ Создадим класс DTO:

final readonly class CreateArticleDTO
{
public function __construct(
public string $noscript,
public string $denoscription,
public string $body,
) {}
}


В объявлении класса DTO используем ключевые слова final readonly, то есть запрещаем наследовать класс DTO и переопределять свойства объекта, поэтому после создания класса DTO его контекст нельзя будет изменить, и мы можем быть уверены, что получим на вход именно нашу DTO, которую указали в контракте метода createArticle. Эти действия позволят нам использовать контекстный объект и добавить безопасность типов.

Контроллер теперь выглядит вот так:

public function store(Request $request): JsonResponse
{
return response()->json([
$this->service->createArticle(new CreateArticleDTO(...$request->all())),
Response::HTTP_CREATED
]);
}


Благодаря именованным аргументам в PHP8 мы легко создаем экземпляр CreateArticleDTO, используя конструкцию new CreateArticleDTO (...$request->all())

Такой подход использования DTO позволяет решить проблемы:

Мы всегда знаем какие данные получит метод;
Мы явно указываем тип переданных данных;
Мы не привязаны к объекту CreateArticleRequest и можем вызвать createArticle в любом месте нашего кода.

Теперь код стал более качественным и поддерживаемым👍

А вы в своих Laravel-проектах используете DTO или работаете напрямую с массивами/Request?

#PHP #Laravel #DTO #backend #паттерны_проектирования #чистый_код #туториал
🔥3🤔1
👋 Всем привет!

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

А сегодня поговорим о том, как в Bitrix можно организовать компонент не просто для отображения, а как полноценный контроллер с CRUD-операциями.

Обработчик запросов в классе компонента (файл class.php) позволяет:

Инкапсулировать весь код в одном классе
Повторно использовать методы, данные и параметры компонента
Использовать языковые фразы, шаблоны компонента
Переопределять в компонентах-потомках стандартное поведение

Чтобы класс компонента мог обрабатывать запросы, необходимо:

▶️ Реализовать интерфейс \Bitrix\Main\Engine\Contract\Controllerable

▶️ Определить методы-действия с суффиксом Action

▶️ Реализовать метод configureActions (обычно возвращает пустой массив — конфигурацию по умолчанию)

▶️ Если нужно добавлять, обрабатывать ошибки, то стоит реализовать \Bitrix\Main\Errorable

При выполнении компонента в аяксовом режиме выполняются последовательно:

1️⃣CBitrixComponent::onIncludeComponentLang

2️⃣ CBitrixComponent::onPrepareComponentParams

Запуск действия с фильтрами:
<?php

namespace local\components\vendor\example;

use CBitrixComponent;
use Bitrix\Main\Error;
use Bitrix\Main\Engine\Contract\Controllerable;

if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();

class ExampleComponent extends CBitrixComponent implements Controllerable
{
public function executeComponent()
{
// этот код не будет выполняться при запуске ajax-действий
}

public function configureActions()
{
return [];
}

public function onPrepareComponentParams($arParams)
{
// подготовка параметров
}

public function createAction(array $fields): ?array
{
$example = Example::add($fields);
if (!$example) {
$this->addError(new Error('Could not create example.', {код_ошибки}));
return null;
}
return $example->toArray();
}

public function viewAction(int $id): ?array
{
$example = Example::getById($id);
if (!$example) {
$this->addError(new Error('Could not find example.', {код_ошибки}));
return null;
}
return $example->toArray();
}

public function listAction(array $fields): ?array
{
$examples = Example::getList($fields);
return array_map(static fn ($example) => $example->toArray(), $examples);
}

public function updateAction(int $id, array $fields): ?array
{
$example = Example::update($id, $fields);
if (!$example) {
$this->addError(new Error('Could not update example.', {код_ошибки}));
return null;
}
return $example->toArray();
}

public function deleteAction(int $id): ?array
{
$example = Example::delete($id);
if (!$example) {
$this->addError(new Error('Could not delete example.', {код_ошибки}));
return null;
}
return $example->toArray();
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
👋 Всем привет

Сегодня мы поговорим про CSS и как правильно настроить загрузку шрифтов с помощью дескриптора font-display в @font-face.
Этот дескриптор важен для управления тем, как будет отображаться текст до того, как шрифт будет загружен.

@font-face {
  font-display: auto;
  font-display: block;
  font-display: swap;
  font-display: fallback;
  font-display: optional;
}


🔑 Параметры для font-display:

* auto — поведение по умолчанию, зависит от браузера.
* block — текст скрыт до 3 секунд; если шрифт не загрузился, показывается запасной, потом текст перерисовывается. Плюс: текст с нужным шрифтом без «скачка». Минус: может быть невидимым, может сильно ухудшить UX и LCP.
* swap — сразу показывается запасной шрифт, после загрузки шрифта происходит замена. Плюс: текст виден, улучшает UX и SEO. Минус: возможен визуальный «скачок».
* fallback — скрытие 100 мс, потом запасной шрифт; если основной не загрузился через 3 с, остаётся запасной. Замена на загруженный шрифт без перезагрузки не произойдёт.
* optional — скрытие 100 мс, затем запасной шрифт; замена на основной возможна только после обновления страницы.

Рекомендация: чаще всего используйте swap (он стоит по умолчанию в Google Fonts), а если необходимо избежать мелькания текста, выбирайте optional.

Материалы:
MDN Web Docs — font-display
W3C — CSS Fonts Module Level 4

Как вы настраиваете `font-display` в своих проектах?

#frontend #CSS #UIUX #WebDev
🔥4
Привет всем!

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

Cooperation 🌐
Этот паттерн идеально подходит для команд с устоявшимися коммуникациями. Это может быть одна команда или несколько команд с взаимозависимыми целями, когда успех одной зависит от успеха другой. Главное здесь — это качественная взаимодействие между участниками процесса.

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

Shared Kernel 🧩
Иногда модели, ограниченные контекстом, частично реализованы в разных контекстах. Однако важно, чтобы общее ядро удовлетворяло требованиям обоих контекстов и оставалось минималистичным, чтобы избежать каскадных изменений. В идеале, общее ядро должно содержать только те элементы, которые необходимы для интеграции, и изменения требуют обязательных тестов в обоих контекстах.

В следующем посте продолжим рассматривать, когда и как применять Shared Kernel и перейдем к другим паттернам интеграции!

#development #паттерны #boundedcontext #cooperation #sharedkernel #Partnership
1
Привет снова 👋
Продолжаем нашу серию постов о паттернах, и сегодня поговорим о трех интересных паттернах интеграции, которые помогут наладить взаимодействие между контекстами в условиях автономности команд.

Customer-Supplier 🏷🔗
Этот паттерн можно представить как поток: контекст Customer находится ниже по течению, а Supplier — выше. В отличие от кооперативных паттернов, здесь обе стороны могут решать свои задачи независимо. Однако это может создать дисбаланс во взаимоотношениях.

Conformist 🔄
Здесь Customer принимает контракты, предложенные Supplier, не имея возможности их изменять. Этот паттерн часто встречается в случае использования внешних сервисов или когда Supplier определяет отраслевой стандарт, который должен быть принят всеми контекстами, расположенными ниже.

Anticorruption Layer 🛡🔄
Когда Customer не соглашается с контрактами Supplier, он создает внутренний слой, который трансформирует контракты Supplier в те, которые отвечают потребностям Customer. Этот слой защищает бизнес-логику Customer от внешних изменений и помогает адаптировать контракты под нужды конкретного контекста.

Завершим нашу серию постов в следующем посте, расскажем про Open-Host Service и Separate Ways.

#development #паттерны #customersupplier #anticorruptionlayer #Conformist #boundedcontext
🆒3
Media is too big
VIEW IN TELEGRAM
С пятницей, коллеги!

Сегодня разбавляем серьезный контент... наболевшим🤯

В новом эпизоде нашего подкаста IT ToLк by EvApps системный аналитик Олег и мобильный разработчик Андрей разгоняют про боли и слёзы айтишников.

▶️ Четвертый эпизод уже в ВК - https://vk.cc/cPE3fD
🔥5😁1
Привет, друзья 👋
В этом посте завершаем нашу серию и рассмотрим два последних паттерна, которые могут быть полезны в ситуациях, когда требуется особая защита и автономность для контекстов.

Open-Host Service 🌐🔐
Этот паттерн противоположен Anticorruption Layer. Здесь Supplier создает защитный слой, чтобы обезопасить своих пользователей от изменений в своей системе. Он использует публичные контракты (published language), которые помогают изолировать изменения внутри Supplier и позволить ему развиваться автономно от нижележащих контекстов.

Separate Ways 🛑🚶‍♂️
Последний тип взаимодействия, по сути, исключает само взаимодействие между командами. Этот паттерн применяется в случаях, когда команды не могут или не хотят взаимодействовать по различным причинам.

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

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

Примечание: ⚠️ Следует избегать паттерна Separate Ways в реализации core subdomains.

#development #паттерны #openhostservice #separateways #boundedcontext
Привет 👋

💡 Сегодня разбираем базовую, но важную тему в Git — три способа интеграции изменений между ветками: Merge, Rebase и Fast-Forward.
Каждый из них влияет на историю коммитов и рабочий процесс команды.

🔀Merge
Объединяет изменения из одной ветки в другую, создавая отдельный merge-коммит.
Вид ветки: сохраняет полную и разветвлённую структуру — видно, где ветки расходились и сходились.
Когда использовать: при объединении фич в main ветку, когда важно сохранить контекст разработки, особенно в командной работе.

♻️Rebase
Переносит коммиты ветки на новую, обычно на актуальное состояние main ветки.
Вид ветки: становится линейной, без merge-коммитов — будто работа шла поверх main ветки.
Когда использовать: удобно для локальных веток, чтобы очистить историю перед слиянием.

Fast-Forward Merge
Происходит, если основная ветка не расходилась с твоей. В этом случае Git перемещает указатель вперёд без создания нового коммита.
Вид ветки: полностью линейная, как после rebase.
Когда использовать: если в целевой ветке не было других изменений. Можно указать флаг --ff-only.

📚Отличия:
Структура ветки: git merge сохраняет разветвлённую структуру с merge-коммитами; git rebase создаёт линейную структуру, переписывая коммиты; fast-forward даёт линейную структуру, перемещая указатель.

ID коммитов: git merge сохраняет оригинальные ID коммитов; git rebase создаёт новые ID для перебазированных коммитов.

Безопасность: git merge обычно безопаснее для общих веток, так как не переписывает историю; git rebase следует использовать с осторожностью на общих ветках из-за переписывания истории.

Выбирайте учитывая ваши рабочие процессы и приоритеты команды — что важнее: чистая история или точное сохранение контекста разработки.

🖥 Попрактиковаться с ветками и коммитами можно на интерактивном тренажёре: Learn Git Branching

#git #development #merge #rebase #fastforward #workflow
👍5
Сегодня нейросетям всё чаще доверяют писать и править код, чтобы ускорить разработку.
Но нередко результат — день, потраченный на устранение хаоса.
Этот кейс — не обвинение ИИ, а напоминание: важно понимать, с чем мы работаем

💡 Почему нейросети ломают код?
Нейросеть — не разработчик, а языковая модель.
Она не понимает код — она предсказывает, что выглядит как правильный ответ.
Ее цель — звучать уверенно, а не быть точной.
Поэтому она может предложить «исправление», которое в итоге ломает всё.
Она видит не систему, а текст — и делает то, что статистически похоже на решение.

📘 1. Оптимизирован не под правду, а под уверенность.
Модель обучена звучать убедительно, даже если ошибается.
Её «награда» — не корректность, а ощущение правильности.

💻 2. Не тестируют и не исполняют.
Разработчик проверяет код, запускает тесты, видит окружение и файлы проекта.
AI-ассистенты, даже встроенные в редактор, могут «видеть» файлы и окружение, но не запускают код и не проверяют его работу.
Они просто предсказывают, как может выглядеть «фикс» — не зная, сработает ли он на самом деле.

🧠 3. Не помнит, а делает вид, что помнит.
Контекст диалога ограничен — старые детали теряются.
Но вместо того чтобы признаться, ИИ «достраивает» логику самостоятельно, будто всё под контролем.

⚙️ Нейросети не ломают системы специально.
Они делает это, потому что не понимают, что такое система.
Они не видят ваших дедлайнов, пользователей, окружения — только текст.
А когда текст становится единственной мерой истины, реальность перестаёт иметь значение.

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

#ai #development #code #workflow #risk #vibe_coding
🏆1
Forwarded from Двач
This media is not supported in your browser
VIEW IN TELEGRAM
ChatGPT, когда в очередной раз выдал говёный код с ошибкой:
Всем привет🖐
Недавно, у нас возникла идея рассмотреть lazy loading не только как инструмент оптимизации, но и как возможный способ защиты чувствительного кода.

Lazy-loading — это способ загружать модули, компоненты или шаблоны только тогда, когда они действительно нужны, а не при первом запуске приложения.
В React и других фреймворках это реализуется через:
- разделение модулей,
- конфигурацию роутов,
- отложенную загрузку компонентов.

Преимущества стандартны:
⚡️ быстреее начальная загрузка
📉 меньше трафика
🚀 выше производительность

В React это реализуется через React.lazy() и Suspense:
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}

Такое разбиение кода уменьшает размер бандла и повышает скорость загрузки.

Во время разбирательств, мы заметили странную вещь — почти ни один фреймворк не упоминает lazy-loading в контексте безопасности. Только как оптимизацию.

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

Lazy-loading позволяет решить это:
мы можем не загружать части приложения с чувствительной логикой до тех пор, пока пользователь не авторизован.

Вот пример с использованием OAuth2-библиотеки для клиентской аутентификации:
<AuthenticatedTemplate>
<p>Вход выполнен как: {user?.username}</p>
</AuthenticatedTemplate>

Вместо простого текста здесь может быть целый <ComplexApp /> — с десятками килобайт JS, потенциально раскрывающих уязвимости.

Решение: оборачивать такие блоки в <Suspense> и загружать их только после подтверждённой аутентификации.

Классическое SPA грузит всё и сразу, включая закрытый функционал.
Lazy-loading — загружает только то, что нужно конкретному пользователю в данный момент.

Это не «панацея» для безопасности, но важный слой защиты:
Чем меньше кода попадает в браузер, тем меньше поверхность атаки.

- Должны ли мы использовать lazy-loading не только ради скорости, но и ради защиты кода?
- Да. Определённо должны.

🧠 А вы что думаете? Встречали такой способ использования lazy-loading?

#webdev #security #architecture #frontend #development
3🔥3
Представьте: утро, обычный рабочий день.
Открываешь приложение — и что-то не так.
База не поднимается. Файлы повреждены. Или какой-то баг просто всё удалил.

В голове сразу мысль: «Ладно, не страшно — есть же бэкапы».
Но при попытке восстановить — тишина.
Архивы пустые, повреждённые или восстанавливаются бесконечно.

Горькая правда:
бэкап ничего не стоит, пока ты не попробовал его восстановить.

Недавно у одной из команд случилась такая история.
Меняли схему базы данных в одном из проектов, и во время миграции что-то пошло не так — SQLite база повредилась.
Попробовали развернуть резервную копию — но и она оказалась битой.

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

К счастью, это были тестовые данные. Но если бы прод?
Пользовательские данные, проект, репутация — всё под угрозой.

Часто живёшь в иллюзии, что раз бэкап есть — значит, всё под контролем.
На деле это копия, которая может не сработать в тот момент, когда она нужна.

Что важно бэкапить:
🧠 Код — GitHub, GitLab
🗄 Базу — Postgres, MySQL, Supabase, SQLite
🖼 Файлы пользователей — изображения, документы
🔐 Конфиги и секреты — .env, API-ключи, настройки деплоя

Главная ошибка:
ты не знаешь, работает ли бэкап, пока не попробуешь восстановление.
Без этого — он как кот Шрёдингера, запертый в коробке.

Как делают большие компании:
— GitLab ежедневно тестирует восстановление и следит за метриками
— Basecamp проводит «тесты катастроф» — симулирует исчезновение датацентра

Типовая схема:
• Делать бэкапы автоматически (ночные дампы, снапшоты)
• Восстанавливать в тестовую среду
• Проверять: база поднимается, файлы читаются, логин работает (плюс smoke-тесты)
• Отправлять алерты при сбоях
• Периодически симулировать сбой и измерять скорость восстановления

Что и как проверять:
💻 Код: Push в GitHub/GitLab (с 2FA) → клонировать в новую папку и собрать проект
🗄 База данных: pg_dump / .backup / PITR → восстановить в тестовую БД и проверить данные
🖼 Файлы пользователей: S3 / B2 / GCS → случайно скачать несколько файлов и открыть
🔐 Конфиги и секреты: 1Password / Bitwarden / VeraCrypt → запустить проект, используя только эти конфиги

💡 Для SQLite:
копирование живой .db может её повредить.
Используй:
sqlite3 mydb.db "VACUUM INTO backup.db"

Так создаётся чистая копия без ошибок.

Когда тестировать восстановление:
— Раз в месяц: локально проверить случайный бэкап
— Раз в квартал: полный тест — база + файлы + приложение
— Раз в год: симулировать потерю устройства и попробовать вернуть

Почему это важно:
Один сбой может уничтожить проект, если вы не проверяли восстановление.

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

Потому что бэкапы — это не паранойя.
Это способ быть уверенным, что завтра твой проект не исчезнет. ❤️

#backup #development #risk #code
7
🚀 Почему dev-серверы часто запускаются на localhost:3000 (и сейчас ещё на 5173)

Когда ты запускаешь сервер localhost:3000 или localhost:5173, это не случайность.
За этими портами — история веб-разработки: от Ruby on Rails и Java до Node.js и Vite.

💡 Что такое порт?
Порт — это номер, по которому на компьютере слушает сервис.

Представь, что твой компьютер — это здание, а порты — двери с номерами.
Когда ты заходишь на localhost:3000, ты буквально открываешь дверь № 3000, чтобы увидеть своё приложение.

Всего таких дверей 65 535:
🔹 0–1023 — системные (HTTP 80, HTTPS 443, SSH 22)
🔹 1024–49151 — пользовательские (3000, 8000, 8080 и др.)
🔹 49152–65535 — временные (используются ОС)

Так что 3000 — просто одно из множества возможных значений.

⚙️ Порт 3000 — путь от Rails к Node.js
В Ruby on Rails по умолчанию сервер разработки запускался на 3000.

Позже, с ростом Node.js и Express, в официальных примерах часто встречалась строка:
app.listen(3000, () => console.log('Server running on port 3000'));

Эта привычка закрепилась: её копировали в туториалах, курсах и шаблонах.
React, Next.js и другие фреймворки просто продолжили эту традицию.

Сегодня 3000 — неофициальный “Hello World” порт веб-разработки.

🐍 Порт 8000 — классика Python
Ещё до Node, Python-разработчики поднимали локальные HTTP-сервера командой:
python3 -m http.server

По умолчанию сервер слушал 8000 — безопасный номер, не требующий прав администратора.

Фреймворк Django тоже выбрал этот порт.
Со временем 8000 стал синонимом “запустил и проверил локально”.

☕️ Порт 8080 — наследие Java
В 90-х порты ниже 1024 требовали root-прав.
Java-разработчики из Apache Tomcat и Jetty придумали обход:
80 → 8080

Выглядит похоже, но работает без повышенных прав.
С тех пор 8080 стал стандартом для “серьёзных” серверов вроде Spring Boot.

⚡️ Порт 5173 — современный штрих от Vite
Когда появился Vite, авторы выбрали порт с пасхалкой:
51 = VI
73 = TE
👉 5173 = VITE 😎

Теперь каждый npm run dev открывает localhost:5173 — фирменный почерк нового поколения фронтенда.

🧠 Можно ли использовать другие порты?
Конечно!

Многие привыкают к 3000 и паникуют, увидев:
Error: Port 3000 already in use

Но свободных портов тысячи.
Попробуй:
npm run dev -- --port=42069
vite --port=13337

И никаких конфликтов 😄

🕰 Заключение

8080 — обход ограничений Java
8000 — прагматичный выбор Python
3000 — наследие Rails и Node.js
5173 — фирменный порт Vite

Эти цифры — не просто числа.
Это часть истории веб-разработки с 90-х годов.

Когда в следующий раз запустишь:
👉 http://localhost:3000/
вспомни — за этим числом десятилетия привычек и технологий.

Если порт занят — не убивай процесс.
Просто выбери другое число.
Сделай его своим фирменным портом 😉

Безопасный диапазон: 1024–49151
И когда будешь запускать сервер — делай это с уважением😁

А вы оставляете порт по умолчанию или меняете на свой?

#development #webdev #frontend #backend
👍5🔥1
🚀 Наши друзья из Intelsy проводят вебинар для тех, кто руководит ИТ и хочет навести порядок в хаосе 1С-проектов - а мы такое всецело поддерживаем!

Тема: Будни ИТ-директора: стабильность, быстродействие, борьба с 1С

Дата: 6 ноября в 11:00 по мск

Формат: открытый разговор без маркетинговых лозунгов

О чем поговорим:
– какие вопросы тянет на себе ИТ-директор;
– зачем нужны внешние спецы при собственном штате;
– к кому обращаться за ресурсами и как не ошибиться;
– как работает обращение к внешним ресурсам и что остается за кадром.

📍 Регистрация — по ссылке, без спама и рассылок.

Вебинар
Вебинар
Вебинар

🔥 Залетай, если хочешь меньше хаоса и больше стабильности в своих 1С-проектах.
🚀 Почему спор про Tailwind vs CSS до сих пор живет?

И причина не в том, что один инструмент «лучше».
Просто веб-разработка изменилась, а подходы к стилям — нетривиальны и оба имеют вес.

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

Но обратная сторона тоже очевидна.
Разметка становится перегруженной.
В инструмент добавляется зависимость от сборки, конфигурации, среды.
Отладка усложняется: чтобы понять, почему элемент выглядит так, приходится разбираться в наборе utility-классов, а не открывать небольшой фрагмент CSS.

Пример привычного Tailwind-фрагмента:
<button
class="px-4 py-2 rounded-md bg-indigo-600 text-white font-medium
transition hover:bg-indigo-700 hover:-translate-y-1">
Button
</button>

Работает. Быстро. Но выглядит не всегда читаемо.

С другой стороны, классический CSS постепенно переживает второе дыхание .
Никаких дополнительных инструментов, чистая структура проекта, читаемость.
Современные возможности языка закрывают задачи, которые раньше требовали Tailwind или препроцессоры: container queries, nesting, cascade layers, :has(), переменные — всё это теперь доступно нативно.

Пример варианта на чистом CSS:
<button class="btn">Button</button>
.btn {
padding: .5rem 1rem;
background: #4f46e5;
color: #fff;
border-radius: .5rem;
font-weight: 500;
transition: .2s;
}
.btn:hover {
background: #4338ca;
transform: translateY(-4px);
}

Читается. Легко рефакторится. Но требует самодисциплины, принятой методологии и аккуратности, иначе проект быстро теряет структуру.

🎯 И в итоге вопрос уже не звучит как «что лучше».
Tailwind — это про темп, единообразие и скорость вывода продукта.
CSS — про прозрачность, контроль и устойчивость без зависимостей.
В реальных командах в 2026-м всё выглядит проще:
часть задач удобно решать utility-классами, часть — обычными стилями.
Tailwind часто используют как систему отступов и цветов, а более сложные компоненты пишут вручную.
Настоящий сдвиг — в том, что разработчики наконец начали изучать сам CSS глубже. Tailwind помог ускорить работу, но современные возможности языка вернули интерес к фундаменту.
Скорость важна, но и понятный код никто не отменял.
Поэтому и Tailwind, и CSS останутся. Не как конкуренты — как инструменты для разных ситуаций.

А на чьей стороне ты, Tailwind или CSS

#frontend #web #development #clear_code #чистый_код
Всем привет!
Стартуем линейку постов про транзакции.

🔍 Зачем нужны транзакции и что такое ACID в MySQL + Laravel

Когда проект растёт, главное — не код, а данные.
Если данные кривые → бизнес страдает, деньги теряются, пользователи бесятся.
И вся эта история держится на одной штуке — транзакции.

🔥 Что такое транзакция
Транзакция — это группа операций, которая должна выполниться как одно целое.
Либо всё выполняется, либо ничего.
Представь последовательность:
- списать деньги
- создать заказ
- уменьшить остатки
Если один шаг упал → откатываем всё назад, как будто ничего не было.
Это и спасает от «битых» данных.

🎯 ACID — 4 принципа надёжных транзакций
A — Atomicity (атомарность)
Все изменения — как один выстрел.
Если внутри что-то упало → откат всей пачки.

Пример: списали деньги, но заказ не создался — Atomicity не даст оставить данные в полурежиме.

C — Consistency (согласованность)
Данные обязаны соблюдать правила БД:
- внешние ключи
- ограничения
- уникальность
- валидность значений

Если до транзакции данные были норм — после тоже должны быть.
Это защита от «сломал базу случайно».

I — Isolation (изолированность)
Параллельные запросы не должны портить друг другу картину мира.
Без Isolation возможны ситуации:
- два человека покупают последний товар одновременно
- два процесса списывают деньги по старому балансу
- кто-то читает недописанные данные

Изоляция не допускает такого.

D — Durability (надёжность)
Если транзакция зафиксировалась — данные не пропадут даже при:
- падении сервера
- сбое питания
- перезапуске MySQL

InnoDB пишет всё в журнал, чтобы восстановить изменения.

🧩 Итог
Транзакции — базовый предохранитель реальных продуктов.
Они не дают платежам списаться дважды, заказам зависнуть в «полусозданном» состоянии, а бронированиям ломать расписание.
Многошаговая операция либо проходит полностью, либо откатывается — и бизнес работает без сюрпризов.

Следите за серией постов — дальше будем развивать эту тему😊

#transactions #db #development #webdev #mysql #laravel
👍2
Media is too big
VIEW IN TELEGRAM
💥 Врываемся в твой понедельник с новым эпизодом подкаста IT ToLк!

На этот раз расспросили нашего CEO Альфреда Столярова о галерах об аутстаффинге - и всех самых страшных мифах про это дело 👀

➡️ Смотри скорее по ссылке
И снова всем привет)

Продолжаем линейку постов про транзакции.

🔧 Транзакции в MySQL: нюансы▶️ MySQL / InnoDB
Если хочешь, чтобы транзакции работали как надо, таблицы должны быть в InnoDB.

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

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

▶️ Уровень изоляции
В MySQL (InnoDB) уровень изоляции по умолчанию — REPEATABLE READ.
Он обеспечивает работу транзакции со стабильным снимком данных: все повторные SELECT внутри одной транзакции возвращают одну и ту же версию строк, даже если параллельные транзакции их уже изменили.

❗️Каждый уровень изоляции определяет, какие аномалии конкурентного доступа возможны:
- Dirty reads — чтение незакоммиченных данных другой транзакции.
- Non-repeatable reads — одна и та же строка возвращает разные значения в рамках одной транзакции.
- Phantom reads — при повторном запросе появляются новые строки, которых не было в первой выборке.

В InnoDB dirty reads исключены автоматически (READ UNCOMMITTED фактически не используется).
REPEATABLE READ блокирует non-repeatable reads, но фантомы сохраняются, что на нагруженных системах может приводить к гонкам в логике проверки и вставки данных.

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

▶️ DDL и транзакции: почему MySQL делает implicit commit

В MySQL операции изменения структуры данных (DDL) автоматически вызывают commit до и после выполнения.
Этот механизм встроен в движок и не зависит ни от приложения, ни от фреймворка.

Какие команды вызывают implicit commit:
- CREATE TABLE
- ALTER TABLE
- DROP TABLE / DROP INDEX
- TRUNCATE
- RENAME TABLE
- CREATE/DROP VIEW
- SET AUTOCOMMIT = 1

Как только выполняется любая из этих команд:
1.MySQL выполняет скрытый commit текущей транзакции.
2.Выполняет DDL.
3.Делает ещё один скрытый commit после DDL.

Это означает, что откатить изменения, выполненные до DDL, невозможно — они уже зафиксированы.

Пример сценария:
1. START TRANSACTION;
2. Вставляем данные.
3. Выполняем ALTER TABLE.
4. MySQL автоматически коммитит всё, что было до команды.
5. Попытка ROLLBACK уже не влияет на вставленные данные.

Это критичное поведение, которое разработчик обязан учитывать.
Laravel или любой ORM не могут перехватить или отменить implicit commit, потому что решение принимает сам MySQL на уровне протокола.

⚠️Последствия использования DDL внутри бизнес-операций:
- невозможность гарантировать атомарность операций
- риск частично записанных данных
- неконтролируемые состояния при сбоях
- нарушение инвариантов бизнес-логики

Поэтому DDL строго отделяют от обычных транзакций:
- применяют только через миграции
- выполняют в отдельные деплой-этапы
- не допускают вызовов в runtime

Это базовое правило эксплуатации MySQL в продакшене.

#transactions #db #development #webdev #mysql #DDL #innoDB
🔥1
Делимся важным из жизни наших системных аналитиков😍
Forwarded from Julia Reznichenko
Внимание, у нас важная новость - мы запустили пилотную программу по системному анализу! 🚀

И это не очередной «курс» на просторах интернета - это безопасное пространство, чтобы попробовать, ошибиться, разобраться и увидеть, как все устроено изнутри.

Мы сделали упор на практику и живую поддержку наставников.

Что будем делать?

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

🤗 Учимся без перегруза: в формате индивидуальных разборов, небольших встреч и лёгких игр. И, конечно, даём обратную связь по каждому заданию.

Это пилот, и в будущем мы планируем набирать только маленькие группы до 5 человек. Нам важен результат и реальный опыт каждого 💪

Мы долго готовились, чтобы поделиться тем, что проверили годами практики!

Первый поток ведет лично наш руководитель отдела системного анализа Оксана Соболевская ❤️
У Оксаны за плечами многолетний опыт в анализе и целый отдел аналитиков, который она создала с нуля!

Следите за обновлениями: обязательно расскажем, как все проходит, и о следующих наборах тоже сообщим - у нас уже открыт лист ожидания😉

❗️Если ты тоже хочешь в нем оказаться, пиши нам на почту hr@evapps.ru
1
Сегодня завершим нашу линейку постов про транзакции.

🚨 1) Внешние действия внутри транзакции = проблемы
Любой внешний вызов внутри транзакции может выполниться два и более раз, потому что Laravel автоматически повторяет транзакцию при дедлоках и таймаутах.
DB::transaction($callback, $attempts) сам делает retry, если произошёл:
- дедлок
- lock wait timeout
- потеря блокировки

И важно: При повторе он запускает весь callback с нуля.
Он не понимает, что там было «одноразовым», а что идемпотентным.

Что под раздачу попадает:
- отправка писем
- пуши
- внешние HTTP-запросы
- интеграции (CRM, платёжки)

Если транзакция упала и Laravel её перезапустил → действие повторится, потому что MySQL откатывает только свои изменения, а внешние вызовы уже произошли и не откатываются.
Как избежать:
- всё внешнее — только после commit
- использовать afterCommit() у моделей или dispatch(fn)->afterCommit() — так действие выполняется один раз, после успешной фиксации данных.

🔒 2) Дедлоки из-за порядка блокировок
Дедлок (deadlock) — ситуация, когда две и более транзакций ожидают друг друга бесконечно, и MySQL вынужден прервать одну из них, чтобы система продолжила работать.
Чаще всего дедлоки возникают не из-за MySQL как такового, а из-за разного порядка, в котором код блокирует строки или ресурсы.

Пример типичной ситуации:
- Транзакция A начинает и лочит строки 5 → 10
- Транзакция B начинает и лочит строки 10 → 5
MySQL обнаруживает тупик и прерывает одну транзакцию с ошибкой Deadlock found.

Как снизить риск:
1. Всегда блокировать строки в одном порядке
Обычно по возрастанию ID, чтобы исключить циклические ожидания.
$rows = DB::table('accounts')
->whereIn('id', [$id1, $id2])
->orderBy('id')
->lockForUpdate()
->get();

2. Использовать блокировки корректно
- FOR UPDATE — эксклюзивная блокировка для изменения
- sharedLock() — блокировка для чтения без мешающих других читателей
3. Минимизировать время удержания блокировок
Чем быстрее транзакция завершится, тем ниже шанс дедлока.
4. Разделять независимые операции
Операции по разным таблицам или сущностям лучше выполнять в отдельных транзакциях, чтобы не увеличивать зону возможного конфликта.

🌐 3) Когда транзакции не работают вообще
Стандартные транзакции работают только в рамках одной базы данных и одного соединения.
Если операция затрагивает:
- несколько сервисов (например, микросервисы с разной логикой),
- несколько БД (разные инстансы, разные схемы),
- внешние API (CRM, платёжные системы, сторонние интеграции),то гарантировать атомарность стандартной транзакцией невозможно.

Причина: транзакция контролирует только изменения внутри конкретного движка базы данных.
Внешние действия, например HTTP-запросы или вызовы другой БД, не могут быть откатаны автоматически при ошибке — их состояние уже “зафиксировано” в сторонней системе.

🔹4) Стандартный подход — Saga pattern
Saga pattern — это архитектурный паттерн для управления распределёнными транзакциями.
Его идея проста:
1.Каждый шаг — независимый
Каждое действие выполняется как отдельная транзакция, которая гарантированно сохраняет свои изменения в локальной БД.
2. Компенсирующие действия
Для каждого шага создаётся обратная операция, которая может откатить изменения, если последующий шаг не удался.
Например, для биллинга:
Шаг 1: резервируем средства на счёте клиента (локальная транзакция)
Шаг 2: создаём заказ в системе (локальная транзакция)
Шаг 3: уведомляем склад о сборкеЕсли шаг 2 падает, шаг 1 компенсируется: средства возвращаются на счёт клиента.

Преимущества Saga pattern:
- Нет необходимости в глобальной межсервисной транзакции, которая блокировала бы все сервисы.
- Процесс становится устойчивым к сбоям: каждая ошибка обрабатывается локально и компенсируется.
- Подходит для систем с высокой нагрузкой и распределённой архитектурой: очереди, биллинг, бронирования, логистика.

Есть ли у вас интересные кейсы, когда Saga pattern спасала систему от ошибок?

#laravel #mysql #transactions #webdev #db #devops #saga #aftercommit