Konferencje - 4developers i get.net

konferencja

Konferencje - 4developers i get.net

W ostatnim czasie odbyły się dwie konferencje dla programistów. Na jednej chciałem być, ale nie mogłem. Na drugiej byłem.

4developers

Pierwsza z nich to 4developers w Warszawie. Na tej konferencji nie mogłem być i po imprezie z takim rozmachem (i jak na XXI wiek przystało) spodziewałem się, że poruszane tematy będę mógł nadrobić później w internecie. Niestety tu wielki zawód. Organizatorzy postanowili (nie wiem czemu) nie nagrywać sesji. Gdybym nawet był na tej konferencji i zapłacił już za wejściówkę to ilość ścieżek była tak ogromna, że i tak bym wszystkich nie ogarnął. Tym bardziej chciałbym później obejrzeć te sesje, które ominąłbym na rzecz innych. Niestety.

Uważam, że brak nagrań i późniejszych emisji w obecnych czasach to duże niedopatrzenie organizatorów. Nawet gdyby sesje trwały dwa lub trzy dni. Nagrywanie sesji to obecnie standard.

Widziałem gdzieś na twitterze, że organizatorzy postanowili celowo upchać wszystko w jeden dzień, aby wszyscy mogli się zebrać w tak wielkim i zacnym gronie specjalistów. Skoro chodziło o spotkanie to można było zorganizować BigGeekBeer, a nie konferencję, w której najbardziej chodzi o to, aby Ci co przyjechali mogli wysłuchać tego co prelegenci mają do zaoferowania... ale... ja tam się nie znam.

Ogólnie szkoda, że takie podejście do tej imprezy zastosowali organizatorzy. Mam nadzieję, że wyciągną z tego wnioski na przyszłość. Zwłaszcza, że moją opinię podzielają też inni, z którymi rozmawiałem na ten temat.

get.net

Druga konferencja, która odbyła się zaledwie wczoraj w Łodzi to get.net. Było to pierwsze spotkanie i zdaje się, że nie ostatnie. :)

IMG_6476

Konferencja współorganizowana także dzięki grupie specjalistów IT & .net. Chwała im za taką inicjatywę.

Tomasz Kopacz - WebApi, OData, Windows 8, HTML(5)...

Pierwszy "wystrzelił" Tomek Kopacz, który niczym z karabinu maszynowego opowiadał o sposobie tworzenia aplikacji skalowalnych. Temat rzeka i przedstawienie go w godzinnej sesji jest niemożliwe, dlatego też fajnie, że zaraz po tej sesji były warsztaty w tym temacie. Na warsztatach nie byłem, wolałem kolejne sesje, a poza tym ilość miejsc i tak była mocno ograniczona.

Rafał Legiędź - Rzeczy, których nie widać, gdy CLR zarządza pamięcią

Rafał opowiadał jak clr radzi sobie z zarządzaniem pamięcią. Dla ludzi, którzy dopiero startują w świat programowania w framework.net to lekcja obowiązkowa. Dla tych co już trochę obyci ugruntowanie wiedzy i przypomnienie podstaw. Sesja odbywała się w trochę przymałej salce. Przyszło tyle ludzi, że ledwo się pomieścili. Dobrze przygotowany materiał. :)

Tomasz Woźnica - Javascript everywhere - programistyczna wieża Babel?

Szczerze mówiąc o JavaScript nasłuchałem się ostatnio dużo, więc nie wytrzymałem na tej sesji zbyt długo. Na dodatek Tomkowi nie za dobrze kleił się tekst co spowodowało odpływ ludzi z sali na słoneczko i ławeczki.

IMG_6472

Maciej Aniserowicz - Dependency Injection

Sesja Maćka to (dla mnie) ugruntowanie wiedzy o DI/IoC/IoCP :) Sesja polityczno-merytoryczna. Skąd taki wniosek? Obejrzyjcie prezentację to się sami przekonacie. A tak na serio to była dobra prezentacja z dużą ilością konkretów podana na świeżo. :)

Michał Śliwoń - SignalR

To była fajna sesja, zwłaszcza, że mam zamiar użyć SingalR w obecnym projekcie. Michał konkretnie pokazał co to jest SignalR, jak działa, jak się używa (a robi się to naprawdę prosto) i gdzie SignalR znajduje zastosowanie.

Jakub Gutkowski - Testowanie JS

Tej sesji się troszkę obawiałem, bo ostatnia, bo znów JS, bo Gutka to słucham prawie na każdym developerskim spotkaniu ;) I choć JSa nie męczę tak mocno jak On to ilość wiedzy zawartej podczas tej prezentacji była tak duża, że potrafi naprawdę otworzyć oczy szeroko jak daleko świat programistyczny zaszedł w tak, wydawało by się prozaicznej kwestii jak JavaScript.

Sesje super, organizacja także na wysokim poziomie, wspaniali ludzie. Jakież to zadowolenie mnie ogarnęło, kiedy to z samego rana zobaczyłem na tyłach sali, że stoi kamera. Pomyślałem, że kurcze, dopiero startują, pierwsza edycja konferencji w Łodzi i już widać światowy poziom. Będą sesje online.

Niestety jak się okazało zaraz po oficjalnym otwarciu kamera gdzieś znikła. Szkoda. :( Nie mniej, jeśli za rok będzie druga edycja to jak tylko czas pozwoli zapewne przybędę. A Ci co nie przybyli (a widziałem masę identyfikatorów nieodebranych) niech żałują :)

Nie testuję kontrolerów - część 1

asp.net-mvc, infrastruktura

Nie testuję kontrolerów - część 1

Przykłady z sieci

Ostatnio w sieci ukazało się wiele przykładów jak posprzątać kod w kontrolerach mvc. Sam podjąłem próbę walki z ciągle powtarzającym się kodem. Rozdmuchany kontroler z akcjami wypchanymi kodem wszelakiej maści, od walidacji po wywołania serwisów, repozytoriów, łapanie wyjątków i jak do tego jeszcze zaplącze się jakaś cząstka logiki biznesowej nie przyniesie nam nic dobrego. Prosty przykład:

[HttpPost]
public ActionResult CreateGroup(GroupFormModel newGroup)
{
    var userId = User.Identity.GetUserId();
    Group group = Mapper.Map<GroupFormModel, Group>(newGroup);
    var errors = groupService.CanAddGroup(group).ToList();
    ModelState.AddModelErrors(errors);
    if (ModelState.IsValid)
    {
        var createdGroup = groupService.CreateGroup(group, userId);
        return RedirectToAction("Index", new { id = createdGroup.GroupId });
    }
    return View("CreateGroup", newGroup);
}

Wygląda fatalnie prawda? A jeszcze jak kontroler ma kilka akcji to już kompletna kaszanka. Tu kolejny przykładzik:

public ViewResult Index(int id)
{
    GroupViewModel group = Mapper.Map<Group, GroupViewModel>(groupService.GetGroup(id));
    group.Goals = Mapper.Map<IEnumerable<GroupGoal>, IEnumerable<GroupGoalViewModel>>(groupGoalService.GetGroupGoals(id));

    foreach (var item in group.Goals)
    {
        var user = userService.GetUser(item.GroupUser.UserId);

        item.UserId = user.Id;
        item.User = user;
    }
    var assignedgroupuser = groupUserService.GetGroupUser(User.Identity.GetUserId(), id);
    if (assignedgroupuser != null)
    {
        group.GoalsAssignedToOthers = Mapper.Map<IEnumerable<GroupGoal>, IEnumerable<GroupGoalViewModel>>(groupGoalService.GetAssignedGoalsToOthers(assignedgroupuser.GroupUserId));
        group.GoalsAssignedToMe = Mapper.Map<IEnumerable<GroupGoal>, IEnumerable<GroupGoalViewModel>>(groupGoalService.GetAssignedGoalsToMe(assignedgroupuser.GroupUserId));
    }
    group.Focus = focusService.GetFocussOFGroup(id);

    //group.GroupUserId = groupUserService.GetGroupUserByuserId(((SocialGoalUser)(User.Identity)).UserId).GroupUserId;
    group.Users = groupUserService.GetMembersOfGroup(id);
    //if (group.GroupUser.UserId == ((SocialGoalUser)(User.Identity)).UserId)
    if (groupUserService.GetAdminId(id) == User.Identity.GetUserId())
        group.Admin = true;
    var status = 0;
    foreach (var item in group.Users)
    {
        if (item.Id == (User.Identity.GetUserId()))
            status = 1;
    }
    if (status == 1)
        group.IsAMember = true;
    if (groupRequestService.RequestSent((User.Identity.GetUserId()), id))
        group.RequestSent = true;
    if (groupInvitationService.IsUserInvited(id, (User.Identity.GetUserId())))
        group.InvitationSent = true;
    return View("Index", group);
}

Brrrr... Na sam widok człowiek może dostać drgawki. Niestety w wielu przykładach na sieci (powyższe wycinki też znalezione na sieci) taki kod to normalka. Gołym okiem widać, że kontroler, którego zadaniem jest koordynowanie zadań między modelem a widokiem robi dużo za dużo. Przydało by się posprzątać. :)

Dobra. Miałem tego nie wstawiać, ale wrzucam jeszcze konstruktor:

public GroupController(
    IGroupService groupService,
    IGroupUserService groupUserService,
    IUserService userService,
    IMetricService metricService,
    IFocusService focusService,
    IGroupGoalService groupgoalService,
    IGroupInvitationService groupInvitationService,
    ISecurityTokenService securityTokenService,
    IGroupUpdateService groupUpdateService,
    IGroupCommentService groupCommentService,
    IGoalStatusService goalStatusService,
    IGroupRequestService groupRequestService,
    IFollowUserService followUserService,
    IGroupCommentUserService groupCommentUserService,
    IGroupUpdateSupportService groupUpdateSupportService,
    IGroupUpdateUserService groupUpdateUserService)
{
    this.groupService = groupService;
    this.groupInvitationService = groupInvitationService;
    this.userService = userService;
    this.groupUserService = groupUserService;
    this.metricService = metricService;
    this.focusService = focusService;
    this.groupGoalService = groupgoalService; ;
    this.securityTokenService = securityTokenService;
    this.groupUpdateService = groupUpdateService;
    this.groupCommentService = groupCommentService;
    this.goalStatusService = goalStatusService;
    this.groupRequestService = groupRequestService;
    this.followUserService = followUserService;
    this.groupCommentUserService = groupCommentUserService;
    this.groupUpdateSupportService = groupUpdateSupportService;
    this.groupUpdateUserService = groupUpdateUserService;
}

Uch... Spociłem się... Wow... Niezłe co? :)

Rozwiązanie

Ok. Starczy tego. Co byście powiedzieli, gdyby konstruktor wyglądał tak:

public GroupController() { }

Tak. Dobrze myślicie. Jak widać jawna deklaracja konstruktora w ogóle nie jest potrzebna. Zatem akcja odpowiedzialna za wyświetlenie formatki tworzenia nowej grupy:

[Get]
public ActionResult Create()
{
    return View<CreateView>();
}

A wyświetlenie formatki do edycji tak:

[Get]
public ActionResult Update(int id)
{
    return View<Group, UpdateView>(id);
}

No dobra. To jeszcze akcja Update typu POST:

[Post]
public ActionResult Update(UpdateRequest model)
{
    return Handle(model)
        .With(rq => Dispatcher.Execute(rq))
        .OnSuccess(rq => this.RedirectTo(x => x.Update(rq.ID)))
        .OnSuccessNotify(Resources.UpdateSuccessNotify)
        .OnFailure(rq => this.RedirectTo(x => x.Update(rq.ID)));
}

Wygląd tych akcji wyciągnąłem z kodu projektu nad którym obecnie pracuję. To efekt szukania i zebrania w całość wszelkich pomysłów własnych jak i tych znalezionych na sieci.

Co zatem kryje się pod metodami View<> i Handle? O tym w kolejnym poście.

PS

Acha... Chyba teraz już powoli zaczyna być widoczne dlaczego nie testuję kontrolerów. Jeśli jednak to za mało, to mam nadzieję, że przekonam Was w kolejnych wpisach. :)

Onion Architecture

architektura, asp.net-mvc

Onion Architecture

Zarys

Spodobała mi się sama nazwa "Onion Architecture" jak i to co pod tą nazwą przedstawił Jeffrey Palermo. Niby zasady znane od dawna, a jednak zostały one umiejętnie pokazane na nowo. Niestety na sieci ciężko znaleźć szkielet przykładowej aplikacji, która spełniała by podstawowe założenia.

W związku z tym na szybko zmontowałem takowy. Oto widok solucji przykładowej aplikacji, która nic nie robi, ale obrazuje zależności między projektami.

Solution APP

Od razu rzuca się w oczy, że App.Domain nie ma żadnych referencji do innych projektów, a tym bardziej do żadnej zewnętrznej biblioteki. Takie podejście pozwala nam skupić się na modelu i implementacji logiki biznesowej w oderwaniu od wszelakiej infrastruktury.

Niektórzy pewnie spostrzegą Repository Pattern. Och... temat rzeka... :) Ucinam go w tej chwili. Miałem różne przemyślenia na ten temat. Szukałem, czytałem. Ile rozwiązań tyle wątpliwości. Za każdym razem pozostawiały one pewien niedosyt wynikający ogólnie z niedomówień. Zastosowanie Repository Pattern jest bardzo dobrym pomysłem, ale tylko w odpowiednim miejscu. Tyle już było o tym pisane... także i ja pozwolę sobie na swoje ostatnie przemyślenia w którymś z kolejnych wpisów. :)

Solution UI

Wracając do naszego szkieletu. Powyżej aplikacja UI z rozwiniętą sekcją referencji. Tutaj widać, że nie ma referencji ani do App.DependencyResolution ani do App.Infrastructure. Kod z tych projektów potrzebny jest do tego, aby podczas uruchomienia aplikacji zainicjowały się odpowiednie części systemu oraz do tego, aby obsłużyć działanie naszej logiki biznesowej w tych obszarach, gdzie musimy się odwołać do zewnętrznych zasobów takich jak baza danych, serwer pocztowy, itp.

UI odpowiedzialne jest, jak sama nazwa wskazuje, za udostępnienie interfejsu użytkownika pozwalającego na interakcję z naszym modelem oraz za wyświetlanie danych, które ten model przetworzył i zrzucił do zbiornika danych. Ech... strasznie koślawo to brzmi, ale napisałem tak celowo. Naszym zbiornikiem danych nie musi być baza danych, a tym bardziej relacyjna baza danych. Mogą to być zwykłe pliki, baza dokumentowa, web-serwisy, albo kto tam sobie coś jeszcze innego wymyśli. Co więcej, możemy trzymać dane w wielu różnych technologiach jednocześnie, a nasz Domain Model powinien żyć w nieświadomości.

Skąd dane do widoków?

Spytacie pewnie skąd zatem wziąć dane do widoków. Pytanie podstępne i odpowiedź niestety też będzie dwojaka. Otóż w większych projektach niż te, z którymi miałem/mam przyjemność pracować całkowicie odseparowałbym warstwę dostępu do danych poprzez jakąś warstwę abstrakcji. I nie, nie było by to Repository. Więcej o tym innym razem.

Teraz odpowiedź na miarę projektu mniejszego: do widoków dane ciągniemy łącząc się bezpośrednio do bazy. Kropka. Jeśli aplikacja była by oparta o bazę dokumentową to skorzystałbym ze wszystkich dobrodziejstw API dostępowego do takiej bazy. Do bazy relacyjnej - NHibernate i wszystko to co daje nam za free. Innymi słowy w projekcie UI dodajemy referencję do NHibernate.dll.

Czy to nie jest "over-architected"?

Na pierwszy rzut oka tak się wydaje. Po pierwsze nie rozmawiamy tu o małych/prostych aplikacyjkach. Chcemy zbudować podwaliny pod projekt średniego bądź nawet dużego kalibru, który będzie zawierał trochę bardziej rozbudowaną logikę biznesową. Nie mówimy tu też o projektach klasy Enterprise. Po pierwsze takich projektów robi się duuuużo mniej niż tych średnich. Po drugie sam nie mam większego doświadczenia w tak ogromnych przedsięwzięciach. Nie poruszam także tematu CQRS. Więcej o tym możecie poczytać na blogu Szymona. CQRS jest fajne, ale nie we wszystkich projektach.

Po drugie, faktycznie start od zera jest mozolny i wymaga pewnego obsadzenia projektu w kod infrastrukturalny, dzięki któremu aplikacja w ogóle ruszy. Powiem jednak stanowczo, że robi się to raz na początku, a z czasem przestaje się w ogóle tam zaglądać. Po prostu wszystko działa, a my możemy zająć się tym co najbardziej wartościowe.

Gdzie jest kod?

Nie wiem czy jest jakikolwiek sens udostępniać czyste projekty, w których nic nie ma. W kolejnych wpisach na pewno nie zabraknie przykładowych listingów, ale od czego jest gist. :)

Kod aplikacji znajduje się na githubie. https://github.com/dario-l/OnionExample

Co dalej?

Dalej proponuję, abyśmy bliżej przyjrzeli się naszej aplikacji UI. Tu jest punkt styku użytkownika z aplikacją. To UI generuje dane, które nasz model będzie przetwarzał. To użytkownik będzie uruchamiał procesy, których zachowanie będzie zaimplementowane w domenie.

Obrazek pochodzi ze strony http://www.planetgeek.ch/2013/11/26/chop-onions-instead-of-layers/

Jak podejść do architektury projektu

architektura

Jak podejść do architektury projektu

W sieci można znaleźć mnóstwo przykładów w jaki sposób projektować aplikację. Tyle samo, albo i więcej, jest pytań programistów. Jedni szukają wiedzy, bo dopiero wchodzą w ten fascynujący świat. Inni szukają tylko potwierdzenia czy własne pomysły się sprawdzą.

Sam nie raz szukałem odpowiedzi na pytanie czy moja koncepcja jest już przez kogoś gdzieś użyta. Jeśli znalazłem rozwiązanie, które było podobnie zrealizowane jak moje to czułem się pewniejszy, że nie tylko ja na to wpadłem. Znaczy się nie jestem taki w ciemię bity i co najważniejsze – skoro ktoś w podobny sposób rozwiązał problem to może oznaczać to, że nie stanę za chwilę przed ścianą nie do przejścia i w perspektywie najbliższego czasu nie będę musiał przerabiać połowy aplikacji.

Wiele pytań, które są publikowane na znanym wszystkim stackoverflow to pytania techniczne. Czy stosować Repository Pattern? Czy można wstrzykiwać repozytoria do obiektów biznesowych? Czy można wstrzykiwać serwisy do obiektów biznesowych? Czy ValueObject zawsze musi być niezmienny? I tak w nieskończoność.

Sam nie raz pytałem, bo miałem nadzieję, że prawidłowa odpowiedź doprowadzi mnie do tego, że problemy odpłyną w siną dal i będę miał pięknie działającą aplikację bez żadnych wad, z pięknym, wręcz referencyjnym kodem. Co z tego, że ten piękny kod w oczach klienta nie będzie miał żadnego znaczenia jeśli aplikacja nie będzie spełniać założeń biznesowych. Satysfakcja jest. :)

Są miejsca w sieci, gdzie można by skierować większość pytających, aby obejrzeli kilka porządnych wykładów na temat architektury i to powinno rozwiązać problem. Dlaczego? Jest kilka maksym, które przy projektowaniu aplikacji są stałe i niezmienne. Reszta to detal. Aplikacja ma działać i realizować założenia projektowe. Sposób implementacji tak naprawdę jest sprawą drugorzędną.

Jakieś konkrety?

Pisząć projekty (w szczególności odnoszę się tu do własnego podwórka, czyli do aplikacji internetowych) można dojść do kilku wniosków i podzielić architekturę na kilka warstw:

  • Część biznesowa powinna być odseparowana od reszty systemu i nie powinna mieć jakiejkolwiek infrastrukturalnej implementacji, żadnych referencji do zewnętrznych bibliotek. No chyba, że do takiej, która na przykład zawiera szereg klas pomocniczych przy budowaniu modelu biznesowego, czemu nie. Dlaczego miałbym od nowa pisać klasy, które zawierają przydatne Extension-Methods rozszerzające możliwości typów podstawowych.
  • Odseparowanie części biznesowej wiąże się z łatwością testowania założeń biznesowych, naszego modelu, serwisów domenowych, itp, itd, i co tam jeszcze w naszej domenie się znajdzie. Tak naprawdę zastosowanie się do punktu pierwszego powoduje, że techniczne rozwiązanie jakiegoś problemu, czy to będzie dotyczyło tego gdzie i jak możemy używać repozytorium czy też serwisów, staje się sprawą drugorzędną. Jak tylko jesteśmy w stanie napisać nasz kod tak, aby był testowalny (TDD bardzo w tym pomoże) to techniczna implementacja nie jest już tak ważna.
  • Kod infrastrukturalny powinien znajdować się z boku. Poniekąd jest to taki mały śmietniczek, który prawdopodobnie będzie zawierał referencje do większości, jak nie do wszystkich, zewnętrznych bibliotek potrzebnych do działania naszej aplikacji. Ważne jest to że, w tej części systemu nie powinno się znaleźć ani grama kodu implementującego założenia biznesowe. Tu tylko implementacja funkcjonalności związanych z zewnętrzną infrastrukturą (implementacja odwołań do danych, do webserwisów, do usług typu email, itp).
  • Interface użytkownika służy do wyświetlania danych (read side) i zmiany stanu modelu (write side).
    • Read side to nic innego jak pobranie danych z bazy i wrzucenie tego na formatkę. Ogólnie prosta sprawa. Owszem, czasami zdarza się, że  wyświetlenie danych opatrzone jest dodatkową logiką biznesową, która musi coś dodatkowo wyliczyć, sprawdzić, itp. W takim przypadku posiłkujemy się dodatkowymi serwisami z naszej części biznesowej.
    • Write side to zmiana stanu, czyli w skrócie mówiąc zapis do bazy działań wykonanych przez użytkownika. Tu wkraczają reguły biznesowe, od których zależy funkcjonowanie naszej aplikacji.
  • Wszystko razem spinamy posiłkując się kontenerem, który w automatyczny sposób zawija całą aplikację w jedną całość.

Powyższy słowno-muzyczny opis architektury aplikacji zapewne jest Wam wielu znany.

Oczywiście lepiej jest jeśli powyższe założenia będą w jakiś sposób sformalizowane. Porządek w kodzie się przyda. W związku z czym w kolejnych wpisach postaram się przejść (w miarę szczegółowo) przez każdą z warstw i pokazać w jaki sposób możemy sobie ułatwić życie i stworzyć aplikację, która nie będzie się nam śniła w najgorszych koszmarach.

Jak nie robić MVC

zawodowo

Jak nie robić MVC

Jakiś czas temu od znajomego dostałem link do przykładowej aplikacji, która wg. autora ma pokazać wzorce dla:

  • Domain Driven Design (DDD)
  • Test-Driven Development (TDD)
  • Repository Pattern & Generic Repository
  • Unit of Work
  • Dependency Injection

Pomyślałem, że będzie fajna lekturka, fajnych przykładów nigdy za wiele. Niestety bardzo szybko okazało się, że prezentowany kod daleko odbiega od założeń. A jak otworzyłem kod jednego z kontrolerów moim oczom ukazał się las…

public GroupController(
    IGroupService groupService,
    IGroupUserService groupUserService,
    IUserService userService,
    IMetricService metricService,
    IFocusService focusService,
    IGroupGoalService groupgoalService,
    IGroupInvitationService groupInvitationService,
    ISecurityTokenService securityTokenService,
    IGroupUpdateService groupUpdateService,
    IGroupCommentService groupCommentService,
    IGoalStatusService goalStatusService,
    IGroupRequestService groupRequestService,
    IFollowUserService followUserService,
    IGroupCommentUserService groupCommentUserService,
    IGroupUpdateSupportService groupUpdateSupportService,
    IGroupUpdateUserService groupUpdateUserService)
{
    this.groupService = groupService;
    this.groupInvitationService = groupInvitationService;
    this.userService = userService;
    this.groupUserService = groupUserService;
    this.metricService = metricService;
    this.focusService = focusService;
    this.groupGoalService = groupgoalService; ;
    this.securityTokenService = securityTokenService;
    this.groupUpdateService = groupUpdateService;
    this.groupCommentService = groupCommentService;
    this.goalStatusService = goalStatusService;
    this.groupRequestService = groupRequestService;
    this.followUserService = followUserService;
    this.groupCommentUserService = groupCommentUserService;
    this.groupUpdateSupportService = groupUpdateSupportService;
    this.groupUpdateUserService = groupUpdateUserService;
}

...las serwisów. Jak to zobaczyłem to aż złapałem się za głowę. Dalej nie jest lepiej. Przykładowa metoda typu POST z tego samego kontrolera:

    [HttpPost]
    public ActionResult CreateGroup(GroupFormModel newGroup)
    {
        var userId = User.Identity.GetUserId();
        Group group = Mapper.Map&lt;GroupFormModel, Group&gt;(newGroup);
        var errors = groupService.CanAddGroup(group).ToList();
        ModelState.AddModelErrors(errors);
        if (ModelState.IsValid)
        {
            var createdGroup = groupService.CreateGroup(group, userId);
            return RedirectToAction(&quot;Index&quot;, new { id = createdGroup.GroupId });
        }
        return View(&quot;CreateGroup&quot;, newGroup);
    }

Później można trafić jeszcze na takie kwiatki jak:

  • anemiczny model domeny
  • rozdmuchane serwisy, które robią wszystko
  • repozytoria ściśle powiązane z infrastrukturą
  • itd.

Powyższe przykłady obrazują kilka problemów. Pierwszy z nich to wielgachny konstruktor. Samo to daje mocno do myślenia, że jest tu coś nie tak. Kolejny to powtarzalność sporej ilości kodu w kontrolerze. W pokazanej wyżej akcji CreateGroup można zauważyć standardowy kod, który przewija się przez większość akcji typu POST, czyli:

  • mapowanie danych z modelu widoku  do modelu domeny,
  • walidacja stanu modelu,
  • ręczne sprawdzanie czy ModelState zawiera błędy,
  • przekierowanie w zależności od wystąpienia błędu.

Pomijam zupełnie fakt, że użycie AutoMapper'a do bezmyślnego skopiowania danych z modelu widoku do modelu domeny jest anty-wzorcem DDD, ale tego tematu nie będę poruszał, przynajmniej na razie.

Z akcjami typu GET jest ponownie. Tak samo wyglądające kawałki kodu, które robią prawie to samo, a kod kontrolera staje się rozbudowany. Na koniec okazuje się, że kontroler z kilkoma akcjami zajmuje nam kilkaset linii, zamiast kilkadziesiąt.

To nie jest recenzja aplikacji SocialGoal. Przypadek spowodował, że akurat ten kod wpadł mi w łapki właśnie wtedy kiedy zbierałem materiały do tego wpisu. Czego jeszcze należy unikać tworząc aplikacje mvc?