DON'T STOP AND CODE – Telegram
DON'T STOP AND CODE
102 subscribers
58 photos
2 videos
1 file
119 links
Мой путь в программировании
#python

Для связи: @avagners
Download Telegram
[Domain Services в DDD: Логика, которая не принадлежит агрегатам]
(Когда и зачем использовать доменные сервисы?)

Продолжаю знакомиться с DDD. Следующий паттерн Domain Services.

Domain Service - это класс, который реализует бизнес-правила, выходящие за рамки одного агрегата.

В Domain-Driven Design (DDD) не вся бизнес-логика уместна внутри агрегатов или сущностей.
Иногда операции:
- Затрагивают несколько агрегатов,
- Зависят от внешних систем (например, проверка кредитного рейтинга),
- Не имеют естественного места в какой-то одной сущности.

Для такой логики создают Domain Services (доменные сервисы).

Отличия от других сервисов в DDD
Domain Service — содержит только бизнес-логику, не зависит от инфраструктуры.
Application Service — оркестрирует вызовы Domain-сервисов и инфраструктуры (например, «создать заказ → сохранить в БД → отправить уведомление»).
Infrastructure Service — технические детали (отправка почты, запросы к API).

Когда использовать Domain Service?
Логика требует нескольких агрегатов.
Зависит от доменных правил, но не принадлежит ни одной сущности (например, проверка сложных условий).
Чистая бизнес-логика без инфраструктурных деталей.

Не используйте, если:
- Логика относится к одному агрегату (лучше поместить в агрегат).
- Нужен доступ к БД, API и т.д. (это Application/Infrastructure Service).

✔️ Плюсы:
- Четкое разделение ответственности.
- Удобство тестирования (чистая логика без побочных эффектов).

Минусы:
- Риск превращения в "God Object" (если сервис делает слишком много).

Domain Services — это мост между агрегатами для сложной доменной логики.

P.s.
Полезная ссылка по теме:
https://enterprisecraftsmanship.com/posts/domain-vs-application-services/

#DDD #DomainServices #CleanArchitecture
👍3
120 кг в жиме лёжа;
(мой вес 82,5 кг)

Недели 3 назад сделал несколько безуспешных попыток. Далее сказал себе, что не подойду к этому весу пока не буду уверенно работать со штангой 110 кг.

И вот позавчера (31 марта) впервые взял этот вес.

Получил много эмоций от достижения очередного результата)

#gym #life
🔥10
[Памятка: как делать правильные тесты]

Раньше я писал юнит-тесты как будто "на автомате" — просто покрывал методы, проверял, что возвращается нужное значение, и считал, что всё ок.

Но это не совсем правильно. Тестировать методы ради покрытия — выглядит логично, но часто оказывается поверхностным и ненадёжным.

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

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

А свойства — это живое описание инвариантов кода. И это крайне важно.

Правильный подход — тестировать свойства поведения фичи, которые должны сохраняться независимо от реализации.

Вот что я теперь делаю:

1) Останавливаюсь и думаю: что вообще значит "правильно работает"?
Не метод за методом, а вся фича в целом.

2) Формулирую свойства корректности:
Например, "если policy создана — она должна быть доступна", "удаление policy делает её недоступной", и т.д.

3) Для каждого свойства выбираю способ тестирования:
Если можно покрыть юнит-тестами — пишу их. Если нужно — делаю фаззинг или просто руками проверяю.

4) Пишу тесты, которые проверяют именно эти свойства, а не случайные детали реализации.

Что изменилось?

- Тесты стало проще поддерживать: они не ломаются из-за мелких рефакторингов.
- Я лучше понимаю, зачем вообще эта фича нужна, а не просто "чтобы метод не падал".
- Реже ловлю баги в проде — потому что я реально проверяю смысл, а не поведение "по случайке".

#unittests #python #testdesign #tests
👍3
[Репозитории в DDD: как правильно работать с данными в доменном слое]

Привет! Сегодня разберём ещё одну важную концепцию Domain-Driven Design — Репозитории (Repositories). Когда я только начал изучать DDD, репозитории казались мне просто "прослойкой над базой данных". Давайте разберёмся, зачем они нужны и как их правильно использовать!

Репозиторий (Repository) — это абстракция, которая:
✔️ Инкапсулирует доступ к данным (БД, API, файлы и т. д.).
✔️ Предоставляет "коллекцию" доменных объектов (как если бы они хранились в памяти).
✔️ Работает только с Aggregate Roots (не с отдельными Entity или Value Object!).

Пример из жизни:
Если Order — это агрегат, то OrderRepository позволяет:
- Сохранять заказ (save).
- Загружать заказ по ID (find_by_id).
- Искать заказы по критериям (find_all_by_customer).

Но он НЕ даёт прямого доступа к OrderItem — только через корень (Order).

Ниже пример кода.
from abc import ABC, abstractmethod
from uuid import UUID
from typing import List, Optional

from .order import Order # Агрегат

class OrderRepository(ABC):
"""Абстракция репозитория"""
@abstractmethod
def save(self, order: Order) -> None:
pass

@abstractmethod
def find_by_id(self, order_id: UUID) -> Optional[Order]:
pass

@abstractmethod
def find_all_by_customer(self, customer_id: UUID) -> List[Order]:
pass


from sqlalchemy.orm import Session

class SQLOrderRepository(OrderRepository):
"""SQL реализация (через SQLAlchemy)"""
def __init__(self, session: Session):
self.session = session

def save(self, order: Order) -> None:
self.session.add(order)
self.session.commit()

def find_by_id(self, order_id: UUID) -> Optional[Order]:
return self.session.query(Order).filter_by(order_id=order_id).first()

def find_all_by_customer(self, customer_id: UUID) -> List[Order]:
return self.session.query(Order).filter_by(customer_id=customer_id).all()


Почему репозитории — это важно?

1) Отделение домена от инфраструктуры
Домен не знает, как хранятся данные (БД, API, CSV и т. д.).

2) Гибкость
Можно легко поменять базу данных, не трогая доменную логику.

3) Соблюдение DDD-принципов
Работа только с агрегатами, а не с отдельными таблицами БД.

Где должны находиться репозитории в структуре проекта?

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

Ниже пример классической структуры проекта по слоям DDD.
```bash
src/
├── domain/
│ ├── models/
│ ├── repositories/
│ └── services/

├── infrastructure/
│ ├── db/
│ ├── repositories/
│ └── caching/

├── application/
│ └── services/

└── presentation/

```

Типичные ошибки
Репозиторий для каждой Entity
Репозиторий должен быть только для Aggregate Root!

Возвращение DTO вместо доменных объектов
Репозиторий должен возвращать Order, а не OrderData.

Нарушение CQRS
Для запросов (Read) иногда лучше использовать отдельные запросы в cлое приложения, а не репозитории.

P.s.
Полезные ссылки по теме:
1) https://dddinpython.hashnode.dev/implementing-the-repository-pattern
2) https://dddinpython.hashnode.dev/implementing-the-repository-pattern-using-sqlalchemy

#DDD #Repository #DomainDrivenDesign #Программирование #Python
👍3
[Use Case в DDD: как проектировать сценарии взаимодействия с системой]

Привет! Сегодня поговорим про Use Case (Сценарии использования) — один из ключевых элементов DDD и чистой архитектуры.

1. Что такое Use Case?
Use Case (Сценарий использования) — это:
✔️ Описание конкретного бизнес-действия (например, "Оформление заказа", "Отмена бронирования").
✔️ Изолированная логика, которая координирует работу домена и инфраструктуры.
✔️ Мост между presentation (API/UI) и domain (бизнес-правила).

Примеры:
CreateOrderUseCase → создание заказа.
CancelBookingUseCase → отмена бронирования.
ProcessPaymentUseCase → обработка платежа.

2. Где находятся Use Case в структуре проекта?

Они относятся к application-слою (слое приложения):
src/
├── domain/
├── application/ # Use Cases + Application Services/CQS
│ ├── use_cases/
│ │ ├── commands/
│ │ └── queries/
│ └── services/
├── infrastructure/
└── api/


3. Пример use case "Создание заказа"
# /delivery/core/application/use_cases/commands/create_order/create_order_handler.py
from dataclasses import dataclass

from core.domain.model.order_aggregate.order import Order
from core.domain.model.shared_kernel.location import Location
from core.application.use_cases.commands.create_order.create_order_command import CreateOrderCommand
from infrastructure.adapters.postgres.unit_of_work import UnitOfWork


@dataclass
class CreateOrderCommandHandler:
unit_of_work: UnitOfWork

def handle(self, massege: CreateOrderCommand) -> None:

# Получаем геопозицию из Geo (пока ставим рандомное значение)
location = Location.create_random_location()

# Создаем заказ
order = Order(
order_id=massege.basket_id, # ID заказа совпадает с ID корзины
location=location
)

# Сохраняем
with self.unit_of_work as uow:
uow.orders.add(order)


4. Почему Use Case — это важно?
- Чёткое разделение ответственности
Presentation-слой (API/CLI) не должен содержать бизнес-логику.
Domain-слой не должен знать о внешних сервисах (API, БД).

- Упрощение тестирования
Use Case можно тестировать изолированно, подменяя репозитории на заглушки.

- Гибкость
Один Use Case может быть вызван из разных мест: API, CLI, фоновой задачи.

- Документирование системы
Названия Use Cases (CreateOrder, CancelBooking) явно описывают, что делает система.

5. Типичные ошибки

Use Case = "God Object"
Не нужно пихать всю логику в один Use Case. Разбивайте на мелкие сценарии.

Логика в контроллерах
Код вида if user.is_admin: ... должен быть в Use Case или домене, не в API.

#DDD #UseCase #CleanArchitecture #Python
👍1
[Input Adapters в DDD: как внешний мир общается с приложением]

Привет! Сегодня про Input Adapters — элемент гексагональной архитектуры (и DDD), который отвечает за преобразование внешних запросов (HTTP, CLI, события) в команды приложения.

1. Что это такое?

Input Adapter — это не только про REST-эндпоинты, а это единая точка входа для всех внешних взаимодействий.

Он:
- Парсит входящие данные (JSON, protobuf, CLI-аргументы).
- Валидирует их (например, через Pydantic).
- Вызывает Use Case (Command или Query).
- Не содержит бизнес-логики (это задача домена).

Примеры Input Adapters:
- REST API (FastAPI, Django View).
- CLI-команды (Click, Typer).
- Обработчики событий (Kafka Consumer).
- WebSocket-хэндлеры.

Ниже пример REST API
# presentation/api/rest/order_adapters.py
from fastapi import APIRouter, Depends

from application.commands import CreateOrderCommand
from presentation.schemas import OrderCreateRequest

router = APIRouter()

@router.post("/orders")
async def create_order(
request: OrderCreateRequest, # Pydantic-модель для валидации
command: CreateOrderCommand = Depends(), # Use Case
):
# Преобразуем запрос в DTO (без бизнес-логики!)
order_id = command.execute(
user_id=request.user_id,
items=[item.to_domain() for item in request.items]
)
return {"order_id": order_id}


2. Где находятся Input Adapters в структуре проекта?

Они относятся к presentation-слою, но могут выноситься в отдельный модуль для сложных сценариев:

src/
├── domain/
├── application/
├── infrastructure/
└── presentation/ # Input Adapters
├── api/ # HTTP-адаптеры
│ ├── rest/ # REST (FastAPI)
│ └── graphql/ # GraphQL
├── cli/ # CLI-адаптеры (Click)
└── events/ # Обработчики событий (Kafka, RabbitMQ)


3. Почему Input Adapters — это важно?

- Изоляция домена
Домен не знает, откуда пришёл запрос (HTTP, Kafka или CLI).

- Гибкость
Можно добавить новый адаптер (например, gRPC), не меняя Use Case.

- Тестируемость
Адаптеры легко тестировать в изоляции (например, мокая Use Case).

- Согласованность
Все входные данные проходят одинаковую валидацию перед передачей в Use Case.

4. Типичные ошибки

Бизнес-логика в адаптерах
Код вида if user.role == "admin" должен быть в домене, а не в REST-хэндлере.

Прямая работа с репозиториями
Адаптер вызывает только Use Case, не лезет в БД напрямую.

5. Вывод
Input Adapters — это "входные ворота" приложения, которые:
🔹 Преобразуют внешние запросы в команды/запросы.
🔹 Не содержат логики (только парсинг и валидацию).
🔹 Делают систему гибкой к изменениям протоколов.

#DDD #CleanArchitecture #InputAdapters #Python
👍3
[📌 Кейс из практики: как я восстановил работу таблицы в HDFS]

Порой я не знаю о чем написать. Рабочие моменты кажутся скучными. Но в этот раз я решил поделиться поучительной историей.

📉 Пришли с проблемой: отчёт в Power BI больше не работает — запросы просто падают по таймауту.

🔍 Начал разбираться и выяснил, что таблица в HDFS содержит более 75 000 мелких файлов на 477 партиций.
Причина — неудачная логика записи: в каждую партицию складывалось множество крошечных файлов.
Также полностью отсутствовала статистика по таблице.

Почему это проблема:

- Каждый файл — это нагрузка на NameNode. Когда их десятки тысяч, планирование и выполнение запросов начинают страдать.
- Без статистики оптимизатор не может строить адекватные планы выполнения — особенно в системах вроде Hive или Presto.
- Мелкие файлы хуже сжимаются. Компрессия эффективнее на крупных блоках, поэтому итоговый объём данных на диске получается больше, чем при хранении тех же данных в виде агрегированных файлов.

Решения и результаты:

1. Пересчитал статистику по таблице.
ANALYZE TABLE имя_таблицы COMPUTE STATISTICS;


После этого таблица "ожила" — запросы перестали падать, выполнялись за ~220 секунд. Уже хорошо. Пользователь уже был очень доволен.

2. Провёл частичную компакцию (объединение файлов).
Сократил количество файлов до 1900 (на 477 партиций), пересчитал статистику повторно — и запрос начал выполняться за 120 секунд.

3. Запустил полную компакцию.
Сделал по одному файлу на каждую партицию (итого 477 файлов). После очередного пересчёта статистики:

- Первый запуск запроса — 58 секунд
- Повторный — 17 секунд!
- В Trino — практически мгновенное выполнение

💾 Также заметно уменьшился размер таблицы на диске — компрессия начала работать в полную силу.

💡 Выводы:

🔹 Мелкие файлы = деградация производительности + перерасход хранилища.
🔹 Регулярная компакция + расчет статистики = must-have для стабильной работы.
🔹 Даже простые меры могут дать существенный прирост производительности и повысить удовлетворённость пользователей.

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

#bigdata #hdfs #datalake #hive #dataengineering #hadoop
🔥7👍1
[💪Соревнования по жиму лежа и новый личный рекорд]

Продолжаю ходить в тренажерный зал. Есть новые результаты:
1) Занял 2-е место в местных соревнованиях по жиму лежа с результатом 120 кг (для меня это первое подобное мероприятие);
2) Побил личный рекорд, взяв 125 кг (правда эта попытка была уже вне конкурса);

P.s. собственный вес 83,8 кг.

#gym #life
🔥10👏5🏆3
[Не писал ничего весь май и все лето]

Всем привет!

——
Прежде всего хочу поблагодарить, что не отписались за время моего отсутствия.
Спасибо! =)
——

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

Честно, эти 3 года, особенно последние 2 года дались мне крайне не просто. Как я упоминал, прошлый год был годом потрясений. Текущий год продолжает "радовать" в этом отношении.

Ведь у тебя есть свои ожидания на эту жизнь?) Вот у меня были и есть. Вот живешь N-лет согласно этим ожиданиям, все идет своим чередом, по "сценарию". Ты думаешь и дальше все будет развиваться в таком русле. Все будет также хорошо.

Но! Это только твои ожидания.) Рядом с тобой живут близкие люди, у которых свои ожидания. Представляешь?) Какое-то время ваши ожидания совпадают, пересекаются - тогда кажется что все хорошо. Но жизнь устроена таким образом, что эти ожидания со временем могут меняться - как у тебя, так и у близких людей.

Эти изменения происходят постепенно. Это может выражаться в каких-то мелких недопониманиях. Сначала не придаешь этому значения. Списываешь на бытовуху и усталость. Но со временем изменения набирают критическую массу.

Сначала есть сильное сопротивление этому процессу. Это может выражаться в протестах. В конфликтах. Ты не понимаешь что вообще происходит. Ведь все шло своим чередом. Ты постоянно задаешь себе вопрос: "Что происходит?!".

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

Нужно принять тот факт, что такова жизнь. Она такая какая есть. И чем ты больше пытаешься сопротивляться происходящему, тем больнее.

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

Для себя же я решил, что я не один. Рядом с тобой тоже люди. Близкие люди. И "уговорить", "переубедить", "договориться", "идти до конца" - может быть еще большей ошибкой, чем просто сделать шаг назад.

---
Вот такая заметка у меня получилась после долгого перерыва.
Постараюсь сильно не теряться и писать. О жизни, о работе, о коде.
🔥3💯3👀1
[Продолжение о бренности бытия. Проявление слабости]

Скажу честно, все эти события выбили меня из колеи. Вот уже больше 2-х лет.
Цели на год? Честно, было не до этого.

Это проявление слабости? Да, однозначно! Но по-другому я прожить этот опыт не смог. Где-то прочитал, что иногда стоит взять паузу и это нормально.

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

Возможно, это называется взросление.

Можно ли было этот период сократить и не выпадать на 2 года? Думаю, да.

--
Сейчас вернулся вкус к жизни, интерес, любопытство. Появились новые желания.
Это выражается в том числе этими заметками в канале.

Также это видно в работе. Снова вернулись энтузиазм, инициатива, проактивность. Хочется работать и развиваться в профессии. Ведь это так интересно!)
👍9🏆1👀1
[Интересные темы]

На этой неделе погрузился в темы F-ограниченого полиморфизма и функциональных интерфейсов.

По дороге узнал интересную информацию про номинативную и структурную типизации в языках.

Понял в чем разница между ABC и Protocol в Python и зачем они нужны.
👍5
[K8s для разработчиков]

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

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

На сегодня прошел основные абстракции кубера:
- pod
- replicaSet
- deployment
- namespace
- resources

Познакомился с наиболее полезными командами kubectl.
👍9
[K8s. Переменные окружения и секреты]

Вчера изучил способы работы с переменными окружения, секретами, Valumes.

- env
- configMap
- Secret
- Downward API

В Secret данные не шифруются, а кодирутся. Например, если у пользователя есть права на выполнение команды get secrets или exec, то он может получить чувствительные данные и декодировать их.
👍5
[Усталость]

Вы не устали от большого потока информации?

Сегодня увидел новость про то, что люди стали меньше проводить времени в социальных сетях. Задумался на эту тему.

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

Прошу прощения, но 90% личных каналов в телеграм я не читаю, хотя и подписан. Или захожу посмотреть буквально 1 раз в месяц.

Как у вас дела с этим?
💯6🫡3👍1
[4 типа задач]

- развить то, что работает;
- починить то, что не работает;
- достроить то, чего не хватает;
- проверить то, что еще не пробовали;
👍5
[Развернуть k8s локально]

Я тут познакомился с Minikube. С помощью него можно у себя на ноутбуке развернуть k8s.

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

Установил на два ноутбука. Без каких-либо проблем сразу завелся и на mac и на windows с wsl. Установка максимально простая и можно смело идти по оф. гайду.

P.s. если установлен docker, то установку гипервизора можно пропустить. Достаточно установить kubectl и сам minikube.
После выполнить команду minikube start.

Ссылки:
https://kubernetes.io/ru/docs/tasks/tools/install-minikube/
👍5
[Горячая перезагрузка в k8s]

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

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

Создал репозиторий с рабочим примером приложения на Python. Для тех кто хочет попробовать - Добро пожаловать).

В README.md пошаговый гайд.

Ссылки:
https://github.com/avagners/k8s_local_dev
👍31👀1
[AI. Нужен совет]

Коллеги, кто-то из вас использует в своей работе AI? (думаю уже все)
Как и что? Какие модели используете? Какие задачи решаете?

Поделитесь, пож-та, рабочими инструментами.
Если есть ссылки на гайды как все настроить, буду особо благодарен.
Интересна именно интеграция с vscode.

———
P.s. на работе внутри контура развернуты различные LLM-ки. Можно работать через привычный чат в браузере. Можно также интегрировать в vscode с помощью, например, расширения continue.

Я как раз на этом этапе освоения. Можно сказать практически перестал пользоваться чатами. Пока удается решать небольшие задачи с переменным успехом)
[пусть будет]
👍6
[Полезное]

Вот тут можно посмотреть характеристики всех популярных моделей.

Например, можно отсортировать по размеру контекста.
На сегодня топ-1 занимает Llama 4 Scout с 10млн токенов.

Очень крутой и полезный портал.

https://artificialanalysis.ai/leaderboards/models
👍4
Продолжаем знакомство с различными LLM.
👀4