8 октября 2009 в 09:02

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

.NET*

Введение

Данная статья предназначена к прочтению разработчикам и архитекторам распределенных систем на платформе .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.
Vitaly Baum @butaji
карма
100,2
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (36)

  • +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

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