«А что если», Event Sourcing

Наверное, про Event Sourcing слышал каждый, кто хоть раз пересекался с темой CQRS и DDD. Это подход хранения данных, при котором вместо конечного результата храниться череда записей о событиях происшедших с некоторой сущностью. На сайте Мартина Фаулера есть подробное описание, а мы же остановимся на фундаменте, основных «печенюшках», а также проблемах в его применении.

Фундамент


Ключевые механизмы, которые как раз и позволяют строить разный полезный функционал следующие:
  • Каждому событию дается имя, которое определяет его значение, т.е. присутствует семантика. Согласитесь есть огромная разница между «Событие 1» и «Корабль Отплыл».
  • Нет ограничений на кол-во событий для сущности. Соответственно новые события могут отражать, как и новые виды совершенных действий, так и расширять уже существующие, скажем, добавили новое свойство в его 2-ой версии.
  • Произошедшие события неизменны («immutable»).


«Печенюшки»


Естественно, раз подход применяется, значит, в нем есть то самое, «ОНО», ради чего игра стоит свеч.

История событий

Начнем с того, что Event Sourcing – это мечта для тех, кто хочет помнить все «как и почему», «что и когда», произошло в системе. Скажем, это аудит записи, только еще лучше за счет хранения информации «почему». Мне запоминался пример, который Greg Young использовал на одной из своих презентаций:

Банк хранит сведения о том, где проживают их клиенты и в один день происходит изменение одного из почтовых адресов. При хранении информации в таблице, мы максимум сможем увидеть, что запись обновилась. Если был подключен аудит, то в целом мы можем посмотреть на историю всех предыдущих значений. Но сможем ли мы узнать, почему она изменилась: может это была изначальная ошибка?, а может клиент переехал в другое место? С помощью Event Sourcing мы как раз и смогли бы ответить на этот вопрос, т.к. у нас было бы 2 события: КлиентПереехал, ИсправленаОшибкаАдреса.


Проекции

Естественно, раз вся полезная информация о сущности сохранена в потоке событий, то напрямую ей воспользоваться нельзя. Для UI нам необходимы плоские модели (проекции), которые мы как раз и создаем, используя обработчики событий, которые называются денормализаторы (denormalizers). Отличительным свойством можно назвать то, что такой обработчик можно менять и добавлять, как и когда угодно. В любой момент проекцию можно «выкинуть», проиграть все события от начала и новая готова. Небольшой пример кода:
public void Consume (ShipArrived message) {
readModel.Dock.Ships.Add(message.Ship);
} 
public void Consume (ShipDeparted message) {
readModel.Dock.Ships.Remove(message.Ship);
}

Конечно, кроме проекций для интерфейса, денормализация событий – это мощное средство построения разнообразных отчетов и проведения анализа event sourced сущности.

UI ориентированный на процесс

Замена основного хранилища данных (source of Truth) с нормализованных данных в таблицах, и CRUD операций над ними, на записи о событии, подталкивает на изменения к подходу в дизайне. Не выгодно иметь события в стиле CRUD, т.к. тогда теряется весь смысл в их семантике и привязке к происходящему. Значит необходимо строить такой интерфейс, где пользователь выполняет небольшие атомарные действия над сущностью. Т.е. дизайн интерфейса должен быть workflow driven, а не data input-based.

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

Технические моменты

Пару технически интересных полезностей при таком подходе:
  • При интеграции с внешними системами можно передавать отфильтрованный поток событий. Это может упростить задачу в некоторых случаях.
  • Event Store можно сделать очень быстрым и расширяемым, т.к. события неизменны и могут только добавляться.
  • Проекции можно гео-реплицировать.


Проблемы


Как можно догадаться, они значительны.

Ад для рефакторинга

Итак, о чудо, наши события immutable, а их поток append-only. Хм, чтобы это значило? А значит это, что любое единожды происшедшее события необходимо поддерживать навечно. Их нельзя ни отменить, ни удалить. Можно создавать новые версии событий, события которые компенсируют ошибки, — а это все новые и новые классы, которые необходимо добавить в имеющиеся обработчики и пронести по всей цепочке использования сущности. Это все равно, что отнять право на ошибку у программиста, и никакого тебе “rollback” плана. Разве что вернуть базу событий к предыдущему снэпшоту, потеряв все действия пользователей, если конечно вы на такое будете согласны :-)

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

Просуммировав, это значит, что модель событий необходимо тщательно продумывать, отрисовывать и джуниора/мидла к ней не подпустишь. А если события уходят в другие компоненты… вообщем необходимо разграничить сразу внешние события и внутренние, а также запереть их определение в отдельный репозиторий. Ссылаться только через NuGet.

Нет готовых инструментов

Начиная разработку используя Event Sourcing, будьте готовы к тому, что все инструменты для работы с событиями придется написать самим. Конечно, есть EventStore от Jonathan Oliver, но это лишь малая часть. Вам понадобится графический и программный интерфейс их управлением: просмотра и поиска, построения и обновления проекций, создания снэпшотов для оптимизации чтения и др.

Взаимно компенсирующие события

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

Поддержка

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

Выводы


Как сказал один мой знакомый: «Мы не решаем проблемы, а переносим их в более комфортную для себя плоскость». Также можно сказать и про Event Sourcing. Каждый должен определить, нужен ли он для решения конкретной задачи, или нет.

Я же могу сказать следующее: используйте Event Sourcing для хорошо изученных сущностей и точечно, не пытайтесь построить на нем всю систему. Желательно, когда это не первая, а может и не вторая ее реализация.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 106

    +1
    Кто бы из коллег рассказал про как раз такие примеры, где на Event Source построена только часть системы.
      0
      Было на хабре — «Ведение в CQRS + Event Sourcing»: часть 1 и часть 2
        0
        Спасибище! Как то я пропустил эти две статьи.
        0
        В одной из систем, часть сущностей, были обычными таблицами и ORM к ним. В событиях мы тогда просто ссылались на эту сущность по ID, а в denormalizers вычитывали по этому ID, когда надо было.
      • UFO just landed and posted this here
          0
          Да, сам подход для решения задач не новый. Просто как-то с DDD и CQRS его подняли выше в архитектурный паттерн. CQRS — это тот же CQS только опять же выше по архитектуре.

          В целом, я так понял Ваш алгоритм архивирования событий напоминает Snapshots в Event Sourcing. Разница в том, что у Вас были скорее всего таблицы с установленной схемой (поправьте если не так), а тут при хранения событий в БД — одна колонка «Event Data» (скажем в XML или JSON). В целом одно и тоже, однако с первым работать проще.
          • UFO just landed and posted this here
          –1
          В БД обычно создаются таблицы — дубликаты Audit, где есть полная копия колонок + колонки Timestamp, Comment. И на триггерах висят добавления записей в таблицу аудита. Раз в месяц, например, идет копирование в хранилище с затиранием всех старых записей
            0
            Как я и говорил — запись в таблице или ее история может только подсказать природу изменений, особенно если код работает с ней через ORM. Event Sourcing дает больше, но и заплатить надо не мало.
            +1
            Можно еще дать ссылку на сайт этого продукта Event Store.
              0
              Это случаем не Greg Young делает?
                0
                Да, насколько мне известно, это его продукт.
              +2
              Наверное, если не впадать в крайности, то CQRS себя вполне оправдывает. Моя прошлая работа была связана с автоматизацией розничной торговли. Там вообще не было возможности прямо повлиять на остатки. Зато была совокупность документов: акт о приёмке (ТОРГ-1), накладная (ТОРГ-12), внутреннее перемещение (ТОРГ-13), Z-отчёт, различные акты об инвентаризации, пересорте, браке и т.п. Разумеется, когда такие документы публикуются (проводятся), на них ставятся подписи, причём в некоторых случаях на нескольких копиях несколькими сторонами, и тут уж нельзя говорить о каких-либо дальнейших изменениях. А актуальные остатки и остатки на конец каждого периода (дня, недели, месяца) высчитываются теми самыми listener'ами. Такие системы писались уже давно, когда ни о DDD, ни о CQRS не слышали даже, а многие вещи так вообще возникли ещё во времена бумажного документооборота. При этом вещи вроде информации о товарах и складах — обычный CRUD. Ещё были такие вещи, как сотрудники, которые сочетали в себе CRUD (имя, дата рождения) и CQRS (признак уволенности, должность, оклад), но непосредственно торговли они не касались, а автоматизацией кадровых вопросов занимались совсем другие люди в совсем другой системе, так что с этим знаком понаслышке. Делать же документы вроде «акт изменения названия товара» или «акт исправления адреса магазина» — это уж слишком. Ну максимум — на каждое редактирование сущности добавлять в системный журнал запись со ссылками на сущность и на пользователя, который внёс изменения.
                0
                CQRS — это разделение записей от чтений, только на уровне архитектуры. Его можно применять и без Event Sourcing. Может речь шла о последнем?

                Но в целом да, подход не новый и сегодня он называется Event Sourcing. Однозначно хороший инструмент, если знать как и когда применить.
                +1
                Вероятно, я не открою большого секрета, но, модель с хранением событий и хранением состояний не является по сути дела противоречивой.
                Могу показать на примере платформы 1С: предприятие.

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

                При этом, таблицы можно свернуть (wrap) на определенный момент времени, подготовив, так называемые, начальные остатки (исходное состояние, до которого нет истории событий)

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

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

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

                Тем интереснее и полезнее подобные статьи, особенно в ключе их обсуждения.
                Как говорится, каменты жгут!
                  0
                  Только надо понимать что за такой архитектурой стоит модель «двойной бухгалтерской записи», которая появилась задолго до компьютеров и, даже, самой бухгалтерии. Именно двойная запись дает гибкость и мощность системы. Это не «вшивание паттерна», а просто реализация двойной записи, приближенной к физическому ведению бухгалтерских книг (далеко не оптимальная реализация).

                  Программисты, не знакомые с теорией учета часто изобретают велосипеды (паттерны) вместо реализации двойной записи. Но вот эти паттерны потом довольно сложно применять за пределами первоначальной предметной области.
                    0
                    Прошу меня простить, но должен Вам возразить: регистры не основаны на двойной записи. Есть специальный регистр — бухгалтерский. Он, и только он, реализует механизм корреспонденции счетов. Регистры оперативного учета реализуют именно шаблон разделения оборотов и остатков, для обеспечения более высокой эффективности формирования отчетов.
                      0
                      А как вы думаете ведется учет на бумаге? Вот есть двойная запись, в одну колонку пишем откуда пришло, во вторую куда ушло и объем. Со временем эта простыня становится очень большой и ответ на вопрос «сколько сейчас товара на полке» получить почти невозможно.

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

                      В 1C эту модель перенесли почти один-в-один.

                      Еще раз повторюсь, это все не из-за «паттернов», а из-за низлежащей математической модели.

                      Кстати в современных СУБД, c механизмами материализованных индексов, делать явный пересчет остатков и оборотов нет смысла. Можно все сделать индексом и работать это будет в разы быстрее.
                        0
                        Я понимаю о чем Вы. Но регистры в платформе 1С: Предприятие — не об этом. Понимаете? Регистры не имеют отношения к двойной записи, хоть и дают возможность ее реализовать, когда это необходимо. Идея-фикс насчет того, что 1С: Предприятия это чисто бухгалтерский инструмент, увы, соответствует реалиям 1С: Бухгалтерии, вплоть до версии 6.0. То есть — примерно на 1996-1997 год. С тех пор немало воды утекло и бухгалтерские принципы в платформе 1С отражены примерно в 5% системы. Остальные 95% — совершенно не связано с задачами бухгалтерского учета.

                        Насчет остатков и оборотов, попробую объяснить.

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

                        Каким образом материализованные индексы помогут Вам считать агрегаты (и не всегда это просто сумма значений в отобранных записях) по миллиардам записей, при этом формируя отчет за секунды? Уже даже десятки секунд — неприемлемо.

                        Поэтому механизм промежуточных итогов, предрассчитанных в момент совершения записей, или в отложенном режиме (но все равно до того, как данные будут востребованы для анализа) помогает распределить время расчета агрегатов, снять нагрузку с системы в момент формирования расчетов.
                          0
                          Классическая же статья есть: rsdn.ru/article/db/RDBMS.xml

                          Причем описанные там методы опять таки пришли из реального мира и являются прямыми следствиями «двойной записи», только сделано все образцово-показательно.

                          Кстати «двойная запись» подходит вообще для любого количественного учета, а не только для бухгалтерии. 1С это убедительно доказывает.

                          Поймите же, нету мегапаттернов в 1С. Есть нормальная математическая модель.

                          Что касается CQRS, то у него нет адекватной модели, в отличие от REST например.
                            0
                            Поймите и Вы, я очень хорошо понимаю, что такое двойная запись, поскольку эксперт в этом вопросе. Только я не про нее, а про реализацию оперативных регистров. Которым неоднократно учил разработчиков корпоративных систем, изобретающих отвратительные модели данных. Именно поэтому, регистр остатков/оборотов в 1С — это замечательный шаблон проектирования. А вот как раз нормальной математической модели в основе платформы 1С — нет.

                            Что касается CQRS — не знаю, Вам виднее. Буду изучать :)
                            Спасибо за Ваши комментарии.
                              0
                              Да нет там двойной записи, кроме регистров бухгалтерии. Посмотрите регистр накопления СвободныеОстатки, например. Товар ВНаличии и ВРезерве рассчитывается в разрезе складов и номенклатуры. Из этой таблицы вы не узнаете кто кому чего продал и сколько остался должен.

                              При двойной записи была бы проводка 41-60 или 62-41 в регистре бухгалтерии из которой было бы видно какой товар от кого поступил и на какую сумму.
                                0
                                Относительно предложенной статьи
                                В 2000 году нам пришлось реализовать для 1С: Предприятие 7.7 свою систему проводок и отчетов, поскольку производительности стандартных бухгалтерского и даже оперативных регистров нам не хватало. Бухгалтерский регистр вообще не давал адекватных результатов: конечные остатки уже на март не совпадали с начальными остатками на апрель. Модель переделанная на оборотных регистрах справлялась с итогами за 3 года, но формирование отчета занимало до 40 минут.

                                Мы переделали структуру на прямые запросы к таблицам MS SQL и начали высчитывать, на какое количество оборотных записей есть смысл делать промежуточные итоги.
                                Выяснилось, что без всяких материальных индексов, до 50 млн. записей, быстрее оказывается получить агрегаты по таблице оборотов, чем усложнить запрос, с вычислением промежуточных итогов и выравнивания до них таблицы оборотов.

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

                                К чему это все я рассказал.

                                Тест на 1 млн записей ни о чем не говорит.
                                Кроме того, как раз нет проблемы для простого (иерархиченого) агрегирования итогов.

                                Куда сложнее, когда алгоритм формирования показателей оборотов не тривиален (например, есть признак операции для каждой строки, по которому определяется, как и по каким ресурсам будут изменены итоги). Еще хуже, когда операции между собой взаимосвязаны (например — списание по партиям).

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

                                К примеру, бывали ситуации, когда проведение некоторого документа по десятку регистров может требовать несколько десятков секунд.

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

                                Практически, система представляет кластер: часть расчетов делается на клиентских компьютерах (которых больше 500), часть расчетов на сервере в режиме онлайн, часть на сервере — по ночам и в выходные. И, к сожалению, полный пересчет итогов в такой системе требует привлечения дополнительных ресурсов. Например — расчетов в облаках. В одной организации, такой пересчет удалось произвести за 4.5 месяца, исключив часть наименее приоритетных показателей, которыми оказалось проще пожертвовать, и задействовав на это время арендованные сервера, в четыре раза превышающие стандартно используемые мощности.
                                  0
                                  Когда количество проводок переваливает за 10 миллионов, то уже надо не средствами СУБД пытаться победить (они не предназначены для этого), а использовать OLAP решение. Оно само делает то, что вы описываете.

                                  Правда я так и не понял что за операции, которые не сводятся к просто суммированию, я таких в природе не встречал.

                                  Пример на хабре есть: habrahabr.ru/post/66920/

                                  Кстати тоже самое касается архитектуры вообще. Пока данных мало — встроенные средства БД помогают. Когда превышает критический порог — нужно использовать специализированные инструменты для пережевывания большого массива данных.

                                  ЗЫ. Строить отчеты по документам, особенно в 1С — жесть, врагу не пожелаешь.
                                    0
                                    Ну, собственно 1c-ники и не строят отчеты по документам.

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

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

                                    Поэтому не всегда возможно применение OLAP. Да и не всегда обосновано. Регистр остатков/оборотов — это прекрасный шаблон проектирования, имеющий свои ограничения и свою область применения, где им нет равных.
                                      0
                                      Все равно не понимаю. Ну и что что зависит что-то от состояния?
                                      Проводка описывает движение объектов учета и, возможно, денег. Она не описывает состояние. Состояние описывает «остаток», дельту состояния — «оборот». И то и другое вычисляется по проводкам,

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

                                      Это и есть математическая модель учета.

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

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

                                        Тот же партионный учет не строят на проводках — не эффективно. Вы понимаете, что такое партионный учет? Если понимаете — должны понимать, почему его не строят на проводках.

                                        А многопередельное производство и разнообразная логистика может быть построена только на оперативных регистрах. Как Вы себе представляете, к примеру, реализацию диспетчирезации доставки с помощью двойной записи? Это строго оперативная задача. Вы понимаете, что такое многопередельное производство? Понимаете, что такое межцеховая логистика? Что такое диспетчеризация заявок? Если понимаете, то должны понимать, что в этих задачах самих корреспондирующих объектов нет и быть не может.

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

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

                                        Это все навскидку, из опыта. Есть ли у Вас подобный опыт?
                                          0
                                          Вы скрываете суть за терминами. Попробуйте раскрыть любой из них. Уверен что ничего сложно за этим нет. Более того уверен что и на проводки оно все ляжет очень даже неплохо.

                                          Я вот тоже знаю кучу терминов, о которых вы и не догадываетесь, но мне абсолютно незачем их тут писать.
                                            0
                                            Какой термин Вам раскрыть? Только учтите, что за большинством из них многостраничная постановка задач. И если Вы ими не занимались, то вряд ли Вы успокоетесь на первом уровне декомпозиции. Для проектирования таких систем работают команды аналитиков, общий объем трудозатрат — человеко-годы. Только результирующие модели в IDEF0 составляют сотни диаграмм. Число промежуточных измерятся ящиками бумаги.

                                            Может быть Вы думаете, что я Вам пыль в глаза пускаю?
                                            Мне кажется, Вы преувеличиваете свои способности консультанта — не представляете истинного масштаба задачи.
                                              0
                                              Я думаю вы не до конца понимаете как делается автоматизация.

                                              Объясните простым русским языком что такое «партионный учет» и чем он отличается от обычного учета.
                                              Если вы и этого не можете, то скорее всего вы и сами не очень понимаете.

                                              Простой тест Фейнмана: «если вы за 5 минут не можете объяснить ребенку чем занимаетесь, то вы шарлатан».

                                              Кстати как вы с таким подходом людей то обучаете? им тоже рассказываете про команды аналитиков и человеко-годы?
                                                0
                                                >>Объясните простым русским языком что такое «партионный учет» и чем он отличается от обычного учета.

                                                Партионный учет это метод учета запасов и расчета себестоимости товаров в разрезе партий товаров (документов поступления).

                                                А вот что такое «обычный учет» вопрос более интересный
                                                  0
                                                  И все? То есть надо к проводкам привязать Id партии, а потом по нему группировать при вычислений оборотов и остатков.

                                                  Это и есть самый обычный учёт. Таблица проводок с аналитическими признаками.
                                                    0
                                                    Да в общем ничего сложного, только без таблицы итогов и сотнях миллионов записей сервер при проведении документов будет умирать и требовать к себе нотариуса для написания завещания. Да и не требуется здесь двойной записи, нужна таблица остатков партий
                                                      0
                                                      на 100М я просто возьму olap. Он такое пережевывает и не кряхтит. Там тебе и остатки, и обороты и любой разрез за долю секунды.
                                                        0
                                                        Когда вы построите OLAP-кубы, работающие в режиме realtime (данные секунду назад являются устаревшими), учтете возможные блокировки таблиц и очередь проведения документов, тогда у вас получится та самая таблица остатков из 1С, только вид сбоку
                                                          0
                                                          А зачем мне в olap реалтайм? Учитывая специфику бухгалтерии и склада — большая часть операций проводятся задним числом, то процессинг изменений можно делать раз час и не чаще.

                                                          Для реалтайма надо индексированные представления, а при количестве проводок до 10М можно только на них и жить.

                                                          Да, это как раз будет что-то вроде таблицы остатков в 1С. Об этом я и говорю — за банальной таблицей остатков лежит математическая модель, только она в 1С не очень хорошо реализована.

                                                          rsdn.ru/article/db/RDBMS.xml
                                                          почитайте внимательно, от начала до конца. Статья кстати 10-летней давности, в ней описано решение проблем, над которыми до сих пор ломают голову многие разработчики.

                                                          Я пару раз делал решения по учету на основе информации из этой статьи. Работало так быстро, что даже не верили. 1С на подобной задаче думал по полчаса (хотя я не в курсе что там накрутили).
                                                            0
                                                            >>А зачем мне в olap реалтайм?
                                                            Например, для контроля остатков и резервирования. Представьте, что у вас два менеджера одновременно создают документ с одними и теми же позициями. Проводят документ и фигу там, не могут отправить товар, потому что его уже унес третий менеджер час назад, пока ваши кубы перестраивались.
                                                            Например, для правильного списания партий. Проводим один документ, он списывает партию номер 274, проводим второй документ, он спишет тот же документ, не видя изменений, остатки по партии уйдут в минус, весь партионный учет летит к чертям.
                                                            В случае проведения задним числом это как раз вдвойне актуально. Если бы всё проводилось только оперативно, то можно было бы взять остатки из OLAP на какую-то дату и по движениям с этой даты по настоящее время вычислить изменения. При проведении задним числом исправляем документ месячной давности, текущие остатки меняются, но в OLAP остаются неактуальные остатки, менеджеры не увидят товар на складе или наоборот видят то, чего уже нет.

                                                            >>Да, это как раз будет что-то вроде таблицы остатков в 1С. Об этом я и говорю — за банальной таблицей остатков лежит математическая модель, только она в 1С не очень хорошо реализована.
                                                            Не очень хорошо в каком плане? Есть какие-то замеры скорости? По-крайней мере в 1С это реализовано так, что разработчикам вообще не нужно думать ни про какие OLAP кубы, система сама построит всё что нужно и предоставит инструменты для получения остатков и оборотов простыми запросами, типа

                                                            ВЫБРАТЬ Остатки.КоличествоОстаток КАК Количество,
                                                            Остатки.СуммаОстаток Как Сумма,
                                                            Остатки.Номенклатура Как номенклатура
                                                            ИЗ РегистрыНакопления.ОстаткиТоваров Как Остатки
                                                      0
                                                      Нет, не достаточно по нему группировать при вычислении оборотов и остатков.
                                                      Еще важно то, что есть несколько процессов запроса партии, когда у нас большое количество пользователей.

                                                      Вот перечень операций
                                                      1. Партия формируется (товар поступает на склад)
                                                      2. Партия резервируется под ранее созданное размещение заказа
                                                      3. Партия попадает в свободный остаток
                                                      4. Партия резервируется из свободного остатка под заказ
                                                      5. Партия отгружается из резерва под заказу
                                                      6. В связи с дефицитом по приоритетному заказу резервы по партиям перераспределяются между заказами, остаток дефицита размещается к авторезервированию при поступлению
                                                      7. По партии возникает рекламация
                                                      8. Партия возвращается на склад от покупателя
                                                      9. Партия уходит на исправление брака
                                                      10. Партия возвращается после исправления брака

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

                                                      Дополнительная сложность. У Вас 20 тысяч позиций номенклатуры, 200 менеджеров, каждый делает порядка 15-20 запросов в минуту (один запрос — одна позиция номенклатуры, результат запроса — сводка по номенклатуре — свободный остаток, резерв по клиентам с приоритетом ниже текущего, резерв по клиентам с приоритетом равным текущему, ожидаемые поступления по дням на ближайшие 10 дней, свободные к размещению заказа, размещенные в заказах для клиентов с приоритетом ниже приоритета текущего клиента, размещенные в заказах для клиентов с приоритетом равным приоритету текущего клиента, конкурентные запросы по позиции в настоящий момент, отсортированные по приоритетам клиентов, эти данные необходимо изменять на клиенте онлайн).
                                                      Время принятия решения по запросу менеджером может меняться от 2-5 секунд, до 200-500 секунд. В то время, пока менеджер принимает решение необходимо, чтобы конкруренция за позицию на всех клиентах обновлялась как можно оперативнее.

                                                      Возьметесь делать на проводках? Будете использовать OLAP?
                                                        0
                                                        А что сложного? 10 операций, два набора индексированных представлений: по номенклатуре и по партиям.
                                                        Остальное в OLAP, так как поступления обычно заносятся не в real-time и смысла нет нагружать базу online пересчетом.

                                                        Только очень неправдоподобно 15-20 запросов в минуту от одного человека по номенклатуре в 20,000 товаров. Они их не будут успевать искать, менеджеры не роботы все таки. Статистика говорит что даже во время активной работы в информационной системе пользователь делает 4-5 действий в минуту. Остальное время он анализирует полученную информацию.

                                                        ЗЫ. бюджет какой?
                                                          0
                                                          Нужен полный онлайн. Документ поступления регистрируется еще до завершения приемки на складе и по отмашке проводится, чтобы как можно быстрее закрыть дефицит. Менеджеры заполняют отгрузки с высокой степенью автоматизации подобора, запросы нужны, чтобы как раз увидеть, что из запланированных позиций не может быть отгружено прямо сейчас и когда необходимо согласовать отгрузку.

                                                          Ручного анализа немного — только информация полезная для быстрого принятия решения с клиентом по ту сторону провода.

                                                          Бюджет был более менее, но — впритык. Полностью на IT (железо, SLA, лицензионный софт, внутренние и внешние разработчики) выделялось около 10 млн. долларов в год. На данную разработку (оперативная система управления сбытом) — меньше 25 млн. рублей
                                                            0
                                                            OLAP не справится по скорости загрузки данных. Без шансов
                                              0
                                              Вы видимо все же не понимаете, когда пытаетесь мне объяснять по проводкам. Я эксперт в управленческом учете, обучаю финансовых директоров, экономистов, плановиков. Вы верите, что можете чему-то меня научить в использовании проводок? Я — не верю.

                                              За спиной больше 1000 самых разных проектов автоматизации — от предпринимателя с десятью работниками, до холдинга, на 10000 сотрудников. Попробуйте понять, что не в проводках вопрос, ладно?
                                                0
                                                Верить или нет — ваше дело. То что вы эксперт в учете еще не значит что вы эксперт в архитектуре. Более того, практика показывает что эксперты в предметной области слабо представляют как автоматизировать эту самую область.

                                                Я вот тоже много кого обучаю, и проекты у меня покрупнее есть. Но это все нерелевантно.

                                                А вопрос был в том что я не видел чтобы учет не укладывался в проводки. Вы говорите что такой есть, но вместо объяснения сути — сыпите терминами. Именно это заставляет сомневаться в том что вы понимаете что я тут пишу. И никакой ваш опыт не оправдывает.
                                                  0
                                                  Не вижу смысла продолжать разговор. Если Вы не знаете терминов — учите матчасть. Не нужно заниматься шапкозакидательством.
                                                    0
                                                    Незнание не проблема, проблема — прятать незнание и непонимание за кучей терминов.
                                                      0
                                                      Кто же от Вас что-то прячет? :) Просто Вы не знаете их. Какая куча? Я Вам предложил назвать термин, который Вы хотите понять. Что Вам объяснить?

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

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

                                                      Если уж для Вас партионный учет — это нечто из категории «кучи терминов», то простите, что отнимаю Ваше время. Вы — чрезвычайно смелый человек, Вам горы по плечу, море по колену.

                                                      Знаете, в 20-25 лет все мы такие. Энергичные, деловые, низвергатели мифов и срыватели покровов. Все ж вокруг только бла-бла-бла, конечно же, а тут всего делов — взяться и сделать, правда?

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

                                                      Но меня не учите, ладно? Знания не нужно прятать. Их порой так трудно передать.
                                                        0
                                                        Не надо мне свои фантазии приписывать… я выше указал конкретный термин, другой человек ответил. Оказалось ничего там сложного, все укладывается в модель с проводкам. И нету там человеколет чегототам.
                                                          0
                                                          Ок, я там Вам подключусь к дискуссии, не видел
                                          0
                                          Сколько же там было данных, что они так долго операции выполняются? У нас в восьмерке база около 50Гб, тоже MS SQL, около 50 пользователей одновременно работают, один сервер нормально справляется средствами платформы, без специальных костылей и прямых запросов.
                                            0
                                            Ну, а Вы представьте себе крупную сбытовую систему многопрофильного холдинга, в котором 16 исходных баз данных, в каждой из которых от 10 до 60 пользователей-операторов, каждый из которых вводит в систему около 50-80 документов в день, в каждом из которых от 20 до 500 позиций. Это порядка 20 млн. строк в день. Ежедневный рост базы данных около гигабайта в день. Общий размер базы данных за неполных пять лет больше 5 Тб.

                                            А общая модель включает в себя еще 11 непростых производственных систем. Плюс логистика закупок. Плюс система доставки. Плюс 20 бухгалтерских отделов и 4 финансовых. Общий кластер из 15 очень приличных серверов.
                              –1
                              CQRS — маркетинговый буллшит. Асинхронная обработка, когда данные записываются сначала в быстрое, оптимизированное на запись, хранилище, а потом асинхронно агрегируются в информацию, пригодную для вывода, была известна очень давно. В некоторых приложениях это вообще единственный способ обеспечить приемлемое время отклика интерфейса.

                              А потом кто-то придумал что такой подход масштабируется на любое приложение.

                              Так вот: не масштабируется. Я не виде ни одного приложения, которое бы выйграло от внедрения CQRS просто потому что автору так захотелось. Архитектура, построенная на принципах REST оказывается в разы более понятной, гибкой и производительной. Асинхронную обработку к rest прикрутить довольно просто.

                              Особую популярность CQRS придали апологеты DDD, для которых CQRS единственный способ оправдать тормоза, вызванные DDD.
                                0
                                Про первые 3 абзаца соглашусь, но вот с тормозами DDD вы немного перегнули. Чему там тормозить то?
                                  0
                                  Ну как же — выводу тормозить. Показать список сущностей для DDD это проблема, потому что все грузится агрегатами. Если делать Lazy Load, то начинаются приколы вроде SELECT N+1, которые еще сильнее и совершенно непредсказуемо для внешнего наблюдателя бьют по производительности.

                                  Апологеты DDD возлагают всю часть, связанную с отображением данных, на CQRS. То есть агрегаты не пишут в сущности, а дают «события» из которых потом происходит сборка объектов для отображения. При этом сами агрегаты начинают тормозить еще сильнее (так как требуется их сборка), но в среднем только 5% времени происходит изменение данных и 95% отображение, поэтому увеличенные тормоза не видны.

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

                                  В итоге вместо прямой записи в базу ставится очереди, несколько воркеров, работают тяжелые механизмы синхронизации, все это покрыто весьма запутанным кодом с нечетким разделением на слои.
                                    0
                                    Где вы этих ужасов насмотрелись? :))
                                      0
                                      В реальных проектах. Подрабатываю консультантом, много всего повидал.
                                      0
                                      Видно сразу тех, кто сталкивался с DDD + Event Sourcing + CQRS :-) Так и есть: очереди, воркеры, агрегаты, синхронизация команд…

                                      А доводилось видеть код еще и где denormalizers записывают отображения в NoSql (т.н. reads store)?
                                        0
                                        Все доводилось. Но, имхо, переусложнение на ровном месте. Чаще всего приходится чинить производительность путём убирания всех этих наворотов.
                                        0
                                        Если делать Lazy Load, то начинаются приколы вроде SELECT N+1

                                        Проблема n + 1 возникает либо из-за неумения ORM c ней бороться (Hibernate умеет), либо от неумения пользоваться её фичами или непонимая принципов работы. Но вообще, на практике проблема n + 1 достаточно редко возникает, ибо DDD — это не отображение данных, а про бизнес-логику. Отображение данных — какая же там бизнес-логика? Я ещё понимаю, когда нужно какой-то тривиальный случай показать, ну, скажем, список сущностей показать в табличке — тогда да, можно и entity из БД грузить. И всё равно, в таблички данные небольшими порциями грузят, так что даже n + 1 в такой задаче не сильная помеха. Но кто же хибернейтом отчёты делает? Конечно, там чистый SQL, ну или какой-нибудь MyBatis, чтобы весь SQL в одном месте держать, а не распылять по коду.
                                          0
                                          На практике в каждом DDD приложении была или проблема SELECT N+1, или были слишком толстые агрегаты, которые банально тормозили при получении списка. Это вполне естественно для DDD, так как основная идея сделать манипуляции с данными так, как-будто это манипуляции с объектами.

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

                                          Я прекрасно понимаю что для оптимизации DDD надо выкинуть, что я неоднократно делал во многих проектах. Это как раз не в пользу DDD аргумент ;)

                                            0
                                            Дык если реально выкинуть DDD, то это реально минус 70% кода приложения. Слишком глобально же!
                                              0
                                              Я это к тому, что интересно послушать реальный опыт борьбы с тормозами в DDD :)
                                                0
                                                Я могу рассказать про способы борьбы с тормозами DDD, если вы сможете мне нормально донести, где же эти тормоза возникают. Я с тормозами DDD не боролся никогда, т.к. попросту не встречался с ними.
                                                  0
                                                  Значит у вас приложения не были под нагрузкой или они слишком простые, чтобы тормоза могли возникнуть.
                                                  А тормоза возникают вот так:
                                                  Делается модель (набор классов), она отображается в БД. При выполнении операции объекты начинают обращаться к связанным объектам, которые достаются с помощью Lazy Load. Поэтому если пишешь foreach цикл по вложенным объектам у тебя получается SELECT N+1. Причем цикл написать очень просто если работает несколько человек.

                                                  Пример.

                                                  Первый программист пишет метод:
                                                  class Order
                                                  {
                                                      public void ProcessOrder()
                                                      {
                                                          if(this.Customer.Age < 18) //Тут кастомер загружается LL, вроде ниче страшного
                                                          {
                                                              //Some logic
                                                          }
                                                      }
                                                  }
                                                  


                                                  Второй программист потом пишет такой код:
                                                  class Manager
                                                  {
                                                      public void BatchProcessOrders()
                                                      {
                                                          foreach(Order o in this.Orders)
                                                          {
                                                              o.ProcessOrder(); //SELECT N+1, только об этом узнать нельзя пока не заглянешь внутрь
                                                          }
                                                      }
                                                  }
                                                  


                                                  Самая большая проблема когда такой код в разных слоях.

                                                  Но вот проблема найдена, но как её исправить? Обычно тут вспоминают агрегаты. Говорят что будет Order грузится сразу с Customer. А потом из-за аналогичных проблем Order должен грузиться вместе с OrderLines.
                                                  А дальше банальный вывод списка 100 ордеров начинает тянуть мегабайт данных. При мало-мальской нагрузке падает все.
                                                  Далее, если есть голова на плечах, люди просто сносят DDD, делают anemic модель и управляют загрузкой данных. А вот если головы нет, то начинается политики оптимизации Hibernate, подсовываемые через AOP, CQRS, NoSQL, гигабайтные распределенные кеши и прочая гадость.

                                                  Для того чтобы не было таких проблем изначально надо соблюдать простое правило: бизнес логика должна явно управлять загрузкой (кешированием) данных и тянуть как можно меньше данных из базы. Это, к сожалению многих «архитекторов», ломает всю идею DDD.
                                                    0
                                                    В целом да, проблема с LL и ассоциациями имеет место быть. Даже если ты не пытаешься сделать DDD, а просто «в лоб» пользуешься ORM… Как её решать в терминах DDD лично мне пока не совсем понятно :-(
                                                      0
                                                      foreach(Order o in this.Orders)

                                                      Проблема в этой строке. Лично я вообще очень редко делаю маппинг one-to-many. Только для случав, когда «many» — это не более штук 20. Например, у того же Order можно так замаппить lines, и то не всегда, а только в каких-нибудь интернет-магазинах, где их реально будет штук 20, а не в логистических системах, где их спокойно может быть 2000. Если же имеет отношение вида «one-to-really-many», то, разумеется, надо выносить логику в service и грузить через запросы. В том же JPQL есть join fetch, есть query hints.

                                                      Но опять же, проблему производительности надо решать тогда, когда она действительно есть. Сколько заказов в секунду обрабатывается? Ну я помню, что в розничной сети на 100 магазинов в 30 регионах присутствия их было несколько сотен в сутки. Возникает он так — менеджер принимает решение что-то заказать и набирает заказ. Или создаёт по шаблону. Или за него их создаёт автоматическая система заказов (доля последних была небольшой из-за огромной ассортиментной матрицы, и никакие автозаказы не работали в принципе). Вот и надо мне явно управлять загрузкой в этом случае? Я за 5 минут напишу неторопливый процессинг заказов, который будет занимать 0.01% времени работы системы, чем изначально потрачу пару часов на анемик с явной загрузкой. Зато потом сяду и буду думать, как на чистом SQL и триггерах сделать хардкорную передачу чеков. Заметьте, логики тут мало, потому что в ERP попадут только Z-отчёты, а чеки нужны только для OLAP-системы, поэтому DDD тут не применим не столько из-за производительности, сколько по своему определению. А z-отчёты, конечно, лучше обработать в сервисах, где пойдут любые средства — хоть query хинты, хоть прямые запросы к базе. И маппить всю простыню z-отчёта на объект вовсе не обязательно в этом случае.
                                                        0
                                                        Если проблема в строке, то как её обойти? Не делать таких связей? Но это модель предметной области, в предметной области есть такие связи.

                                                        Вот видишь — ты сам признаешь что в любой нетривиальной системе (где есть one-to-really-many или другие сложные вещи) уже не работает DDD и надо что-то куда-то выносить.

                                                        А вот не-DDD подход, назовем его Function Driven Design (FDD), такими проблемами не страдает. Я спокойно делал обработку сотен тысяч элементов Linq запросами, просто оптимизируя индексы в базе. Для миллиона уже пришлось немного пошаманить с денормализацией и кешированием данных. И все это с сотнями одновременных пользователей.
                                                          0
                                                          Если проблема в строке, то как её обойти? Не делать таких связей? Но это модель предметной области, в предметной области есть такие связи.

                                                          Что мы моделируем? Мы моделируем абстрактную сущность. Заказ — это сам по себе модель будущей коробки, которая поедет в машине. Какая цель достигается за счёт прямого соответствия любого слова, написанного аналитиком, любому слову, написанного программистом? Полное взаимопонимание? Это утопия, и если есть смысл что-то в программе отразить не так, как аналитик пишет в своих опусах, то надо так и поступать. Для меня DDD — это не попытка подружить аналитика и программиста (хотя зачастую так и получается), а возможность создать удобный API, который позволит мне думать о логике программы, а не о том, когда что из каких источников загружать.

                                                          Вот видишь — ты сам признаешь что в любой нетривиальной системе (где есть one-to-really-many или другие сложные вещи) уже не работает DDD и надо что-то куда-то выносить.

                                                          А кто сказал, что DDD перестаёт работать в этом случае? Волне себе работает, только уже с использованием не вполне традиционных инструментальных средств и напильника. Но волшебный напильник всегда возникает, если нужна производительность, а высокая производительность нужна далеко не всегда. Вон недавно Роман Елизаров рассказывал, как они повыкидывали строки и объекты и всё запихнули в ByteBuffer'ы, и у них всё офигенно быстро заработало. Что, теперь так повсеместно и будем делать в любой программе?
                                                            0
                                                            Для того чтобы все не тормозило ты вынужден думать о том что и откуда ты загружаешь, увы. Хранилище пока что самая медленная часть любой системы.

                                                            То что ты называешь «напильником» по сути является не DDD, так как не соотвествует ни одному из его принципов. Даже CQRS не является DDD, ибо нету понятия команд и событий в переметной области если ты явно туда не включишь их.

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

                                                            Кроме того надо помнить о масштабируемости. Неоптимальные запросы, рождаемые DDD тормозят при десятках тысяч записей, просто выкидыванием DDD и небольшой оптимизацией запросов можно довести до ста тысяч с приемлимым быстродействием.
                                                              0
                                                              Для того чтобы все не тормозило ты вынужден думать о том что и откуда ты загружаешь, увы. Хранилище пока что самая медленная часть любой системы.

                                                              Если какой-то метод вызывается у меня раз в сутки и работает 10 секунд, мне нет смысла оптимизировать его до одной секунды. Зачем мне в этом методе думать, что я гружу и бороться с N + 1?
                                                              То что ты называешь «напильником» по сути является не DDD, так как не соотвествует ни одному из его принципов

                                                              Каковы принципы-то DDD?
                                                              Про высокую производительность я не говорил, я говорю о достаточно производительности при мало-мальски серьезной нагрузке, когда нагружается один сервак средней руки.

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

                                                              Какие неоптимальные запросы? DDD не про запросы :-) ORM — это инструмент, генерящий запросы, например. Не всегда запросы, генерящиеся ORM, являются узким местом. Где являются — там всегда можно делать прямые запросы. Кроме того, тут критикуют не столько сами запросы, сколько их последовательность. Например, N + 1 запрос вместо join'а (кстати, в ряде случаев n + 1 запрос сделать даже быстрее, чем один с join'ом). Ну это уже в асбтракции дело. Я не спорю, что самый быстрый код — это тот, который прямо из фронтэнда (или прямо из клиента) сделает запрос именно на те штуки, которые нужны для конкретного представления или конкретного удаленного сервиса. Вот в одном из комментов был пример с каким-то процессиногом shipment'а, и пример с тем, как сделать процессинг shipment'а, на который ведёт ссылочка от order'а. Так вот, всё можно было сделать проще: не делать лишний запрос на получение id shipment'а по id order'а, а сразу сделать join. Но тогда продублировалась бы часть запроса. Если есть время и желание на такие вещи — флаг в руки.
                                                                0
                                                                Поясню предыдущего оратора. Про неоптимальные запросы из-за DDD — это вопрос к тому, что мы в теории должны грузить связанные сущности из агрегатов. А агрегат — из репозитория. И сам агрегат не очень должен иметь доступа к репозиторию, чтобы качественно нам что-то отфильтровать\отсортировать. Он может только вернуть ссылку на полную неотсортированную коллекцию, а вы там дальше развлекайтесь как хотите, даже если вам нужно просто count(*)… where… по данной ассоциации.
                                                                  0
                                                                  Что заставляет нас в реализации того или иного метода использовать именно загрузку из агрегата/репозитория? Почему бы для конкретной ситуации, когда мы знаем, что всё будет вырождаеть в count(*), не запросить всё этим самым count и не поместить такой метод в сервис?
                                                                    0
                                                                    Требования DDD нас заставляют :) Репозитории только для агрегатов, всё остальное — уже через них (агрегаты). Полезете напрямую — gandjustas назовёт это «выкинуть DDD» :)
                                                                      0
                                                                      Понимаю некоторую иронию этого коммента. Но если серьёзно, то хочу сказать, что репозитории, агрегаты, сервисы и т.д. — это всё больше инструменты и паттерны, помогающие в решении определённых задач в рамках DDD-подхода. Соответственно, если они мешают, то их использовать и не надо. А DDD, ИМХО, это просто фраза «а давайте-ка представим нашу модель как совокупность объектов».
                                                                  0
                                                                  Ты все правильно делал, задача у тебя была очень простая. Не было ни сложных связей, ни больших нагрузок. Я бы в такой ситуации вообще взял какойнить конструктор приложений и не парился.

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

                                                                  Вообще о применимости DDD давно еще писал в блоге: gandjustas.blogspot.ru/2011/12/ddd.html

                                                                  ЗЫ. Проблем с дублированием запрос в linq нет, так как они прекрасно декомпозируются.
                                                                    0
                                                                    Да не просто по-другому структурируя код. Нужно было либо переходить на ужасный intly typing, либо плодить кучу обёрток для id, так же соорудить кучу различных DTO на все случаи жизни и вместо приятного chaining'а методов постоянно получать эти самые DTO и что-то из них выковыривать. Учитывая, что всё это на чистом SQL — никакой самодокументируемости модели и никакой инкапсуляции.
                                                                      0
                                                                      Ты читал ссылку что я дал? Я там все расписал. Можно уйти от DDD не не создавая тонны DTO, будет не очень оптимально, но все равно быстрее и гибче, чем DDD.
                                                                        0
                                                                        Читал. Там опять же, нарушается type safety, благодаря всем этим id. Плюс всё-таки непонятно, а что делать, если в бизнес-методе где-то необходимо получить несколько разных атрибутов объекта, с которым работаем, и что-то сделать с их значениями? Ну хорошо, если прямо из запроса получили, то прочитаем их из анонимного класса. А если есть метод, который их списком получает по какой-то сложной логике? Далее, как там с инкапсуляцией? Если у меня есть объект Shipment, у него свойство deliveryDate. К нему будет публичный getter и приватный сеттер, который будет вызываться в методе deliver. В анемике по сути вся модель лежит в БД, поэтому я всегда могу сделать update shipments set deliveryDate = current_timestamp. В DDD я тоже так могу сделать, но для DDD прямые запросы — это редкость, а анемик весь на них построен.
                                                                          0
                                                                          Господа, вы меня убедили. Вы оба (возьмём именно этот глагол, но возможны варианты) забыли что такое bounded context. Эванс всё прекрасно расписал. Про применимость DDD, исключения, уход от него и прочее.
                                                                0
                                                                На самом деле что-то мне подсказывает, что вы оба говорите об одном и том же и имя этому — bounded context из DDD. Очень такой красивый костыль :)
                                                          +1
                                                          Реальный опыт — при выводе списков использовать не доменные объекты, а легковесные DTO-шки. И всего делов.
                                                            0
                                                            Да-да, всего-то не использовать DDD ;)
                                                              0
                                                              На самом деле списки — это одно. Но если нужно именно в рамках бизнес-логики пройтись по ассоциациям, причём выбрать только удовлетворяющие условию связанные объекты и что-то над ними зашаманить — ещё один кандидат на N+1 или мегабайты данных из БД на каждый чих, т.к. нет прямого доступа к ORM и указанию стратегий загрузки, а есть только ссылка на агрегат.
                                                                0
                                                                Как я уже написал в одном из комментов — не надо на каждый чих плодить по ассоциации one-to-many. Скажем, вот есть строки у заказа, их немного, их есть смысл маппить. Но каждая строка ссылается ещё на товар. Вот делать маппинг вида «товар -> строки заказов, где он встречается» — совсем нет никакой необходимости. Такие вещи тянутся через ORM-ные запросы.
                                                                  0
                                                                  Да я знаю :) Но спасибо за очередное напоминание :)
                                                            0
                                                            Мой опыт показывает, что как раз DDD позволяет много бойлерплейта выкинуть. Вот скажем, начинаем мы писать в анемик-стиле. Что нам нужно? Ну во-первых, type safety. Ведь об объекте мы знаем по его ссылке. Не будем же мы кидать все данные об объекте, скажем, Shipment, в метод ShipmentService.deliver? Конечно, мы туда кинем какой-нибудь ShipmentKey, в котором будет только id, ибо кидать просто int — это уже не типобезопасно. Бойлерплейт. Ну а, скажем, наш Shipment назначен на обработку некоторому employee. Разумеется, что у нашего Shipment будет свойство employeeId типа EmployeeKey (опять же, тащить сюда просто int — не типопезопасно). Вот хотим мы в нашем методе deliver обратиться к каким-либо свойствам employee, например, имени. Вместо DDD-ного shipment.getEmployee().getName() придётся писать employeeRepository.getEmployee(shipment.getEmployeeId()).getName(). А если нужно вывести табличку shipment'ов с именами тех, кому они назначены? Ну конечно, на каждый из N shipment'ов надо вызвать employeeRepository.getEmployee(). N + 1!

                                                            А если включить голову, то можно отбросить догматы и разграничить те места, где наш анемик применим, а где — нет. Вот, скажем, код, отбирающий данные по delivery из таблички — это не бизнес-логика, сложного там ничего нет, а использоваться он, кроме тривиального случая в слое представления, нигде не будет. Значит, выносим во фронтэнд. Или, если клиент приложения совсем толстый (т.е. коннектится сразу к базе) — вообще чуть ли не в слой представления, возможно, через тоненькую обёртку. Ну так что же мешает сделать то же самое при использовании DDD?
                                                              0
                                                              А зачем тебе типобезопасность? Какую проблему ты собираешься решать передавая ShipmentKey вместо int? Кстати наличие таких оберток над данными затрудняет понимание кода очень сильно.

                                                              Если ты хочешь обратится к Employee.Name по Shipment.Id, то ты пишешь запрос:
                                                              from s in ctx.Shipments
                                                              where s.Id == id
                                                              select s.Employee.Name
                                                              


                                                              А если тебе надо табличку вывести, то ты делаешь так:
                                                              from s in ctx.Shipments
                                                              select new VievModel
                                                              {
                                                                 ShipmentId = s.id,
                                                                 ShipmentDate = s.Date,
                                                                 ShipmentAddress = s.Address,
                                                                 EmployeeName = s.Employee.Name
                                                              }
                                                              


                                                              Причем такие запросы прекрасно декомпозируются, вместо ctx.Shipments ты можешь передать уже отфильтрованный набор.

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

                                                              Ты просто не понимаешь как делать нормальный Data Access, у тебя мозг уже поражен DDD и ты DDDшные паттерны несознательно пытаешься воткнуть везде.
                                                                0
                                                                Эволюция на хабре. Совсем недавно то же самое говорили про ООП :)
                                                                  +1
                                                                  Кстати, если выводить данные именно таким образом, а обрабатывать логику в стиле DDD — вот мы и получаем намёк на CQRS :))
                                                                    0
                                                                    Если ты хочешь обратится к Employee.Name по Shipment.Id, то ты пишешь запрос:

                                                                    id shipment'а в этом запросе я откуда взял? Скажем, есть какой-то метод, который ожидает на входе id shipment'а? Тогда хочу разочаровать — этот метод менее понятен. Пока я не прочитаю документацию, трудно понять, что за целое число туда можно передать. Я могу взять и случайно взять целое число из другого источника. Например, shipment возник на основании order'а, у меня есть order и я хочу вызвать наш метод для того shipment'а, который возник на основании order'а. Как быть? Ну, допустим, я напишу
                                                                    from s in ctx.Shipments
                                                                    where s.Order.Id = orderId
                                                                    select s.id
                                                                    

                                                                    А потом возьму, и вместо полученного таким образом id передам ещё раз orderId?
                                                                      0
                                                                      Мне кажется вы уже немного перегибаете. Метод с подобной сигнатурой:
                                                                      public void ProcessShipment(int shipmentId)
                                                                      

                                                                      не менее понятен, нежели
                                                                      public void ProcessShipment(ShipmentKey id)
                                                                      

                                                                      и т.д.
                                                                        0
                                                                        Ну хорошо, а если есть InboundShipment и OutboundShipment, то какой из них подразумевался в методе shipmentId? Кроме того, хорошо, насчёт документации я действительно несколько перегибаю, но вот как быть с уходом от типизации? Как бы контроль типов — это очень и очень большое подспорье в деле написания качественного ПО.
                                                                          0
                                                                          А в этом случае уже не перегибаете :) Но, вполне возможно, на вход можно принять и сам объект. 50\50.
                                                                        0
                                                                        В чем проблема с пониманием?
                                                                        class BusinessLogic
                                                                        {    
                                                                            public int GetShipmentIdByOrderId(int orderId) 
                                                                            {
                                                                                //И без реализации понятно что метод делает...
                                                                            }
                                                                        }
                                                                        


                                                                        Самое важное что в таком методе мне не нужно инстанцировать класс Shipment и\или Order для получения значения. Ибо если Shipment или Order окажутся очень толстыми (агрегатами), то оно «внезапно» станет тормозить в 10 раз больше. И все ляжет при появлении серьезной нагрузки.
                                                                          0
                                                                          И что, так и будет int-based typing? Или лучше обозвать intly typing? idy typing? И так по всей бизнес-логике?

                                                                          Кстати, ещё забыл один момент про все эти id. Полиморфизма нет.
                                                                            0
                                                                            Да, будет. А что в этом плохого?
                                                                            И зачем тебе полиморфизм?
                                                                              0
                                                                              Ну я даже не знаю, как объяснить. Контроль типов позволяет избежать множества ошибок. Не панацея конечно же, можно совершить много ошибок, которые система типов не поймает, но всё же, если среда сама поймает 80% моих ошибок — это уже очень и очень хорошо. Любые подходы, которые это ломают (использование динамической типизации вместо статической, пресловутый stringly typing или вот этот intly typing — это) стоит воспринимать крайне осторожно и стараться выбирать альтернативы, которые используют статическую типизацию. Но всё равно, это остаётся вопросом долгого холивара, и я уж скажу так — либо мы принимаем требование «статическая типизация» как данность, либо нам вообще не о чем говорить.

                                                                              Зачем полиморфизм. А вот задачка. Система управления бизнес-процессами выдаёт задачки сотрудникам. Есть общий абстрактный класс Task и у него абстрактный метод execute. Каждая конкретная задача наследуется от этого класса и определяет свой execute. Как такое сделать в анемик-стиле, я представляю, но уж как-то очень костыляво выходит. А в DDD даже делать ничего не надо, всё поддерживается ООП-фичами языка.
                                                                                0
                                                                                Ты же понимаешь что ничего бесплатно не бывает. тебе надо будет писать тонну кода, чтобы поддерживать типизированные Id. А решаемые проблемы крайне абстрактны. Не помню ни одного случая чтобы не тот Id был передан.

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

                                                                                  0
                                                                                  Вообще когда мы говорим о работе с данными в SQL, то ООП только палки в колеса вставляет.
                                                                                  Sad but true.
                                                                    0
                                                                    Вывод данных случается 95% времени и занимает 70% кода приложения

                                                                    С этим тезисом я согласен, однако…
                                                                    Это самая важная часть логики приложения, потому что при плохом выводе приложением невозможно пользоваться

                                                                    Вывод данных — это просто не бизнес-логика. Бизнес-логика — это что будет делаться, например, при публикации документа (отправка сообщений системам с которыми интегрируемся, рассылка почты заинтересованным пользователям, ну и пересчёт агрегатов, если необходимо, и т.п). Соответственно, эти вещи находятся в ведении DDD. Но DDD просто ничего не говорит о выводе данных. И я считаю, что раз сложной логики там нет. Сложным может быть UI, но это уже слой представления, и там нет никакого разговора о БД. А то, что касается БД, там тривиально до невозможности — простой select. Такой select пишется руками без каких-либо проблем. Но главное, что такой select не будет нигде больше использоваться, кроме как для представления. Задача DDD — предоставить удобный объектный API к бизнес-логике. Но API к select'у… Зачем? Точнее, API предоставить можно, но зачем select'у возвращать живую сущность, с которой что-то можно сделать? Достаточно обычного анемичного POJO/POCO/что ещё там в других языках.
                                                                    Я прекрасно понимаю что для оптимизации DDD надо выкинуть, что я неоднократно делал во многих проектах. Это как раз не в пользу DDD аргумент ;)

                                                                    Ну конечно, всегда можно не освоить какой-то инструмент и объявить, что он не нужен, ибо порождает больше проблем. А можно пересилить себя и разобраться. Так некоторые люди и ООП выкидывают, и что угодно другое. Ну и да, действительно, DDD — это не универсальное средство, не везде он годится. Скажем, для системы генерации отчётов или для интеграции enterprise-систем — ну вообще никак не подходит.
                                                                      0
                                                                      Для пользователя нет никакой разницы что ты называешь бизнес-логикой, а что нет. Пользователь видит интерфейс программы, интерфейс плохой === программа плохая. DDD не позволяет сделать хороший интерфейс ибо все тормозит.

                                                                      ЗЫ. Кстати частый паттерн у апологетов DDD — обвинять других в незнании DDD. Я тебя расстрою, я DDD делал еще в 10 лет назад, на делфях, когда еще и ORMов не было. И даже тогда это была не очень хорошая идея.
                                                                        0
                                                                        Для пользователя нет никакой разницы что ты называешь бизнес-логикой, а что нет. Пользователь видит интерфейс программы, интерфейс плохой === программа плохая. DDD не позволяет сделать хороший интерфейс ибо все тормозит.

                                                                        Последнее утверждение — голословное. А то что нужно писать хорошие программы для пользователей — да, я не спорю. А ещё нужно писать хорошие программы для программистов, потому что если на очередную самую маленькую хорошесть для пользователей компания-разработчик будет требовать $8К, то такую компанию очень быстро пошлют куда подальше, если, конечно, компания не монополист и не SAP.
                                                                          0
                                                                          Как раз наоборот. Компании типа SAP могут совершенно ужасные интерфейсы насождать, а потом драть деньги за обучение. Хотя последнее время у SAP интерфейс вполне юзабельный, чего не скажешь о большинстве кустарных бизнес-приложений.

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

                                                                          Особенно это ярко прослеживается на рынке мобильных приложений — частота использования приложения обратно пропорциональна времени открытия.
                                                                            0
                                                                            Так, про что я выше писал? Про то и писал, что их внедренцы выставляют астрономические счета за самые рядовые фичи. И это, пожалуй, одна из немногих компаний, которых за это не посылают. Любую другую быстро послали бы и пошли к той, которая бы выставляла более разумные ценники. Так вот, отсюда делаем вывод, что программа должна быть не только привлекательной и удобной, она должна быть ещё открытой для внесения изменений. Соответственно, нельзя писать программу, достигая максимума производительности любой ценой, из-за чего, якобы, получим отзывчивый интерфейс. Нужно принимать во внимание разные факторы, в том числе стоимость сопровождения.
                                                            0
                                                            Event sourcing действительно имеет огромную плюшку — полная и качественная история изменений сущности. Но вот почему светлые умы осознанно решили усложнить себе жизнь и восстанавливать состояние объектов исключительно по цепочке событий, попадая на всевозможные «любое единожды происшедшее события необходимо поддерживать навечно» и «отписался от новостей, а затем подписался… и так раз 50 (на протяжении года)» — не пойму.

                                                            Что мешает иметь поток событий, но при этом по старинке изменять и сохранять состояние соответствующих сущностей системы?
                                                              0
                                                              А кто-нибудь спрашивал зачем иметь полный лог всех изменений? В большинстве приложений нужно иметь лог 2-3 сущностей и он явно ведется, и занbмает это не более пары десятков строк. Еще два годна назад писал статью как делать аудит изменений в Entity Framework: gandjustas.blogspot.ru/2010/02/entity-framework.html
                                                              Никакого rocket science нет и сложная архитектура не нужна.
                                                                0
                                                                Понятно что каждому инструменту — своё применение. Я сейчас про личный опыт говорил. Нам полный лог — нужен. Но добровольно вставлять себе палки в колёса с собиранием состояний сущности из команд я не рискнул. Но помимо лога, разделение логики приложения на команды имеет и другие плюшки всё же.

                                                            Only users with full accounts can post comments. Log in, please.