×Закрыть

Event-driven архитектура средствами Java EE

Event-driven архитектура (event-driven architecture, EDA) является архитектурным шаблоном, основанном на создании, подписке на события и реакции на них.

Зачем нужна событийно-ориентированная архитектура? Представим функциональность оформления заказа в интернет-магазине:

public void checkoutShoppingCart(ShoppingOrder order) {
    persistInDatabase(order);
    sendEmailNotification(order);
    shipToTheNearestWarehouse(order);
    scheduleShippingToCustomer(order);
    exportToERP(order);
}

Нетрудно заметить тенденцию, что метод checkoutShoppingCart становится неподдерживаемым беспорядком.

Растущая сложность enterprise приложений часто приводит к плохой архитектуре, и организация тратит больше и больше денег на создание IT систем. Event-driven архитектура призвана частично решить эту проблему путем снижения связанности компонентов ПО или сервисов. Целью event-driven архитектуры является построение систем, в которых компоненты слабо связаны (loosely coupled).

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

Шаблон Observer (наблюдатель) помогает понять концепцию event-driven архитектуры. В шаблоне Observer объект, который называют subject (субъект), содержит список объектов, которые называются observers (наблюдатели), и оповещает их о любом изменении состояния. Использование событий и наблюдателей позволяет сделать сервисы слабо связанными.

Стоит сказать, что называется слабой связанностью (loose coupling). Компоненты слабо связаны, если у них очень мало или вообще нет никакого знания друг о друге. Связанность относится к классам, интерфейсам, сервисам, компонентам ПО. Когда класс содержит ссылку на другой конкретный класс, который предоставляет определенный функционал, они связанны сильно (tightly coupled). Когда используются события и наблюдатели, класс, который создает событие (fire event), ничего не знает о классах, которые наблюдают за событиями и реагируют на них.

Event-driven архитектура помогает создавать высокопроизводительные и высокодоступные системы. Проектирование асинхронных от начала и до конца систем позволяет минимизировать время блокировок потоков на IO операциях и использовать пропускную способность сети на полную мощность. Все наблюдатели могут реагировать на оповещение о событии параллельно, заставляя многоядерные процессоры и кластеры работать на самой высокой мощности. Когда распределенная системы работает в кластере, события могут быть доставлены на любой узел кластера, предоставляя прозрачную балансировку нагрузки (load-balancing) и отказоустойчивость (failover).

События можно использовать и в Java EE CDI приложениях. Рассмотрим следующий пример.

Класс ShoppingOrderEvent определяет событие, используя свойства, у которых есть getter и setter методы.

private ShoppingOrder order;

/*...*/

public ShoppingOrderEvent() {
}

События обрабатываются, используя метод-наблюдатель.

public void persistInDatabase(@Observes ShoppingOrderEvent event) {
    /*...*/
}

public void sendEmailNotification(@Observes ShoppingOrderEvent event) {
    /*...*/
}

public void shipToTheNearestWarehouse(@Observes ShoppingOrderEvent event) {
    /*...*/
}

public void scheduleShippingToCustomer(@Observes ShoppingOrderEvent event) {
    /*...*/
}

public void exportToERP(@Observes ShoppingOrderEvent event) {
    /*...*/
}

Чтобы создать событие (fire event) и оповестить методы-наблюдатели, необходимо вызвать метод javax.enterprise.event.Event#fire(T).

@Inject
private Event<ShoppingOrderEvent> orderEvent;

public void checkoutShoppingCart(ShoppingOrder order) {
    ShoppingOrderEvent orderEventPayload = new ShoppingOrderEvent();
    /*...*/
    orderEvent.fire(orderEventPayload);
}

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

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

Говоря о event-driven архитектуре, стоит упомянуть о message-oriented middleware (MOM). Message-oriented middleware — это ПО, которое занимается отправкой и доставкой сообщений в распределенных системах. События могут быть представлены в виде сообщений. MOM иногда называют messaging система или message брокер. Популярные реализации MOM: ActiveMQ, HornetQ, Tibco EMS.

Messaging системы предоставляют протоколы или API для отправки и получения сообщений:
— JMS (Java Message Service);
— AMQP (Advanced Message Queuing Protocol);
— STOMP (Simple Text Oriented Messaging Protocol);
— RESTful API;
— Java API.

Messaging системы обычно поддерживают 2 стиля асинхронного обмена сообщениями:
— Point-to-Point;
— Publish-Subscribe.

Java Message Service (JMS) — это стандарт промежуточного По для обработки сообщений, который позволяет приложениям создавать, отправлять, получать и читать сообщения. JMS — это Java API, которое является частью спецификации Java EE.

Рассмотрим пример использования JMS 2.0.

Отправка сообщения, используя JMS выглядит следующим образом.

@Resource(mappedName = "java:jboss/jms/queue/exampleQueue")
private Queue exampleQueue;
@Inject
private JMSContext context;

/*...*/

public void sendMessage(String text) {
    context.createProducer().send(exampleQueue, text);
}

Синхронное получение сообщений в JMS:

@Resource(mappedName = "java:jboss/jms/queue/exampleQueue")
private Queue exampleQueue;
@Inject
private JMSContext context;

/*...*/

public String receiveMessage() {
    return context.createConsumer(exampleQueue)
                  .receiveBody(String.class);
}

Получение сообщений при помощи with message-driven bean (MDB), EJB который позволяет Java EE приложениям обрабатывать сообщения асинхронно.

@MessageDriven(name = "ExampleMDB", activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationLookup",
        propertyValue = "java:jboss/jms/queue/exampleQueue"),
    @ActivationConfigProperty(propertyName = "destinationType",
        propertyValue = "javax.jms.Queue"),
    @ActivationConfigProperty(propertyName = "acknowledgeMode",
        propertyValue = "Auto-acknowledge")})
public class ExampleMDB implements MessageListener {

    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                /*...*/
            }
        } catch (JMSException e) {
            throw new RuntimeException(e);
        }
    }
}

Шаблон Message Queue:
1) Сообщение отправляется в очередь;
2) Сообщение сохраняется, чтобы предоставить гарантию доставки;
3) Messaging система доставляет сообщение получателю;
4) Получатель обрабатывает и подтверждает получение (acknowledge) сообщения;
5) Сообщение удаляется из очереди, после чего больше недоступно для доставки;
6) Если система падает до того, как messaging система получает acknowledgement от получателя, то при восстановлении (recovery) сообщение снова будет доставлено получателю.

Шаблон Publish-Subscribe:
1) Сообщение отправляется в topic;
2) Каждая подписка получает копию каждого сообщения отправленного в topic;
3) Долговечные (durable) подписки получают все сообщения, отправленные в topic, даже если получатель был недоступен какое-то время;
4) Недолговечные (non durable) подписки получают только те сообщения, которые были отправлены, когда получатель был доступен.

Все JMS провайдеры предоставляют надежную доставку сообщений. Доставка сообщений осуществляется в два этапа:
— На первом этапе сообщение от отправителя попадает в место назначение на брокере;
— На втором этапе сообщение с брокера доставляется получателю.

Сообщение может быть потеряно на одном из трех участков:
— При передаче от отправителя на брокер;
— При передачи с брокера получателю;
— Пока оно находится в памяти брокера, и брокер упадет.

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

Для обеспечения надежной доставки используется 2 механизма: подтверждение получения (acknowledgment) сообщения и транзакции. Чтобы удостовериться, что сообщение было благополучно получено, messaging система сохраняет сообщения в постоянном хранилище, которое называется журналом. Если брокер упадет до того, как сообщение будет получено, его сохраненная копия будет повторно доставлена при восстановлении.

Сообщения бывают долговечными (durable) и недолговечными (non-durable). Долговечные сообщения будут сохранены и могут пережить падение или рестарт брокера. Недолговечные сообщения не переживут падение или рестарт брокера.

Если брокер упадет, доставка некоторых сообщений может быть не успешной. Такие сообщения возвращаются в JMS queue или topic и будут доставлены повторно.

Чтобы предотвратить засорение системы сообщениями, которые безуспешно доставляются снова и снова, messaging системы вводят концепцию dead letter queue (DLQ) или dead message queue (DMQ). После указанного числа неудачных попыток доставки, сообщение удаляется из очереди и отправляется в DLQ. Messaging систему также вводят концепцию повторной доставки с задержкой (delayed redelivery). Повторная доставка будет запланирована с определенной задержкой, которая может увеличиваться экспоненциально между попытками.

Время, на протяжении которого сообщения находятся в messaging системе, до того как будут удалены, может быть ограничено при помощи параметра timeToLive при отправке JMS сообщения или свойство priority класса MessageProducer. Когда срок хранения истекает, сообщения удаляются из очереди или topic’а и отправляются в expiry очередь (expiry queue).

По умолчанию messaging системы работают в режиме FIFO. Когда сообщениям явно устанавливается приоритет, JMS очередь будет работать как очередь с приоритетами (priority queue). Приоритет сообщения может использоваться для влияния на очередность доставки сообщений. Значение приоритета сообщения может принимать значения от 0 (минимальное) до 9 (максимальное). Сообщения с более высоким приоритетом будут доставлены раньше сообщений с более низким приоритетом. По умолчанию сообщения имеют приоритет 4.

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

Алгоритм может выглядеть следующим образом:
1) Отправить сообщение с определенным приоритетом в очередь, чтобы стартовать бизнес-процесс;
2) Сервис, который получает сообщения, должен выполнить соответствующие действия и в конце отправить еще одно сообщение в следующую очередь, чтобы продолжить бизнес-процесс, сохраняя оригинальный приоритет сообщения;
3) Если в очереди есть сообщения с более высоким приоритетом и вычислительных ресурсов недостаточно, сообщения с более высоким приоритетом будут обработаны раньше и только потом будут обработаны эти сообщения с более низким приоритетом;
4) Бизнес-процесс с низким приоритетом будет «приостановлен» чтобы дать возможность завершиться бизнес-процессу с высоким приоритетом.

Большинство messaging систем предоставляют возможность запланировать доставку сообщения. Это свойство будет полезно в случаях, когда:
— Бизнес-процесс не должен начаться сразу после отправки сообщения;
— Бизнес-процесс должен быть отменен после определенного timeout’а.

Пример запланированной доставки сообщения в HornetQ.

/*...*/
TextMessage message = session.createTextMessage(
    "This is a scheduled message message which will be delivered in 5 sec.");
message.setLongProperty("_HQ_SCHED_DELIVERY", System.currentTimeMillis() + 5000);
producer.send(message);
/*...*/
// message will not be received immediately but 5 seconds later
TextMessage messageReceived = (TextMessage) consumer.receive();
/*...*/

Чтобы отменить бизнес-процесс после определенного timeout’а достаточно отправить сообщение с максимальным приоритетом с запланированной доставкой после указанного timeout’а. Если получив сообщение соответствующее запросу на отмену бизнес-процесса, сам бизнес-процесс еще не завершился, будут предприняты соответствующие действия для его отмены.

Несмотря на то, что спецификации JMS существует с 2001 года, а в 2013 получила обновление до версии 2.0, она все еще имеет недостатки и ограничения. К таким недостаткам можно отнести отсутствие в стандарте возможности подтвердить получение (acknowledgment) индивидуально для каждого сообщения и в последовательности, отличной от той, в которой они были получены. В режиме CLIENT_ACKNOWLEDGE при вызове метода javax.jms.Message#acknowledge() будут подтверждено получение всех сообщений, полученных текущей сессией. ActiveMQ, Tibco EMS и другие реализации JMS предоставляют собственные acknowledgment режимы, которые не являются частью стандарта, и позволяют подтвердить получение каждого отдельного сообщения. Например, режим INDIVIDUAL_ACKNOWLEDGE в ActiveMQ, который без сомнений является очень полезным.

Еще одним недостатком стандарта JMS является отсутствие негативных подтверждений получения сообщения. В некоторых случаях, если сообщение вызвало исключение в процессе обработки, это сообщение не будет доставлено повторно брокером, пока оригинальная сессия получателя не отключится. Было бы намного удобнее, если бы спецификация предоставляла способ сделать негативное подтверждение получения (negative acknowledgment), чтобы сервер повторно доставил это сообщение той же или другой сессии. JMS предоставляет метод javax.jms.Session#recover(), который останавливает получение новых сообщений сессией и возвращает все сообщение, на которые не были отправлены оповещения о получении обратно на брокер для повторной доставки. Как и в случае с CLIENT_ACKNOWLEDGE, Session#recover() не позволяет вернуть конкретное сообщение на брокер. Например, messaging система RabbitMQ, которая является реализацией AMQP, позволяет сделать негативное подтверждение получения сообщения при помощи методов basic.reject и basic.nack.

JMS сессии могут участвовать в распределенных транзакциях. Отправка и получение сообщений может быть частью больших распределенных транзакций, которые включают операции с другими ресурсами, как база данных. Менеджер распределенных транзакций (transaction manager) должен использоваться для работы распределенных транзакций. Такие менеджеры транзакций предоставляются Java EE серверами приложений.

Распределенные транзакции с двухфазным commit’ом (two-phase commit, 2PC) также называются XA транзакциями. Поддержка распределенных транзакций означает, что клиенты могут участвовать в распределенных транзакциях, используя интерфейс XAResource, предоставляемый JTA. Этот интерфейс предоставляет набор методов, которые используются в реализации двухфазного commit’а. Фаза 1 — подготовка (prepare). Координатор транзакций просит всех участников транзакции пообещать сделать commit или rollback транзакции. Если какой-то из ресурсов не может подготовиться, транзакция откатывается. Фаза 2 — сommit или rollback. Если все участники ответили координатору, что они готовы (prepared), координатор просит все ресурсы сделать commit транзакции.
2PC делается автоматически менеджером транзакций, который обычно является частью Java EE сервера приложений, и не требует дополнительных усилий от рахработчика.

Best effort one-phase commit (best effort 1PC) — еще один подход для работы с распределенными транзакциями. Best effort 1PC шаблон синхронизирует однофазные commit’ы нескольких ресурсов. JMS транзакция начинается до транзакции базы данных, и они заканчиваются (commit или rollback) в противоположном порядке:
1) Начать транзакцию JMS;
2) Получить сообщение;
3) Начать транзакцию базы данных;
4) Обновить базу данных;
5) Сделать commit транзакции базы данных;
6) Сделать commit транзакции JMS.

Если commit базы данных падает, JMS транзакция откатывается. Если commit базы данных происходит успешно, но commit JMS транзакции падает, это приведет к повторному получению сообщения при повторной доставке (дубликат). Best effort 1PC может использоваться в системах, которые способны надлежащим образом обрабатывать дубликаты.

Пример best effort 1PC в Spring.

<!--...-->
<bean id="nonTransactionalConnectionFactory"
      class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter">
    <property name="targetConnectionFactory" ref="hornetQConnectionFactory"/>
    <property name="username" value="guest"/>
    <property name="password" value="guest"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy">
    <property name="targetConnectionFactory"
              ref="nonTransactionalConnectionFactory"/>
    <property name="synchedLocalTransactionAllowed" value="true"/>
</bean>
<!--...-->
<bean id="transactionManager" 
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSource"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>
<!--...-->
<jms:listener-container connection-factory="connectionFactory"
                        transaction-manager="transactionManager"
                        concurrency="10">
    <jms:listener destination="exampleQueue" ref="myListener"/>
</jms:listener-container>
<!--...-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="sessionTransacted" ref="true"/>
</bean>
<!--...-->

Общепринятой практикой является полагаться на повторную доставку сообщений (redelivery), когда откатывается распределенная транзакция. Если для бизнес-процесса допустимо повторное выполнение, обработка исключений в коде может быть пропущена. Вся распределенная транзакция может быть откачена, сообщение вернется в очередь и будет доставлено получателям, которые доступны в данный момент, для повторной обработки.

В данной статье мы рассмотрели, какие механизмы, предоставляемые Java EE, могут использоваться для создания приложения с event-driven архитектурой. О технологиях, которые вы используете для создания приложений с event-driven архитектурой, о подходах работы с ними пишите в комментариях.

LinkedIn

67 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

[Event-driven архитектура] — это универсальный интерфейс. Что угодно можно сделать через неё. Любые приключения.

Вот все понимаешь. А когда я сказал что разработал систему на этом принципе и очень сильно лучше народ заскучал..

Тому що для переважної більшості це темний ліс. Настільки темний, що навіть чорна діра буде здаватися яскравою зіркою.

Мізки програмістів роками засиралися послідовним програмуванням з прямим керуванням процесу виконання коду. Навколо цього вирос цілий Всесвіт-Одоробло, який був простим для розуміння (с початку) та який вважався істинно правильним. Так, не секрет, що накидати просту програмуліну на MVC+ORM можна доволі швидко. Швидше за таку в подійному світі. Але рано чи пізно така поробка доволі швидко почне деградувати під вагою складності процесів, які вона мусить реалізовувати. Єдиний вихід — повна асинхронність, а ще й бажано із ситуативним процесом виконання, коли обробка однієї й тієї події з кожним запуском може змінюватися в залежності від стану системи. Здається, що розробка таких систем буде адом адським. Це тільки здається. В більшості випадків програмування зводиться до простого конфігурування. Складність коду не зростає з розміром системи та складністю процесів, які вона описує.

Это да. Пришлось здорово голову поломать что б перенастроить ее на асинхронное выполнение. Но получилось очень удачно. Даже получилось легче и надежней чем обычное программирование. Причем легче не только логически, и с увеличении объема задачи сложность увеличивается менее чем линейно. А алгоритмы вообще не надо составлять. Есть класс задач, которые тупо формулируешь как создание соответствующих событий и как оно там будет работать пофигу причем параллельное выполнение Т.е. это ж вообще шикарно.. Хочу объяснить на примере.. Если надо чего-то найти и посчитать, подписываешься на события от которых это зависит. Формулируешь условия (что ищешь) и вызываешь метод, который это обрабатывает.. Это все!!! Как оно будет выполняться вообще плевать.. Хочу напомнить что выполнение логического выражения не изменяет память!!! Вот она в чем разгадка параллельного выполнения!!! Но, одной головы мало.. И надо набрать опыт в этой парадигме.. Потому как там все другое.. Принципиально другое..

Блін, я не одинак! Нарешті зустрів таких як я!
Сльози щастя...

Ничего мы и не встретились. И вряд ли встретимся..

Извиняюсь. Для тех, кто в танке я приведу пример.. Например, надо посчитать комбинацию букв «АБС». Создаешь новое событие с логическим выражением A[I]=А" & A[I+1]=Б & A[I+2]=С" . Подписываешь это событие к событию «Chage Value» индекса I и такого же события длины текста N и при возникновении этого события вызываешь метод наращивающий индекс I сразу на 3 и счетчик +1. P.S. Событие считается сработавшим при истинности логического выражения. Это все!!! Никакого алгоритма нет вообще!!!

Ага. А теперь из реальной жизни:
1) Есть до 6 DECT трубок. Они подключены по радио к донглу, засунутому в USB.
2) Есть 8 SIP accounts, сидящих где-то на сокетах.
3) Еще есть файлики истории звонков и телефонной книги, которые надо обновлять.
4) Еще есть приложение-менеджер, которому надо отсылать нотификации о значимых событиях, и обрабатывать команды от него.
5) Еще есть голос, который надо гонять в реальном времени между SIP (сетью) и DECT (USB), и для настройки которого надо сделать кучу всего.
И все это асинхронно. И все это независимо.
Например: из сети приходит звонок на трубки, но одна из трубок в этот же момент пытается звонить на другую трубку.
Например: трубка принимает звонок, мы начинаем подключать голос, но из сети приходит сообщение о том, что там звонок оборвали.
Например, у нас ограничение железа в 4 одновременных голосовых канала, но часть трубок может держать несколько логических звонков, шаря между ними физический голосовой канал.
А еще трубки и сервера по-разному себя ведут и глючат, когда что-то происходит не так, как они ожидают.
Итого: надо обрабатывать кучу сообщений из разных независимых источников; большинство цепочек событий ненадежны так как либо действие, их вызвавшее, может быть отменено в процессе выполнения, либо пока мы пособираем информацию от разных частей системы, их состояние уже поменяется, и информация устареет, а ограниченные ресурсы заберет другой участник.

И что? Очень даже интересная задача.. В чем вопрос то? Я много интересных задач из жизни встречал. Некоторые вообще головомойка решать обычным императивным методом.А событийным легко и очевидно.. У меня не обычные события, а принимающие параметр и передающие параметры для вызова метода или подписанного события.. Потому нет вопросов с «устаревшими» и «потерянными» данными. Они передаются как параметры.. Вот как придут, так и придут.. Сеть распределенная.. Это уже дело принимающей стороны реагировать.. Значит надо предусмотреть. Приятно то, что это «предусмотреть» не меняет остальную схему событий и ничего не надо «редактировать». Параметры это выражения для которых доступны все свойства и функции объекта в котором возникло событие.

Ну... я пока не придумал, как такое делается «легко и очевидно». Часть ответов прийдет в состояние, неспособное на следующий шаг в нормальной цепочке событий: запрашиваешь выделение голосового канала, а в это время трубку положили. И вместо того, чтобы устанавливать кодеки, надо отдавать голосовой канал (и все остальные ресурсы, которые уже успел повыделять) обратно.
Или пытешься установить кодек, а в это время из сети для этого же звонка приходит запрос на смену кодека. И что будешь делать? Посылать запрос на смену кодека трубке нельзя, так как неизвестен результат запроса установки кодека, который уже отослали. Ставить запрос на смену кодека в очередь и ждать ответа на установку кодека? Тут вылазит вопрос, сколько таких очередей надо, ведь, например, если пришло сообщение о разрыве звонка — его надо сразу обработать. А типов сообщений — десятки; иногда один пакет данных может нести несколько логических действий.

Ну, с протоколом там отдельная песня.. Это при обычном подходе разные логические действия. Тут включается вторая фишка и тоже мое изобретение.. У меня все есть концепт.. Концепт состоит из свойств, методов и событий. Вообще говоря еще из подписок и вложений. Но, вложения это тоже концепты, а подписки недоступны. Потому действий с концептом может быть только два. Адресация к свойству или вызов метода. Короче это не здесь надо обсуждать.. Если интересует реально. Рассказываешь задачу, я думаю за решение.. И обсуждаем..

Можно поговорить, но в реальной жизни event-based системы — сущий ад, который многомерными стейт-машинами не опишешь толком.

Ви просто не перебудували свій мозок на інший тип мислення. Якщо ви й надалі будете підходити імперативно до рішення подійних задач, то ви дійсно будете страждати.

Намекну еще что у меня нет оператора If. Любой выполняемый концепт (методы и операторы тоже концепты) имеют события на которые тоже можно подписываться. Т.е. установился или не установился кодек это тоже события..

А скорость работы этого счастья без ifов?

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

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

Я понимаю что тормознутость.. Но, она не критическая.. Для большинства задач хватает и 20 мгц. Можно поставить и 200мгц, а можно и 10 контроллеров.. Разработан еще межпроцессорный интерфейс со скоростью SPI до 16 контроллеров.. А арбитражем и всякой херней.. А если уж совсем надо круто, то можно многоядерный проц сделать со своей архитектрурой.. Система для того и сделана..

Наличие событий не должно нас волновать и проверяться. Нас интересуют только операторы на которые есть подписка.. А это один бит. Если он взведен, то отрабатываем логическое выражение этого события и если оно «True», переходим по адресу подписки.. И это тоже можно делать параллельно

Уявіть собі граф. Ви зараз знаходитеся в певній точці. Ви можете піти різними шляхами далі. Прийняття рішення лежить або в точці, або ззовні (залежить від архітектури)
Але у вас все одне є кінцевий набір входів та виходів. Все програмування зводиться до прийняття рішення та виконання всього одного переходу з точки А в точку Б. Декілька логічних дій? Вони будуть складені в ланцюг A — B — C — D...
Є вимога розірвати ланцюг в будь-який час та повернутися до стану Z?
Тоді ваш граф буде наступним

A — B — C — D —

|____|____|____|

Z___Z___Z___Z

Якщо вам треба після С повертатися до Z через Y — просто малюєте ще один лінк C — Y — Z
Все тупо, як двері

Хочу только дополнить. Слова «принять решение» у меня означает выполнить логическое выражение входящее в определение события. Обычно это выражение и есть постановка локальной подзадачи . Отсюда два следствия. Первое-задача раскладывается на подзадачи и формулировка этой подзадачи и есть это выражение. И, второе-вычисление логического выражения не изменяет память. Его можно выполнять параллельно!

Воно тупо, доки кількість вимірів цього кінцевого автомата та кількість можливих станів у кожному вимірі малі.
Стан DECT трубки за вимірами:
1) Незареєстрована, реєструється, ввімкнена (ми так думаємо), вимкнена (ми так думаємо), в процесі розреєстрації.
2) Підтримка фіч (основні підвиміри): CAT-iq 1 (кодеки), CAT-iq 2 (паралельні дзвінки).
3) Фізичний дзвінок — напрям: нема, вхідний, вихідний, з’єднаний.
4) Фізичний дзвінок — стадія: 26 станів, отут www.etsi.org/...​0/en_30017505v020501p.pdf сторінка 192.
5) Кодеки: не встановлені, є пропозиція з бази, є пропозиція з трубки, встановлений, запрошено зміну кодека з бази, запрошено зміну кодека з трубки.
6) Логічні дзвінки — існують як абстракція над фізичним дзвінком — стадія: setup, setup ack, proceeding, alerting, connected, disconnecting, under transfer.
7) Логічні дзвінки — конференція: так, ні.
8) Логічні дзвінки — напрямок: вхідний, вихідний, неважливо.
9) Логічні дзвінки — утримання: так, ні.
10) Логічні дзвінки — утримання іншою стороною: так, ні.
А тепер намалюйте «тупо» оцей «кінцевий набір» на одному папірці, наприклад, для 3 трубок в системі, кожна може тримати 2 логічних дзвінки одночасно. І половина станів частково незалежні один від одного (ортогональні).
А в нас 6 трубок, і ще є SIP з’єднання, з яких (на які) теж дзвінки відбуваються.

Ви думаєте це неможливо?

Я думаю, для однієї трубки Ви отримаєте десь так із сотню-дві станів. Для 6 трубок 100**6 станів)). А результат дзвінка між трубками залежить від поточного стану кожної з них)
От ще приклад: Ви кажете про один ланцюг подій. А тут, наприклад, слухавка робить запит на call transfer (з 2х дзвінків, в яких бере учать, видалити себе і з’єднати інші кінці). При цьому дзвінки були з різними кодеками, тому треба змінити кодек. А поки ми це робимо, з інтернета приходить сигнал, що наш дзвінок на утриманні. Який кінцевий та проміжні стани? Ми йдемо до стану «слухавка 2 розмовляє з інтернетом» чи до «слухавка 2 використовує кодек G726, який залізо перетворює на PCMU» чи до стану «в слухавки 2 дзвінок на утриманні» чи «в інтернеті виставлено кодек PCMU» чи «слухавка 1 поза дзвінком»? Чи ми цілимося в стан, що має усі ці результати своїми вимірами? Але тоді (якщо помножити усі виміри) в нас буде стільки таких можливих станів, що 32-бітний проц ії не заадресує.

Ви неправильно рахуєте стани. В цьому ваша проблема. Ви вважаєте значення якоїсь змінної окремим станом. Ні, в цьому немає сенсу. Краще вийдіть на вищий рівень абстракції, де ви керуєте простими процесами, а не Всесвітом. На більш вищому рівні ви отримаєте наступні стани системи: очікування, встановлення з’єднання, розмова, завершення тощо. Кожну з високорівневих подій ви вже можете розкласти на більш низькорівневі (але не сильно заглиблюючися)

Я б занялся. Хотя б с точки зрения интереса.. Хотел бы уточнить термин «состояние». Здесь не как в теории доказательств правильности программ считается вся память. Состоянием программы можно назвать значение свойств концептов (объектов). А переход из одного состояние в другое только по возникновению событий. Другого механизма взаимодействия между концептами нет. Это уже значительно приятней..чем вся память..И еще.. Если выполнение происходит по событийному механизму, то допускается параллельное выполнение и здесь о конечном автомате может идти речь только как об одной конкретной исполняемой ветке.. В общем случае это даже не алгоритм, потому как нет последовательности шагов.. Господа, добро пожаловать в мир параллельных вычислений!!)

А что мы делаем, когда для принятия решения о правильном поведении нужно опросить несколько концептов?
Последний (простой) пример из жизни: между трубками можно сделать внутренний звонок — с одной трубки на другую. Для этого нажимаешь кнопку int, и затем — номер трубки, на которую звонить. Вот пользователь захотел, чтобы если в системе зарегистрировано всего 2 трубки, номер не надо было набирать, и звонок шел автоматом по нажатию кнопки int.

Я не очень разбираюсь в технологии мобильной связи. Именно потому и сказал что занялся б для интересу. На Ваш вопрос могу сказать что подход с опросом в принципе не правильный. Всю работу надо строить как реакцию на события. Если где-то таки приходится опрашивать (например, датчик температуры), после опроса и анализа вывод переформулируйте в новое событие называя его согласно результату анализа. (в случае датчика температуры, это будет событие «Change Value».. Так будет правильно..

В вашем случае где-то должны быть события «регистрация нового телефона» со счетчиком количества телефонов, а дальше два события одно с 2 телефонами, и второе больше двух..

Там послідовні події, але однакові для будь-якої кількості трубок
При натисканні кнопки int спрацьовує обробник, основна задача якого дізнатися, скільки абонентів існує. Цю інформацію він записує в подію. Наступний обробник — роутер подій. Якщо абонентів 1, запускаємо подію «call», якщо більше — тихо вмираємо. На подію «call» спрацьовує обробник, який перевіряє валідність номеру. Якщо номер не валідний, вбиваємо подію або записуємо результат перевірки. Наступний хендлер буде вже запускати процес пошуку абоненту. Ще наступний запустить процес з’єднання (інша подія). Думаю, ви зрозуміли. Хендлери мусять робити гарно одну єдину річ. Якщо треба запускати більш складні процеси — генерується відповідна подія.

Сподіваюся, на цьому прикладі стане зрозуміліше, що таке подійне програмування.

Еще проще.. Создаем 2 события которые подписаны на Int. Первое — с логическим выражением если абонент 1 и подписываем его на Call с параметром его номер. И второе событие если абонентов больше 1. И ждем выполнения следующего события-набора номера.. Кстати, я еще не сказал что кроме объектов и методов ввел новое понятие «автомат». Это типа класса, но при создании объекта параметрами являются объекты с установленными подписками.. Т.е. классом автомата задано взаимодействие между объектами.. Обычный класс создает «Чистый объект» без подписок.

JavaEE
Як там, в нульових?
JavaEE
Як там, в нульових?
Ну не J2EE все-таки.

Java EE 7 — 2013 год. То что технология существует давно и при этом периодически обновляется — скоррее плюс, чем минус.

Мне кажется что построено не умно. Хотя ход мысли правильный. Во первых, событие это самостоятельное понятие объекта (у меня называется концепт) такое же как свойства и методы. Событие имеет группу событий на которые оно подписано и логическое выражение которое выполняется при возникновении одного из событий из группы на которую оно подписано. И если это логическое выражение истинно, то создаваемое событие считается произошедшим. Такой подход позволяет обойтись без программы наблюдателя. Назовем этот кусок программы диспетчером событий.. Хотя там реально штук 5 команд ассемблерных.. Схема работы очень простая.. Подписки на событие находятся в объекте инициаторе цепочки событий, по подписке выходим на событие. В нем ищем логическое выражение, выполняем и запускаем либо процедуру, либо проверяем на подписку на следующее событие.. Для логического выражения доступны переменные с объекта подписки. Это ж другой объект. И еще при вызове процедуры обработки события (и в подписываемое событие) может передаваться значение-выражение из переменных объекта инициатора события. Понятно что в приложении такое организовать сложно. И даже невозможно. Потому как при трансляции надо иметь адреса для связывания. А объекта инициатора может и не быть на этом этапе.. он может вообще находиться в другом компе.. Ну, для этого я и построил свою систему.. Заодно и язык пришлось придумать.. Что-то мне java показалась неуклюжей и не годится для системы построенной на событийном механизме..

Лет 10 назад статья бы зашла.

Жаль что нет ни слова про Akka / RxJava / Quasar.

public void checkoutShoppingCart(ShoppingOrder order) {
persistInDatabase(order);
sendEmailNotification(order);
shipToTheNearestWarehouse(order);
scheduleShippingToCustomer(order);
exportToERP(order);
}
Нетрудно заметить тенденцию, что метод checkoutShoppingCart становится неподдерживаемым беспорядком.

Да, полный беспорядок. Совершенно непонятно что делает этот метод. И как это безобразие вообще поддерживать? То ли дело, события..

Тело метода написано в процедурном стиле. Если придерживаться ОО подхода, то добавление нового действия повлечёт за собой внедрение новой зависимости (и сильной связности с ней), что, впрочем, не всегда проблема, особенно если это происходит в рамках одного сервиса в стиле «своя рука владыка».

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

А критический плюс event driven подхода — это всё же неблокирование ресурсов.

Совершенно непонятно что делает этот метод.
Я нигде не писал, что непонятно, что делает этот метод. Слово «непонятно» не встречается в статье. Я писал о том, что это сложно поддерживать. Почему с событиями легче на мой взгляд? Потому что мы добавляем новый функционал путем добавления нового кода, а не модификации существующего кода. Больше подробностей в статье.
Я писал о том, что это сложно поддерживать. Почему с событиями легче на мой взгляд? Потому что мы добавляем новый функционал путем добавления нового кода, а не модификации существующего кода.
А как вы знаете кто слушает ваши события? И как вы проверяете что никто не «отрефакторил» (просто грохнул) некоторые слушатели?

Зависит от технологий, которые используются для построения EDA. У Java EE CDI хорошая подержка в IDE. Кликая мышкой по соответствующим иконкам можно узнать о всех слушателях. В случае Apache Camel — текстовым поиском, но при желаниее можно тоже пользоваться средствами IDE. В случае JMS, конечно, сложнее, как и всегда с распределенными приложениями. Но появление в архитектуре MOM вероятно связано также с использованием в приложении каких-то свойств, которые этот middleware предоставляет.

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

Очевидно, что код из первого параграфа вашей статьи намного проще для понимания, сопровождения, расширения и отладки по сравнению с типичным enterprise over-engineering кошмаром, который следует в последующих частях статьи.

Это очевидно вам, но не всем. Спасибо за мнение. Кстати, количество абстракций во всех частях статьи одинаковое.

Это очевидно вам, но не всем.
Да, это очевидно как правило людям у которых есть опыт работы с эвент-бейсед системами.
Суть где-то такая (цитаты будут из вашего коммента выше):
Кликая мышкой по соответствующим иконкам можно узнать о всех слушателях
Если выброска и обработка события находятся в одном проекте, то события вам не надо, ибо на coupling (просто чтобы не путаться с русскими терминами) события существенно не повлияют.
Так же остается очень важный риск, который вы, пока, проинорировали:
И как вы проверяете что никто не «отрефакторил» (просто грохнул) некоторые слушатели?
Куда кликать чтобы увидеть что удалили? Эта проблема актуальна для ситуаций когда у вас больше одной команды и/или когда у вас больше 5-7 разработчиков.
Да, это очевидно как правило людям у которых есть опыт работы с эвент-бейсед системами.
Всем людям, у которых есть опыт с event-based системами? И все эти люди уполномочили вас говорить от их имени?
Если выброска и обработка события находятся в одном проекте, то события вам не надо, ибо на coupling (просто чтобы не путаться с русскими терминами) события существенно не повлияют.
А вот люди, которые создавали спецификацию CDI в Java EE 6 с вами не согласны, раз добавили такую возможность в спецификацию. В Apache Camel тоже есть in-memory компоненты: VM, Direct, Seda и т.д.
И как вы проверяете что никто не «отрефакторил» (просто грохнул) некоторые слушатели?
Я это проверяю также, как проверяю код в процедурном стиле — автоматизированными тестами. Как вы проверяете, что команда из 5-7 разработчиков не отрефакторила (просто грохнула) реализацию сервиса, который вызывается из другого сервиса? Автоматизированные тесты? Code review? С event-driven подходом тоже самое.
Всем людям, у которых есть опыт с event-based системами? И все эти люди уполномочили вас говорить от их имени?
Нет, не уполномочили. Это банальное наблюдение и собственный опыт, за максимум 0.5-1 год понимаешь, что если можно сделать без них, то лучше делать без событий и тд.
А вот люди, которые создавали спецификацию CDI в Java EE 6 с вами не согласны, раз добавили такую возможность в спецификацию.
В этом мире много чего есть, но это не значит что все это хорошо и полезно. Уверен есть задачи, где та или иная возможность могут пригодится.
Я это проверяю также, как проверяю код в процедурном стиле — автоматизированными тестами.
От только проходят они в 100500 раз дольше (со всеми вытекающими), ибо
— надо поднимать контекст (брокер или типа того),
— надо следить чтобы он не загрязнялся (чтобы тесты были изолированными),
— надо в синхронном тесте дожидаться асинхронного процесса.
Code review?
Может сработать, но тогда он требует чтобы ревьювер вникал в бизнес задачу, а это в разы увеличивает врем ревью.
Как вы проверяете, что команда из 5-7 разработчиков не отрефакторила (просто грохнула) реализацию сервиса, который вызывается из другого сервиса? Автоматизированные тесты?
Супер.
Проблема:
Упал тест, которому более года. Проблема в том что после scheduleShippingToCustomer не происходит exportToERP.
Решение 1:
Вставить в обработчик scheduleShippingToCustomer вызов
exportToERP
Результат 1:
Вы поимели цепочку событий. Ждите канкаренси проблем в проде.
Решение 2:
Архитектор сказал что мы должны использовать события, поэтому побыстрячку пилим ShippingEvent.
Результат 2:
Количество ивентов растет линейно количеству багов. + потенциально получаем Результат 1.
А архитектор ничего сделать не может, ибо он ревьювит 10-й коммит, а баг который надо отдавать завтра в ЮАТ/прод имеет номер 20 :)
.
В случае с «процедурным» (что не факт что правда) checkoutShoppingCart. Человек простым дебагом находит где происходит проблема, смотрит историю и видит что было изменено. В случае с событиями, он не знает в каком месте (модуле возможно будет найти проще) искать изменение. То есть фикс багов потребует просмотра всех коммитов.
.
который вызывается из другого сервиса
А вот тут уж определитесь:
1) или у вас взаимодействие между сервисами
2) или «Кликая мышкой по соответствующим иконкам можно узнать о всех слушателях».
Если у вас это смешано, то вы банально усложнили себе (а скорее всего не себе, а команде) жизнь. И «процедурного» решения хватило бы с головой.

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

Теперь у читателей будет представление и о недостатках данного подхода.
От только представление «и о недостатках» (а не просто пересказ туториала) должно было появится из статьи, если цель была именно рассказать об архитектуре.

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

По поводу 10 и 20 коммита хочу заметить, что это вопрос к SDLC на проекте. Если вы отдаете в UAT/prod код, который не прошел code review при падающих автоматизированных тестах не проводя тестирование на тестовых средах, то это действительно может привести к проблеме. Но эти проблемы будут возникать в случаее процедурного подхода, объектно-ориентированного, функционального и т.д., а не только в случаее EDA.

По поводу 10 и 20 коммита хочу заметить, что это вопрос к SDLC на проекте.
Не только еще это может зависеть от сложности проекта:
Если проект на 5-10 классов, то можно и поиграться с разными подходами. Именно поиграться, потому что эффективнее ее решать тем что вы назвали «процедурный стиль».
Если вы отдаете в UAT/prod код, который не прошел code review при падающих автоматизированных тестах
Я не говорил про падающие тесты, я даже сказал как они будут пофикщены :)
А вот про ревью, сразу видно что у вас нет опыта ревью в больших проектах. Если б был, то вы бы понимали что отловом багов и тем более пониманием бизнес-задачи при ревью заниматься очень дорого.
Но эти проблемы будут возникать в случаее процедурного подхода, объектно-ориентированного, функционального и т.д., а не только в случаее EDA.
Именно «эти» не возникают, ибо удаление обработчика происходит при изменении вызывающего места и вы легко понимаете почему так было сделано.
Вторая проблема — это оверхед в плане рисков утечки ресурсов.
И как бы, евенты — это архитектурный стиль, он параллелен ПП, ООП, ФП и тд.
С описанным вами я не согласен, поэтому логично, что я не добавил это в статью.
Не согласны или не сталкивались (ввиду очень ограниченого опыта)?

В моем профиле есть ссылка на LinkedIn, где указан весь опыт.

Я хочу завершить эту беседу следующим утверждением. Я не считаю возможным делать заявления, что какой-то архитектурный стиль нельзя использовать. Заявления от имени всех людей, что какой-то архитектурный чтиль нельзя использовать — это мания величия. Если у кого-то есть негативный опыт работы с чем-то — это субъективное мнение. Вы поделились своим опытом, спасибо, за это. В любом подходе есть плюсы и минусы. В статье я описал досточно много интересных свойств, которые предоставляет EDA, которые другими средствами реализовать сложнее. Цитировать вторую половину своей статьи я не буду. Более того EDA часто используется на практике, крупные компании создают ПО для создания event-based систем, известные авторы пишут книги об этом подходе. Говорить, что все они неправы я бы не стал, а лучше задумался.

Если вам не понравился сам пример, который я привел в статье — предложите лучше. Тут есть пространство для обсуждения.

Я не считаю возможным делать заявления, что какой-то архитектурный стиль нельзя использовать.
Я тоже. Речь об этом и не шла. Речь была о том что ваш пример не демонстрирует преимущества эвент-бейсед подхода.
Если у кого-то есть негативный опыт работы с чем-то — это субъективное мнение.
Нет, не мнения, а факт. Факт который вы не озвучили в вашей статье.
Более того EDA часто используется на практике, крупные компании создают ПО для создания event-based систем, известные авторы пишут книги об этом подходе.
Вы бы почитали эти книги, для начала,там написано для чего используется (эффективно использовать) эвент-бейсед подход.
.
А теперь самое главное:
Если вам не понравился сам пример, который я привел в статье — предложите лучше.
Отдавайте статьи на вычитку. Если вы не смогли придумать примера лучше, то не стояло и писать статью.
Вам уже указывали в комментария к предыдущим опусам, что вы просто пересказываете туториал. А в данном случае подаете этот туториал как объяснение архитекторного стиля.

Приятно, что следите за всеми комментариями к моим статьям на ДОУ. Жду ссылки на туториалы, которые я пересказал.

1+1=2 — это факт. Если кто-то считает процедурный подход лучше event-based — это не факт, а его личное мнение.

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

Приемы из второй половины статьи сложнее реализовать, используя другие подходы. Ради этих свойств и стоит использовать EDA. В статье все написано, можете почитать.

Пожалуйста, список книг, которые мне «нужно почитать хотя бы, для начала». И на какие конкретно главы обратить внимание, вдруг кто-то читает эти комментарии, а от вас никакой конкретики, только субъективное мнение и просьба верить на слово вашему опыту, которого может и нет.

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

Если вы не смогли придумать примера лучше, то не стояло и писать статью.
Так от вас будет лучше (на ваш взгляд) пример? Может в виде отдельной статьи? Или нет времени писать и объяснять?
что вы просто пересказываете туториал
Но я так и не дождался от автора комментариев ссылок на туториалы, которые я якобы пересказываю.
Или нет времени писать и объяснять?
dou.ua/...ts/java-digest-27/#966298
Чтобы сделать хоть сколько-то качественно, времени нет, а тяп-ляп — с этим и вы неплохо справляетесь. :)

Видите, как я угадал, что времени нет :)

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

Вторая проблема — это оверхед в плане рисков утечки ресурсов
Ризиків ще менше, тому що обробники в решті решт будуть настільки примітивні, що там просто банально не буде чому текти. Та й архітектура обробника буде примітивною: стандарні параметри на вхід (зазвичай instance of Event), стандартні параметри на вихід (той самий екземпляр Event).

SDLC, EDA, UAT, MOM, CDI — что-то многовато «умных» аббривеатур на единицу текста в вашей речи. Будьте проще.

Расшифровать для вас аббревиатуры?

аббривеатур

Обратите внимание, как пишется слово «аббревиатуры». Или это ваш способ быть проще?

И интересно узнать, как вы различаете «умные» и «не умные» аббревиатуры?

Сильно уж ентерпрайзная еда получается. Кстати, JMS и особенно двухфазный коммит скейлятся плохо.
<srach>

Не могу сказать, что Java EE — это хорошо или плохо. Эта платформа позволяет решать много проблем, существует давно и последние ее версии (например, Java EE 7) имеют хорошую репутацию.

Конечно, построить EDA систему можно с использованием других библиотек, фреймворков и платформ. Будет интересно почитать про ваши любимые библиотеки/фреймворки/платформы для построения EDA.

JMS — это спецификация, Java API. Проблемы с two-phase commit могут быть у конкретной реализации JMS в паре с transaction manager’ом. Больше всего я работал с HornetQ (реализация JMS) и Arjuna (transaction manager) и никаких проблем с распределенными транзакциями не было.

Не подумайте, что я придираюсь, технические статьи на доу это всегда очень хорошо.

Конечно, построить EDA систему можно с использованием других библиотек, фреймворков и платформ. Будет интересно почитать про ваши любимые библиотеки/фреймворки/платформы для построения EDA.

Для большой высоконагруженной системы я бы предложил архитектуру построенную на event sourcing и Kafka для pub/sub между сервисами. По сути это BASE вместо ACID, что даст на порядки лучший scalability чем 2PC, и при этом можно будет работать с множеством дата центров, а не внутри одного. И как бонус — более легкое тестирование и аудит для сервисов.

p.s. У вас проблем потому и нет, что нет больших нагрузок, впрочем откуда им взяться в ентерпрайзе.

Подписаться на комментарии