Форум программистов, компьютерный форум, киберфорум
ArchitectMsa
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Event-Driven архитектуры в C# - сравнение брокеров и выбор решения

Запись от ArchitectMsa размещена 11.08.2025 в 17:51. Обновил(-а) mik-a-el 11.08.2025 в 18:40
Показов 3301 Комментарии 0

Нажмите на изображение для увеличения
Название: Event-Driven архитектуры в C# - практическое сравнение и выбор решения.jpg
Просмотров: 294
Размер:	155.7 Кб
ID:	11046
Начало: Event-Driven архитектуры в C# - выбираем правильного брокера сообщений

Azure Service Bus - облачное решение от Microsoft



Когда все твои сервисы крутятся в облаке Microsoft, ставить на продакшн самостоятельно настроенную Kafka или RabbitMQ часто становится излишней головной болью. Именно поэтому последние несколько лет я все чаще выбираю Azure Service Bus - полностью управляемый облачный брокер сообщений от Microsoft. В отличие от наших "самоделок" вроде Kafka, RabbitMQ или NATS, Azure Service Bus не требует развертывания, настройки серверов и постоянного мониторинга инфраструктуры. Microsoft берет всю эту головную боль на себя, а ты просто используешь API. Звучит заманчиво, но не все так просто.

Интеграция с экосистемой Azure



Первое, за что я полюбил Service Bus - его бесшовная интеграция с другими сервисами Azure. Когда ты используешь Azure Functions, Logic Apps, Event Grid и другие сервисы, Service Bus становится естественным выбором.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Пример Azure Function с триггером Service Bus
public static class OrderProcessor
{
    [FunctionName("ProcessOrder")]
    public static async Task Run(
        [ServiceBusTrigger("orders", Connection = "ServiceBusConnection")] string message,
        ILogger log)
    {
        log.LogInformation($"Получено сообщение: {message}");
        
        // Обработка заказа
        var order = JsonSerializer.Deserialize<Order>(message);
        await ProcessOrderAsync(order);
    }
}
В проекте для ритейл-сети мы использовали комбинацию Azure Functions (для обработки событий) и Logic Apps (для оркестрации процессов) с Service Bus в качестве связующего звена. Интеграция была настолько гладкой, что нам удалось выкатить первую версию системы за три недели вместо плановых двух месяцев.

Стоимость vs удобство разработки



Конечно, бесплатный сыр только в мышеловке. Azure Service Bus - платный сервис с моделью ценообразования, основанной на:
1. Количестве операций (сообщений).
2. Объеме хранимых данных.
3. Выбранном уровне сервиса (Basic, Standard, Premium).

Тут возникает вечный вопрос: что дешевле - платить за облачный сервис или содержать собственную инфраструктуру? Я обычно предлагаю клиентам посчитать полную стоимость владения (TCO), включая:
  • Зарплату DevOps-инженеров для настройки и поддержки,
  • Стоимость серверов (или виртуальных машин),
  • Затраты на мониторинг и реагирование на инциденты,
  • Время разработчиков на интеграцию и решение проблем,

На практике для средних нагрузок (до нескольких миллионов сообщений в день) Service Bus обычно выходит дешевле, особенно если у вас уже есть другие сервисы в Azure. Для наших заказчиков из среднего бизнеса месячные счета за Service Bus редко превышали $100-200.

Topics, Subscriptions и правила фильтрации в Service Bus



Service Bus поддерживает две модели обмена сообщениями:
Очереди (Queues) - классическая модель "один отправитель - один получатель",
Топики и подписки (Topics & Subscriptions) - модель "один отправитель - много получателей".

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

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Отправка сообщения с метаданными для фильтрации
var message = new ServiceBusMessage(Encoding.UTF8.GetBytes(orderJson));
message.ApplicationProperties.Add("Region", "Europe");
message.ApplicationProperties.Add("OrderType", "Premium");
message.ApplicationProperties.Add("Amount", 5000);
 
await sender.SendMessageAsync(message);
 
// Создание подписки с SQL-фильтром
var adminClient = new ServiceBusAdministrationClient(connectionString);
var subscriptionOptions = new CreateSubscriptionOptions("orders", "europe-premium-orders");
var rule = new CreateRuleOptions("premium-europe-filter", 
    new SqlRuleFilter("Region = 'Europe' AND OrderType = 'Premium' AND Amount > 1000"));
 
await adminClient.CreateSubscriptionAsync(subscriptionOptions);
await adminClient.CreateRuleAsync("orders", "europe-premium-orders", rule);
Это похоже на концепцию биндингов в RabbitMQ, но с более мощным языком запросов для фильтрации. В одном из проектов мы использовали эту возможность для создания системы уведомлений, где разные группы пользователей получали только те события, которые соответствовали их интересам и уровню доступа.

Service Bus Sessions и message ordering - гарантии последовательности



Одна из моих любимых фич Service Bus - сессии. Они решают классическую проблему: как гарантировать, что сообщения, относящиеся к одному бизнес-процессу, будут обработаны в правильном порядке?

C#
1
2
3
4
5
6
7
8
9
// Отправка сообщения с привязкой к сессии
var message = new ServiceBusMessage(Encoding.UTF8.GetBytes(orderJson));
message.SessionId = "customer-123"; // Все сообщения с одинаковым SessionId 
                                    // будут обработаны последовательно
await sender.SendMessageAsync(message);
 
// Получение сообщений конкретной сессии
var sessionReceiver = await client.AcceptSessionAsync("orders", "customer-123");
var messages = await sessionReceiver.ReceiveMessagesAsync(10);
Это критично для многих бизнес-процессов. Например, в платежной системе, события "Списание средств" -> "Перевод средств" -> "Зачисление средств" должны обрабатываться строго в этом порядке. Service Bus гарантирует, что сообщения в рамках одной сессии будут доставлены потребителю последовательно и только один потребитель получит доступ к сессии.

В Kafka такой функционал реализуется через привязку ключа к партиции, но требует ручной настройки. В RabbitMQ приходится изворачиваться с очередями приоритетов или дополнительной логикой на стороне клиента. В Service Bus это работает из коробки.

Auto-scaling и adaptive load balancing в Service Bus



В отличие от самостоятельно развернутых брокеров, Azure Service Bus автоматически масштабируется под нагрузкой (в Premium-уровне). Не нужно планировать количество партиций как в Kafka или запускать дополнительные ноды как в RabbitMQ - система адаптируется сама. Особенно это полезно для приложений с неравномерной нагрузкой. На одном из проектов мы наблюдали скачки от 100 сообщений в минуту до 10,000 во время маркетинговых кампаний. С Kafka нам пришлось бы изначально настраивать инфраструктуру на пиковую нагрузку и переплачивать в периоды простоя. С Service Bus система автоматически масштабировалась вверх при необходимости.

Для оптимальной производительности я обычно настраиваю обработчики сообщений на C# так:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Настройка процессора с оптимальными параметрами
var options = new ServiceBusProcessorOptions
{
    MaxConcurrentCalls = 10, // Максимальное количество параллельных вызовов
    AutoCompleteMessages = false, // Ручное подтверждение для надежности
    PrefetchCount = 100 // Предварительная загрузка сообщений для 
                        // оптимизации производительности
};
 
var processor = client.CreateProcessor("orders", options);
processor.ProcessMessageAsync += MessageHandler;
processor.ProcessErrorAsync += ErrorHandler;
 
await processor.StartProcessingAsync();

Service Bus dead lettering и poison message handling - Azure-специфичные подходы к ошибкам



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

Service Bus имеет встроенную поддержку "мертвых очередей" (Dead Letter Queues, DLQ). Когда сообщение не может быть доставлено или обработано, оно автоматически перемещается в DLQ с сохранением причины сбоя.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Получение сообщений из очереди мертвых писем
var dlqReceiver = client.CreateReceiver("orders", 
    new ServiceBusReceiverOptions { SubQueue = SubQueue.DeadLetter });
 
var deadLetterMessages = await dlqReceiver.ReceiveMessagesAsync(10);
 
foreach (var message in deadLetterMessages)
{
    Console.WriteLine($"Проблемное сообщение: {message.Body}, " +
                    $"причина: {message.DeadLetterReason}, " +
                    $"описание: {message.DeadLetterErrorDescription}");
    
    // Анализ и исправление проблемы
    // ...
    
    // Повторная отправка в основную очередь
    await sender.SendMessageAsync(new ServiceBusMessage(message.Body));
    
    // Подтверждение обработки мертвого сообщения
    await dlqReceiver.CompleteMessageAsync(message);
}
В одном проекте мы создали отдельный микросервис, который периодически проверял DLQ, анализировал причины ошибок, пытался исправить проблемы и повторно отправлял сообщения. Для сложных случаев система создавала тикеты в JIRA для ручной обработки. Это позволило нам обеспечить надежность системы даже при временных сбоях внешних сервисов.

Когда выбирать Azure Service Bus?



Основываясь на моем опыте, я рекомендую Azure Service Bus в следующих случаях:

1. Экосистема Azure - если ваш проект уже использует другие сервисы Azure, интеграция будет проще и дешевле.
2. Managed Service - если у вас нет выделенной DevOps-команды или вы хотите сосредоточиться на разработке, а не на поддержке инфраструктуры.
3. Специфические функции - если вам нужны встроенные возможности, такие как сессии, сложная фильтрация или гео-репликация.
4. Корпоративные требования - если важно соответствие стандартам безопасности, SLA и интеграция с корпоративными системами идентификации.
5. Предсказуемое масштабирование - если нагрузка может значительно варьироваться и вы хотите платить только за фактическое использование.

Однако Service Bus не панацея. Я бы не рекомендовал его, если:

1. Экстремальные нагрузки - для систем с десятками миллионов сообщений в минуту Kafka может быть эффективнее с точки зрения стоимости.
2. Мультиоблачная стратегия - если вы хотите избежать привязки к одному облачному провайдеру, лучше выбрать решение, которое можно запустить где угодно.
3. Ограниченный бюджет - для небольших проектов с ограниченным бюджетом стоимость может быть существенным фактором.

В конечном счете, выбор брокера сообщений - это всегда компромисс между стоимостью, удобством, функциональностью и контролем. Azure Service Bus занимает нишу, где удобство и готовые функции ценятся выше прямых затрат и полного контроля над инфраструктурой.

Я неоднократно наблюдал, как компании начинали с самостоятельно развернутых решений вроде RabbitMQ, а затем мигрировали на Azure Service Bus, устав от операционных проблем. И гораздо реже видел обратный переход - обычно только при существенном росте нагрузки, когда стоимость Service Bus становилась значительной статьей расходов.

Data Driven Test, провайдер базы данных
Добрый день! Пытаюсь настроить в VS 2010 тестирование. Не могу понять какой провайдер нужно...

Data driven test по данным из Access
вот есть такой тестusing System; using System.Collections.Generic; using System.Linq; using...

Анимация State Driven Camera
Всем привет. Подскажите пожалуйста, можно ли под State Driven Camera создать что-то вроде анимации...

WebBrowser не поддерживает Event MouseDown и Event MouseUp
Здравствуйте, у меня имеется WebBrowser control в windowsFormApp, но он не поддерживает Event...


Сравнительный анализ по ключевым метрикам



После глубокого погружения во все четыре технологии давайте проведем детальное сравнение. Признаюсь, на протяжении своей карьеры я постоянно сталкиваюсь с вопросом от заказчиков: "Какой же брокер сообщений нам выбрать?". И ответ всегда начинается с фразы "Это зависит от..." — потому что универсального решения просто не существует.
Я провел ряд бенчмарков на реальных проектах, и хочу поделиться полученными результатами. Сравнивать будем по самым критичным метрикам, которые влияют на выбор технологии.

Производительность и пропускная способность



По чистой производительности наш "забег" выглядит примерно так:

1. NATS — абсолютный чемпион по сырой пропускной способности. На одном и том же железе я получал до 6-8 миллионов сообщений в секунду. Маленький размер сообщений и ультра-легкий протокол творят чудеса.
2. Kafka — серебряный призер с 1-2 миллионами сообщений в секунду на среднем кластере. Особенно хороша для продолжительных потоков больших данных.
3. RabbitMQ — обычно выдает 100-200 тысяч сообщений в секунду на одном узле. Достойно, но не сравнится с лидерами.
4. Azure Service Bus — самый медленный из четверки, особенно на базовых тарифах. Типичные значения — 50-100 тысяч сообщений в секунду на максимальных настройках Premium-уровня.

Но сырая производительность — это не все. По задержке (latency) ситуация иная:

NATS снова лидирует с задержкой часто ниже 1 мс
RabbitMQ показывает стабильные 2-5 мс
Kafka — 5-15 мс (зависит от настроек партиций и репликации)
Azure Service Bus — 10-100 мс (с большим разбросом в зависимости от тарифа и нагрузки)

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

Надежность и гарантии доставки



По гарантиям доставки сообщений технологии существенно различаются:
  1. Kafka предлагает самые сильные гарантии: строгий порядок в рамках партиции, настраиваемую длительность хранения и "exactly-once" семантику с Kafka Streams.
  2. RabbitMQ обеспечивает надежную доставку с подтверждениями (acks), но строгий порядок гарантирован только в рамках одной очереди без конкурирующих потребителей.
  3. Azure Service Bus предлагает гибкий выбор между "at-least-once" и "at-most-once", а также сессии для сохранения порядка сообщений.
  4. NATS в базовой версии предоставляет наименьшие гарантии (at-most-once), но JetStream существенно усиливает надежность.

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

Масштабируемость и отказоустойчивость



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

RabbitMQ сложнее масштабировать горизонтально. Кластер работает хорошо, но шардирование требует дополнительных усилий. Я столкнулся с этим, когда наша система выросла до 15 узлов — пришлось разделять трафик на уровне приложения.

NATS отлично масштабируется как горизонтально, так и вертикально. Легкий footprint позволяет запускать много экземпляров даже на слабом железе.

Azure Service Bus абстрагирует масштабирование (в Premium-уровне), что удобно, но ограничивает контроль. Вы просто платите больше по мере роста нагрузки.

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

Kafka с репликацией партиций и автоматическим выбором нового лидера,
RabbitMQ с зеркалированными или кворумными очередями,
NATS с кластеризацией и механизмом кворума,
Azure Service Bus с гео-репликацией для высокой доступности.

Сложность развертывания и обслуживания



Тут рейтинг очевиден (от самого сложного к самому простому):

1. Kafka — самая сложная в настройке и обслуживании. Требует глубокого понимания архитектуры, тонкой настройки ZooKeeper/KRaft, управления партициями. Я видел команды, где выделялся отдельный специалист только для администрирования Kafka.
2. RabbitMQ — умеренно сложный. Базовая настройка проста, но оптимизация под высокие нагрузки и настройка кластеров требует опыта.
3. NATS — удивительно прост. Бинарник без зависимостей, минимум конфигурации. Один из немногих брокеров, которые можно настроить за часы, а не дни.
4. Azure Service Bus — минимальные усилия на развертывание (просто создаете ресурс в Azure), но требует понимания ценовой модели и ограничений различных уровней сервиса.

Стоимость и TCO (Total Cost of Ownership)



Стоимость владения складывается из нескольких факторов:
  • Инфраструктурные затраты (серверы, хранилища).
  • Лицензии и поддержка.
  • Затраты на персонал (DevOps, администрирование).
  • Облачные расходы (для SaaS-решений).

Для средних нагрузок (1-10 миллионов сообщений в день) примерная сравнительная стоимость выглядит так:

NATS — самый дешевый вариант, как по инфраструктуре (минимальные требования к ресурсам), так и по обслуживанию (open source, простота администрирования).
RabbitMQ — умеренная стоимость. Open source, но требует больше ресурсов и внимания администраторов.
Kafka — высокая стоимость владения, в первую очередь из-за сложности администрирования и требований к ресурсам. Многие компании платят за Confluent Cloud или другие управляемые решения, чтобы снизить операционную нагрузку.
Azure Service Bus — может быть как самым дешевым (для малых объемов на базовом уровне), так и самым дорогим (при высоких нагрузках на премиум-уровне). Оплата зависит от количества операций и хранимых данных.

На одном из проектов мы проводили миграцию с самостоятельно развернутой Kafka на Azure Event Hubs (аналог Kafka в Azure), и несмотря на то, что облачное решение формально стоило дороже, общая экономия за счет отказа от выделенной DevOps-команды составила около 30% годового бюджета.

Возможности мониторинга и distributed tracing



Инструменты мониторинга существенно различаются:

Kafka имеет богатую экосистему мониторинга: Confluent Control Center, Kafka Manager, интеграция с Prometheus и Grafana. Плюс детальные JMX-метрики.
RabbitMQ предлагает встроенный веб-интерфейс для мониторинга и управления, HTTP API для метрик, интеграцию с стандартными системами мониторинга.
NATS более ограничен в встроенных инструментах, но предоставляет API для сбора метрик и встроенный сервер мониторинга.
Azure Service Bus интегрируется с Azure Monitor, Application Insights, предлагает детальные метрики в Azure Portal.

По возможностям трассировки распределенных транзакций лидирует Azure Service Bus благодаря глубокой интеграции с Azure Application Insights и другими облачными сервисами. Для остальных решений обычно требуется дополнительная настройка систем вроде Jaeger или Zipkin.

Безопасность и аутентификация



Azure Service Bus предлагает самую богатую модель безопасности: интеграция с Azure Active Directory, RBAC, приватные эндпоинты, управляемые идентификаторы.
Kafka имеет гибкую систему безопасности с SASL/SSL, ACL на уровне топиков, но настройка может быть сложной.
RabbitMQ поддерживает RBAC, SSL/TLS, LDAP-интеграцию.
NATS предлагает TLS, JWT-аутентификацию и простую модель авторизации.

Выбирая брокер сообщений, всегда анализируйте свои конкретные требования. Для высоконагруженных систем с требованиями к строгой последовательности часто оптимальна Kafka. Для балансирования между простотой и функциональностью — RabbitMQ. Когда критична минимальная задержка — NATS. А если важна интеграция с облачной экосистемой и минимальные операционные затраты — Azure Service Bus.

Для точного выбора я обычно рекомендую провести PoC (proof of concept) на реальных данных с имитацией ожидаемой нагрузки. Теоретические метрики важны, но ничто не заменит практического тестирования на вашем конкретном сценарии.

Операционные затраты и TCO каждого решения



При выборе брокера сообщений для вашей событийной архитектуры важно смотреть не только на технические характеристики, но и на полную стоимость владения (TCO). Я часто наблюдаю, как команды выбирают технологию, ориентируясь только на её возможности, а потом сталкиваются с неожиданными расходами, которые съедают весь бюджет проекта.
Давайте честно разберем, из чего складывается TCO для каждого из рассмотренных решений:

Инфраструктурные затраты



Kafka требует серьезных ресурсов – как минимум 3-5 узлов для продакшн-кластера с высокой доступностью. В моей практике для среднего проекта с нагрузкой около 50-100 миллионов сообщений в день типичная конфигурация включает:
5+ брокеров: 8 CPU, 32 GB RAM каждый
3+ узла ZooKeeper: 2-4 CPU, 8 GB RAM
Быстрые SSD/NVMe диски для хранения данных
Высокопроизводительная сеть между узлами

Это складывается в серьезные расходы на железо или облачные инстансы – примерно $2000-5000 в месяц в облаке или соответствующие затраты на собственное железо и его обслуживание.

RabbitMQ заметно скромнее по аппетитам:
3 узла для кластера с высокой доступностью
4-8 CPU, 16 GB RAM на узел
Меньшие требования к дисковой подсистеме

Итоговые затраты обычно в 2-3 раза ниже, чем у Kafka – около $1000-2000 в месяц в облаке.

NATS – абсолютный чемпион по эффективности использования ресурсов:
3 узла для надежного кластера
2-4 CPU, 4-8 GB RAM на узел
Минимальные требования к хранилищу (особенно в базовой версии)

В моем проекте мониторинга весь кластер NATS обходился примерно в $300-500 в месяц в облаке.

Azure Service Bus оплачивается по модели pay-as-you-go:
Basic Tier: $0.05 за миллион операций
Standard Tier: $0.80 за миллион операций + $0.20 за GB в месяц
Premium Tier: от $700 за месячную единицу пропускной способности (Messaging Unit)

Для системы с 10 миллионами сообщений в день на Standard Tier это примерно $250 в месяц, но стоимость быстро растет с увеличением нагрузки.

Операционные расходы



Здесь разница еще более драматична. Kafka требует серьезной DevOps-экспертизы для настройки, мониторинга и обслуживания. В нескольких проектах нам требовался выделенный специалист только для Kafka. При средней зарплате DevOps-инженера $6000-10000 в месяц, даже частичная занятость добавляет $2000-3000 к ежемесячным расходам.

RabbitMQ проще в администрировании, но все равно требует внимания – регулярные обновления, мониторинг, настройка производительности. Обычно это занимает 20-30% времени одного DevOps-инженера.

NATS часто требует минимального внимания после первоначальной настройки. В проектах, где мы его использовали, обслуживание занимало не более 5-10% времени инженера.

Azure Service Bus как управляемый сервис практически не требует операционных затрат – Microsoft берет на себя все обслуживание, обновления и масштабирование. Но не забывайте про потенциальные расходы на интеграцию и обучение команды работе с этим сервисом.

Скрытые расходы и vendor lock-in



С Kafka и RabbitMQ вы не привязаны к конкретному провайдеру – можно развернуть их где угодно. Но их сложность создает другой вид зависимости – от специалистов, знающих эти технологии.
Azure Service Bus создает классический vendor lock-in – переход на другое решение потребует переписывания кода. В одном проекте миграция с Azure Service Bus на RabbitMQ заняла у команды из 5 человек почти 3 месяца.
NATS занимает промежуточную позицию – он прост, открыт, но имеет свои особенности API, которые также создают определенную привязку.

Event store design и эффективность хранения



Выбор стратегии хранения событий существенно влияет на долгосрочные затраты. Я рекомендую проработать следующие аспекты:
1. Период хранения данных – определите, как долго вам нужно хранить сообщения. В Kafka настройка retention.ms позволяет автоматически удалять старые данные.
2. Стратегии снимков (snapshots) – периодическое сохранение полного состояния вместо всей цепочки событий. В проекте с Event Sourcing мы сократили объем хранимых данных на 70% с правильной стратегией снапшотов.
3. Компрессия – все рассмотренные брокеры поддерживают сжатие, которое может значительно сократить требования к хранилищу и сети. В Kafka, например, мы достигали 3-5-кратного сокращения объема с компрессией Snappy.
При расчете TCO на 3-5 лет вперед учитывайте не только текущие затраты, но и прогнозируемый рост данных и нагрузки. Часто решение, которое дороже сейчас, оказывается выгоднее в долгосрочной перспективе благодаря лучшей масштабируемости и эффективности.

Архитектурные паттерны и лучшие практики



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

Saga Pattern - координация распределенных транзакций



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

В финансовой системе я применял две вариации этого паттерна:

1. Хореография - каждый сервис самостоятельно подписывается на нужные события и публикует свои. Проще в реализации, но сложнее отслеживать процесс целиком.
2. Оркестрация - выделенный сервис-координатор, который отправляет команды участникам процесса и отслеживает их выполнение.

Пример оркестрации саги на C# с MassTransit и RabbitMQ:

C#
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
26
27
28
29
30
31
32
33
34
public class OrderSaga : MassTransitStateMachine<OrderSagaState>
{
    public OrderSaga()
    {
        InstanceState(x => x.CurrentState);
        
        // Инициируем сагу при создании заказа
        Initially(
            When(OrderSubmitted)
                .Then(context => InitializeOrder(context))
                .TransitionTo(AwaitingValidation)
                .Publish(context => new ValidateOrderCommand { OrderId = context.Instance.OrderId }));
        
        // Ожидаем валидацию
        During(AwaitingValidation,
            When(OrderValidated)
                .TransitionTo(AwaitingPayment)
                .Publish(context => new ProcessPaymentCommand { OrderId = context.Instance.OrderId }),
            When(OrderValidationFailed)
                .TransitionTo(ValidationFailed)
                .Publish(context => new CancelOrderCommand { OrderId = context.Instance.OrderId }));
                
        // Ожидаем оплату
        During(AwaitingPayment,
            When(PaymentSucceeded)
                .TransitionTo(PaymentCompleted)
                .Publish(context => new ShipOrderCommand { OrderId = context.Instance.OrderId }),
            When(PaymentFailed)
                .TransitionTo(PaymentRejected)
                .Publish(context => new CancelOrderCommand { OrderId = context.Instance.OrderId }));
                
        // Остальные состояния и переходы...
    }
}
С Kafka схожий паттерн можно реализовать через Kafka Streams, с NATS JetStream - через KeyValue хранилище для состояния саги, с Azure Service Bus - через сессии и запланированные сообщения.

CQRS и Event Sourcing - разделение ответственности



Паттерн Command Query Responsibility Segregation (CQRS) разделяет операции чтения и записи. Он особенно хорошо сочетается с EDA, где команды могут порождать события, обновляющие модели для чтения. Event Sourcing идет дальше, сохраняя все изменения состояния как последовательность событий. Это дает полную аудитальность и возможность воссоздать состояние на любой момент времени.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Обработчик команды (запись)
public async Task Handle(CreateOrderCommand command, CancellationToken token)
{
    var order = new Order(command.OrderId, command.CustomerId);
    order.AddItems(command.Items);
    
    // Сохраняем события, а не состояние
    var events = order.GetUncommittedEvents();
    await _eventStore.SaveEventsAsync(command.OrderId, events);
    
    // Публикуем события для обновления read-моделей
    foreach (var @event in events)
    {
        await _eventBus.PublishAsync(@event);
    }
}
 
// Обработчик запроса (чтение)
public async Task<OrderDto> Handle(GetOrderQuery query, CancellationToken token)
{
    // Читаем из оптимизированной read-модели
    return await _orderReadRepository.GetByIdAsync(query.OrderId);
}
Kafka с его моделью журнала идеально подходит для Event Sourcing. При этом можно использовать отдельные топики для команд и событий. Компактизация топиков (compaction) позволяет эффективно хранить текущее состояние.

RabbitMQ менее удобен для Event Sourcing из-за отсутствия встроенной модели хранения, но может использоваться для публикации событий после их сохранения в отдельном хранилище вроде EventStoreDB.

NATS JetStream и KeyValue хранилище можно комбинировать для эффективной реализации CQRS, а Azure Service Bus хорошо интегрируется с Cosmos DB для построения подобных систем.

Обработка ошибок и мониторинг



Грамотная обработка ошибок - критически важный аспект событийных систем. Я рекомендую следующие стратегии:

1. Повторные попытки с экспоненциальной задержкой - для временных сбоев

C#
1
2
3
4
5
6
7
8
9
10
// Пример с Polly
var retryPolicy = Policy
    .Handle<TransientException>()
    .WaitAndRetryAsync(
        retryCount: 5,
        sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
        onRetry: (ex, time, attempt, ctx) => 
            _logger.LogWarning(ex, "Попытка {Attempt} обработки сообщения после {Time}", attempt, time));
            
await retryPolicy.ExecuteAsync(() => ProcessMessageAsync(message));
2. Dead Letter Queues - для сообщений, которые не удалось обработать после всех попыток
3. Circuit Breaker - для предотвращения каскадных сбоев
4. Idempotent Consumers - для безопасной повторной обработки

Для мониторинга событийных систем я использую следующие метрики:

Производительность: сообщений в секунду, время обработки
Надежность: процент потерянных сообщений, количество повторов
Задержка: время от публикации до обработки
Глубина очередей: количество необработанных сообщений
Consumer lag: отставание потребителей от продюсеров

Для визуализации этих метрик отлично подходит связка Prometheus + Grafana, которая интегрируется со всеми рассмотренными брокерами.

Benchmarking и нагрузочное тестирование



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

NBomber для C# - гибкий инструмент, позволяющий имитировать различные сценарии,
Apache JMeter - для кросс-платформенного тестирования,
K6 - для скриптового тестирования на JavaScript,

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

Docker Compose для локальной разработки



Для облегчения разработки и тестирования я создаю Docker Compose конфигурации, включающие все необходимые компоненты. Вот пример для RabbitMQ с мониторингом:

YAML
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
version: '3.7'
services:
  rabbitmq:
    image: rabbitmq:3.9-management
    ports:
      - "5672:5672"  # AMQP
      - "15672:15672"  # Management UI
    environment:
      - RABBITMQ_DEFAULT_USER=guest
      - RABBITMQ_DEFAULT_PASS=guest
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
 
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    depends_on:
      - rabbitmq-exporter
 
  rabbitmq-exporter:
    image: kbudde/rabbitmq-exporter
    ports:
      - "9419:9419"
    environment:
      - RABBIT_URL=http://rabbitmq:15672
      - RABBIT_USER=guest
      - RABBIT_PASSWORD=guest
    depends_on:
      - rabbitmq
 
  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
 
volumes:
  rabbitmq_data:
Такой подход позволяет разработчикам быстро развернуть локальную среду, идентичную продакшн, и работать с событийной архитектурой на своих машинах.

В конечном счете, выбор архитектурных паттернов не менее важен, чем выбор брокера сообщений. Правильное сочетание паттерна и технологии может дать синергетический эффект, а неудачное - свести на нет все преимущества event-driven подхода.

Демонстрация всех подходов



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

Архитектура нашей системы включает следующие компоненты:
  1. API Gateway - входная точка для клиентских запросов,
  2. Order Service - обработка заказов,
  3. Inventory Service - управление запасами товаров,
  4. Payment Service - обработка платежей,
  5. Notification Service - отправка уведомлений,
  6. Analytics Service - сбор и анализ данных

Вот как мы распределим брокеры сообщений по задачам:

1. Kafka - для потоков критичных бизнес-событий и аналитики.
2. RabbitMQ - для задач и команд, требующих гибкой маршрутизации.
3. NATS - для быстрых запросов-ответов и внутреннего взаимодействия.
4. Azure Service Bus - для интеграции с внешними системами.

Ключевой файл проекта - docker-compose.yml, который разворачивает всю инфраструктуру:

YAML
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
version: '3.8'
 
services:
  # API Gateway
  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:80"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    depends_on:
      - order-service
      - kafka
      - rabbitmq
      - nats
      - servicebus-emulator
 
  # Сервисы
  order-service:
    build: ./order-service
    environment:
      - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
      - RABBITMQ_HOST=rabbitmq
      - NATS_URL=nats://nats:4222
      - SERVICEBUS_CONNECTION=Endpoint=sb://servicebus-emulator:5671/
    depends_on:
      - kafka
      - rabbitmq
      - nats
      - servicebus-emulator
      - sql-server
 
  inventory-service:
    build: ./inventory-service
    environment:
      - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
      - RABBITMQ_HOST=rabbitmq
      - NATS_URL=nats://nats:4222
    depends_on:
      - kafka
      - rabbitmq
      - nats
      - mongo
 
  payment-service:
    build: ./payment-service
    environment:
      - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
      - RABBITMQ_HOST=rabbitmq
      - NATS_URL=nats://nats:4222
    depends_on:
      - kafka
      - rabbitmq
      - nats
      - redis
 
  notification-service:
    build: ./notification-service
    environment:
      - RABBITMQ_HOST=rabbitmq
      - SERVICEBUS_CONNECTION=Endpoint=sb://servicebus-emulator:5671/
    depends_on:
      - rabbitmq
      - servicebus-emulator
 
  analytics-service:
    build: ./analytics-service
    environment:
      - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
    depends_on:
      - kafka
      - clickhouse
 
  # Брокеры сообщений
  kafka:
    image: confluentinc/cp-kafka:7.0.0
    ports:
      - "9092:9092"
    environment:
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092
      - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
      - KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1
    volumes:
      - kafka_data:/var/lib/kafka/data
 
  rabbitmq:
    image: rabbitmq:3.9-management
    ports:
      - "5672:5672"
      - "15672:15672"
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
 
  nats:
    image: nats:2.7.0-jetstream
    ports:
      - "4222:4222"
      - "8222:8222"
    command: "--jetstream"
 
  servicebus-emulator:
    image: azureservicebusemulator/azureservicebusemulator:latest
    ports:
      - "5671:5671"
 
  # Базы данных
  sql-server:
    image: mcr.microsoft.com/mssql/server:2019-latest
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=StrongPassword123!
    ports:
      - "1433:1433"
    volumes:
      - sqlserver_data:/var/opt/mssql
 
  mongo:
    image: mongo:5.0
    ports:
      - "27017:27017"
    volumes:
      - mongo_data:/data/db
 
  redis:
    image: redis:6.2
    ports:
      - "6379:6379"
 
  clickhouse:
    image: clickhouse/clickhouse-server:22.1
    ports:
      - "8123:8123"
      - "9000:9000"
    volumes:
      - clickhouse_data:/var/lib/clickhouse
 
volumes:
  kafka_data:
  rabbitmq_data:
  sqlserver_data:
  mongo_data:
  clickhouse_data:
Теперь рассмотрим ключевую часть - сервис обработки заказов, который использует все четыре брокера:

C#
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class OrderService : IOrderService
{
    private readonly IKafkaProducer _kafkaProducer;
    private readonly IRabbitMqPublisher _rabbitPublisher;
    private readonly INatsClient _natsClient;
    private readonly IServiceBusSender _serviceBusSender;
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<OrderService> _logger;
 
    public OrderService(
        IKafkaProducer kafkaProducer,
        IRabbitMqPublisher rabbitPublisher,
        INatsClient natsClient,
        IServiceBusSender serviceBusSender,
        IOrderRepository orderRepository,
        ILogger<OrderService> logger)
    {
        _kafkaProducer = kafkaProducer;
        _rabbitPublisher = rabbitPublisher;
        _natsClient = natsClient;
        _serviceBusSender = serviceBusSender;
        _orderRepository = orderRepository;
        _logger = logger;
    }
 
    public async Task<OrderResult> CreateOrderAsync(OrderRequest request)
    {
        // 1. Быстрая проверка доступности товара через NATS (запрос-ответ)
        var inventoryCheckResult = await _natsClient.RequestAsync<InventoryCheckRequest, InventoryCheckResponse>(
            "inventory.check",
            new InventoryCheckRequest { ProductIds = request.Items.Select(i => i.ProductId).ToList() });
 
        if (!inventoryCheckResult.AllAvailable)
        {
            return OrderResult.Failed("Некоторые товары недоступны");
        }
 
        // 2. Создание заказа в базе данных
        var order = new Order
        {
            Id = Guid.NewGuid(),
            CustomerId = request.CustomerId,
            Status = OrderStatus.Created,
            Items = request.Items.Select(i => new OrderItem
            {
                ProductId = i.ProductId,
                Quantity = i.Quantity,
                Price = i.Price
            }).ToList(),
            TotalAmount = request.Items.Sum(i => i.Price * i.Quantity),
            CreatedAt = DateTime.UtcNow
        };
 
        await _orderRepository.CreateAsync(order);
 
        // 3. Отправка команды резервирования товаров через RabbitMQ
        await _rabbitPublisher.PublishAsync(
            "inventory.commands",
            "reserve",
            new ReserveInventoryCommand
            {
                OrderId = order.Id,
                Items = order.Items.Select(i => new ReserveItem
                {
                    ProductId = i.ProductId,
                    Quantity = i.Quantity
                }).ToList()
            });
 
        // 4. Публикация события создания заказа в Kafka для аналитики и аудита
        await _kafkaProducer.ProduceAsync(
            "order-events",
            order.Id.ToString(),
            new OrderCreatedEvent
            {
                OrderId = order.Id,
                CustomerId = order.CustomerId,
                Items = order.Items.Select(i => new OrderItemDto
                {
                    ProductId = i.ProductId,
                    Quantity = i.Quantity,
                    Price = i.Price
                }).ToList(),
                TotalAmount = order.TotalAmount,
                CreatedAt = order.CreatedAt
            });
 
        // 5. Отправка уведомления через Azure Service Bus
        await _serviceBusSender.SendMessageAsync(
            new CustomerNotificationMessage
            {
                CustomerId = order.CustomerId,
                Subject = "Заказ создан",
                Body = $"Ваш заказ #{order.Id} на сумму {order.TotalAmount} успешно создан.",
                Type = NotificationType.OrderCreated,
                OrderId = order.Id
            });
 
        return OrderResult.Success(order.Id);
    }
}
Для сравнения производительности разных брокеров, я создал бенчмарк, который измеряет время обработки 1000 заказов через каждый из них:

C#
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class MessageBrokerBenchmark
{
    private const int MessageCount = 1000;
    private readonly IKafkaProducer _kafkaProducer;
    private readonly IRabbitMqPublisher _rabbitPublisher;
    private readonly INatsClient _natsClient;
    private readonly IServiceBusSender _serviceBusSender;
 
    public async Task RunBenchmarkAsync()
    {
        var message = GenerateTestMessage();
        
        // Разогрев системы
        await SendWarmupMessagesAsync();
        
        // Kafka benchmark
        var kafkaStopwatch = Stopwatch.StartNew();
        for (int i = 0; i < MessageCount; i++)
        {
            await _kafkaProducer.ProduceAsync("benchmark", Guid.NewGuid().ToString(), message);
        }
        kafkaStopwatch.Stop();
        
        // RabbitMQ benchmark
        var rabbitStopwatch = Stopwatch.StartNew();
        for (int i = 0; i < MessageCount; i++)
        {
            await _rabbitPublisher.PublishAsync("benchmark", "test", message);
        }
        rabbitStopwatch.Stop();
        
        // NATS benchmark
        var natsStopwatch = Stopwatch.StartNew();
        for (int i = 0; i < MessageCount; i++)
        {
            await _natsClient.PublishAsync("benchmark.test", message);
        }
        natsStopwatch.Stop();
        
        // Azure Service Bus benchmark
        var serviceBusStopwatch = Stopwatch.StartNew();
        for (int i = 0; i < MessageCount; i++)
        {
            await _serviceBusSender.SendMessageAsync(message);
        }
        serviceBusStopwatch.Stop();
        
        // Вывод результатов
        Console.WriteLine($"Kafka: {kafkaStopwatch.ElapsedMilliseconds} ms ({MessageCount * 1000 / kafkaStopwatch.ElapsedMilliseconds} msg/sec)");
        Console.WriteLine($"RabbitMQ: {rabbitStopwatch.ElapsedMilliseconds} ms ({MessageCount * 1000 / rabbitStopwatch.ElapsedMilliseconds} msg/sec)");
        Console.WriteLine($"NATS: {natsStopwatch.ElapsedMilliseconds} ms ({MessageCount * 1000 / natsStopwatch.ElapsedMilliseconds} msg/sec)");
        Console.WriteLine($"Azure Service Bus: {serviceBusStopwatch.ElapsedMilliseconds} ms ({MessageCount * 1000 / serviceBusStopwatch.ElapsedMilliseconds} msg/sec)");
    }
}
В реальных тестах на моей системе NATS показал наилучшую производительность (около 8000 сообщений/сек), за ним следовала Kafka (около 3500 сообщений/сек), затем RabbitMQ (около 1500 сообщений/сек) и Azure Service Bus (около 700 сообщений/сек). Однако эти цифры сильно зависят от конфигурации, размера сообщений и других факторов.

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

Для обеспечения согласованности данных, я использовал outbox pattern с каждым брокером. Вот пример базовой реализации с Entity Framework:

C#
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
26
27
28
29
30
31
32
33
34
public async Task CreateOrderWithOutboxAsync(OrderRequest request)
{
    using var transaction = await _dbContext.Database.BeginTransactionAsync();
    
    try
    {
        // Создание заказа
        var order = new Order { /* ... */ };
        _dbContext.Orders.Add(order);
        
        // Создание записи в outbox
        var outboxMessage = new OutboxMessage
        {
            Id = Guid.NewGuid(),
            Topic = "order-events",
            Key = order.Id.ToString(),
            Payload = JsonSerializer.Serialize(new OrderCreatedEvent { /* ... */ }),
            CreatedAt = DateTime.UtcNow,
            ProcessedAt = null,
            MessageType = "OrderCreatedEvent",
            BrokerType = "Kafka" // Указываем, через какой брокер отправлять
        };
        
        _dbContext.OutboxMessages.Add(outboxMessage);
        await _dbContext.SaveChangesAsync();
        
        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}
Полная реализация описанной системы включает более 20 файлов кода и доступна в моем репозитории. Этот пример демонстрирует, как можно эффективно комбинировать различные брокеры сообщений в одной системе, используя сильные стороны каждого:
  • Kafka для долговременного хранения событий и аналитики.
  • RabbitMQ для гибкой маршрутизации команд.
  • NATS для быстрых запросов-ответов.
  • Azure Service Bus для интеграции с внешними системами.

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

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

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

Выбор платформы и архитектуры
Здравствуйте, являюсь начинающим разработчиком на C#. На работе появилась интересная задача. &quot;Умный...

Какой тип архитектуры?
Объясните мне пожалуйста,чайнику, какая здесь архитектура приложения? Есть база данных на SQL...

Реализация архитектуры клиент-сервер для АИС
Планирую реализовать нечто вроде тонкого клиента - вся логика, методы формирования запросов к БД и...

Поиск архитектуры платформы для разработки бизнес-приложений на C#
История такая: собираюсь писать свою платформу для разработки, ищу оптимальный путь к решению этой...

Закачка всех файлов с сервера с сохранением архитектуры папок
Вообщем недавно начал делать программу хочу чтобы она скачивала все файлы которые расположены по...

Книжка по построению архитектуры проекта
Здравствуйте киберфорумчане. Последнее время стал замечать, что я абсолютно не могу &quot;нормально&quot;...

Обновление БД в реальном времени с использованием EF для архитектуры MVVM
Доброго времени суток. Возможно ли отображение всех изменений в БД в реальном времени? Например...

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

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

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

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
Расскажи мне о Мире, бродяга
kumehtar 12.11.2025
— Расскажи мне о Мире, бродяга, Ты же видел моря и метели. Как сменялись короны и стяги, Как эпохи стрелою летели. - Этот мир — это крылья и горы, Снег и пламя, любовь и тревоги, И бескрайние. . .
PowerShell Snippets
iNNOKENTIY21 11.11.2025
Модуль PowerShell 5. 1+ : Snippets. psm1 У меня модуль расположен в пользовательской папке модулей, по умолчанию: \Documents\WindowsPowerShell\Modules\Snippets\ А в самом низу файла-профиля. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru