Главная
разработано·Чудайкин Александр·Бюро автоматизации процессов
Кто ты?
Команда растёт. Каждый сервис подключён по-своему. Новый разработчик изобретает колесо заново. Архитектурная документация давно не совпадает с кодом.
archtool даёт всей команде один стандарт: объявляй интерфейс, пиши реализацию, archtool подключит сам. Нарушения слоёв ловятся при старте — не на разборе инцидента в три ночи. Архитектура становится чем-то, что можно проверить, а не просто задокументировать.
Ты шипишь быстро. Но каждый срезанный угол в фундаменте обходится вдвое дороже на масштабе.
archtool берёт на себя бойлерплейт — ты фокусируешься на продукте. Добавил модуль, зарегистрировал, готово. Архитектура растёт вместе с командой — никакого переписывания на десятом разработчике.
→ fractal_chunks (в разработке) — проверенные боем модули для auth, users, платежей, уведомлений. Подключаешь нужное; пропускаешь лишнее.
Ты генерируешь код быстрее, чем интегрируешь его. Узкое место — разводка.
archtool убирает этот шаг. ИИ пишет сервис, ты объявляешь интерфейс — archtool подключает всё при старте. Никакого бойлерплейта. Никаких цепочек импортов.
→ fractal_chunks (в разработке) — растущий каталог production-модулей, каждый как кубик Lego. Добавь users, auth-jwt, notifications в новый проект за минуты, а не дни.
Ты уже чистил чужие service-locator'ы. Ты знаешь, что бывает, когда DI неформальный.
archtool делает правильные паттерны единственным путём. Интерфейсы — единственный контракт. Реализации обнаруживаются, не регистрируются. Нарушения слоёв падают быстро при старте. Clean Architecture — без лишних церемоний.
Проблема, которую рано или поздно встречает каждый Python-проект
Начинаешь чисто. Один сервис, один репозиторий, может быть контроллер. Проект растёт.
Где-то на пятом модуле появляется это:
# entrypoints/run.py — кладбище благих намерений
import sys
sys.path.insert(0, "..") # ← зачем это вообще здесь?
from app.users.repos import UserRepo
from app.users.services import UserService
from app.orders.repos import OrderRepo
from app.orders.services import OrderService
from app.payments.repos import PaymentRepo
from app.payments.services import PaymentService
from app.notifications.services import NotificationService
user_repo = UserRepo()
order_repo = OrderRepo()
payment_repo = PaymentRepo()
user_service = UserService()
user_service.repo = user_repo # ← не забыть это
order_service = OrderService()
order_service.repo = order_repo
order_service.user_service = user_service # ← и это
payment_service = PaymentService()
payment_service.repo = payment_repo
payment_service.order_service = order_service # ← и это
notification_service = NotificationService()
notification_service.payment_service = payment_service # ← и это
# ... ещё 40 строк ...
Именно это archtool заменяет — полностью.
Решение
# entrypoints/run.py с archtool
from pathlib import Path
from archtool.dependency_injector import DependencyInjector
from archtool.global_types import AppModule
injector = DependencyInjector(
modules_list=[
AppModule("app.users"),
AppModule("app.orders"),
AppModule("app.payments"),
AppModule("app.notifications"),
],
project_root=Path(__file__).parent.parent,
)
injector.inject()
archtool сканирует твои модули, обнаруживает каждую пару интерфейс–реализация, инстанциирует их в порядке зависимостей и связывает всё вместе. Никакого бойлерплейта регистрации. Никаких sys.path-хаков. Никаких скрытых багов проводки.
Что ты получаешь
Объяви зависимость как аннотацию класса на конкретном классе. archtool прочитает её и вызовет setattr при сборке.
Нарушения границ слоёв — сервис импортирует напрямую из репо, контроллер лезет во внутренности домена — выявляются при старте, а не в продакшене в 3 ночи.
Встроенные слои следуют Clean Architecture. Но ты определяешь свои:
class IntegrationsLayer(Layer):
depends_on = InfrastructureLayer
class Components:
clients = ComponentPattern("clients", superclass=ABCClient)
Любое имя файла, любой базовый класс — archtool адаптируется к твоей архитектуре, а не наоборот.
- S — каждый
AppModuleвладеет одним ограниченным контекстом - O — добавляешь модуль, ничего больше не меняется
- L — заменяешь репо на заглушку, потребители не знают об этом
- I — интерфейсы остаются минимальными и сфокусированными
- D — всё зависит от абстракций, никогда от конкреций
Посмотри как работает
# app/users/services.py from .interfaces import UserServiceABC, UserRepoABC class UserService(UserServiceABC): repo: UserRepoABC # ← archtool подставит UserRepo сюда def get_name(self) -> str: return self.repo.find_all()[0] # entrypoints/run.py injector = DependencyInjector( modules_list=[AppModule("app.users")], project_root=Path(__file__).parent.parent, ) injector.inject() svc = injector.get_dependency(UserServiceABC) print(svc.get_name()) # → "alice"
# app/orders/services.py from app.users.interfaces import UserServiceABC # кросс-модульная зависимость from .interfaces import OrderServiceABC, OrderRepoABC class OrderService(OrderServiceABC): repo: OrderRepoABC user_svc: UserServiceABC # ← подтянется из app.users автоматически def place(self, user_id: int, items: list) -> dict: user = self.user_svc.get_name() return {"user": user, "items": items, "status": "placed"} # entrypoints/run.py injector = DependencyInjector( modules_list=[ AppModule("app.users"), AppModule("app.orders"), # кросс-модульная зависимость резолвится ], project_root=ROOT, ) injector.inject()
# app/fraud/services.py ← Domain layer from .interfaces import FraudServiceABC, FraudControllerABC class FraudService(FraudServiceABC): # Domain зависит от Application — нарушение! controller: FraudControllerABC # entrypoints/run.py injector = DependencyInjector( modules_list=[AppModule("app.fraud")], layers=default_layers, # контроль слоёв включён project_root=ROOT, ) injector.inject() # ← падает здесь, а не в 3 ночи в проде
Установка
Поддерживается Python 3.10 · 3.11 · 3.12 · 3.13.
Старт за пять минут
→ Быстрый старт — рабочий проект с нуля.
→ Зачем archtool? — полное описание проблемы и как мы её решаем.
→ Как это работает — алгоритм двухпроходной инъекции объяснён.