Вы подготовились к приходу AutoMapper?

    Введение

    Данная статья предназначена к прочтению разработчикам и архитекторам распределенных систем на платформе .NET. В ней будет рассмотрен гибкий каркас для объектно-объектного преобразования (далее маппинга). Так же будут рассмотрены некоторые аспекты Domain-Driven Design’а.

    Зачем мне нужен объектно-объектный маппинг?

    Следуя основным принципам DDD, мы реализуем так называемую Rich Domain Model (эти объекты также должны соответствовать принципу POxO). Объекты реального мира, нашедшие отражение в нашем приложении частенько так же передают достаточную сложность, следовательно, достаточно корректно построенная модель крайне тяжело поддаётся перемещению между слоями приложения (не путать с легкостью вносимых изменений). Data Transfer Object Тем не менее достаточно часто появляется необходимость “распределения” (я имею ввиду создание промежуточных сущностей, а не растекание модели по слоям) модели между слоями, для отображения, к примеру, атрибутов её сущностей пользователям (в шаблонах представления MVx), а так же передаче по сервисам (Data Transfer Object). Порой бывает даже, что модель “распределяется” для тестирования некоторых аспектов. Предположим, мы в Африке, у нас банановая плантация, всё классно, выращиваем, продаём, выращиваем, продаём, но тут внезапно внутренний рынок переполняется и нам надо расширятся (к примеру вести бананы в Россию), мы пишем WCF сервис, который будет слать наши бананы. Так как бананы в Африке имеют несколько иное значение, чем в России, то, соответственно нам понадобятся лишь некоторые атрибуты (остальные фактически не имеют значения), которые мы забубеним в наш DTO
    Правильнее было бы дать классу BananaWrapper название BananaDTO, для того, чтобы точно отображать его функциональное назначение, но я оставлю название таким для большего уровня абстракции, к примеру, если нам понадобится сделать автомат по продажи бананов и поместить этот объект в Presenter Model
    Хочу заметить, что порой задача преобразования объектов становится довольно-таки нетривиальной и в лучшем случаем выглядит примерно подобным образом (это решение в лоб, есть ещё более изощренные методы ;)):
    1. public class Banana
    2. {
    3.  public string Country { get; set; }
    4.  public double Price { get; set; }
    5.  public double NationalTax { get; set; }
    6.  public bool CocaineInjection { get; set; }
    7.  public bool FreshForFooding { get; set; }
    8.  public string AkunamatataName { get; set; }
    9.  public string BananaGeneration { get; set; }
    10.  public int Age { get; set; }
    11. }
    12.  
    13. public class BananaWrapper
    14. {
    15.  public string Country { get; set; }
    16.  public double Price { get; set; }
    17.  public int Age { get; set; }
    18. }
    19. public class BananaMapper
    20. {
    21.  public BananaWrapper GetWrapper(Banana banana)
    22.  {
    23.   return new BananaWrapper
    24.     {
    25.      Country = banana.Country,
    26.      Price = banana.Price,
    27.      Age = banana.Age
    28.     };
    29.  }
    30. }
    * This source code was highlighted with Source Code Highlighter.
    Думаю, что такой код писать, а тем более сопровождать, мало кому будет в радость, в последнее время я как раз частенько встречался с такого рода задачами, и находился в поиске решения проблемы.

    AutoMapper

    И тут на сцену выходит наш персонаж – AutoMapper, и сразу же говорит мне: — послушай, ты что такое пишешь? тебе не лень? ты не боишься допустить ошибок? хочешь я тебе помогу?!.. Я конечно же соглашаюсь, и получаю в ответ следующее решение моей проблемы:
    1.  public class BananaMapper
    2. {
    3.   public BananaMapper()
    4.   {
    5.    Mapper.CreateMap<Banana, BananaWrapper>();
    6.   }
    7.  
    8.   public BananaWrapper GetWrapper(Banana banana)
    9.   {
    10.    return Mapper.Map<Banana, BananaWrapper>(banana); ;
    11.   }
    12. }
    * This source code was highlighted with Source Code Highlighter.
    Класс, и это действительно всё, что мне понадобится. Сложность, вышележащего примера снизилась в моих глазах до нуля. Итак, что же за механизмы лежат внутри AutoMapper? AutoMapper проверяет есть соответствующие поля в указанных типах, соответствие проводится как по имени свойства, так и по его типу. Даже такие нюансы, как Product.Name и ProductName будут учтены и обработаны автоматически (wow!). Плюс ко всему методы GetXXX() будут ложится на свойства XXX (да, ну и естественно для особо раздражительных все эти прелести можно отключить и переопределить всё в своих собственных таблицах соответствия (далее мапках)). Кастомная конфигурация выглядит примерно следующим образом:
    1. Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
    2.   .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
    3.   .ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour))
    4.   .ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute));
    * This source code was highlighted with Source Code Highlighter.
    Кстати, все ваши кастомные конфигурации легко поддаются проверке с помощью следующего метода:
    1. Mapper.AssertConfigurationIsValid();
    * This source code was highlighted with Source Code Highlighter.
    Так же не плохо работает с:

    История

    Проект появился в конце’08-начале’09, около полугода находился в версии 0.31, сейчас же добрался до RC 1.0, думаю, что релиз уже совсем скоро.

    Overhead?

    banana Дебаты по поводу того, насколько быстрее будет работать AutoMapper и присвоение свойств в ручную (и прочие мульки) я игнорирую, т.к. готов пойти на любые жертвы производительности, если получу ясный, читабельный код. Ах, да, автор AutoMapper позаботился об этих вопросах и написал бенчмарки, смотреть здесь: http://code.google.com/p/automapperhome/source/browse/#svn/trunk/src/Benchmark

    Ресурсы

    Скачать проект, а так же ознакомиться с исходными кодами можно здесь: http://code.google.com/p/automapperhome/ Обсуждения каркаса здесь: http://groups.google.com/group/automapper-users Так же примеры использования есть здесь: http://automapper.codeplex.com/ Кстати проект разрабатывает Joe Benninghoven, который так же пишет BDD фреймворк для .NET под названием NBehave.
    Поделиться публикацией
    Комментарии 37
      +1
      спасибо за статью,
      буквально только вчера прочитал статью Дино Эспозито про плюсы и минусы DTO, а тут еще немного материала для размышления

      * по моему для лучшего понимания стоило в примере сделать класс Banana более сложным, с большим количеством полей, чтобы показать отличие от DTO-класса
        0
        пожалуй так и сделаю
          0
          эх DTO весчь
          мы сначала кидались DO объектами прямо взятыми из EF контекста через сервер, но потом сделали DTO и все стало гораздо удобнее). и вот AutoMapper то чего так не хватало
            +1
            оверкодинга много? в смысле напряжно ли было вам делать DTO-классы? и делали ли вы их для всех сущностей или выборочно как-то? вообще, может статью напишите со своим опытом? интересно
              0
              много,
              сами классы не напряжно, а вот то что тут названно мэппингом, а я бы назвал конвертером, было тяжко. do объекты имеют связи замкнутые, колец несколько и они еще и друг с другом связаны — в общем жуть
              статью, да имеет смысл — наш пример как раз применение EF в многоуровневом приложении, и чего мы только не словили с ним, и уже 1,5 года и все равно всплавают подробности пейзажа.
              вот завтра улетаю в отпуск, постараюсь изложить за месяц
                0
                отлично, жду с нетерпением
                0
                Оверкодинга нет, если не делать DO и оставить только дто :) Т.е, не юзать сторонний ОРМ. Возникает задачка написания кода, который будет класть дто в бд и читать в дто из базы, но как показывает практика, все неплохо инкапсулировалось до уровня «это-сюда». Контроля больше, скорости выше:)
                  0
                  не использовать ORM — это не айс :-) зачем тогда DTO нужны?
                    0
                    Как интерфейсная часть между слоями:)
                      0
                      гложат меня сомнения о том, что хуже: самописный велосипед для доступа к данным или небольшой оверкодинг? :)
                        0
                        В чем лисапед? Лисапеда нет:)
                        адо.нет и все такое это не лисапед:)
            0
            Мне тоже приходилось сталкиваться с этой задачей. Мне кажется что пример мог бы быть и посложнее, потому как обычное копирование полей или свойств можно реализовать через DynamicMethod – заплатить стоимость reflection один раз, а дальше пользоваться. Также для простых примеров наверняка неплохо подойдет Boo.
              0
              писал под level 100, с примером решил не усложнять
                0
                будет вебкаст?
              0
              Я с трубом поняла, о чём речь.
              Пришлось дважды прочитать.
              • НЛО прилетело и опубликовало эту надпись здесь
                  –1
                  Нда, сначала создаем себе трудности, а потом героически их преодолеваем.
                    0
                    альтернатива?
                      0
                      Не лезть в DDD.
                      DDD вообще, неокрепшим девелоперам разрывает мозг напроч — вышеизложенная статья характерный пример.
                      90% кода занимается тем, что перекладывает из одного в другое и, конечно же мы нарисуем супермаппер, чтобы облегчить эту тяжелую работу.

                        0
                        как бээ что то не делать — это не альтернатива
                        альтернатива это делай не так а вот так))
                          0
                          Это альтернатива. Альтернативой созданию себе трудностей собственными руками, является не создавание себе трудностей. Хорошо, переформулирую свою мысль — «делай не DDD».
                          По признанию самого Фаулера, область применения Rich Dmain Model — очень узка, по моим наблюдениям, ее не видно и в микроскоп, но в последнее время складывается впечатление, что как только девелопер открывает книжку про DDD, мозг у него отключается. Народ начинает писать строго следуя тому, что завещал великий (Фаулер/ Эванс/Нильсен — нужное подчеркнуть), а потом мужественно борется с последствиями и на выходе получаются такие вот фреймворки по снижению негативного влияния других фреймворков.
                          Поэтому, прежде чем влезать в DDD надо задать себе вопрос «а зачем?», «какую проблему я решаю, применяя здесь DDD, Rich Domain и прочие околоархитектурные изыски?». Такой вопрос в принципе неплохо бы себе по чаще задавать…
                          Есть еще более правильный путь — понять, что стоит за DDD, и при разработке пользоваться базовыми принципами. И если следуя этоим базовым принципам, таки получилось что-то похожее на DDD, значит в этом проекте действительно должно быть DDD, но вот если DDD не срастается — значит не судьба. В этом случае, необходимость в подобных автомаперах сильно уменьшится, а код будет проще, понятнее и его будет легче сопровождать и расширять, проверено на людях.
                            0
                            вы правы. два года и фейл после ддд в проекте. сейчас переписываем на обобщенные дто:) идет неплохо. и никаких фреймворков.
                              0
                              А расскажите в чём именно фейл случился? Интересно было бы почитать.
                                0
                                acerv.habrahabr.ru/blog/59210/
                                ну вот вкратце. правда, придется меня в друзья добавить:)
                                  0
                                  что-то я публикацию всё равно не могу прочитать. Доступ закрыт.
                        0
                        В DDD лазать не надо, факт.

                        Но паттерны DDD можно успешно применять и в небольших проектах. Например, работать с объектной моделью все-таки проще, чем с Typed DataSet. А передавать в UI эти, условно назовем их, объекты-сущности как-то некайфово. Например, у меня есть класс Product, и у него свойство Category типа ProductCategory. Тогда, например, в какой-нибудь EditModel мне понадобится свойство CategoryId, чтобы воспользоваться дропдауном. А во ViewModel — свойство CategoryName. Навешивать эти свойства на наш класс Product — нарушать SRP. Класс распухнет и станет неуправляемым, даже если у него нет какого-то существенного поведения. А вот dto можно, при желании, сгенерить через T4, отмаппить автомаппером, и никаких дополнительных хлопот у нас не будет.

                        Короче, если не комплексовать по поводу того, что у меня анемичная доменная модель, то вполне можно разделять entity и DTO, и не жужжать…
                    –1
                    Начало статьи показалось весьма сложным и чуть было даже не отложить чтиво, но пересилил себя и дочитал. К великому удивлению к концу статьи все оказалось до нельзя очевидным и понятным.

                    Возможно стоило начать с примера и закончить теорией — для большей доступности материала для тех, кто «не в теме», так сказать.

                    За статью спасибо, покрайней мере определился что в текущем проекте этот мапер мне на врятли понадобится. Но на будущее — буду иметь ввиду.
                      +1
                      public bool CocaineInjection { get; set; } а ты шутник :)
                        0
                        Прикольная поделка. На таких самое приятное программить и кишки платформы изучать. А главное – огромная польза! ;)
                          0
                          А Business Logic Toolkit не пробовали? bltoolkit.net/

                          RSDN-овский проект ( www.rsdn.ru ), с гораздо более длинной историей чем год развития и обширными возможностями.
                          С мэпингом объектов там все очень культурно: bltoolkit.net/doc/Mapping/ObjectToObject.htm

                          Как видно, наглядность на высоте. :)

                          А если учесть что это и не 1\100 его фич, например оно так же замечательно мэпит базы:
                          bltoolkit.net/doc/DataAccess/index.htm — так и вообще сказка. :)
                            +1
                            emitmapper.codeplex.com/
                            работает на два порядка быстрее аутомаппера и блтулкита.
                              0
                              в песочнице появилась неплохая статья про emitmapper.

                              habrahabr.ru/sandbox/8980/
                                0
                                спасибо, дело сделано ;)
                              • НЛО прилетело и опубликовало эту надпись здесь
                                  0
                                  Не понимаю в чем преимущество маппера над extension методами а-ля
                                  public static BananaWrapper ToBananWrapper(this Banana entity)
                                  вынесенными в отдельный класс DataExtensions
                                    0
                                    В том, что не придется писать кучу однотипных присваиваний значений полей между связываемыми типами?

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

                                  Самое читаемое