{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/automapper-to-zlo-czy-na-pewno","result":{"data":{"markdownRemark":{"html":"<p>Jakiś czas temu przeczytałem <a href=\"https://www.admu.pl/automapper-to-zlo/\">artykuł pn. \"AutoMapper to zło\"</a>. Autor w skrócie odradza używanie AutoMappera, z co najmniej dwóch powodów:</p>\n<ul>\n<li>fakt, że mapowanie między różnymi typami mamy \"gratis\", zachęca do nadmiernego tworzenia kolejnych \"warstw\" modeli, co często nie jest potrzebne</li>\n<li>niestandardowa logika mapowania jest potencjalnym źródłem poważnych i trudnych do wychwycenia błędów.</li>\n</ul>\n<p>O ile daruję autorowi clickbaitowy tytuł wpisu, to jednak pozwolę sobie nie zgodzić się z tak postawioną tezą: że AutoMapper to biblioteka, która więcej szkodzi, niż daje pożytku.</p>\n<p>Zacznijmy od początku - co sami autorzy AutoMappera o nim mówią:</p>\n<blockquote>\n<p>AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. This type of code is rather dreary and boring to write, so why not invent a tool to do it for us?</p>\n</blockquote>\n<p>W skrócie chodzi więc o to, by nie musieć pisać ręcznie kodu, który przekopiowuje dane z obiektu typu A do obiektu typu B. Kropka. Nie ma tu mowy o żadnych warstwach, DTOsach itp. - po prostu mamy obiekt typu A i chcemy go \"przekonwertować\" na typ B. Powinniśmy więc najpierw zadać sobie pytanie, kiedy z takimi sytuacjami możemy się spotkać, a dopiero potem - czy AutoMapper jest właściwym do tego narzędziem.</p>\n<p>Gdy myślę o użyciu AutoMappera - a więc o sytuacjach konwersji danych z typu A na typ B - to przychodzą mi do głowy następujące przykłady:</p>\n<ul>\n<li>chcemy odseparować / odróżnić model API od modelu, którego używamy w aplikacji,</li>\n<li>chcemy przenieść dane pomiędzy typami, które o sobie nic nie wiedzą</li>\n<li>checemy uprościć model zwracany do klienta, bo ten bazodanowy - ze względu na użycie ORMa - jest trochę niewygodny.</li>\n</ul>\n<p>Poniżej opiszę te przypadki.</p>\n<h1>Jak odseparować model API od modelu aplikacji?</h1>\n<p>Wyobraźmy sobie, że mamy prostą REST-owo-CRUD-ową nakładkę na bazę danych. Mamy tam encję <code class=\"language-text\">Product</code>. Dopóki jest ona prosta, to po prostu w endpointach przyjmujemy i zwracamy dokładnie tę samą klasę <code class=\"language-text\">Student</code>, której używamy w modelu bazdanowym (np. używając EF Core).</p>\n<p>Co jeśli chcemy jednak zapisywać też informacje \"audytowe\", tj. kto i kiedy zmodyfikował encję. Dodajemy pola <code class=\"language-text\">LastModifiedDate</code> i <code class=\"language-text\">LastModifiedBy</code>. Jeśli chcemy, by np. na stronie te dane były wyświetlane, to świetnie, od razu dostajemy je gratis, bo przecież używamy tej samej klasy <code class=\"language-text\">Student</code>. Ale co z endpointami od aktualizacji? Czy checmy, aby klient mógł \"zapostować\" na endpoint <code class=\"language-text\">/students</code> JSONa z dowolnie wypełnionymi polami <code class=\"language-text\">LastModifiedDate</code> i <code class=\"language-text\">LastModifiedDate</code>? Raczej nie, to by mogło pozowlić komuś podszyć się pod innego użytkownika albo antydatować zmianę. Możemy to oczywiście obsłużyć/zabezpieczyć na poziomie np. kontrolera, tj. zawsze odpowiednio nadpisywać te pola, jeśli robimy aktualizację. No ale wówczas po co te pola w modelu API? Jeśli opisujemy nasze API za pomocą np. Swaggera, to w wygenerowanej dokumentacji będą one uwzględnione - jak powiedzieć klientowi, że \"nie, te pola są nieistotne, możesz je zignorować\". Trochę słabo. :/ W końcu, skoro używamy języka typowanego (C#), to typy powinny opisywać dokładnie to, co się dzieje.\nW takiej sytuacji AutoMapper może być pomocą - wprowadzamy typy <code class=\"language-text\">StudentViewApiModel</code>, <code class=\"language-text\">StudentUpsertApiModel</code> i możemy z nich / do nich mapować naszą klasę <code class=\"language-text\">Student</code>.</p>\n<h1>Jak przenieść dane pomiędzy typami, które o sobie nic nie wiedzą?</h1>\n<p>Weźmy teraz na tapetę inną sytuację - mamy trzy projekty:</p>\n<ul>\n<li><code class=\"language-text\">Students.Domain</code> - logika biznesowa</li>\n<li><code class=\"language-text\">Students.WebApi</code> - Web API dla tej logiki</li>\n<li><code class=\"language-text\">Students.FunctionApp</code> - cloudowa funkcja, która też operuje na tej logice (nie wiem, czyta z kolejki, uruchamia się w jakichś odstępach czasowych itp.)\nOczywiście dwa ostatnie projekty zależą od pierwszego. Jeślibyśmy chcieli korzystać wszędzie z klasy <code class=\"language-text\">Student</code> pochodzącej z projektu <code class=\"language-text\">Students.Domain</code>, to możemy natrafić na schody: jakiś problem typu opisany w poprzedniej części. Załóżmy zatem, że zdecydowaliśmy się wprowadzić oddzielny typ - powiedzmy <code class=\"language-text\">StudentQueueModel</code> - np. w projekcie <code class=\"language-text\">Students.FunctionApp</code> i chcemy jakoś przekonwertować dane z tego typu na ten domenowy. Klasa <code class=\"language-text\">Student</code> nic nie wie o klasie <code class=\"language-text\">StudentQueueModel</code>, nie możemy zrobić więc metody <code class=\"language-text\">Student FromQueueModel(StudentQueueModel model)</code>. Możemy w drugą stronę - w klasie <code class=\"language-text\">StudentQueueModel</code> zrobić metodę <code class=\"language-text\">Student ToDomainModel()</code> i pisać mapowanie ręcznie. Albo - możemy też użyć AutoMappera.</li>\n</ul>\n<h1>Jak uprościć model, który mamy \"w środku\", zanim wyślemy go do klienta, \"na zewnątrz\"?</h1>\n<p>Może być tak, że biblioteka ORM wymaga od nas pewnych dodatkowych pól lub konstrukcji, których nie chcielibyśmy wystawiać klientowi. (Ja natrafiłem na taki problem z podkolekcją elementu typu prostego dla EF Core: zamiast <code class=\"language-text\">ICollection&lt;string></code> musiałem stworzyć typ <code class=\"language-text\">SubItem</code>, z unikalnym kluczem i wartością typu <code class=\"language-text\">string</code> - i jego użyć w kolekcji <code class=\"language-text\">ICollection&lt;SubItem></code>.) Może być tak, że jakieś dane ściągamy z zewnętrznego serwisu - albo w ogóle nasze API jest tylko nakładką na jakiś stary system - i ten oryginalny format jest niestrawny lub niewygodny dla klienta. Ot, choćby wartości logiczne są zapisane jako string (np. <code class=\"language-text\">\"true\"</code>).\nMapowanie możemy napisać ręcznie - ale możemy też użyć AutoMappera.</p>\n<h1>Podsumowanie</h1>\n<p>Moim zdaniem istnieją sytuacje, w których albo chcemy mieć dodatkową warstwę modelu, albo wręcz taką wprost mamy narzuconą z zewnątrz (ciągniemy dane ze starego systemu). Wówczas - AutoMapper może być pomocny, bo nie tylko przyspiesza <em>development</em>, ale zabezpiecza przed błędami (nie trzeba pisać mapowania dla nowego pola - o ile występuje w obu typach mapowania, źródłowym i docelowym). Oczywiście, jak każde narzędzie, także i AutoMapper, może być źle użyty lub nadużyty: ale to już jest opowiedziane znanym powiedzeniem, mówiącym, że dla trzymającego tylko młotek wszystko wydaje się gwoździem.\nWydaje mi się więc, że autor rzeczonego artykułu wylewa dziecko z kąpielą. Jeśli czasami zdarza się, że dodajemy niepotrzebne warstwy modeli i mapowania między nimi, to nie znaczy, że narzędzie do mapowania jest złe - to znaczy, że podjęliśmy złą decyzję i niepotrzebnie doprowadziliśmy do sytuacji, kiedy mapowanie jest w ogóle użyte.</p>","excerpt":"Jakiś czas temu przeczytałem artykuł pn. \"AutoMapper to zło\". Autor w skrócie odradza używanie AutoMappera, z co najmniej dwóch powodów: fakt, że mapowanie…","frontmatter":{"date":"01 July, 2020","path":"/blog/automapper-to-zlo-czy-na-pewno","title":"AutoMapper to zło - czy na pewno?"},"fields":{"readingTime":{"text":"5 min read"}}}},"pageContext":{}},"staticQueryHashes":["3649515864","63159454"]}