All streams
Search
Write a publication
Pull to refresh
61
22
Стас Выщепан @gandjustas

Умею оптимизировать программы

Send message
Ты читал ссылку что я дал? Я там все расписал. Можно уйти от DDD не не создавая тонны DTO, будет не очень оптимально, но все равно быстрее и гибче, чем DDD.
Как раз наоборот. Компании типа SAP могут совершенно ужасные интерфейсы насождать, а потом драть деньги за обучение. Хотя последнее время у SAP интерфейс вполне юзабельный, чего не скажешь о большинстве кустарных бизнес-приложений.

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

Особенно это ярко прослеживается на рынке мобильных приложений — частота использования приложения обратно пропорциональна времени открытия.
Да, будет. А что в этом плохого?
И зачем тебе полиморфизм?
Ты все правильно делал, задача у тебя была очень простая. Не было ни сложных связей, ни больших нагрузок. Я бы в такой ситуации вообще взял какойнить конструктор приложений и не парился.

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

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

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

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

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

Кроме того надо помнить о масштабируемости. Неоптимальные запросы, рождаемые DDD тормозят при десятках тысяч записей, просто выкидыванием DDD и небольшой оптимизацией запросов можно довести до ста тысяч с приемлимым быстродействием.
В чем проблема с пониманием?
class BusinessLogic
{    
    public int GetShipmentIdByOrderId(int orderId) 
    {
        //И без реализации понятно что метод делает...
    }
}


Самое важное что в таком методе мне не нужно инстанцировать класс Shipment и\или Order для получения значения. Ибо если Shipment или Order окажутся очень толстыми (агрегатами), то оно «внезапно» станет тормозить в 10 раз больше. И все ляжет при появлении серьезной нагрузки.
Если проблема в строке, то как её обойти? Не делать таких связей? Но это модель предметной области, в предметной области есть такие связи.

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

А вот не-DDD подход, назовем его Function Driven Design (FDD), такими проблемами не страдает. Я спокойно делал обработку сотен тысяч элементов Linq запросами, просто оптимизируя индексы в базе. Для миллиона уже пришлось немного пошаманить с денормализацией и кешированием данных. И все это с сотнями одновременных пользователей.
А зачем тебе типобезопасность? Какую проблему ты собираешься решать передавая 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шные паттерны несознательно пытаешься воткнуть везде.
Для пользователя нет никакой разницы что ты называешь бизнес-логикой, а что нет. Пользователь видит интерфейс программы, интерфейс плохой === программа плохая. DDD не позволяет сделать хороший интерфейс ибо все тормозит.

ЗЫ. Кстати частый паттерн у апологетов DDD — обвинять других в незнании DDD. Я тебя расстрою, я DDD делал еще в 10 лет назад, на делфях, когда еще и ORMов не было. И даже тогда это была не очень хорошая идея.
Да-да, всего-то не использовать DDD ;)
Значит у вас приложения не были под нагрузкой или они слишком простые, чтобы тормоза могли возникнуть.
А тормоза возникают вот так:
Делается модель (набор классов), она отображается в БД. При выполнении операции объекты начинают обращаться к связанным объектам, которые достаются с помощью 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.
Все равно не понимаю. Ну и что что зависит что-то от состояния?
Проводка описывает движение объектов учета и, возможно, денег. Она не описывает состояние. Состояние описывает «остаток», дельту состояния — «оборот». И то и другое вычисляется по проводкам,

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

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

Вроде же написано в той статье на которую я давал ссылку.
На практике в каждом DDD приложении была или проблема SELECT N+1, или были слишком толстые агрегаты, которые банально тормозили при получении списка. Это вполне естественно для DDD, так как основная идея сделать манипуляции с данными так, как-будто это манипуляции с объектами.

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

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

Когда количество проводок переваливает за 10 миллионов, то уже надо не средствами СУБД пытаться победить (они не предназначены для этого), а использовать OLAP решение. Оно само делает то, что вы описываете.

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

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

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

ЗЫ. Строить отчеты по документам, особенно в 1С — жесть, врагу не пожелаешь.
Все доводилось. Но, имхо, переусложнение на ровном месте. Чаще всего приходится чинить производительность путём убирания всех этих наворотов.
В реальных проектах. Подрабатываю консультантом, много всего повидал.
Классическая же статья есть: rsdn.ru/article/db/RDBMS.xml

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

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

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

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

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

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

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

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

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

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

Кстати в современных СУБД, c механизмами материализованных индексов, делать явный пересчет остатков и оборотов нет смысла. Можно все сделать индексом и работать это будет в разы быстрее.

Information

Rating
333-rd
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Software Architect, Delivery Manager
Lead
C#
.NET Core
Entity Framework
ASP.Net
Database
High-loaded systems
Designing application architecture
Git
PostgreSQL
Docker