Коли я почав розробляти систему для трьох типів ботів, то дуже швидко зрозумів: звичайний підхід не підходить.
У мене був Integration Bot, який мав розсилати повідомлення. Constructor Bot, який мав обробляти користувацькі команди. І User Bot, який мав бути розумним з AI. Кожен мав свою логіку, свої потреби, свої способи спілкуватися з базою даних.
Я йшов від простого до складного, оскільки не було остаточного рішення що це має бути, з початку це був лише конструктор ботів через веб інтерфейс за допомогою хендлерів на php — один бекенд.
Але з архітектурою, яка зробить три типи ботів незалежними, але зв’язаними через спільну AI систему.
Чому Clean Architecture?
Clean Architecture — це не якась магія, це просто добра організація коду. Ідея проста: чим далі від центру, тим більше залежностей від реального світу.
Уявіть круги:
- Самий центр — ваша бізнес-логіка. Чиста, без залежностей, тестована.
- Наступний круг — правила додатку. Як вони спілкуються.
- Третій круг — http, база даних, зовнішні сервіси.
- Зовнішній круг — інтерфейси, API, користувачі.
Залежності іду завжди від зовні до центру. Таким чином внутрішній код не знає про базу даних, про API, про те, який фреймворк використовується.
Мені це дало свободу. Я міг змінити базу з MySQL на PostgreSQL — внутрішній код не знає про це. Я міг додати Redis для кешування — і це не впливає на внутрішній код.
Але найголовніше — я зробив код тестованим. Без бази, без API, без будь-чого реального.
Як це виглядає у мене?
У мене є чотири окремих мікросервіси, кожен в своєму Docker контейнері, кожен зі своїм docker-compose:
1. Laravel Backend (PHP)
Це серце системи. Тут живе вся бізнес-логіка ботів, управління користувачами, сховище даних.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
app/ ├── Domain/ ← чиста бізнес-логіка │ ├── Entities/ ← Bot, Message, User │ ├── Repositories/ ← інтерфейси для роботи з даними │ └── Services/ ← бізнес-правила ├── Application/ ← use cases │ ├── Commands/ ← що можна робити │ └── Queries/ ← що можна запитувати ├── Infrastructure/ ← робота з реальним світом │ ├── Repositories/ ← реалізація для MySQL │ ├── Services/ ← інтеграція з зовнішніми API │ └── Jobs/ ← асинхронні завдання └── Interfaces/ ← HTTP контролери, routes |
Це чиста архітектура — Domain не знає про Laravel, Database не знає про HTTP.
2. AI Gateway (Node.js)
Окремий сервіс, який сидить між Laravel й AI провайдерами. Його робота — обрати найкращого AI помічника, побудувати контекст, відправити запит, повернути відповідь.
Чому окремо? Тому що:
- Потрібна швидкість — Node.js краще для асинхронної обробки
- Потрібна незалежність — якщо Laravel упаде, AI Gateway продовжить працювати
- Потрібна масштабованість — можу запустити декілька інстансів AI Gateway
3. Content Parser (Go)
Мікросервіс, який витягує контент з web сторінок, Reddit, Telegram каналів. Написаний на Go тому що потрібна швидкість та мінімальні ресурси.
Його робота проста: “Дай мені URL, я витягну звідти головне й повернусь”.
4. React Frontend (TypeScript)
Вебінтерфейс для користувачів. Спілкується з Laravel API.
Як вони живуть разом?
Тут приходить цікава частина. У мене не один docker-compose.yml, а три окремих, які спілкуються через shared network.
Три окремих docker-compose файли:
1. docker-compose-php83.yml (Laravel + Workers)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<span class="token key">services</span><span class="token">:</span> <span class="token key">nginx</span><span class="token">:</span> <span class="token key">ports</span><span class="token">:</span> <span class="token">-</span> <span class="token">"80:80"</span> <span class="token">-</span> <span class="token">"443:443"</span> <span class="token key">php-8</span><span class="token">:</span> <span class="token key">environment</span><span class="token">:</span> <span class="token key">AI_GATEWAY_URL</span><span class="token">:</span> http<span class="token">:</span>//ai<span class="token">-</span>gateway<span class="token">-</span>service<span class="token">:</span><span class="token">3002</span> <span class="token key">CONTENT_PARSER_URL</span><span class="token">:</span> http<span class="token">:</span>//content<span class="token">-</span>parser<span class="token">:</span><span class="token">8080</span> <span class="token key">mysql</span><span class="token">:</span> <span class="token"># База для ботів та статей</span> <span class="token key">redis</span><span class="token">:</span> <span class="token"># Черга для jobs</span> <span class="token key">article-generation-worker</span><span class="token">:</span> <span class="token key">environment</span><span class="token">:</span> <span class="token key">AI_GATEWAY_URL</span><span class="token">:</span> http<span class="token">:</span>//ai<span class="token">-</span>gateway<span class="token">-</span>service<span class="token">:</span><span class="token">3002</span> <span class="token key">CONTENT_PARSER_URL</span><span class="token">:</span> http<span class="token">:</span>//content<span class="token">-</span>parser<span class="token">:</span><span class="token">8080</span> <span class="token key">networks</span><span class="token">:</span> <span class="token key">shared_network</span><span class="token">:</span> <span class="token key">external</span><span class="token">:</span> <span class="token">true</span> <span class="token"># ← Зовнішня мережа для спілкування!</span> |
2. docker-compose.yml (AI Gateway)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span class="token key">services</span><span class="token">:</span> <span class="token key">ai-gateway-service</span><span class="token">:</span> <span class="token key">ports</span><span class="token">:</span> <span class="token">-</span> <span class="token">"3002:3000"</span> <span class="token key">environment</span><span class="token">:</span> <span class="token key">DATABASE_URL</span><span class="token">:</span> postgresql<span class="token">:</span>//ai_admin<span class="token">:</span><span class="token">...</span>@ai<span class="token">-</span>database<span class="token">:</span>5432/<span class="token">...</span> <span class="token key">ai-database</span><span class="token">:</span> <span class="token"># PostgreSQL з pgvector для embeddings</span> <span class="token key">ai-redis</span><span class="token">:</span> <span class="token"># Redis для AI кешування</span> <span class="token key">networks</span><span class="token">:</span> <span class="token key">shared_network</span><span class="token">:</span> <span class="token key">external</span><span class="token">:</span> <span class="token">true</span> <span class="token"># ← Та ж мережа!</span> |
3. docker-compose.yml (Content Parser)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<span class="token key">services</span><span class="token">:</span> <span class="token key">content-parser</span><span class="token">:</span> <span class="token key">ports</span><span class="token">:</span> <span class="token">-</span> <span class="token">"8080:8080"</span> <span class="token key">environment</span><span class="token">:</span> <span class="token key">DATABASE_URL</span><span class="token">:</span> postgresql<span class="token">:</span>//ai_admin<span class="token">:</span><span class="token">...</span>@ai<span class="token">-</span>database<span class="token">:</span>5432/<span class="token">...</span> <span class="token key">AI_GATEWAY_URL</span><span class="token">:</span> http<span class="token">:</span>//ai<span class="token">-</span>gateway<span class="token">-</span>service<span class="token">:</span><span class="token">3002</span> <span class="token key">ai-database</span><span class="token">:</span> <span class="token"># PostgreSQL для кешування парсеного контенту</span> <span class="token key">networks</span><span class="token">:</span> <span class="token key">shared_network</span><span class="token">:</span> <span class="token key">external</span><span class="token">:</span> <span class="token">true</span> <span class="token"># ← Та ж мережа знову!</span> |
Як це працює?
Крок 1: Створюємо shared network
|
1 |
<span class="token">docker</span> network create shared_network |
Крок 2: Запускаємо кожен docker-compose окремо
|
1 2 3 4 5 6 7 8 9 10 11 |
<span class="token"># Terminal 1: AI Gateway</span> <span class="token">cd</span> ai-gateway <span class="token">docker-compose</span> up <span class="token"># Terminal 2: Content Parser</span> <span class="token">cd</span> content-parser <span class="token">docker-compose</span> up <span class="token"># Terminal 3: Laravel</span> <span class="token">cd</span> ./ <span class="token">docker-compose</span> -f docker-compose-php83.yml up |
Результат: Три окремих системи, але всі в одній мережі shared_network. Вони бачать одна одну через hostname:
- Laravel через
http://ai-gateway-service:3002 - Laravel через
http://content-parser:8080 - Content Parser через
http://ai-gateway-service:3002
Це дає мені максимальну гнучкість:
- Запусти тільки AI Gateway для тестування
- Запусти тільки Content Parser для розробки парсера
- Запусти все разом для повної системи
- Легко масштабуй кожну частину незалежно
Реальний флоу повідомлення
Щоб зрозуміти, як це працює, давайте відстежимо одне повідомлення від користувача до AI відповіді:
Крок 1: Користувач пише в Telegram
Боти отримує повідомлення через webhook від Telegram.
Крок 2: Laravel обробляє
Telegram webhook → Laravel Route → Controller → Use Case → Service
Laravel отримує повідомлення, перевіряє юзера, витягає контекст з бази.
Крок 3: Запит іде до AI Gateway
Laravel відправляє HTTP запит до ai-gateway:3002/api/generate:
|
1 2 3 4 5 6 7 8 9 10 |
<span class="token">{</span> <span class="token">"user_id"</span><span class="token">:</span> <span class="token">12345</span><span class="token">,</span> <span class="token">"bot_id"</span><span class="token">:</span> <span class="token">"user-bot-123"</span><span class="token">,</span> <span class="token">"message"</span><span class="token">:</span> <span class="token">"Привіт! Як справи?"</span><span class="token">,</span> <span class="token">"context"</span><span class="token">:</span> <span class="token">{</span> <span class="token">"recent_messages"</span><span class="token">:</span> <span class="token">[</span>...<span class="token">]</span><span class="token">,</span> <span class="token">"user_facts"</span><span class="token">:</span> <span class="token">[</span>...<span class="token">]</span><span class="token">,</span> <span class="token">"personality"</span><span class="token">:</span> <span class="token">{</span>...<span class="token">}</span> <span class="token">}</span> <span class="token">}</span> |
Крок 4: AI Gateway обробляє контекст
- Вибирає найрелевантнішу інформацію
- Обирає AI провайдера (OpenAI? Google? Claude?)
- Будує промпт
- Відправляє запит
Крок 5: Відповідь повертається
|
1 2 3 4 5 |
<span class="token">{</span> <span class="token">"response"</span><span class="token">:</span> <span class="token">"Привіт! Я добре, дякую за запитання!"</span><span class="token">,</span> <span class="token">"provider"</span><span class="token">:</span> <span class="token">"google"</span><span class="token">,</span> <span class="token">"tokens_used"</span><span class="token">:</span> <span class="token">150</span> <span class="token">}</span> |
Крок 6: Laravel зберігає й відправляє
Результат зберігається в базі, відправляється назад боту в Telegram.
Все це — декілька мікросервісів, які спілкуються через HTTP.
Чому мікросервіси?
Якби я писав все в одному монолітному PHP додатку, я мав би такі проблеми:
- Масштабування — якщо мені потрібна більше потужності для AI обробки, я мусив масштабувати весь додаток
- Технологія lock-in — якщо я захотів використати Go для парсера (тому що він швидший), мені довелось би переписувати на PHP
- Падіння системи — якщо AI Gateway впадав, весь додаток зупинявся
- Development — коли я змінював AI Gateway код, мені довелось перезапускати весь Laravel
З мікросервісами:
- Незалежне масштабування — запускаю більше AI Gateway інстансів, якщо потрібна більша пропускна спроможність
- Вибір технології — контент парсер на Go, AI Gateway на Node, backend на PHP — все мішається й працює
- Стійкість — одна частина впадає, інші працюють
- Development швидкість — змінюю AI Gateway, перезапускаю тільки його контейнер
Dependency Injection — як вони спілкуються?
Но як Laravel контролер знає, куди послати запит до AI Gateway? Як вона знає адресу, порт, які параметри?
Тут допомагає Dependency Injection. Я не пишу адреси “в жорстко” в коді. Я передаю залежності через конструктор:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<span class="token">class</span> <span class="token class-name-definition">SendMessageCommand</span> <span class="token">{</span> <span class="token">private</span> <span class="token">$aiGateway</span><span class="token">;</span> <span class="token">private</span> <span class="token">$botRepository</span><span class="token">;</span> <span class="token">public</span> <span class="token">function</span> <span class="token function-definition">__construct</span><span class="token">(</span> <span class="token type-declaration">AIGatewayService</span> <span class="token">$aiGateway</span><span class="token">,</span> <span class="token type-declaration">IBotRepository</span> <span class="token">$botRepository</span> <span class="token">)</span> <span class="token">{</span> <span class="token this">$this</span><span class="token">-></span><span class="token">aiGateway</span> <span class="token">=</span> <span class="token">$aiGateway</span><span class="token">;</span> <span class="token this">$this</span><span class="token">-></span><span class="token">botRepository</span> <span class="token">=</span> <span class="token">$botRepository</span><span class="token">;</span> <span class="token">}</span> <span class="token">public</span> <span class="token">function</span> <span class="token function-definition">execute</span><span class="token">(</span><span class="token">$message</span><span class="token">)</span> <span class="token">{</span> <span class="token">$bot</span> <span class="token">=</span> <span class="token this">$this</span><span class="token">-></span><span class="token">botRepository</span><span class="token">-></span><span class="token">find</span><span class="token">(</span><span class="token">$message</span><span class="token">-></span><span class="token">bot_id</span><span class="token">)</span><span class="token">;</span> <span class="token">$response</span> <span class="token">=</span> <span class="token this">$this</span><span class="token">-></span><span class="token">aiGateway</span><span class="token">-></span><span class="token">generate</span><span class="token">(</span><span class="token">$message</span><span class="token">,</span> <span class="token">$bot</span><span class="token">)</span><span class="token">;</span> <span class="token">return</span> <span class="token">$response</span><span class="token">;</span> <span class="token">}</span> <span class="token">}</span> |
У .env файлі я вказую:
|
1 2 |
AI_GATEWAY_URL=http://ai-gateway:3002 AI_GATEWAY_TIMEOUT=30 |
Код не знає де живе AI Gateway. Він просто знає, що є якийсь AIGatewayService, який робить роботу.
Це дозволяє мені:
- Тестувати — замісти реального AI Gateway, я можу передати mock
- Міняти реалізацію — замісти HTTP, я можу використати гря, або Redis queue
- Конфігурувати — змінити URL в
.env— й все переналаштовується
Резюме: Три рівні архітектури
Рівень 1: Clean Architecture (код) Мій код організований так, щоб внутрішня логіка не знала про зовнішній світ.
Рівень 2: Мікросервіси (сервіси) Я розбив систему на чотири незалежних сервіси, кожен з своєю відповідальністю.
Рівень 3: Docker Compose (оркестрація) Все це живе в Docker контейнерах, які спілкуються через HTTP й network.
Результат:
- 🧩 Модульність — кожна частина робить одне й робить добре
- 🚀 Масштабованість — додаю нові інстанси без зміни коду
- 🛡️ Стійкість — одна частина впадає, система продовжує працювати
- 🧪 Тестованість — можу тестувати кожну частину окремо
- 🔄 Гнучкість — можу міняти технології без переписування всього
Коли система почне рости, архітектура врятує мене від хаосу. Окрім того мікросервіси можна використати в інших проектах, той же парсер чи AI Gateway
Наступна стаття: Smart Context — як система розуміє контекст розмови без тонн даних.
Автор, один на один з компіллером