Ответ
Мы использовали подход, основанный на Domain-Driven Design (DDD) и принципах bounded context. Основные шаги:
1. Выявление доменов и контекстов:
- Провели event storming-сессии с бизнес-аналитиками и разработчиками.
- Выделили core domains (например,
Order,Payment,Delivery,Catalog). - Определили границы контекстов, чтобы минимизировать связи между ними.
2. Критерии разделения на сервисы:
- Бизнес-возможность: Каждый сервис отвечал за одну четкую бизнес-функцию (например, обработка платежей).
- Независимое развертывание и масштабирование: Сервис
Notificationмог масштабироваться отдельно отOrderв период распродаж. - Владение данными: У каждого сервиса была своя приватная база данных. Обмен данными шел только через публичные API или события.
3. Реализация коммуникации:
- Синхронная (REST/gRPC): Для операций, требующих немедленного ответа (например, проверка наличия товара).
- Асинхронная (Event-Driven через Kafka): Для обеспечения слабой связанности и надежности. Пример события:
// Общий контракт события в отдельной библиотеке public class OrderPlacedEvent { public Guid OrderId { get; set; } public decimal TotalAmount { get; set; } public string CustomerEmail { get; set; } }
// Сервис Payment подписывался на это событие для запуска процессинга.
**4. Инфраструктурные решения:**
* **Контракты:** DTO и события выносились в общие NuGet-пакеты.
* **Мониторинг:** Централизованный сбор логов (ELK Stack) и метрик (Prometheus/Grafana) для каждого сервиса.
* **Обнаружение сервисов:** Использовали Consul для динамической маршрутизации.
Главный урок: разделение должно быть по бизнес-границам, а не по техническим слоям (например, не делать отдельный сервис "База данных" или "Логика"). Ответ 18+ 🔞
Да ты посмотри, какая красота! Мы тут, блядь, целую архитектуру выстроили, как пить дать — по канонам Domain-Driven Design (DDD). Не просто так сервисы нарезали, а с мозгом, чтобы потом не пришлось переделывать всё на коленке.
1. Выявление доменов и контекстов:
Собрались мы, значит, с бизнес-аналитиками и разработчиками, устроили мозговой штурм, который они event storming обозвали. Выделили ключевые домены — Order, Payment, Delivery, Catalog. Главное было — границы контекстов нарисовать так, чтобы они друг за друга не цеплялись, как пьяные в подворотне. Чтобы каждый жил своей жизнью, понимаешь?
2. Критерии разделения на сервисы: Тут мы не стали мудрить, а взяли простые и понятные правила:
- Одна бизнес-возможность — один сервис. Платежи — отдельно, заказы — отдельно. Не надо из одного сервиса делать швейцарский нож, который и колбасу режет, и гвозди забивает.
- Независимое развертывание и масштабирование. Это же, блядь, основная фишка микросервисов! Чтобы, когда распродажа, сервис уведомлений
Notificationможно было масштабировать в овердохуища раз, а сервис заказовOrderпри этом не трогать. - Владение данными. Каждый сервис — царь и бог в своей базе. Никакого прямого доступа к чужим таблицам! Общаемся только через API или события. Иначе получится такая каша, что мама не горюй.
3. Реализация коммуникации: А вот тут уже интереснее. Мы не стали пихать всё в один стиль.
- Синхронная (REST/gRPC): Использовали, когда нужен был мгновенный ответ. Ну, например, пользователь добавляет товар в корзину — надо тут же проверить, есть ли он на складе. Ждать событий из Kafka тут было бы идиотизмом.
- Асинхронная (Event-Driven через Kafka): А это наше всё для слабой связанности. Сервис заказов создал заказ — отправил событие
OrderPlacedEventв топик и забыл. А уже сервис платежейPayment, доставкиDeliveryи нотификацийNotificationсами подписались и делают что надо. Красота!
// Контракт события лежал в общем NuGet-пакете, чтобы все одинаково понимали
public class OrderPlacedEvent
{
public Guid OrderId { get; set; }
public decimal TotalAmount { get; set; }
public string CustomerEmail { get; set; }
}
// Сервис Payment, получив такое событие, начинал процессинг платежа. Всё чётко.
4. Инфраструктурные решения: Без нормальной инфраструктуры это всё — просто куча говна, которая развалится при первой же нагрузке.
- Контракты (DTO, события) — вынесли в общие библиотеки, чтобы не было рассинхрона. Иначе один сервис ждёт поле
TotalSum, а другой шлётTotalAmount— и пиши пропало. - Мониторинг. Подняли централизованный сбор логов (ELK Stack) и метрик (Prometheus/Grafana). Чтобы когда что-то ебнулось, не бегать по десяти серверам с фонариком, а сразу видеть, в каком сервисе пиздец.
- Обнаружение сервисов. Взяли Consul, чтобы сервисы друг друга находили динамически, без жёстких прописанных IP-адресов в конфигах.
И главный вывод, который мы сделали, — разделять нужно строго по бизнес-границам, а не по техническим слоям. Нельзя делать отдельный сервис "для всей логики" или, того хуже, "для всей базы данных". Это будет не микросервисная архитектура, а монолит, распиленный на куски болгаркой — бесполезно и опасно.