Вы подготовились к приходу 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.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 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
            Я с трубом поняла, о чём речь.
            Пришлось дважды прочитать.
            • НЛО прилетело и опубликовало эту надпись здесь
              • –1
                Нда, сначала создаем себе трудности, а потом героически их преодолеваем.
                • 0
                  альтернатива?
                  • 0
                    Не лезть в DDD.
                    DDD вообще, неокрепшим девелоперам разрывает мозг напроч — вышеизложенная статья характерный пример.
                    90% кода занимается тем, что перекладывает из одного в другое и, конечно же мы нарисуем супермаппер, чтобы облегчить эту тяжелую работу.

                    • 0
                      как бээ что то не делать — это не альтернатива
                      альтернатива это делай не так а вот так))
                      • 0
                        Это альтернатива. Альтернативой созданию себе трудностей собственными руками, является не создавание себе трудностей. Хорошо, переформулирую свою мысль — «делай не DDD».
                        По признанию самого Фаулера, область применения Rich Dmain Model — очень узка, по моим наблюдениям, ее не видно и в микроскоп, но в последнее время складывается впечатление, что как только девелопер открывает книжку про DDD, мозг у него отключается. Народ начинает писать строго следуя тому, что завещал великий (Фаулер/ Эванс/Нильсен — нужное подчеркнуть), а потом мужественно борется с последствиями и на выходе получаются такие вот фреймворки по снижению негативного влияния других фреймворков.
                        Поэтому, прежде чем влезать в DDD надо задать себе вопрос «а зачем?», «какую проблему я решаю, применяя здесь DDD, Rich Domain и прочие околоархитектурные изыски?». Такой вопрос в принципе неплохо бы себе по чаще задавать…
                        Есть еще более правильный путь — понять, что стоит за DDD, и при разработке пользоваться базовыми принципами. И если следуя этоим базовым принципам, таки получилось что-то похожее на DDD, значит в этом проекте действительно должно быть DDD, но вот если DDD не срастается — значит не судьба. В этом случае, необходимость в подобных автомаперах сильно уменьшится, а код будет проще, понятнее и его будет легче сопровождать и расширять, проверено на людях.
                        • 0
                          вы правы. два года и фейл после ддд в проекте. сейчас переписываем на обобщенные дто:) идет неплохо. и никаких фреймворков.
                          • 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
                                  В том, что не придется писать кучу однотипных присваиваний значений полей между связываемыми типами?

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