Sunday, January 25, 2009

Методы оптимизации производительности Linq to SQL и Entity Framework

Я хотел бы закончить начатое в предыдущих нескольких заметках (часть 1, часть 2) и докладе дело и привести набор методов по оптимизации производительности L2S и EF. В какой-то степени эту заметку можно считать текстом к презентации. Наверняка, это не все, что можно сделать с этой точки зрения, но это как минимум основные способы, которые могут стать отправной точкой для ваших собственных действий.

Зачем нам все это нужно?

Первым делом давайте обсудим, а зачем вообще нужно оптимизировать производительность этих инструментов? Неужели она настолько плоха, что ими нельзя пользоваться без этого? Вовсе нет, можно и даже нужно! Просто нужно понимать, что за огромное ускорение времени разработки, исчезновение нудного процесса создания Data Access Layer вручную и снижение затрат на дальнейшие изменения в базе данных и модели нужно чем-то платить. А платить приходится двумя вещами: временем на изучение нового инструмента (однажды) и некоторой потерей гибкости и производительности. В разных ORM потери производительности разные, но все же вы должны отдавать себе отчет в том, где и когда использовать ORM, а где и когда - нет.

За год работы с Entity Framework на достаточно крупном проекте я использовал в лучшем случае пару способов из приведенных ниже. И это несмотря на то, что как оказалось для нашего приложения использование ORM было, возможно, не самым лучшим выбором. У нас было несколько бизнес правил, которые заставляли нас вытаскивать чуть ли не полбазы в память, чтобы подсчитать некоторые данные для всего лишь одной сущности! При этом из-за иерархичности данных считать их в базе было еще большим злом, чем считать в памяти (поверьте, мы пробовали и меряли производительность). Поэтому, как вы сами понимаете, за dotTrace я брался довольно часто. Но, как правило, обычно все заканчивалось тем, что я находил либо ляп в коде, либо добавлял локальный кеш, чтобы не вытаскивать одни и те же данные из EF сотни, а то и тысячи раз. Надо сказать, что почти все проблемы с производительностью мы побороли через кеширование данных и правильное использование контекста EF. Но ближе к релизу приложения я начал замечать, что мы уже оптимизировали почти все, что можно и что bottleneck медленно, но уверенно переместился к границе EF, и что дальше придется оптимизировать уже его. Следствием этого и стало исследование, результаты которого я привожу сейчас.

Основные места потери производительности

Сначала я хотел бы остановиться на основных местах, где теряется производительность, т.к. от этого зависят и методы оптимизации:

1. Инициализация инфраструктуры. При "холодном старте" и L2S и EF теряют здесь какое-то время. Это происходит всего лишь один раз для апп-домена, но тем не менее может стать неприятной неожиданностью. L2S справляется с этим быстрее, но в EF можно пре-генерировать его внутренние views еще до запуска приложения и таким образом сократить время инициализации в разы. То есть для EF эту потерю (иногда очень значительную при первом запросе) оптимизировать можно.

2. Накладные расходы, связанные с маппингом. И L2S, и EF имеют эти расходы, но в EF они больше, т.к. в нем намного больше вариантов маппинга: наследование, тип на таблицу, несколько типов на таблицу, несколько таблиц на тип, сложные типы. К сожалению, тут особо не разбежишься, т.к. все происходит внутри ORM. Возможно, есть какие-то хаки, но я о них на данный момент не знаю. Поэтому будем считать, что оптимизация этой задачи нам недоступна.

3. Анализ запросов. Это могут быть linq-запросы (Linq to SQL, Linq to Entities) или EntitySQL для EF. Здесь ситуация выглядит получше. Разобранные EntitySQL запросы кешируются на уровне апп-домена, что позволяет не заниматься этим несколько раз. Ключом выступает как раз само текстовое представление запроса, так что будьте внимательнее и используйте параметры. С linq-запросами сложнее. Так как linq - это дерево объектов, которое представляет собой запрос, использовать его в качестве ключа довольно сложно. Но в таком случае запрос можно "откомпилировать" один раз (то есть, грубо говоря, закешировать его разобранный вид и команду, которая будет построена на нем) и потом выполнять его многократно. Тоже помогает.

4. Генерация SQL-запросов. Как и маппинг, происходит глубоко внутри, поэтому добраться туда и как-то повлиять сложно. Но нужно сказать, что почти все провайдеры реализуют внутри себя различные способы оптимизации, такие как кеширование планов запросов. В то же время во всех ORM есть одна и та же проблема: генерируемый SQL-результат не всегда оптимален (хотя как правило это так). Причем, что интересно, EF генерирует запросы в большинстве своем более оптимально, чем L2S. Один из основных советов здесь - в случае получения на выходе неоптимального запроса просто перестройте запрос в коде: поменяйте джойны, разбейте на несколько более мелких. Это помогает.

5. Материализация. Это процесс создания объектов по полученным реляционным данным. Занимает львиную долю времени от времени выполнения самого запроса. Однако оптимизирован за счет того, что контекст как L2S, так и EF (да и сессия NHibernate тоже) сохраняет у себя внутри материализованные объекты и при повторном обращении к этому же объекту уже не создает его повторно (если вы сами не скажете). Конечно, это сделано не для оптимизации, а для того, чтобы в рамках одного контекста у вас был только один объект Заказ с ID=7 в независимости от количества запросов. Ну, и чтобы трекать изменения в этих самых объектах. Но подобное "кеширование" еще и увеличивает производительность, что тоже является хорошей новостью. В защиту процесса материализации нужно добавить еще и то, что даже если вы пишете свой Data Access Layer, то вы все равно в том или ином виде занимаетесь материализацией. Вам же тоже нужно превратить записи из DataReader в какие-то объекты доменной модели. Да, ручной код более оптимален, т.к. заточен для решения лишь одной задачи, но, поверьте, ORM тоже умные люди пишут :)

Методы оптимизации производительности

Итак, какие же методы оптимизации производительности можно предложить. За конкретными цифрами и сравнениями вы можете обратиться к презентации.

1. Пре-генерация views в Entity Framework. По шагам расписана в MSDN и частично здесь, поэтому подробно останавливаться не буду.

2. Отключение трекинга объектов (tracking). Дает возможность немного улучшить производительность за счет того, что материализованные объекты не отслеживаются контекстом. Обратная сторона - сделанные в объектах изменения не могут быть сохранены в базу. Плюс (здесь я могу ошибиться, но вряд ли) отключается кеширование материализованных объектов в контексте, что в большинстве своем намного сильнее ухудшает производительность для большого количества запросов. Так что решайте сами, но мы у себя не использовали.

В L2S трекингом можно управлять через свойство контекста:

context.ObjectTrackingEnabled = false;

В EF - через ObjectQuery<T>, что не так удобно:

entities.Orders.MergeOption = MergeOption.NoTracking;
entities.Customers.MergeOption = MergeOption.NoTracking;

Подробнее про трекинг в EF можно почитать в MSDN.

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

В нашем веб-приложении мы использовали интересную стратегию: создавали один контекст на http request и сохраняли его в Request. Все запросы в рамках одного http request работали через этот контекст, таким образом обеспечивая определенное кеширование. Плюс к этому у нас не было проблем, связанных с тем, что одна сущность была вытянута из одного контекста, а другая - из второго. Ну, и наконец, т.к. обработка http request занимает от силы несколько секунд, то проблемы с конкуренцией были сведены до минимума. В случае же, когда нам нужно было сохранять состояние объектов на протяжении нескольких http request'ов (например, для реализации многостраничных диалогов или многостраничных таблиц с поддержкой сохранения изменений), мы временно переносили контекст в сессию, а когда нужно было сохранять данные, вызывали context.ApplyPendingChanges(), и убирали контекст из сессии. Таким образом, на следующем запросе он снова создавался в рамках http request и все продолжало замечательно работать.

Но не переборщите с этим методом. Настоятельно не рекомендуется использовать один и тот же контекст из нескольких сессий или запросов (например, поместив его в Application). Мало того, что вы получите себе на голову все вышеупомянутые проблемы, так еще и в случае параллельного выполнения нескольких запросов (а каждый http request - это отдельный поток, если вы не знали) вы получите красивый exception :)

4. Компиляция linq-запросов. Как я уже говорил, linq-запросы, в отличие от EntitySQL, не кешируются автоматически. Для того, чтобы скомпилировать запрос, нужно выполнить очень простой вызов:

protected static Func<NorthwindClassesDataContext, IQueryable<Order>> compiledGetOrders =
    CompiledQuery.Compile(
        (NorthwindClassesDataContext ne) =>
            (from o in ne.Orders
             select o));

При этом не имеет особого значения, используете вы Linq to SQL или Linq to Entities. Вы просто передаете туда либо контекст L2S, либо контекст EF.

Однако, хотел бы предостеречь тех людей, которые уже побежали переписывать свой код. Дело в том, что компиляция запроса - процесс довольно длительный, поэтому он имеет смысл лишь в том случае, если вы делаете несколько вызовов одного и того же запроса (пусть и с разными параметрами). И еще лишь в том случае, если у вас действительно сложный linq-запрос. Для случая, приведенного выше, компиляция запросов не дает существенной выгоды. Для случая с одним запросом откомпилированный запрос может работать дольше неоткомпилированного. Так что it depends.

Подробнее про компиляцию запросов можно прочитать все в том же MSDN. Также советую глянуть статью о потоке выполнения запроса в EF.

5. Использование EntitySQL. Во всех источниках написано, что самый быстрый способ работы с EF - это EntitySQL. И, как показывают тесты, это действительно так. В то же время, EntitySQL не так удобен, как linq, не поддерживает code completion (это же простая строка), не типобезопасен (у вас нет проверки уровня компиляции) и в целом выглядит как обычный SQL-код в вашем C#-коде, что не придает ему эстетичности. Да и работает не намного быстрее компилированного linq. Так что я бы советовал использовать его лишь в самых серьезных случаях. Хотя в таких случаях еще лучше написать хранимую процедуру.

Выглядит это где-то так:

string query = "SELECT VALUE p FROM NorthwindEntities.Products AS p"
    + " JOIN NorthwindEntities.Order_Details AS od ON od.ProductID == p.ProductID"
    + " JOIN NorthwindEntities.Orders AS o ON o.OrderID == od.OrderID"
    + " WHERE o.Customers.CustomerID == @customerId";
var products = 
    entities.CreateQuery<Product>(query, new[] { new ObjectParameter("customerId", customerId) }).ToList();

Подробнее почитать про EntitySQL можно в одной из заметок на ADO.NET blogs и в MSDN.

6. Использование жадной загрузки (eager loading). Во-первых, нужно сказать, что и L2S, и EF по умолчанию реализуют ленивую загрузку (lazy loading). Только L2S делает это неявно, то есть сразу при обращении к навигационному свойству или коллекции, а в EF нужно явно вызвать метод Load(). Что лучше, что хуже - можно спорить долго. Жаль, что в EF не сделали включение/выключение явного/неявного режима через какое-нибудь свойство. Поэтому нам вот пришлось самим сделать неявную ленивую загрузку через кодогенерацию (я как-нибудь расскажу об этом подробнее). Но разговор сейчас не об этом. По тестам жадная загрузка, конечно же, выигрывает у ленивой, причем иногда весьма существенно. Однако если вы загрузите кучу данных, а потом не будете ее использовать - какой вам от нее прок? В этом случае, ленивая загрузка оказывается на высоте. Как же написать запрос в "жадном" стиле? В Entity Framework это делается через ObjectQuery.Include():

(from o in entities.Orders.Include("Order_Details")
select o).ToList();

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

var allProductsData = 
    (from cust in entities.Customers
    where cust.CustomerID == customerId
    select new
        {Orders = 
            from ord in cust.Orders
            select new
                {Products = 
                    from det in ord.Order_Details
                    select det.Product}}).ToList();

List<Product> products = new List<Product>();
var productsData = allProductsData.FirstOrDefault();
if (productsData != null)
    foreach (var ordersData in productsData.Orders)
    {
        foreach (Product product in ordersData.Products)
        {
            products.Add(product);
        }
    }

Хоть это и не eager loading в чистом виде, но в то же время этот способ обходит вариант прохождения от Customer до его Products через навигационные свойства, которые бы подняли данные через lazy loading.

7. Оптимизация обновления данных. Здесь в первую очередь хочется отметить, что по тестам EF значительно опережает L2S в этом аспекте. Причем дело не в SQL-запросах, так как их производительность, за некоторыми исключениями, почти одинакова. Проблема кроется где-то внутри L2S, поэтому учитывайте и этот аспект при выборе способа доступа к базе данных. Для оптимизации выполнения сгенерированных SQL-запросов в L2S советую глянуть еще на атрибут UpdateCheck, который определяет будет ли сгенерирован where по всем полям сущности или только по ее Id (это делается для реализации оптимистической конкуренции). В EF такая проверка делается только по измененным колонкам, а в L2S - по всем. Также стоит подумать об реализации колонки Version для этой самой конкуренции.

8. Оптимизация SQL-запросов. Как я уже говорил, единственный способ - следить за ними и менять входные linq или EntitySQL запросы. Также по результатам осмотра можно наставить индексов в базе данных. Ну, и никто не отменял хранимые процедуры, куда можно поместить наиболее серьезные запросы. Так что основной совет здесь - не бояться браться за SQL профайлер в случае необходимости :)

Еще интересен тот факт, что EF в целом генерирует более оптимальные запросы, чем L2S. Так что имейте и это в виду.

Выводы

В целом, я описал все основные средства оптимизации производительности. Если вам интересны подробности, для EF существует целая статья в MSDN, посвященная этому вопросу. Также советую интересную серию заметок в блогах ADO.NET. Для Linq to SQL советую почитать следующую серию заметок, хотя она немного устарела.

В целом, из своего опыта могу сказать, что производительность Entity Framework уже достаточна для реализации кучи приложений. Думаю, в следующей версии она станет еще лучше. Если же вы знаете, что этого вам будет мало, то можете посмотреть на Linq to SQL. И пусть вас не пугает, что MS решила не развивать L2S в той же мере, что и EF - уже есть примеры высоконагруженных приложений, успешно использующих L2S, не говоря о том, что это просто замечательная замена всяким типизированным и прочим датасетам (брррр). Ну, а в особо критичных к производительности случаях нам все равно никуда не деться от старого-доброго ADO.NET :)

Если сравнивать Entity Framework с другими полноценными ORM, такими как NHibernate, LLBLGen Pro и др, то на данный момент я не могу ничего толком сказать. Надо попробовать, благо тестовое приложение, которое я написал, с легкостью расширяется другими тестовыми провайдерами. Могу лишь кое-что сказать об NHibernate, т.к. я успел его попробовать и почитал дополнительные материалы о нем. NH по производительности занимает место где-то в промежутке между L2S и EF. Плюс этот прекрасный ORM уже давно на рынке и обладает множеством других интересных способностей, например, поддержкой Persistence ignorance и кешом второго уровня, которых пока нет в Entity Framework. Так что при выборе ORM я бы однозначно смотрел и в его сторону.

На этой замечательной ноте я бы хотел завершить цикл постов про производительность L2S и EF :) Вполне возможно, в ближайшем будущем у нас появятся дополнительные данные по производительности NHibernate и еще нескольких интересных ORM. Если у вас есть желание помочь в этом - можете скачать тестовое приложение, написать свой тестовый провайдер (это делается абсолютно несложно, поверьте мне), и расшарить результаты.

Всем спасибо!

Saturday, January 24, 2009

Москва

Совсем недавно наткнулся на фотографии из Москвы, которые мы сделали этим летом, будучи там проездом по дороге на Байкал. Когда я выкладывал байкальские, хамар-дабанские и иркутские фотографии, то решил, что Москве уже один фотоальбом был посвящен в 2007 году, поэтому второй можно и отложить. А между тем в фотографиях образца 2008 года тоже было немало хороших, поэтому решил выложить и их на пикасе.

Можно по-разному относится к Москве, как к городу. Кто-то скажет, что Москва - очень красивый город, особенно летом, особенно возле Кремля :) Город с развитой инфраструктурой, большими возможностями, богатой историей. Кто-то возразит, что сейчас город заметно сдал, везде развешана реклама, идет снос старинных зданий, и что вообще ему далеко до Питера. Да и вообще, понаехали тут... Я же скажу, что для меня Москва - это прежде всего один из древнейших на Руси городов, и бывшая столица нашей некогда необъятной родины. Москва всегда была коммерческим, торговым городом, а потом ей еще и не повезло, когда она стала столицей сначала Российской империи, а потом - СССР и России. Это во-многом определило судьбу города, его застройку и дух. Тому же Питеру в этом плане, на мой взгляд, повезло чуть больше. Питер намного моложе, но его расцвет пришелся на расцвет Российской империи, а потом он не попал под жернова столичной жизни. Наверно, поэтому и сохранился как исторический и культурный памятник намного лучше. Ну да хватит сравнивать - сегодня мы поем оду Москве :)

Я никогда не был в Москве долго. Зато уже 4 раза был там проездом :) И скажу честно, 2 года назад город мне не очень понравился. Не смотрелся он по сравнению с Питером, да и с Киевом тем же, откровенно говоря, тоже. Но с каждым следующим приездом я все больше убеждался, что в Москве есть что-то особенное, какая-то своя неповторимая изюминка. Москва поражает не только размахом и шиком, но и своим внутренним духом, который еще не успел исчезнуть под давлением свалившихся буржуазно-демократических отношений. Да, самые красивые для глаза места - это стандартный набор: Кремль, Красная площадь, храм Христа Спасителя, Воробьевы горы, Новодевичий монастырь, Арбат, Царицыно, и др. Но там вы не почувствуете атмосферу города. Это все в основном для туристов. Лучше отойти в сторону от всего этого, убежать в обычные московские кварталы, погулять там. Вот там можно прочувствовать, как и чем живут обычные москвичи, понять их. А может, даже соприкоснуться с духом этого великого во всех отношениях города.

А пока несколько фотографий Москвы образца 2008 года:

 Picture 509 Picture 523 Picture 522

  IMG_8971Picture 558 Picture 554

Picture 563 Picture 529

Доклад на харьковской UNETA. Материалы

Вчера прошел мой первый доклад на харьковской встрече UNETA. Я попытался разобраться с производительностью Linq to SQL и Entity Framework, взяв в качестве бенчмарка производительность аналогичных запросов в чистом ADO.NET, а также дать какие-то советы по их улучшению. Надеюсь, тем, кто пришел, было не очень скучно :)

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

Как я и обещал, выкладываю материалы доклада:

Скачать тестовое приложение и презентацию можно отсюда:

Тестовое приложение: http://data-access-orm-comparison.googlecode.com/files/DataAccessPerformanceTest_v2.zip

Презентация: http://data-access-orm-comparison.googlecode.com/files/L2S%20and%20EF%20performance%20analysis.zip

Постоянная ссылка проекта: http://code.google.com/p/data-access-orm-comparison/

Для запуска тестового приложения вам понадобится лишь поставить базу данных Northwind и изменить строки подключения. Приложение достаточно расширяемо, так что вы можете добавить туда свои собственные тестовые провайдеры, например, для работы с NHibernate или LLBLGen Pro.

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

Удачи в ваших исследованиях!

Sunday, January 4, 2009

Domain Driven Design: введение

Пару месяцев назад мне в руки попала книжка по Domain Driven Design (DDD). Отдельную рецензию на нее я напишу позже, когда дочитаю до конца. А пока что хотелось бы поделиться мыслями, которые у меня возникли в процессе прочтения.

Для начала я бы сказал, что DDD переворачивает взгляд на архитектуру приложения. Слабее, чем это делает MVC, но все же. Как нас учили проектировать в университетах, какие были наши первые проекты в реальной жизни, как программируют в 80-90% случаев в программистких конторах? Очень просто и прямолинейно. Есть у нас требования, анализируем предметную область (domain), находим сущности, анилизируем их поведение, действия, создаем высокоуровневую архитектуру, выбираем технологии, если нужно, строим UML-диаграммы. А дальше проектируем базу данных и от нее уже пляшем: слой доступа к данных (data access layer), слой бизнес-логики (business logic layer), слой представления (presentation layer), сервисы, etc. Т.е. снизу вверх. Все создаваемые таблицы в базе данных, как правило, мапятся на объекты предметной области как один-к-одному (с небольшими исключениями). Далее создаются сущности приложения, которые, как правило, подчиняются структуре сущностей в базе данных для удобства дальнейшей работы (опять же, могут быть исключения). Даже если мы используем какой-нибудь ORM (тот же Entity Framework или NHibernate), но генерируем сущности по базе данных, то мы все равно выходим на т.н. Data Driven Design, когда структура данных диктует нашу объектную модель. Да, это работает для решения многих задач, но не всех.

Domain Driven Design - совершенно другой зверь. Как следует из названия, это просто еще один способ проектирования, который фокусируется прежде всего на предметной области, на объектах реального мира, их поведении и взаимодействии, то есть фокусируется на модели и бизнес-логике, а не на структуре данных. Результатом подобного смещения приоритетов становится то, что мы стараемся перевести объекты предметной области и их поведение сразу в сущности приложения, пытаясь передать в них не только свойства и атрибуты этих объектов, но и поведение. Например, сущность "автомобиль" в DDD будет обладать не только атрибутами марка, цвет, цена, скорость и др., но и, по-возможности, каким-то поведением, связями с другими объектами. При этом проблема сохраняемости этих объектов куда-либо (например, в базу данных) отходит на второй план. Мы об этом не думаем, таким образом достигая сразу нескольких целей:

  1. Откладывание реализации слоя сохранения (persistence layer или data access layer) позволяет реализовать его тогда, когда доменная модель уже спроектирована и реализована, то есть когда она устаканилась. Сколько раз в начале разработки приложения вы добавляете или убираете колонки в таблицах или даже целые таблицы? Сколько при этом приходится переписывать кода слоя доступа к данным? Хорошо еще, если вы используете ORM.
  2. Слой сохранения реализуется как всего лишь один из инфраструктурных инструментов, а не как жизненно важный слой приложения. Акцент смещается на модель, а не на базу данных (или что у вас там) и слой доступа к ней. Это дает намного более сильный уровень независимости ваших классов от слоя сохранения (т.н. persistence ignorance), который дает возможность быстро поменять базу данных или перейти с нее на XML или вообще какие-нибудь внешние сервисы. По сути, это снижение связанности (decoupling).
  3. Persistence ignorance дает еще одну полезную возможность. За счет того, что ваш код может жить и без слоя сохранения, его можно намного проще протестировать. Более того, в начале разработки вообще можно забить как на слой сохранения, так и на слой представления и написать сначала модель (по сути, слой бизнес логики), которую покрыть модульными тестами. Причем тесты здесь выполняют уже не столько роль парашюта на будущее, сколько роль клиентского кода более верхнего или того же слоя, при помощи которого вы можете отточить API классов модели, а также быстро поправить дизайн классов при необходимости. Слой же сохранения в таком случае можно для начала просто закрыть мок-объектами. Если пойти дальше, то в случае необходимости можно написать даже слой представления над моделью, еще даже не начав писать слой сохранения.
  4. Из предыдущего пункта вытекает то, что если вы апологет TDD, то DDD - это точно ваше. Более подходящий для TDD способ проектирования и разработки приложений сложно себе представить.

Кроме всего прочего, Domain Driven Design стимулирует проектировать Rich Domain Model, то есть сущности, которые обладают не только состоянием, но и поведением. В результате разработки посредством Data Driven Design, мы очень часто получаем приложение, разработанное с использованием т.н. анемичной модели (Anemic Domain Model), в которой наши "бизнес" объекты на самом деле являются всего лишь сущностями для передачи данных из базы данных клиенту (UI, сервисы) и назад, т.е. обладают состоянием, но не поведением. Причем получаем мы это не потому что по-другому в Data Driven Design нельзя, а просто потому что в голове у нас это все те же записи из базы данных, просто переведенные на уровень кода. И как записи в базе данных не обладают никаким поведением, так и наши сущности, полученные оттуда, тоже сложно научить этому постфактум. Можно долго спорить о том, хороша ли анемичная модель или нет - это глупо. Просто как и у любого другого инструмента или подхода, у этого также есть свои положительные и отрицательные стороны и своя сфера применения. В приложениях, ориентированных лишь на CRUD операции, анемичная модель работает отлично. В приложениях, где много бизнес логики - уже не так хорошо. Бизнес логику приходится выносить в отдельные классы, которые будут оперировать объектами, хотя очень часто она по сути своей она принадлежит объектам.

Однако это еще не все. DDD - это не просто какая-то очередная методология разработки или догма проектирования. Скорее, это просто набор принципов, шаблонов, на которые можно опираться при проектировании. DDD очень активно оперирует ООП, паттернами, принципами (SOLID и пр.). Это вектор, направляющий разработку в несколько иное русло и помогающий решать задачи по-другому.

А что вы используете при проектировании приложений?

Дополнительно почитать о DDD можно в умных книжках:

  1. Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software
  2. Jimmy Nilsson. Applying Domain-Driven Design and Patterns: With Examples in C# and .NET
  3. Tim McCarthy. .NET Domain-Driven Design with C#: Problem - Design - Solution (Programmer to Programmer)

И в онлайне:

  1. http://domaindrivendesign.org/
  2. http://hinchcliffe.org/archive/2005/03/20/189.aspx
  3. http://www.emxsoftware.com/Domain+Driven+Design/
  4. http://www.infoq.com/articles/ddd-in-practice
  5. http://www.udidahan.com/2007/03/06/better-domain-driven-design-implementation/